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