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;
10 use std::collections::{BTreeMap, BTreeSet};
12 declare_clippy_lint! {
14 /// It lints if a struct has two methods with the same name:
15 /// one from a trait, another not from trait.
17 /// ### Why is this bad?
36 #[clippy::version = "1.57.0"]
39 "two method with same name"
42 declare_lint_pass!(SameNameMethod => [SAME_NAME_METHOD]);
45 impl_methods: BTreeMap<Symbol, Span>,
46 trait_methods: BTreeMap<Symbol, Vec<Span>>,
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();
53 for id in cx.tcx.hir().items() {
54 if !matches!(cx.tcx.hir().def_kind(id.def_id), DefKind::Impl) {
58 let item = cx.tcx.hir().item(id);
59 if let ItemKind::Impl(Impl {
66 if let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind {
67 if !map.contains_key(res) {
71 impl_methods: BTreeMap::new(),
72 trait_methods: BTreeMap::new(),
76 let existing_name = map.get_mut(res).unwrap();
80 let mut methods_in_trait: BTreeSet<Symbol> = if_chain! {
81 if let Some(Node::TraitRef(TraitRef { path, .. })) =
82 cx.tcx.hir().find(trait_ref.hir_ref_id);
83 if let Res::Def(DefKind::Trait, did) = path.res;
86 // `rustc_middle::ty::assoc::AssocItems::items` is public,
87 // we can iterate its keys instead of `in_definition_order`,
88 // which's more efficient
90 .associated_items(did)
91 .in_definition_order()
92 .filter(|assoc_item| {
93 matches!(assoc_item.kind, AssocKind::Fn)
95 .map(|assoc_item| assoc_item.name)
102 let mut check_trait_method = |method_name: Symbol, trait_method_span: Span| {
103 if let Some(impl_span) = existing_name.impl_methods.get(&method_name) {
108 "method's name is the same as an existing method in a trait",
112 &format!("existing `{}` defined here", method_name),
117 if let Some(v) = existing_name.trait_methods.get_mut(&method_name) {
118 v.push(trait_method_span);
120 existing_name.trait_methods.insert(method_name, vec![trait_method_span]);
124 for impl_item_ref in (*items).iter().filter(|impl_item_ref| {
125 matches!(impl_item_ref.kind, rustc_hir::AssocItemKind::Fn { .. })
127 let method_name = impl_item_ref.ident.name;
128 methods_in_trait.remove(&method_name);
129 check_trait_method(method_name, impl_item_ref.span);
132 for method_name in methods_in_trait {
133 check_trait_method(method_name, item.span);
137 for impl_item_ref in (*items).iter().filter(|impl_item_ref| {
138 matches!(impl_item_ref.kind, rustc_hir::AssocItemKind::Fn { .. })
140 let method_name = impl_item_ref.ident.name;
141 let impl_span = impl_item_ref.span;
142 if let Some(trait_spans) = existing_name.trait_methods.get(&method_name) {
147 "method's name is the same as an existing method in a trait",
149 // TODO should we `span_note` on every trait?
150 // iterate on trait_spans?
153 &format!("existing `{}` defined here", method_name),
158 existing_name.impl_methods.insert(method_name, impl_span);