rustc_lint/noop_method_call.rs
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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
use rustc_hir::def::DefKind;
use rustc_hir::{Expr, ExprKind};
use rustc_middle::ty;
use rustc_middle::ty::adjustment::Adjust;
use rustc_session::{declare_lint, declare_lint_pass};
use rustc_span::symbol::sym;
use crate::context::LintContext;
use crate::lints::{
NoopMethodCallDiag, SuspiciousDoubleRefCloneDiag, SuspiciousDoubleRefDerefDiag,
};
use crate::{LateContext, LateLintPass};
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)]
/// 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,
Warn,
"detects the use of well-known noop methods"
}
declare_lint! {
/// The `suspicious_double_ref_op` lint checks for usage of `.clone()`/`.borrow()`/`.deref()`
/// on an `&&T` when `T: !Deref/Borrow/Clone`, which means the call will return the inner `&T`,
/// instead of performing the operation on the underlying `T` and can be confusing.
///
/// ### Example
///
/// ```rust
/// # #![allow(unused)]
/// struct Foo;
/// let foo = &&Foo;
/// let clone: &Foo = foo.clone();
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// Since `Foo` doesn't implement `Clone`, running `.clone()` only dereferences the double
/// reference, instead of cloning the inner type which should be what was intended.
pub SUSPICIOUS_DOUBLE_REF_OP,
Warn,
"suspicious call of trait method on `&&T`"
}
declare_lint_pass!(NoopMethodCall => [NOOP_METHOD_CALL, SUSPICIOUS_DOUBLE_REF_OP]);
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, _, call_span) = &expr.kind else {
return;
};
if call_span.from_expansion() {
return;
}
// We only care about method calls corresponding to the `Clone`, `Deref` and `Borrow`
// traits and ignore any other method call.
let Some((DefKind::AssocFn, did)) = cx.typeck_results().type_dependent_def(expr.hir_id)
else {
return;
};
let Some(trait_id) = cx.tcx.trait_of_item(did) else { return };
let Some(trait_) = cx.tcx.get_diagnostic_name(trait_id) else { return };
if !matches!(trait_, sym::Borrow | sym::Clone | sym::Deref) {
return;
};
let args = cx
.tcx
.normalize_erasing_regions(cx.typing_env(), cx.typeck_results().node_args(expr.hir_id));
// Resolve the trait method instance.
let Ok(Some(i)) = ty::Instance::try_resolve(cx.tcx, cx.typing_env(), did, args) 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);
let arg_adjustments = cx.typeck_results().expr_adjustments(receiver);
// If there is any user defined auto-deref step, then we don't want to warn.
// https://github.com/rust-lang/rust-clippy/issues/9272
if arg_adjustments.iter().any(|adj| matches!(adj.kind, Adjust::Deref(Some(_)))) {
return;
}
let expr_span = expr.span;
let span = expr_span.with_lo(receiver.span.hi());
let orig_ty = expr_ty.peel_refs();
if receiver_ty == expr_ty {
let suggest_derive = match orig_ty.kind() {
ty::Adt(def, _) => Some(cx.tcx.def_span(def.did()).shrink_to_lo()),
_ => None,
};
cx.emit_span_lint(NOOP_METHOD_CALL, span, NoopMethodCallDiag {
method: call.ident.name,
orig_ty,
trait_,
label: span,
suggest_derive,
});
} else {
match name {
// If `type_of(x) == T` and `x.borrow()` is used to get `&T`,
// then that should be allowed
sym::noop_method_borrow => return,
sym::noop_method_clone => cx.emit_span_lint(
SUSPICIOUS_DOUBLE_REF_OP,
span,
SuspiciousDoubleRefCloneDiag { ty: expr_ty },
),
sym::noop_method_deref => cx.emit_span_lint(
SUSPICIOUS_DOUBLE_REF_OP,
span,
SuspiciousDoubleRefDerefDiag { ty: expr_ty },
),
_ => return,
}
}
}
}