rustc_lint/noop_method_call.rs
1use rustc_hir::def::DefKind;
2use rustc_hir::{Expr, ExprKind};
3use rustc_middle::ty;
4use rustc_middle::ty::adjustment::Adjust;
5use rustc_session::{declare_lint, declare_lint_pass};
6use rustc_span::sym;
7
8use crate::context::LintContext;
9use crate::lints::{
10 NoopMethodCallDiag, SuspiciousDoubleRefCloneDiag, SuspiciousDoubleRefDerefDiag,
11};
12use crate::{LateContext, LateLintPass};
13
14declare_lint! {
15 /// The `noop_method_call` lint detects specific calls to noop methods
16 /// such as a calling `<&T as Clone>::clone` where `T: !Clone`.
17 ///
18 /// ### Example
19 ///
20 /// ```rust
21 /// # #![allow(unused)]
22 /// struct Foo;
23 /// let foo = &Foo;
24 /// let clone: &Foo = foo.clone();
25 /// ```
26 ///
27 /// {{produces}}
28 ///
29 /// ### Explanation
30 ///
31 /// Some method calls are noops meaning that they do nothing. Usually such methods
32 /// are the result of blanket implementations that happen to create some method invocations
33 /// that end up not doing anything. For instance, `Clone` is implemented on all `&T`, but
34 /// calling `clone` on a `&T` where `T` does not implement clone, actually doesn't do anything
35 /// as references are copy. This lint detects these calls and warns the user about them.
36 pub NOOP_METHOD_CALL,
37 Warn,
38 "detects the use of well-known noop methods"
39}
40
41declare_lint! {
42 /// The `suspicious_double_ref_op` lint checks for usage of `.clone()`/`.borrow()`/`.deref()`
43 /// on an `&&T` when `T: !Deref/Borrow/Clone`, which means the call will return the inner `&T`,
44 /// instead of performing the operation on the underlying `T` and can be confusing.
45 ///
46 /// ### Example
47 ///
48 /// ```rust
49 /// # #![allow(unused)]
50 /// struct Foo;
51 /// let foo = &&Foo;
52 /// let clone: &Foo = foo.clone();
53 /// ```
54 ///
55 /// {{produces}}
56 ///
57 /// ### Explanation
58 ///
59 /// Since `Foo` doesn't implement `Clone`, running `.clone()` only dereferences the double
60 /// reference, instead of cloning the inner type which should be what was intended.
61 pub SUSPICIOUS_DOUBLE_REF_OP,
62 Warn,
63 "suspicious call of trait method on `&&T`"
64}
65
66declare_lint_pass!(NoopMethodCall => [NOOP_METHOD_CALL, SUSPICIOUS_DOUBLE_REF_OP]);
67
68impl<'tcx> LateLintPass<'tcx> for NoopMethodCall {
69 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
70 // We only care about method calls.
71 let ExprKind::MethodCall(call, receiver, _, call_span) = &expr.kind else {
72 return;
73 };
74
75 if call_span.from_expansion() {
76 return;
77 }
78
79 // We only care about method calls corresponding to the `Clone`, `Deref` and `Borrow`
80 // traits and ignore any other method call.
81
82 let Some((DefKind::AssocFn, did)) = cx.typeck_results().type_dependent_def(expr.hir_id)
83 else {
84 return;
85 };
86
87 let Some(trait_id) = cx.tcx.trait_of_item(did) else { return };
88
89 let Some(trait_) = cx.tcx.get_diagnostic_name(trait_id) else { return };
90
91 if !matches!(trait_, sym::Borrow | sym::Clone | sym::Deref) {
92 return;
93 };
94
95 let args = cx
96 .tcx
97 .normalize_erasing_regions(cx.typing_env(), cx.typeck_results().node_args(expr.hir_id));
98 // Resolve the trait method instance.
99 let Ok(Some(i)) = ty::Instance::try_resolve(cx.tcx, cx.typing_env(), did, args) else {
100 return;
101 };
102 // (Re)check that it implements the noop diagnostic.
103 let Some(name) = cx.tcx.get_diagnostic_name(i.def_id()) else { return };
104 if !matches!(
105 name,
106 sym::noop_method_borrow | sym::noop_method_clone | sym::noop_method_deref
107 ) {
108 return;
109 }
110
111 let receiver_ty = cx.typeck_results().expr_ty(receiver);
112 let expr_ty = cx.typeck_results().expr_ty_adjusted(expr);
113 let arg_adjustments = cx.typeck_results().expr_adjustments(receiver);
114
115 // If there is any user defined auto-deref step, then we don't want to warn.
116 // https://github.com/rust-lang/rust-clippy/issues/9272
117 if arg_adjustments.iter().any(|adj| matches!(adj.kind, Adjust::Deref(Some(_)))) {
118 return;
119 }
120
121 let expr_span = expr.span;
122 let span = expr_span.with_lo(receiver.span.hi());
123
124 let orig_ty = expr_ty.peel_refs();
125
126 if receiver_ty == expr_ty {
127 let suggest_derive = match orig_ty.kind() {
128 ty::Adt(def, _) => Some(cx.tcx.def_span(def.did()).shrink_to_lo()),
129 _ => None,
130 };
131 cx.emit_span_lint(
132 NOOP_METHOD_CALL,
133 span,
134 NoopMethodCallDiag {
135 method: call.ident,
136 orig_ty,
137 trait_,
138 label: span,
139 suggest_derive,
140 },
141 );
142 } else {
143 match name {
144 // If `type_of(x) == T` and `x.borrow()` is used to get `&T`,
145 // then that should be allowed
146 sym::noop_method_borrow => return,
147 sym::noop_method_clone => cx.emit_span_lint(
148 SUSPICIOUS_DOUBLE_REF_OP,
149 span,
150 SuspiciousDoubleRefCloneDiag { ty: expr_ty },
151 ),
152 sym::noop_method_deref => cx.emit_span_lint(
153 SUSPICIOUS_DOUBLE_REF_OP,
154 span,
155 SuspiciousDoubleRefDerefDiag { ty: expr_ty },
156 ),
157 _ => return,
158 }
159 }
160 }
161}