Skip to main content

rustc_lint/
noop_method_call.rs

1use rustc_hir::def::DefKind;
2use rustc_hir::{Expr, ExprKind};
3use rustc_middle::ty;
4use rustc_middle::ty::Unnormalized;
5use rustc_middle::ty::adjustment::{Adjust, DerefAdjustKind};
6use rustc_session::{declare_lint, declare_lint_pass};
7use rustc_span::sym;
8
9use crate::context::LintContext;
10use crate::lints::{
11    NoopMethodCallDiag, SuspiciousDoubleRefCloneDiag, SuspiciousDoubleRefDerefDiag,
12};
13use crate::{LateContext, LateLintPass};
14
15#[doc =
r" The `noop_method_call` lint detects specific calls to noop methods"]
#[doc = r" such as a calling `<&T as Clone>::clone` where `T: !Clone`."]
#[doc = r""]
#[doc = r" ### Example"]
#[doc = r""]
#[doc = r" ```rust"]
#[doc = r" # #![allow(unused)]"]
#[doc = r" struct Foo;"]
#[doc = r" let foo = &Foo;"]
#[doc = r" let clone: &Foo = foo.clone();"]
#[doc = r" ```"]
#[doc = r""]
#[doc = r" {{produces}}"]
#[doc = r""]
#[doc = r" ### Explanation"]
#[doc = r""]
#[doc =
r" Some method calls are noops meaning that they do nothing. Usually such methods"]
#[doc =
r" are the result of blanket implementations that happen to create some method invocations"]
#[doc =
r" that end up not doing anything. For instance, `Clone` is implemented on all `&T`, but"]
#[doc =
r" calling `clone` on a `&T` where `T` does not implement clone, actually doesn't do anything"]
#[doc =
r" as references are copy. This lint detects these calls and warns the user about them."]
pub static NOOP_METHOD_CALL: &::rustc_lint_defs::Lint =
    &::rustc_lint_defs::Lint {
            name: "NOOP_METHOD_CALL",
            default_level: ::rustc_lint_defs::Warn,
            desc: "detects the use of well-known noop methods",
            is_externally_loaded: false,
            ..::rustc_lint_defs::Lint::default_fields_for_macro()
        };declare_lint! {
16    /// The `noop_method_call` lint detects specific calls to noop methods
17    /// such as a calling `<&T as Clone>::clone` where `T: !Clone`.
18    ///
19    /// ### Example
20    ///
21    /// ```rust
22    /// # #![allow(unused)]
23    /// struct Foo;
24    /// let foo = &Foo;
25    /// let clone: &Foo = foo.clone();
26    /// ```
27    ///
28    /// {{produces}}
29    ///
30    /// ### Explanation
31    ///
32    /// Some method calls are noops meaning that they do nothing. Usually such methods
33    /// are the result of blanket implementations that happen to create some method invocations
34    /// that end up not doing anything. For instance, `Clone` is implemented on all `&T`, but
35    /// calling `clone` on a `&T` where `T` does not implement clone, actually doesn't do anything
36    /// as references are copy. This lint detects these calls and warns the user about them.
37    pub NOOP_METHOD_CALL,
38    Warn,
39    "detects the use of well-known noop methods"
40}
41
42#[doc =
r" The `suspicious_double_ref_op` lint checks for usage of `.clone()`/`.borrow()`/`.deref()`"]
#[doc =
r" on an `&&T` when `T: !Deref/Borrow/Clone`, which means the call will return the inner `&T`,"]
#[doc =
r" instead of performing the operation on the underlying `T` and can be confusing."]
#[doc = r""]
#[doc = r" ### Example"]
#[doc = r""]
#[doc = r" ```rust"]
#[doc = r" # #![allow(unused)]"]
#[doc = r" struct Foo;"]
#[doc = r" let foo = &&Foo;"]
#[doc = r" let clone: &Foo = foo.clone();"]
#[doc = r" ```"]
#[doc = r""]
#[doc = r" {{produces}}"]
#[doc = r""]
#[doc = r" ### Explanation"]
#[doc = r""]
#[doc =
r" Since `Foo` doesn't implement `Clone`, running `.clone()` only dereferences the double"]
#[doc =
r" reference, instead of cloning the inner type which should be what was intended."]
pub static SUSPICIOUS_DOUBLE_REF_OP: &::rustc_lint_defs::Lint =
    &::rustc_lint_defs::Lint {
            name: "SUSPICIOUS_DOUBLE_REF_OP",
            default_level: ::rustc_lint_defs::Warn,
            desc: "suspicious call of trait method on `&&T`",
            is_externally_loaded: false,
            ..::rustc_lint_defs::Lint::default_fields_for_macro()
        };declare_lint! {
43    /// The `suspicious_double_ref_op` lint checks for usage of `.clone()`/`.borrow()`/`.deref()`
44    /// on an `&&T` when `T: !Deref/Borrow/Clone`, which means the call will return the inner `&T`,
45    /// instead of performing the operation on the underlying `T` and can be confusing.
46    ///
47    /// ### Example
48    ///
49    /// ```rust
50    /// # #![allow(unused)]
51    /// struct Foo;
52    /// let foo = &&Foo;
53    /// let clone: &Foo = foo.clone();
54    /// ```
55    ///
56    /// {{produces}}
57    ///
58    /// ### Explanation
59    ///
60    /// Since `Foo` doesn't implement `Clone`, running `.clone()` only dereferences the double
61    /// reference, instead of cloning the inner type which should be what was intended.
62    pub SUSPICIOUS_DOUBLE_REF_OP,
63    Warn,
64    "suspicious call of trait method on `&&T`"
65}
66
67pub struct NoopMethodCall;
#[automatically_derived]
impl ::core::marker::Copy for NoopMethodCall { }
#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for NoopMethodCall { }
#[automatically_derived]
impl ::core::clone::Clone for NoopMethodCall {
    #[inline]
    fn clone(&self) -> NoopMethodCall { *self }
}
impl ::rustc_lint_defs::LintPass for NoopMethodCall {
    fn name(&self) -> &'static str { "NoopMethodCall" }
    fn get_lints(&self) -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [NOOP_METHOD_CALL, SUSPICIOUS_DOUBLE_REF_OP]))
    }
}
impl NoopMethodCall {
    #[allow(unused)]
    pub fn lint_vec() -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [NOOP_METHOD_CALL, SUSPICIOUS_DOUBLE_REF_OP]))
    }
}declare_lint_pass!(NoopMethodCall => [NOOP_METHOD_CALL, SUSPICIOUS_DOUBLE_REF_OP]);
68
69impl<'tcx> LateLintPass<'tcx> for NoopMethodCall {
70    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
71        // We only care about method calls.
72        let ExprKind::MethodCall(call, receiver, _, call_span) = &expr.kind else {
73            return;
74        };
75
76        if call_span.from_expansion() {
77            return;
78        }
79
80        // We only care about method calls corresponding to the `Clone`, `Deref` and `Borrow`
81        // traits and ignore any other method call.
82
83        let Some((DefKind::AssocFn, did)) = cx.typeck_results().type_dependent_def(expr.hir_id)
84        else {
85            return;
86        };
87
88        let Some(trait_id) = cx.tcx.trait_of_assoc(did) else { return };
89
90        let Some(trait_) = cx.tcx.get_diagnostic_name(trait_id) else { return };
91
92        if !#[allow(non_exhaustive_omitted_patterns)] match trait_ {
    sym::Borrow | sym::Clone | sym::Deref => true,
    _ => false,
}matches!(trait_, sym::Borrow | sym::Clone | sym::Deref) {
93            return;
94        };
95
96        let args = cx.tcx.normalize_erasing_regions(
97            cx.typing_env(),
98            Unnormalized::new_wip(cx.typeck_results().node_args(expr.hir_id)),
99        );
100        // Resolve the trait method instance.
101        let Ok(Some(i)) = ty::Instance::try_resolve(cx.tcx, cx.typing_env(), did, args) 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        if !#[allow(non_exhaustive_omitted_patterns)] match name {
    sym::noop_method_borrow | sym::noop_method_clone | sym::noop_method_deref
        => true,
    _ => false,
}matches!(
107            name,
108            sym::noop_method_borrow | sym::noop_method_clone | sym::noop_method_deref
109        ) {
110            return;
111        }
112
113        let receiver_ty = cx.typeck_results().expr_ty(receiver);
114        let expr_ty = cx.typeck_results().expr_ty_adjusted(expr);
115        let arg_adjustments = cx.typeck_results().expr_adjustments(receiver);
116
117        // If there is any user defined auto-deref step, then we don't want to warn.
118        // https://github.com/rust-lang/rust-clippy/issues/9272
119        if arg_adjustments
120            .iter()
121            .any(|adj| #[allow(non_exhaustive_omitted_patterns)] match adj.kind {
    Adjust::Deref(DerefAdjustKind::Overloaded(_)) => true,
    _ => false,
}matches!(adj.kind, Adjust::Deref(DerefAdjustKind::Overloaded(_))))
122        {
123            return;
124        }
125
126        let expr_span = expr.span;
127        let span = expr_span.with_lo(receiver.span.hi());
128
129        let orig_ty = expr_ty.peel_refs();
130
131        if receiver_ty == expr_ty {
132            let suggest_derive = match orig_ty.kind() {
133                ty::Adt(def, _) => Some(cx.tcx.def_span(def.did()).shrink_to_lo()),
134                _ => None,
135            };
136            cx.emit_span_lint(
137                NOOP_METHOD_CALL,
138                span,
139                NoopMethodCallDiag {
140                    method: call.ident,
141                    orig_ty,
142                    trait_,
143                    label: span,
144                    suggest_derive,
145                },
146            );
147        } else {
148            match name {
149                // If `type_of(x) == T` and `x.borrow()` is used to get `&T`,
150                // then that should be allowed
151                sym::noop_method_borrow => return,
152                sym::noop_method_clone => cx.emit_span_lint(
153                    SUSPICIOUS_DOUBLE_REF_OP,
154                    span,
155                    SuspiciousDoubleRefCloneDiag { ty: expr_ty },
156                ),
157                sym::noop_method_deref => cx.emit_span_lint(
158                    SUSPICIOUS_DOUBLE_REF_OP,
159                    span,
160                    SuspiciousDoubleRefDerefDiag { ty: expr_ty },
161                ),
162                _ => return,
163            }
164        }
165    }
166}