|
| 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::{Crate, 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 { |
| 49 | + fn check_crate_post(&mut self, cx: &LateContext<'tcx>, krate: &'tcx Crate<'tcx>) { |
| 50 | + let mut map = FxHashMap::<Res, ExistingName>::default(); |
| 51 | + |
| 52 | + for item in krate.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 | +} |
0 commit comments