• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use crate::context::LintContext;
2 use crate::lints::{
3     NoopMethodCallDiag, SuspiciousDoubleRefCloneDiag, SuspiciousDoubleRefDerefDiag,
4 };
5 use crate::LateContext;
6 use crate::LateLintPass;
7 use rustc_hir::def::DefKind;
8 use rustc_hir::{Expr, ExprKind};
9 use rustc_middle::ty;
10 use rustc_middle::ty::adjustment::Adjust;
11 use rustc_span::symbol::sym;
12 
13 declare_lint! {
14     /// The `noop_method_call` lint detects specific calls to noop methods
15     /// such as a calling `<&T as Clone>::clone` where `T: !Clone`.
16     ///
17     /// ### Example
18     ///
19     /// ```rust
20     /// # #![allow(unused)]
21     /// #![warn(noop_method_call)]
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     Allow,
38     "detects the use of well-known noop methods"
39 }
40 
41 declare_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 
66 declare_lint_pass!(NoopMethodCall => [NOOP_METHOD_CALL, SUSPICIOUS_DOUBLE_REF_OP]);
67 
68 impl<'tcx> LateLintPass<'tcx> for NoopMethodCall {
check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>)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)) =
83             cx.typeck_results().type_dependent_def(expr.hir_id)
84         else {
85             return;
86         };
87 
88         let Some(trait_id) = cx.tcx.trait_of_item(did) else { return };
89 
90         if !matches!(
91             cx.tcx.get_diagnostic_name(trait_id),
92             Some(sym::Borrow | sym::Clone | sym::Deref)
93         ) {
94             return;
95         };
96 
97         let substs = cx
98             .tcx
99             .normalize_erasing_regions(cx.param_env, cx.typeck_results().node_substs(expr.hir_id));
100         // Resolve the trait method instance.
101         let Ok(Some(i)) = ty::Instance::resolve(cx.tcx, cx.param_env, did, substs) else {
102             return
103         };
104         // (Re)check that it implements the noop diagnostic.
105         let Some(name) = cx.tcx.get_diagnostic_name(i.def_id()) else { return };
106 
107         let receiver_ty = cx.typeck_results().expr_ty(receiver);
108         let expr_ty = cx.typeck_results().expr_ty_adjusted(expr);
109         let arg_adjustments = cx.typeck_results().expr_adjustments(receiver);
110 
111         // If there is any user defined auto-deref step, then we don't want to warn.
112         // https://github.com/rust-lang/rust-clippy/issues/9272
113         if arg_adjustments.iter().any(|adj| matches!(adj.kind, Adjust::Deref(Some(_)))) {
114             return;
115         }
116 
117         let expr_span = expr.span;
118         let span = expr_span.with_lo(receiver.span.hi());
119 
120         if receiver_ty == expr_ty {
121             cx.emit_spanned_lint(
122                 NOOP_METHOD_CALL,
123                 span,
124                 NoopMethodCallDiag { method: call.ident.name, receiver_ty, label: span },
125             );
126         } else {
127             match name {
128                 // If `type_of(x) == T` and `x.borrow()` is used to get `&T`,
129                 // then that should be allowed
130                 sym::noop_method_borrow => return,
131                 sym::noop_method_clone => cx.emit_spanned_lint(
132                     SUSPICIOUS_DOUBLE_REF_OP,
133                     span,
134                     SuspiciousDoubleRefCloneDiag { ty: expr_ty },
135                 ),
136                 sym::noop_method_deref => cx.emit_spanned_lint(
137                     SUSPICIOUS_DOUBLE_REF_OP,
138                     span,
139                     SuspiciousDoubleRefDerefDiag { ty: expr_ty },
140                 ),
141                 _ => return,
142             }
143         }
144     }
145 }
146