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