1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
use crate::context::LintContext;
use crate::LateContext;
use crate::LateLintPass;
use rustc_errors::fluent;
use rustc_hir::def::DefKind;
use rustc_hir::{Expr, ExprKind};
use rustc_middle::ty;
use rustc_span::symbol::sym;

declare_lint! {
    /// The `noop_method_call` lint detects specific calls to noop methods
    /// such as a calling `<&T as Clone>::clone` where `T: !Clone`.
    ///
    /// ### Example
    ///
    /// ```rust
    /// # #![allow(unused)]
    /// #![warn(noop_method_call)]
    /// struct Foo;
    /// let foo = &Foo;
    /// let clone: &Foo = foo.clone();
    /// ```
    ///
    /// {{produces}}
    ///
    /// ### Explanation
    ///
    /// Some method calls are noops meaning that they do nothing. Usually such methods
    /// are the result of blanket implementations that happen to create some method invocations
    /// that end up not doing anything. For instance, `Clone` is implemented on all `&T`, but
    /// calling `clone` on a `&T` where `T` does not implement clone, actually doesn't do anything
    /// as references are copy. This lint detects these calls and warns the user about them.
    pub NOOP_METHOD_CALL,
    Allow,
    "detects the use of well-known noop methods"
}

declare_lint_pass!(NoopMethodCall => [NOOP_METHOD_CALL]);

impl<'tcx> LateLintPass<'tcx> for NoopMethodCall {
    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
        // We only care about method calls.
        let ExprKind::MethodCall(call, receiver, ..) = &expr.kind else {
            return
        };
        // We only care about method calls corresponding to the `Clone`, `Deref` and `Borrow`
        // traits and ignore any other method call.
        let did = match cx.typeck_results().type_dependent_def(expr.hir_id) {
            // Verify we are dealing with a method/associated function.
            Some((DefKind::AssocFn, did)) => match cx.tcx.trait_of_item(did) {
                // Check that we're dealing with a trait method for one of the traits we care about.
                Some(trait_id)
                    if matches!(
                        cx.tcx.get_diagnostic_name(trait_id),
                        Some(sym::Borrow | sym::Clone | sym::Deref)
                    ) =>
                {
                    did
                }
                _ => return,
            },
            _ => return,
        };
        let substs = cx
            .tcx
            .normalize_erasing_regions(cx.param_env, cx.typeck_results().node_substs(expr.hir_id));
        // Resolve the trait method instance.
        let Ok(Some(i)) = ty::Instance::resolve(cx.tcx, cx.param_env, did, substs) else {
            return
        };
        // (Re)check that it implements the noop diagnostic.
        let Some(name) = cx.tcx.get_diagnostic_name(i.def_id()) else { return };
        if !matches!(
            name,
            sym::noop_method_borrow | sym::noop_method_clone | sym::noop_method_deref
        ) {
            return;
        }
        let receiver_ty = cx.typeck_results().expr_ty(receiver);
        let expr_ty = cx.typeck_results().expr_ty_adjusted(expr);
        if receiver_ty != expr_ty {
            // This lint will only trigger if the receiver type and resulting expression \
            // type are the same, implying that the method call is unnecessary.
            return;
        }
        let expr_span = expr.span;
        let span = expr_span.with_lo(receiver.span.hi());
        cx.struct_span_lint(NOOP_METHOD_CALL, span, fluent::lint_noop_method_call, |lint| {
            lint.set_arg("method", call.ident.name)
                .set_arg("receiver_ty", receiver_ty)
                .span_label(span, fluent::label)
                .note(fluent::note)
        });
    }
}