]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/same_name_method.rs
New upstream version 1.63.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / same_name_method.rs
CommitLineData
923072b8 1use clippy_utils::diagnostics::span_lint_hir_and_then;
c295e0f8
XL
2use rustc_data_structures::fx::FxHashMap;
3use rustc_hir::def::{DefKind, Res};
923072b8 4use rustc_hir::{HirId, Impl, ItemKind, Node, Path, QPath, TraitRef, TyKind};
c295e0f8
XL
5use rustc_lint::{LateContext, LateLintPass};
6use rustc_middle::ty::AssocKind;
7use rustc_session::{declare_lint_pass, declare_tool_lint};
8use rustc_span::symbol::Symbol;
9use rustc_span::Span;
10use std::collections::{BTreeMap, BTreeSet};
11
12declare_clippy_lint! {
13 /// ### What it does
a2a8927a 14 /// It lints if a struct has two methods with the same name:
c295e0f8
XL
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 /// ```
a2a8927a 36 #[clippy::version = "1.57.0"]
c295e0f8
XL
37 pub SAME_NAME_METHOD,
38 restriction,
39 "two method with same name"
40}
41
42declare_lint_pass!(SameNameMethod => [SAME_NAME_METHOD]);
43
44struct ExistingName {
923072b8 45 impl_methods: BTreeMap<Symbol, (Span, HirId)>,
c295e0f8
XL
46 trait_methods: BTreeMap<Symbol, Vec<Span>>,
47}
48
49impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
923072b8 50 #[expect(clippy::too_many_lines)]
c295e0f8
XL
51 fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
52 let mut map = FxHashMap::<Res, ExistingName>::default();
53
04454e1e
FG
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
c295e0f8 64 {
04454e1e
FG
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();
c295e0f8 75
04454e1e
FG
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()
c295e0f8 97 }
04454e1e 98 };
c295e0f8 99
04454e1e 100 let mut check_trait_method = |method_name: Symbol, trait_method_span: Span| {
923072b8
FG
101 if let Some((impl_span, hir_id)) = existing_name.impl_methods.get(&method_name) {
102 span_lint_hir_and_then(
04454e1e
FG
103 cx,
104 SAME_NAME_METHOD,
923072b8 105 *hir_id,
04454e1e
FG
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 );
c295e0f8 115 }
04454e1e
FG
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]);
c295e0f8 120 }
04454e1e
FG
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;
923072b8 141 let hir_id = impl_item_ref.id.hir_id();
04454e1e 142 if let Some(trait_spans) = existing_name.trait_methods.get(&method_name) {
923072b8 143 span_lint_hir_and_then(
04454e1e
FG
144 cx,
145 SAME_NAME_METHOD,
923072b8 146 hir_id,
04454e1e
FG
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 }
923072b8 159 existing_name.impl_methods.insert(method_name, (impl_span, hir_id));
04454e1e
FG
160 }
161 },
c295e0f8
XL
162 }
163 }
164 }
165 }
166}