| 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, | |
| ); | |
| } | |
| } |