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