]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_lint/src/noop_method_call.rs
3fba95f0750d8013f7b63dc77c00d2925749c9f2
[rust.git] / compiler / rustc_lint / src / noop_method_call.rs
1 #![deny(rustc::untranslatable_diagnostic)]
2 #![deny(rustc::diagnostic_outside_of_impl)]
3 use crate::context::LintContext;
4 use crate::lints::NoopMethodCallDiag;
5 use crate::rustc_middle::ty::TypeVisitable;
6 use crate::LateContext;
7 use crate::LateLintPass;
8 use rustc_hir::def::DefKind;
9 use rustc_hir::{Expr, ExprKind};
10 use rustc_middle::ty;
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_pass!(NoopMethodCall => [NOOP_METHOD_CALL]);
42
43 impl<'tcx> LateLintPass<'tcx> for NoopMethodCall {
44     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
45         // We only care about method calls.
46         let ExprKind::MethodCall(call, receiver, ..) = &expr.kind else {
47             return
48         };
49         // We only care about method calls corresponding to the `Clone`, `Deref` and `Borrow`
50         // traits and ignore any other method call.
51         let did = match cx.typeck_results().type_dependent_def(expr.hir_id) {
52             // Verify we are dealing with a method/associated function.
53             Some((DefKind::AssocFn, did)) => match cx.tcx.trait_of_item(did) {
54                 // Check that we're dealing with a trait method for one of the traits we care about.
55                 Some(trait_id)
56                     if matches!(
57                         cx.tcx.get_diagnostic_name(trait_id),
58                         Some(sym::Borrow | sym::Clone | sym::Deref)
59                     ) =>
60                 {
61                     did
62                 }
63                 _ => return,
64             },
65             _ => return,
66         };
67         let substs = cx
68             .tcx
69             .normalize_erasing_regions(cx.param_env, cx.typeck_results().node_substs(expr.hir_id));
70         // Resolve the trait method instance.
71         let Ok(Some(i)) = ty::Instance::resolve(cx.tcx, cx.param_env, did, substs) else {
72             return
73         };
74         // (Re)check that it implements the noop diagnostic.
75         let Some(name) = cx.tcx.get_diagnostic_name(i.def_id()) else { return };
76         if !matches!(
77             name,
78             sym::noop_method_borrow | sym::noop_method_clone | sym::noop_method_deref
79         ) {
80             return;
81         }
82         let receiver_ty = cx.typeck_results().expr_ty(receiver);
83         let expr_ty = cx.typeck_results().expr_ty_adjusted(expr);
84         if receiver_ty != expr_ty {
85             // This lint will only trigger if the receiver type and resulting expression \
86             // type are the same, implying that the method call is unnecessary.
87             return;
88         }
89         let expr_span = expr.span;
90         let span = expr_span.with_lo(receiver.span.hi());
91         cx.emit_spanned_lint(
92             NOOP_METHOD_CALL,
93             span,
94             NoopMethodCallDiag { method: call.ident.name, receiver_ty, label: span },
95         );
96     }
97 }