rust/compiler/rustc_lint/src/noop_method_call.rs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

95 lines
3.6 KiB
Rust
Raw Normal View History

2021-01-05 09:07:50 +00:00
use crate::context::LintContext;
2022-09-05 16:42:58 +00:00
use crate::lints::NoopMethodCallDiag;
2021-01-05 09:07:50 +00:00
use crate::LateContext;
use crate::LateLintPass;
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)]
2021-02-17 09:06:23 +00:00
/// #![warn(noop_method_call)]
2021-01-05 09:07:50 +00:00
/// 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,
2021-02-16 21:39:05 +00:00
Allow,
2021-01-05 09:07:50 +00:00
"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<'_>) {
2021-01-13 18:39:25 +00:00
// We only care about method calls.
let ExprKind::MethodCall(call, receiver, ..) = &expr.kind else {
return
2021-01-13 18:39:25 +00:00
};
// 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) {
2021-01-13 18:39:25 +00:00
// 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)
2021-10-04 21:11:22 +00:00
if matches!(
cx.tcx.get_diagnostic_name(trait_id),
Some(sym::Borrow | sym::Clone | sym::Deref)
) =>
2021-01-13 18:39:25 +00:00
{
did
2021-01-05 09:07:50 +00:00
}
2021-01-13 18:39:25 +00:00
_ => return,
},
_ => return,
};
let substs = cx
.tcx
.normalize_erasing_regions(cx.param_env, cx.typeck_results().node_substs(expr.hir_id));
2021-01-13 18:39:25 +00:00
// Resolve the trait method instance.
let Ok(Some(i)) = ty::Instance::resolve(cx.tcx, cx.param_env, did, substs) else {
return
2021-01-13 18:39:25 +00:00
};
// (Re)check that it implements the noop diagnostic.
2021-11-16 19:12:38 +00:00
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;
2021-01-05 09:07:50 +00:00
}
2021-11-16 19:12:38 +00:00
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());
2022-09-05 16:42:58 +00:00
cx.emit_spanned_lint(
NOOP_METHOD_CALL,
span,
NoopMethodCallDiag { method: call.ident.name, receiver_ty, label: span },
);
2021-01-05 09:07:50 +00:00
}
}