use crate::context::LintContext; use crate::rustc_middle::ty::TypeFoldable; 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)] /// #![warn(noop_method_call)] /// 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, Allow, "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<'_>) { // We only care about method calls. let (call, elements) = match expr.kind { ExprKind::MethodCall(call, _, elements, _) => (call, elements), _ => return, }; // We only care about method calls corresponding to the `Clone`, `Deref` and `Borrow` // traits and ignore any other method call. let (trait_id, did) = match cx.typeck_results().type_dependent_def(expr.hir_id) { // 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) if matches!( cx.tcx.get_diagnostic_name(trait_id), Some(sym::Borrow | sym::Clone | sym::Deref) ) => { (trait_id, did) } _ => return, }, _ => return, }; let substs = cx.typeck_results().node_substs(expr.hir_id); if substs.definitely_needs_subst(cx.tcx) { // We can't resolve on types that require monomorphization, so we don't handle them if // we need to perfom substitution. return; } let param_env = cx.tcx.param_env(trait_id); // Resolve the trait method instance. let i = match ty::Instance::resolve(cx.tcx, param_env, did, substs) { Ok(Some(i)) => i, _ => return, }; // (Re)check that it implements the noop diagnostic. for s in [sym::noop_method_clone, sym::noop_method_deref, sym::noop_method_borrow].iter() { if cx.tcx.is_diagnostic_item(*s, i.def_id()) { let method = &call.ident.name; let receiver = &elements[0]; 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 note = format!( "the type `{:?}` which `{}` is being called on is the same as \ the type returned from `{}`, so the method call does not do \ anything and can be removed", receiver_ty, method, method, ); let span = expr_span.with_lo(receiver.span.hi()); cx.struct_span_lint(NOOP_METHOD_CALL, span, |lint| { let method = &call.ident.name; let message = format!( "call to `.{}()` on a reference in this situation does nothing", &method, ); lint.build(&message) .span_label(span, "unnecessary method call") .note(¬e) .emit() }); } } } }