Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
&& let [source] = &**args
1use clippy_config::Conf;2use clippy_utils::diagnostics::span_lint_and_then;3use clippy_utils::mir::{PossibleBorrowerMap, enclosing_mir};4use clippy_utils::msrvs::{self, Msrv};5use clippy_utils::res::{MaybeDef, MaybeResPath};6use clippy_utils::sugg::Sugg;7use clippy_utils::{is_in_test, last_path_segment, local_is_initialized, sym};8use rustc_errors::Applicability;9use rustc_hir::{self as hir, Expr, ExprKind};10use rustc_lint::{LateContext, LateLintPass};11use rustc_middle::mir;12use rustc_middle::ty::{self, Instance, Mutability};13use rustc_session::impl_lint_pass;14use rustc_span::{Span, SyntaxContext};1516declare_clippy_lint! {17 /// ### What it does18 /// Checks for code like `foo = bar.clone();`19 ///20 /// ### Why is this bad?21 /// Custom `Clone::clone_from()` or `ToOwned::clone_into` implementations allow the objects22 /// to share resources and therefore avoid allocations.23 ///24 /// ### Example25 /// ```rust26 /// struct Thing;27 ///28 /// impl Clone for Thing {29 /// fn clone(&self) -> Self { todo!() }30 /// fn clone_from(&mut self, other: &Self) { todo!() }31 /// }32 ///33 /// pub fn assign_to_ref(a: &mut Thing, b: Thing) {34 /// *a = b.clone();35 /// }36 /// ```37 /// Use instead:38 /// ```rust39 /// struct Thing;40 ///41 /// impl Clone for Thing {42 /// fn clone(&self) -> Self { todo!() }43 /// fn clone_from(&mut self, other: &Self) { todo!() }44 /// }45 ///46 /// pub fn assign_to_ref(a: &mut Thing, b: Thing) {47 /// a.clone_from(&b);48 /// }49 /// ```50 #[clippy::version = "1.78.0"]51 pub ASSIGNING_CLONES,52 pedantic,53 "assigning the result of cloning may be inefficient"54}5556impl_lint_pass!(AssigningClones => [ASSIGNING_CLONES]);5758pub struct AssigningClones {59 msrv: Msrv,60}6162impl AssigningClones {63 pub fn new(conf: &'static Conf) -> Self {64 Self { msrv: conf.msrv }65 }66}6768impl<'tcx> LateLintPass<'tcx> for AssigningClones {69 fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {70 if let ExprKind::Assign(lhs, rhs, _) = e.kind71 && let typeck = cx.typeck_results()72 && let (call_kind, fn_name, fn_def, fn_arg, fn_gen_args) = match rhs.kind {73 ExprKind::Call(f, [arg])74 if let ExprKind::Path(fn_path) = &f.kind75 && let Some(def) = typeck.qpath_res(fn_path, f.hir_id).opt_def(cx) =>76 {77 (CallKind::Ufcs, last_path_segment(fn_path).ident.name, def, arg, typeck.node_args(f.hir_id))78 },79 ExprKind::MethodCall(name, recv, [], _) if let Some(def) = typeck.type_dependent_def(rhs.hir_id) => {80 (CallKind::Method, name.ident.name, def, recv, typeck.node_args(rhs.hir_id))81 },82 _ => return,83 }84 && let ctxt = e.span.ctxt()85 // Don't lint in macros.86 && ctxt.is_root()87 && let which_trait = match fn_name {88 sym::clone if fn_def.assoc_fn_parent(cx).is_diag_item(cx, sym::Clone) => CloneTrait::Clone,89 sym::to_owned90 if fn_def.assoc_fn_parent(cx).is_diag_item(cx, sym::ToOwned)91 && self.msrv.meets(cx, msrvs::CLONE_INTO) =>92 {93 CloneTrait::ToOwned94 },95 _ => return,96 }97 && let Ok(Some(resolved_fn)) = Instance::try_resolve(cx.tcx, cx.typing_env(), fn_def.1, fn_gen_args)98 // TODO: This check currently bails if the local variable has no initializer.99 // That is overly conservative - the lint should fire even if there was no initializer,100 // but the variable has been initialized before `lhs` was evaluated.101 && lhs.res_local_id().is_none_or(|lhs| local_is_initialized(cx, lhs))102 && let Some(resolved_impl) = cx.tcx.impl_of_assoc(resolved_fn.def_id())103 // Derived forms don't implement `clone_from`/`clone_into`.104 // See https://github.com/rust-lang/rust/pull/98445#issuecomment-1190681305105 && !cx.tcx.is_builtin_derived(resolved_impl)106 // Don't suggest calling a function we're implementing.107 && resolved_impl.as_local().is_none_or(|block_id| {108 cx.tcx.hir_parent_owner_iter(e.hir_id).all(|(id, _)| id.def_id != block_id)109 })110 && let resolved_assoc_items = cx.tcx.associated_items(resolved_impl)111 // Only suggest if `clone_from`/`clone_into` is explicitly implemented112 && resolved_assoc_items.in_definition_order().any(|assoc|113 match which_trait {114 CloneTrait::Clone => assoc.name() == sym::clone_from,115 CloneTrait::ToOwned => assoc.name() == sym::clone_into,116 }117 )118 && !clone_source_borrows_from_dest(cx, lhs, rhs.span)119 && !is_in_test(cx.tcx, e.hir_id)120 {121 span_lint_and_then(122 cx,123 ASSIGNING_CLONES,124 e.span,125 match which_trait {126 CloneTrait::Clone => "assigning the result of `Clone::clone()` may be inefficient",127 CloneTrait::ToOwned => "assigning the result of `ToOwned::to_owned()` may be inefficient",128 },129 |diag| {130 let mut app = Applicability::Unspecified;131 diag.span_suggestion(132 e.span,133 match which_trait {134 CloneTrait::Clone => "use `clone_from()`",135 CloneTrait::ToOwned => "use `clone_into()`",136 },137 build_sugg(cx, ctxt, lhs, fn_arg, which_trait, call_kind, &mut app),138 app,139 );140 },141 );142 }143 }144}145146/// Checks if the data being cloned borrows from the place that is being assigned to:147///148/// ```149/// let mut s = String::new();150/// let s2 = &s;151/// s = s2.to_owned();152/// ```153///154/// This cannot be written `s2.clone_into(&mut s)` because it has conflicting borrows.155fn clone_source_borrows_from_dest(cx: &LateContext<'_>, lhs: &Expr<'_>, call_span: Span) -> bool {156 let Some(mir) = enclosing_mir(cx.tcx, lhs.hir_id) else {157 return false;158 };159 let PossibleBorrowerMap { map: borrow_map, .. } = PossibleBorrowerMap::new(cx, mir);160161 // The operation `dest = src.to_owned()` in MIR is split up across 3 blocks *if* the type has `Drop`162 // code. For types that don't, the second basic block is simply skipped.163 // For the doc example above that would be roughly:164 //165 // bb0:166 // s2 = &s167 // s_temp = ToOwned::to_owned(move s2) -> bb1168 //169 // bb1:170 // drop(s) -> bb2 // drop the old string171 //172 // bb2:173 // s = s_temp174 if let Some(terminator) = mir.basic_blocks.iter()175 .map(mir::BasicBlockData::terminator)176 .find(|term| term.source_info.span == call_span)177 && let mir::TerminatorKind::Call { ref args, target: Some(assign_bb), .. } = terminator.kind178 && let [source] = &**args179 && let mir::Operand::Move(source) = &source.node180 && let assign_bb = &mir.basic_blocks[assign_bb]181 && let assign_bb = match assign_bb.terminator().kind {182 // Skip the drop of the assignment's destination.183 mir::TerminatorKind::Drop { target, .. } => &mir.basic_blocks[target],184 _ => assign_bb,185 }186 // Skip any storage statements as they are just noise187 && let Some(assignment) = assign_bb.statements188 .iter()189 .find(|stmt| {190 !matches!(stmt.kind, mir::StatementKind::StorageDead(_) | mir::StatementKind::StorageLive(_))191 })192 && let mir::StatementKind::Assign(box (borrowed, _)) = &assignment.kind193 && let Some(borrowers) = borrow_map.get(&borrowed.local)194 {195 borrowers.contains(source.local)196 } else {197 false198 }199}200201#[derive(Clone, Copy)]202enum CloneTrait {203 Clone,204 ToOwned,205}206207#[derive(Copy, Clone)]208enum CallKind {209 Ufcs,210 Method,211}212213fn build_sugg<'tcx>(214 cx: &LateContext<'tcx>,215 ctxt: SyntaxContext,216 lhs: &'tcx Expr<'_>,217 fn_arg: &'tcx Expr<'_>,218 which_trait: CloneTrait,219 call_kind: CallKind,220 app: &mut Applicability,221) -> String {222 match which_trait {223 CloneTrait::Clone => {224 match call_kind {225 CallKind::Method => {226 let receiver_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind {227 // If `ref_expr` is a reference, we can remove the dereference operator (`*`) to make228 // the generated code a bit simpler. In other cases, we don't do this special case, to avoid229 // having to deal with Deref (https://github.com/rust-lang/rust-clippy/issues/12437).230231 let ty = cx.typeck_results().expr_ty(ref_expr);232 if ty.is_ref() {233 // Apply special case, remove `*`234 // `*lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)`235 Sugg::hir_with_applicability(cx, ref_expr, "_", app)236 } else {237 // Keep the original lhs238 // `*lhs = self_expr.clone();` -> `(*lhs).clone_from(self_expr)`239 Sugg::hir_with_applicability(cx, lhs, "_", app)240 }241 } else {242 // Keep the original lhs243 // `lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)`244 Sugg::hir_with_applicability(cx, lhs, "_", app)245 }246 .maybe_paren();247248 // Determine whether we need to reference the argument to clone_from().249 let clone_receiver_type = cx.typeck_results().expr_ty(fn_arg);250 let clone_receiver_adj_type = cx.typeck_results().expr_ty_adjusted(fn_arg);251 let mut arg_sugg = Sugg::hir_with_context(cx, fn_arg, ctxt, "_", app);252 if clone_receiver_type != clone_receiver_adj_type {253 // The receiver may have been a value type, so we need to add an `&` to254 // be sure the argument to clone_from will be a reference.255 arg_sugg = arg_sugg.addr();256 }257258 format!("{receiver_sugg}.clone_from({arg_sugg})")259 },260 CallKind::Ufcs => {261 let self_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind {262 // See special case of removing `*` in method handling above263 let ty = cx.typeck_results().expr_ty(ref_expr);264 if ty.is_ref() {265 // `*lhs = Clone::clone(self_expr);` -> `Clone::clone_from(lhs, self_expr)`266 Sugg::hir_with_applicability(cx, ref_expr, "_", app)267 } else {268 // `*lhs = Clone::clone(self_expr);` -> `Clone::clone_from(&mut *lhs, self_expr)`269 // mut_addr_deref is used to avoid unnecessary parentheses around `*lhs`270 Sugg::hir_with_applicability(cx, ref_expr, "_", app).mut_addr_deref()271 }272 } else {273 // `lhs = Clone::clone(self_expr);` -> `Clone::clone_from(&mut lhs, self_expr)`274 Sugg::hir_with_applicability(cx, lhs, "_", app).mut_addr()275 };276 // The RHS had to be exactly correct before the call, there is no auto-deref for function calls.277 let rhs_sugg = Sugg::hir_with_context(cx, fn_arg, ctxt, "_", app);278279 format!("Clone::clone_from({self_sugg}, {rhs_sugg})")280 },281 }282 },283 CloneTrait::ToOwned => {284 let rhs_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind {285 // `*lhs = rhs.to_owned()` -> `rhs.clone_into(lhs)`286 // `*lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, lhs)`287 let sugg = Sugg::hir_with_applicability(cx, ref_expr, "_", app).maybe_paren();288 let inner_type = cx.typeck_results().expr_ty(ref_expr);289 // If after unwrapping the dereference, the type is not a mutable reference, we add &mut to make it290 // deref to a mutable reference.291 if matches!(inner_type.kind(), ty::Ref(_, _, Mutability::Mut)) {292 sugg293 } else {294 sugg.mut_addr()295 }296 } else {297 // `lhs = rhs.to_owned()` -> `rhs.clone_into(&mut lhs)`298 // `lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, &mut lhs)`299 Sugg::hir_with_applicability(cx, lhs, "_", app).maybe_paren().mut_addr()300 };301302 match call_kind {303 CallKind::Method => {304 let receiver_sugg = Sugg::hir_with_context(cx, fn_arg, ctxt, "_", app);305 format!("{receiver_sugg}.clone_into({rhs_sugg})")306 },307 CallKind::Ufcs => {308 let self_sugg = Sugg::hir_with_context(cx, fn_arg, ctxt, "_", app);309 format!("ToOwned::clone_into({self_sugg}, {rhs_sugg})")310 },311 }312 },313 }314}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.