use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::is_copy; use rustc_errors::Applicability; use rustc_hir::{BindingMode, ByRef, Expr, ExprKind, MatchSource, Node, PatKind, QPath}; use rustc_lint::LateContext; use rustc_middle::ty::adjustment::Adjust; use rustc_middle::ty::print::with_forced_trimmed_paths; use rustc_middle::ty::{self}; use rustc_span::symbol::{Symbol, sym}; use super::CLONE_ON_COPY; /// Checks for the `CLONE_ON_COPY` lint. #[allow(clippy::too_many_lines)] pub(super) fn check( cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>, args: &[Expr<'_>], ) { let arg = if method_name == sym::clone && args.is_empty() { receiver } else { return; }; if cx .typeck_results() .type_dependent_def_id(expr.hir_id) .and_then(|id| cx.tcx.trait_of_item(id)) .zip(cx.tcx.lang_items().clone_trait()) .is_none_or(|(x, y)| x != y) { return; } let arg_adjustments = cx.typeck_results().expr_adjustments(arg); let arg_ty = arg_adjustments .last() .map_or_else(|| cx.typeck_results().expr_ty(arg), |a| a.target); let ty = cx.typeck_results().expr_ty(expr); if let ty::Ref(_, inner, _) = arg_ty.kind() { if let ty::Ref(..) = inner.kind() { return; // don't report clone_on_copy } } if is_copy(cx, ty) { let parent_is_suffix_expr = match cx.tcx.parent_hir_node(expr.hir_id) { Node::Expr(parent) => match parent.kind { // &*x is a nop, &x.clone() is not ExprKind::AddrOf(..) => return, // (*x).func() is useless, x.clone().func() can work in case func borrows self ExprKind::MethodCall(_, self_arg, ..) if expr.hir_id == self_arg.hir_id && ty != cx.typeck_results().expr_ty_adjusted(expr) => { return; }, // ? is a Call, makes sure not to rec *x?, but rather (*x)? ExprKind::Call(hir_callee, [_]) => matches!( hir_callee.kind, ExprKind::Path(QPath::LangItem(rustc_hir::LangItem::TryTraitBranch, ..)) ), ExprKind::MethodCall(_, self_arg, ..) if expr.hir_id == self_arg.hir_id => true, ExprKind::Match(_, _, MatchSource::TryDesugar(_) | MatchSource::AwaitDesugar) | ExprKind::Field(..) | ExprKind::Index(..) => true, _ => false, }, // local binding capturing a reference Node::LetStmt(l) if matches!(l.pat.kind, PatKind::Binding(BindingMode(ByRef::Yes(_), _), ..)) => { return; }, _ => false, }; let mut app = Applicability::MachineApplicable; let snip = snippet_with_context(cx, arg.span, expr.span.ctxt(), "_", &mut app).0; let deref_count = arg_adjustments .iter() .take_while(|adj| matches!(adj.kind, Adjust::Deref(_))) .count(); let (help, sugg) = if deref_count == 0 { ("try removing the `clone` call", snip.into()) } else if parent_is_suffix_expr { ("try dereferencing it", format!("({}{snip})", "*".repeat(deref_count))) } else { ("try dereferencing it", format!("{}{snip}", "*".repeat(deref_count))) }; span_lint_and_sugg( cx, CLONE_ON_COPY, expr.span, with_forced_trimmed_paths!(format!( "using `clone` on type `{ty}` which implements the `Copy` trait" )), help, sugg, app, ); } }