158 lines
5.4 KiB
Rust
158 lines
5.4 KiB
Rust
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.param_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.param_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,
|
|
}
|
|
}
|
|
}
|
|
}
|