]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/same_name_method.rs
Auto merge of #102068 - cjgillot:erased-lifetime-print, r=eholk
[rust.git] / clippy_lints / src / same_name_method.rs
1 use clippy_utils::diagnostics::span_lint_hir_and_then;
2 use rustc_data_structures::fx::FxHashMap;
3 use rustc_hir::def::{DefKind, Res};
4 use rustc_hir::{HirId, 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, HirId)>,
46     trait_methods: BTreeMap<Symbol, Vec<Span>>,
47 }
48
49 impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
50     #[expect(clippy::too_many_lines)]
51     fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
52         let mut map = FxHashMap::<Res, ExistingName>::default();
53
54         for id in cx.tcx.hir().items() {
55             if matches!(cx.tcx.def_kind(id.def_id), DefKind::Impl)
56                 && let item = cx.tcx.hir().item(id)
57                 && let ItemKind::Impl(Impl {
58                   items,
59                   of_trait,
60                   self_ty,
61                   ..
62                                       }) = &item.kind
63                 && let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind
64             {
65                 if !map.contains_key(res) {
66                     map.insert(
67                         *res,
68                         ExistingName {
69                             impl_methods: BTreeMap::new(),
70                             trait_methods: BTreeMap::new(),
71                         },
72                     );
73                 }
74                 let existing_name = map.get_mut(res).unwrap();
75
76                 match of_trait {
77                     Some(trait_ref) => {
78                         let mut methods_in_trait: BTreeSet<Symbol> = if_chain! {
79                             if let Some(Node::TraitRef(TraitRef { path, .. })) =
80                                 cx.tcx.hir().find(trait_ref.hir_ref_id);
81                             if let Res::Def(DefKind::Trait, did) = path.res;
82                             then{
83                                 // FIXME: if
84                                 // `rustc_middle::ty::assoc::AssocItems::items` is public,
85                                 // we can iterate its keys instead of `in_definition_order`,
86                                 // which's more efficient
87                                 cx.tcx
88                                     .associated_items(did)
89                                     .in_definition_order()
90                                     .filter(|assoc_item| {
91                                         matches!(assoc_item.kind, AssocKind::Fn)
92                                     })
93                                     .map(|assoc_item| assoc_item.name)
94                                     .collect()
95                             }else{
96                                 BTreeSet::new()
97                             }
98                         };
99
100                         let mut check_trait_method = |method_name: Symbol, trait_method_span: Span| {
101                             if let Some((impl_span, hir_id)) = existing_name.impl_methods.get(&method_name) {
102                                 span_lint_hir_and_then(
103                                     cx,
104                                     SAME_NAME_METHOD,
105                                     *hir_id,
106                                     *impl_span,
107                                     "method's name is the same as an existing method in a trait",
108                                     |diag| {
109                                         diag.span_note(
110                                             trait_method_span,
111                                             &format!("existing `{}` defined here", method_name),
112                                         );
113                                     },
114                                 );
115                             }
116                             if let Some(v) = existing_name.trait_methods.get_mut(&method_name) {
117                                 v.push(trait_method_span);
118                             } else {
119                                 existing_name.trait_methods.insert(method_name, vec![trait_method_span]);
120                             }
121                         };
122
123                         for impl_item_ref in (*items).iter().filter(|impl_item_ref| {
124                             matches!(impl_item_ref.kind, rustc_hir::AssocItemKind::Fn { .. })
125                         }) {
126                             let method_name = impl_item_ref.ident.name;
127                             methods_in_trait.remove(&method_name);
128                             check_trait_method(method_name, impl_item_ref.span);
129                         }
130
131                         for method_name in methods_in_trait {
132                             check_trait_method(method_name, item.span);
133                         }
134                     },
135                     None => {
136                         for impl_item_ref in (*items).iter().filter(|impl_item_ref| {
137                             matches!(impl_item_ref.kind, rustc_hir::AssocItemKind::Fn { .. })
138                         }) {
139                             let method_name = impl_item_ref.ident.name;
140                             let impl_span = impl_item_ref.span;
141                             let hir_id = impl_item_ref.id.hir_id();
142                             if let Some(trait_spans) = existing_name.trait_methods.get(&method_name) {
143                                 span_lint_hir_and_then(
144                                     cx,
145                                     SAME_NAME_METHOD,
146                                     hir_id,
147                                     impl_span,
148                                     "method's name is the same as an existing method in a trait",
149                                     |diag| {
150                                         // TODO should we `span_note` on every trait?
151                                         // iterate on trait_spans?
152                                         diag.span_note(
153                                             trait_spans[0],
154                                             &format!("existing `{}` defined here", method_name),
155                                         );
156                                     },
157                                 );
158                             }
159                             existing_name.impl_methods.insert(method_name, (impl_span, hir_id));
160                         }
161                     },
162                 }
163             }
164         }
165     }
166 }