|
1 | 1 | use crate::context::LintContext; |
2 | | -use crate::lints::NoopMethodCallDiag; |
| 2 | +use crate::lints::{NoopMethodCallDiag, SuspiciousDoubleRefDiag}; |
3 | 3 | use crate::LateContext; |
4 | 4 | use crate::LateLintPass; |
5 | 5 | use rustc_hir::def::DefKind; |
6 | 6 | use rustc_hir::{Expr, ExprKind}; |
7 | 7 | use rustc_middle::ty; |
| 8 | +use rustc_middle::ty::adjustment::Adjust; |
8 | 9 | use rustc_span::symbol::sym; |
9 | 10 |
|
10 | 11 | declare_lint! { |
@@ -35,14 +36,44 @@ declare_lint! { |
35 | 36 | "detects the use of well-known noop methods" |
36 | 37 | } |
37 | 38 |
|
38 | | -declare_lint_pass!(NoopMethodCall => [NOOP_METHOD_CALL]); |
| 39 | +declare_lint! { |
| 40 | + /// The `suspicious_double_ref_op` lint checks for usage of `.clone()`/`.borrow()`/`.deref()` |
| 41 | + /// on an `&&T` when `T: !Deref/Borrow/Clone`, which means the call will return the inner `&T`, |
| 42 | + /// instead of performing the operation on the underlying `T` and can be confusing. |
| 43 | + /// |
| 44 | + /// ### Example |
| 45 | + /// |
| 46 | + /// ```rust |
| 47 | + /// # #![allow(unused)] |
| 48 | + /// struct Foo; |
| 49 | + /// let foo = &&Foo; |
| 50 | + /// let clone: &Foo = foo.clone(); |
| 51 | + /// ``` |
| 52 | + /// |
| 53 | + /// {{produces}} |
| 54 | + /// |
| 55 | + /// ### Explanation |
| 56 | + /// |
| 57 | + /// Since `Foo` doesn't implement `Clone`, running `.clone()` only dereferences the double |
| 58 | + /// reference, instead of cloning the inner type which should be what was intended. |
| 59 | + pub SUSPICIOUS_DOUBLE_REF_OP, |
| 60 | + Warn, |
| 61 | + "suspicious call of trait method on `&&T`" |
| 62 | +} |
| 63 | + |
| 64 | +declare_lint_pass!(NoopMethodCall => [NOOP_METHOD_CALL, SUSPICIOUS_DOUBLE_REF_OP]); |
39 | 65 |
|
40 | 66 | impl<'tcx> LateLintPass<'tcx> for NoopMethodCall { |
41 | 67 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { |
42 | 68 | // We only care about method calls. |
43 | | - let ExprKind::MethodCall(call, receiver, ..) = &expr.kind else { |
44 | | - return |
| 69 | + let ExprKind::MethodCall(call, receiver, _, call_span) = &expr.kind else { |
| 70 | + return; |
45 | 71 | }; |
| 72 | + |
| 73 | + if call_span.from_expansion() { |
| 74 | + return; |
| 75 | + } |
| 76 | + |
46 | 77 | // We only care about method calls corresponding to the `Clone`, `Deref` and `Borrow` |
47 | 78 | // traits and ignore any other method call. |
48 | 79 | let did = match cx.typeck_results().type_dependent_def(expr.hir_id) { |
@@ -70,25 +101,39 @@ impl<'tcx> LateLintPass<'tcx> for NoopMethodCall { |
70 | 101 | }; |
71 | 102 | // (Re)check that it implements the noop diagnostic. |
72 | 103 | let Some(name) = cx.tcx.get_diagnostic_name(i.def_id()) else { return }; |
73 | | - if !matches!( |
74 | | - name, |
75 | | - sym::noop_method_borrow | sym::noop_method_clone | sym::noop_method_deref |
76 | | - ) { |
77 | | - return; |
78 | | - } |
| 104 | + |
| 105 | + let op = match name { |
| 106 | + sym::noop_method_borrow => "borrow", |
| 107 | + sym::noop_method_clone => "clone", |
| 108 | + sym::noop_method_deref => "deref", |
| 109 | + _ => return, |
| 110 | + }; |
| 111 | + |
79 | 112 | let receiver_ty = cx.typeck_results().expr_ty(receiver); |
80 | 113 | let expr_ty = cx.typeck_results().expr_ty_adjusted(expr); |
81 | | - if receiver_ty != expr_ty { |
82 | | - // This lint will only trigger if the receiver type and resulting expression \ |
83 | | - // type are the same, implying that the method call is unnecessary. |
| 114 | + let arg_adjustments = cx.typeck_results().expr_adjustments(receiver); |
| 115 | + |
| 116 | + // If there is any user defined auto-deref step, then we don't want to warn. |
| 117 | + // https://github.com/rust-lang/rust-clippy/issues/9272 |
| 118 | + if arg_adjustments.iter().any(|adj| matches!(adj.kind, Adjust::Deref(Some(_)))) { |
84 | 119 | return; |
85 | 120 | } |
| 121 | + |
86 | 122 | let expr_span = expr.span; |
87 | 123 | let span = expr_span.with_lo(receiver.span.hi()); |
88 | | - cx.emit_spanned_lint( |
89 | | - NOOP_METHOD_CALL, |
90 | | - span, |
91 | | - NoopMethodCallDiag { method: call.ident.name, receiver_ty, label: span }, |
92 | | - ); |
| 124 | + |
| 125 | + if receiver_ty == expr_ty { |
| 126 | + cx.emit_spanned_lint( |
| 127 | + NOOP_METHOD_CALL, |
| 128 | + span, |
| 129 | + NoopMethodCallDiag { method: call.ident.name, receiver_ty, label: span }, |
| 130 | + ); |
| 131 | + } else { |
| 132 | + cx.emit_spanned_lint( |
| 133 | + SUSPICIOUS_DOUBLE_REF_OP, |
| 134 | + span, |
| 135 | + SuspiciousDoubleRefDiag { call: call.ident.name, ty: expr_ty, op }, |
| 136 | + ) |
| 137 | + } |
93 | 138 | } |
94 | 139 | } |
0 commit comments