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