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