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}