]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/same_name_method.rs
Rollup merge of #96748 - GuillaumeGomez:reexports-in-search, r=notriddle
[rust.git] / src / tools / clippy / clippy_lints / src / same_name_method.rs
1 use clippy_utils::diagnostics::span_lint_and_then;
2 use rustc_data_structures::fx::FxHashMap;
3 use rustc_hir::def::{DefKind, Res};
4 use rustc_hir::{Impl, ItemKind, Node, Path, QPath, TraitRef, TyKind};
5 use rustc_lint::{LateContext, LateLintPass};
6 use rustc_middle::ty::AssocKind;
7 use rustc_session::{declare_lint_pass, declare_tool_lint};
8 use rustc_span::symbol::Symbol;
9 use rustc_span::Span;
10 use std::collections::{BTreeMap, BTreeSet};
11
12 declare_clippy_lint! {
13     /// ### What it does
14     /// It lints if a struct has two methods with the same name:
15     /// one from a trait, another not from trait.
16     ///
17     /// ### Why is this bad?
18     /// Confusing.
19     ///
20     /// ### Example
21     /// ```rust
22     /// trait T {
23     ///     fn foo(&self) {}
24     /// }
25     ///
26     /// struct S;
27     ///
28     /// impl T for S {
29     ///     fn foo(&self) {}
30     /// }
31     ///
32     /// impl S {
33     ///     fn foo(&self) {}
34     /// }
35     /// ```
36     #[clippy::version = "1.57.0"]
37     pub SAME_NAME_METHOD,
38     restriction,
39     "two method with same name"
40 }
41
42 declare_lint_pass!(SameNameMethod => [SAME_NAME_METHOD]);
43
44 struct ExistingName {
45     impl_methods: BTreeMap<Symbol, Span>,
46     trait_methods: BTreeMap<Symbol, Vec<Span>>,
47 }
48
49 impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
50     fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
51         let mut map = FxHashMap::<Res, ExistingName>::default();
52
53         for id in cx.tcx.hir().items() {
54             if matches!(cx.tcx.hir().def_kind(id.def_id), DefKind::Impl)
55                 && let item = cx.tcx.hir().item(id)
56                 && let ItemKind::Impl(Impl {
57                     items,
58                     of_trait,
59                     self_ty,
60                     ..
61                 }) = &item.kind
62                 && let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind
63             {
64                 if !map.contains_key(res) {
65                     map.insert(
66                         *res,
67                         ExistingName {
68                             impl_methods: BTreeMap::new(),
69                             trait_methods: BTreeMap::new(),
70                         },
71                     );
72                 }
73                 let existing_name = map.get_mut(res).unwrap();
74
75                 match of_trait {
76                     Some(trait_ref) => {
77                         let mut methods_in_trait: BTreeSet<Symbol> = if_chain! {
78                             if let Some(Node::TraitRef(TraitRef { path, .. })) =
79                                 cx.tcx.hir().find(trait_ref.hir_ref_id);
80                             if let Res::Def(DefKind::Trait, did) = path.res;
81                             then{
82                                 // FIXME: if
83                                 // `rustc_middle::ty::assoc::AssocItems::items` is public,
84                                 // we can iterate its keys instead of `in_definition_order`,
85                                 // which's more efficient
86                                 cx.tcx
87                                     .associated_items(did)
88                                     .in_definition_order()
89                                     .filter(|assoc_item| {
90                                         matches!(assoc_item.kind, AssocKind::Fn)
91                                     })
92                                     .map(|assoc_item| assoc_item.name)
93                                     .collect()
94                             }else{
95                                 BTreeSet::new()
96                             }
97                         };
98
99                         let mut check_trait_method = |method_name: Symbol, trait_method_span: Span| {
100                             if let Some(impl_span) = existing_name.impl_methods.get(&method_name) {
101                                 span_lint_and_then(
102                                     cx,
103                                     SAME_NAME_METHOD,
104                                     *impl_span,
105                                     "method's name is the same as an existing method in a trait",
106                                     |diag| {
107                                         diag.span_note(
108                                             trait_method_span,
109                                             &format!("existing `{}` defined here", method_name),
110                                         );
111                                     },
112                                 );
113                             }
114                             if let Some(v) = existing_name.trait_methods.get_mut(&method_name) {
115                                 v.push(trait_method_span);
116                             } else {
117                                 existing_name.trait_methods.insert(method_name, vec![trait_method_span]);
118                             }
119                         };
120
121                         for impl_item_ref in (*items).iter().filter(|impl_item_ref| {
122                             matches!(impl_item_ref.kind, rustc_hir::AssocItemKind::Fn { .. })
123                         }) {
124                             let method_name = impl_item_ref.ident.name;
125                             methods_in_trait.remove(&method_name);
126                             check_trait_method(method_name, impl_item_ref.span);
127                         }
128
129                         for method_name in methods_in_trait {
130                             check_trait_method(method_name, item.span);
131                         }
132                     },
133                     None => {
134                         for impl_item_ref in (*items).iter().filter(|impl_item_ref| {
135                             matches!(impl_item_ref.kind, rustc_hir::AssocItemKind::Fn { .. })
136                         }) {
137                             let method_name = impl_item_ref.ident.name;
138                             let impl_span = impl_item_ref.span;
139                             if let Some(trait_spans) = existing_name.trait_methods.get(&method_name) {
140                                 span_lint_and_then(
141                                     cx,
142                                     SAME_NAME_METHOD,
143                                     impl_span,
144                                     "method's name is the same as an existing method in a trait",
145                                     |diag| {
146                                         // TODO should we `span_note` on every trait?
147                                         // iterate on trait_spans?
148                                         diag.span_note(
149                                             trait_spans[0],
150                                             &format!("existing `{}` defined here", method_name),
151                                         );
152                                     },
153                                 );
154                             }
155                             existing_name.impl_methods.insert(method_name, impl_span);
156                         }
157                     },
158                 }
159             }
160         }
161     }
162 }