| 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}; |
| use rustc_lint::LateContext; |
| use rustc_middle::ty; |
| use rustc_middle::ty::adjustment::Adjust; |
| use rustc_middle::ty::print::with_forced_trimmed_paths; |
| |
| use super::CLONE_ON_COPY; |
| |
| /// Checks for the `CLONE_ON_COPY` lint. |
| pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) { |
| if cx |
| .typeck_results() |
| .type_dependent_def_id(expr.hir_id) |
| .and_then(|id| cx.tcx.trait_of_assoc(id)) |
| .zip(cx.tcx.lang_items().clone_trait()) |
| .is_none_or(|(x, y)| x != y) |
| { |
| return; |
| } |
| let arg_adjustments = cx.typeck_results().expr_adjustments(receiver); |
| let arg_ty = arg_adjustments |
| .last() |
| .map_or_else(|| cx.typeck_results().expr_ty(receiver), |a| a.target); |
| |
| let ty = cx.typeck_results().expr_ty(expr); |
| if let ty::Ref(_, inner, _) = arg_ty.kind() |
| && 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) |
| if cx.tcx.qpath_is_lang_item(qpath, 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, receiver.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, |
| ); |
| } |
| } |