Skip to content

Commit 3ade53b

Browse files
committed
Add ambiguous_method_calls lint
1 parent 889e1b9 commit 3ade53b

File tree

6 files changed

+238
-0
lines changed

6 files changed

+238
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4776,6 +4776,7 @@ Released 2018-09-13
47764776
[`almost_complete_letter_range`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_complete_letter_range
47774777
[`almost_complete_range`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_complete_range
47784778
[`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped
4779+
[`ambiguous_method_calls`]: https://rust-lang.github.io/rust-clippy/master/index.html#ambiguous_method_calls
47794780
[`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
47804781
[`arc_with_non_send_sync`]: https://rust-lang.github.io/rust-clippy/master/index.html#arc_with_non_send_sync
47814782
[`arithmetic_side_effects`]: https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic_side_effects
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
use std::sync::{Mutex, OnceLock};
2+
3+
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
4+
use clippy_utils::is_trait_impl_item;
5+
use rustc_data_structures::fx::FxHashMap;
6+
use rustc_hir::intravisit::FnKind;
7+
use rustc_hir::{Body, FnDecl};
8+
use rustc_lint::{LateContext, LateLintPass};
9+
use rustc_middle::ty::Ty;
10+
use rustc_session::{declare_lint_pass, declare_tool_lint};
11+
use rustc_span::def_id::LocalDefId;
12+
use rustc_span::symbol::Ident;
13+
use rustc_span::{Span, SpanData, Symbol};
14+
15+
declare_clippy_lint! {
16+
/// ### What it does
17+
/// Looks for methods in trait impls and struct impls with the same name,
18+
/// as well as method call sites.
19+
///
20+
/// ### Why is this bad?
21+
/// It is easy to accidentally override a trait method impl with a method
22+
/// of the same name in a struct impl. Inherent methods are preferred if
23+
/// the call site is unqualified, and naming conflicts will happen silently.
24+
///
25+
/// ### Example
26+
/// ```rust
27+
/// trait MyTrait {
28+
/// fn ambiguous(&self);
29+
/// }
30+
///
31+
/// struct Base;
32+
///
33+
/// impl Base {
34+
/// fn ambiguous(&self) {
35+
/// println!("struct impl");
36+
/// }
37+
/// }
38+
///
39+
/// impl MyTrait for Base {
40+
/// fn ambiguous(&self) {
41+
/// println!("trait impl");
42+
/// }
43+
/// }
44+
///
45+
/// Base.ambiguous(); // prints "struct impl"
46+
/// ```
47+
/// Use instead:
48+
/// ```rust
49+
/// trait MyTrait {
50+
/// fn ambiguous(&self);
51+
/// }
52+
///
53+
/// struct Base;
54+
///
55+
/// impl Base {
56+
/// fn unambiguous(&self) {
57+
/// println!("struct impl");
58+
/// }
59+
/// }
60+
///
61+
/// impl MyTrait for Base {
62+
/// fn ambiguous(&self) {
63+
/// println!("trait impl");
64+
/// }
65+
/// }
66+
///
67+
/// Base.unambiguous(); // prints "struct impl"
68+
/// Base.ambiguous(); // prints "trait impl"
69+
/// ```
70+
#[clippy::version = "1.74.0"]
71+
pub AMBIGUOUS_METHOD_CALLS,
72+
pedantic,
73+
"declarations and calls for same-named methods in struct impls and trait impls"
74+
}
75+
declare_lint_pass!(AmbiguousMethodCalls => [AMBIGUOUS_METHOD_CALLS]);
76+
77+
impl<'tcx> LateLintPass<'tcx> for AmbiguousMethodCalls {
78+
fn check_fn(
79+
&mut self,
80+
cx: &LateContext<'tcx>,
81+
kind: FnKind<'tcx>,
82+
_: &'tcx FnDecl<'_>,
83+
_: &'tcx Body<'_>,
84+
_: Span,
85+
def_id: LocalDefId,
86+
) {
87+
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id);
88+
let is_trait_impl = is_trait_impl_item(cx, hir_id);
89+
90+
if let FnKind::Method(ident, _) = kind {
91+
let parent_item = cx.tcx.hir().get_parent_item(hir_id).to_def_id();
92+
let parent_type = cx.tcx.type_of(parent_item).skip_binder();
93+
let parent_ty_str = format!("{parent_type}");
94+
95+
insert_method(is_trait_impl, parent_type, ident);
96+
97+
if has_ambiguous_name(parent_type, ident) {
98+
let trait_methods = trait_methods().lock().unwrap();
99+
let struct_methods = struct_methods().lock().unwrap();
100+
101+
span_lint(
102+
cx,
103+
AMBIGUOUS_METHOD_CALLS,
104+
trait_methods.get(&(parent_ty_str.clone(), ident.name)).unwrap().span(),
105+
"ambiguous trait method name",
106+
);
107+
span_lint_and_help(
108+
cx,
109+
AMBIGUOUS_METHOD_CALLS,
110+
struct_methods.get(&(parent_ty_str, ident.name)).unwrap().span(),
111+
"ambiguous struct method name",
112+
None,
113+
"consider renaming the struct impl's method",
114+
);
115+
}
116+
}
117+
}
118+
119+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'_>) {
120+
if let rustc_hir::ExprKind::MethodCall(path, receiver, _, call_span) = &expr.kind {
121+
let recv_ty = cx.typeck_results().expr_ty(receiver).peel_refs();
122+
let recv_ty_str = format!("{recv_ty}");
123+
124+
let struct_methods = struct_methods().lock().unwrap();
125+
let trait_methods = trait_methods().lock().unwrap();
126+
if struct_methods.contains_key(&(recv_ty_str.clone(), path.ident.name))
127+
&& trait_methods.contains_key(&(recv_ty_str, path.ident.name))
128+
{
129+
span_lint_and_help(
130+
cx,
131+
AMBIGUOUS_METHOD_CALLS,
132+
*call_span,
133+
"ambiguous struct method call",
134+
None,
135+
"consider renaming the struct impl's method or explicitly qualifying the call site",
136+
);
137+
}
138+
}
139+
}
140+
}
141+
142+
fn has_ambiguous_name<'tcx>(ty: Ty<'tcx>, ident: Ident) -> bool {
143+
let ty_str = format!("{ty}");
144+
let trait_methods = trait_methods().lock().unwrap();
145+
let struct_methods = struct_methods().lock().unwrap();
146+
147+
trait_methods.contains_key(&(ty_str.clone(), ident.name)) && struct_methods.contains_key(&(ty_str, ident.name))
148+
}
149+
150+
fn trait_methods() -> &'static Mutex<FxHashMap<(String, Symbol), SpanData>> {
151+
static NAMES: OnceLock<Mutex<FxHashMap<(String, Symbol), SpanData>>> = OnceLock::new();
152+
NAMES.get_or_init(|| Mutex::new(FxHashMap::default()))
153+
}
154+
155+
fn struct_methods() -> &'static Mutex<FxHashMap<(String, Symbol), SpanData>> {
156+
static NAMES: OnceLock<Mutex<FxHashMap<(String, Symbol), SpanData>>> = OnceLock::new();
157+
NAMES.get_or_init(|| Mutex::new(FxHashMap::default()))
158+
}
159+
160+
fn insert_method<'tcx>(is_trait_impl: bool, ty: Ty<'tcx>, ident: Ident) {
161+
let ty_str = format!("{ty}");
162+
let mut trait_methods = trait_methods().lock().unwrap();
163+
let mut struct_methods = struct_methods().lock().unwrap();
164+
165+
if is_trait_impl {
166+
trait_methods.insert((ty_str, ident.name), ident.span.data());
167+
} else {
168+
struct_methods.insert((ty_str, ident.name), ident.span.data());
169+
}
170+
}

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
4040
crate::absolute_paths::ABSOLUTE_PATHS_INFO,
4141
crate::allow_attributes::ALLOW_ATTRIBUTES_INFO,
4242
crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO,
43+
crate::ambiguous_method_calls::AMBIGUOUS_METHOD_CALLS_INFO,
4344
crate::approx_const::APPROX_CONSTANT_INFO,
4445
crate::arc_with_non_send_sync::ARC_WITH_NON_SEND_SYNC_INFO,
4546
crate::as_conversions::AS_CONVERSIONS_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ mod renamed_lints;
6868
mod absolute_paths;
6969
mod allow_attributes;
7070
mod almost_complete_range;
71+
mod ambiguous_method_calls;
7172
mod approx_const;
7273
mod arc_with_non_send_sync;
7374
mod as_conversions;
@@ -1117,6 +1118,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
11171118
msrv(),
11181119
))
11191120
});
1121+
store.register_late_pass(|_| Box::new(ambiguous_method_calls::AmbiguousMethodCalls));
11201122
// add lints here, do not remove this comment, it's used in `new_lint`
11211123
}
11221124

tests/ui/ambiguous_method_calls.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#![allow(dead_code)]
2+
#![warn(clippy::ambiguous_method_calls)]
3+
4+
trait MyTrait {
5+
fn ambiguous(&self);
6+
}
7+
8+
struct Base;
9+
10+
impl Base {
11+
fn ambiguous(&self) {
12+
println!("struct impl");
13+
}
14+
15+
fn unambiguous(&self) {
16+
println!("unambiguous struct impl");
17+
}
18+
}
19+
20+
impl MyTrait for Base {
21+
fn ambiguous(&self) {
22+
println!("trait impl");
23+
}
24+
}
25+
26+
struct Other;
27+
28+
impl MyTrait for Other {
29+
fn ambiguous(&self) {
30+
println!("not actually ambiguous")
31+
}
32+
}
33+
34+
fn main() {
35+
Base.ambiguous();
36+
Other.ambiguous();
37+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
error: ambiguous trait method name
2+
--> $DIR/ambiguous_method_calls.rs:21:8
3+
|
4+
LL | fn ambiguous(&self) {
5+
| ^^^^^^^^^
6+
|
7+
= note: `-D clippy::ambiguous-method-calls` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::ambiguous_method_calls)]`
9+
10+
error: ambiguous struct method name
11+
--> $DIR/ambiguous_method_calls.rs:11:8
12+
|
13+
LL | fn ambiguous(&self) {
14+
| ^^^^^^^^^
15+
|
16+
= help: consider renaming the struct impl's method
17+
18+
error: ambiguous struct method call
19+
--> $DIR/ambiguous_method_calls.rs:35:10
20+
|
21+
LL | Base.ambiguous();
22+
| ^^^^^^^^^^^
23+
|
24+
= help: consider renaming the struct impl's method or explicitly qualifying the call site
25+
26+
error: aborting due to 3 previous errors
27+

0 commit comments

Comments
 (0)