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)] check_crate_post(&mut self, cx: &LateContext<'tcx>)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.owner_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 `{method_name}` defined here"), 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 `{method_name}` defined here"), 155 ); 156 }, 157 ); 158 } 159 existing_name.impl_methods.insert(method_name, (impl_span, hir_id)); 160 } 161 }, 162 } 163 } 164 } 165 } 166 } 167