src/tools/clippy/clippy_lints/src/assigning_clones.rs RUST 315 lines View on github.com → Search inside
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}

Code quality findings 10

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>.
warning correctness unchecked-indexing
&& let [source] = &**args
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>.
warning correctness unchecked-indexing
&& let assign_bb = &mir.basic_blocks[assign_bb]
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>.
warning correctness unchecked-indexing
mir::TerminatorKind::Drop { target, .. } => &mir.basic_blocks[target],
Maintainability Info: `todo!()` or `unimplemented!()` macros indicate incomplete code paths that will panic at runtime if reached. Ensure these are replaced with actual logic before production use.
info correctness todo-unimplemented
/// fn clone(&self) -> Self { todo!() }
Maintainability Info: `todo!()` or `unimplemented!()` macros indicate incomplete code paths that will panic at runtime if reached. Ensure these are replaced with actual logic before production use.
info correctness todo-unimplemented
/// fn clone_from(&mut self, other: &Self) { todo!() }
Maintainability Info: `todo!()` or `unimplemented!()` macros indicate incomplete code paths that will panic at runtime if reached. Ensure these are replaced with actual logic before production use.
info correctness todo-unimplemented
/// fn clone(&self) -> Self { todo!() }
Maintainability Info: `todo!()` or `unimplemented!()` macros indicate incomplete code paths that will panic at runtime if reached. Ensure these are replaced with actual logic before production use.
info correctness todo-unimplemented
/// fn clone_from(&mut self, other: &Self) { todo!() }
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
&& let (call_kind, fn_name, fn_def, fn_arg, fn_gen_args) = match rhs.kind {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
&& let which_trait = match fn_name {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
&& let assign_bb = match assign_bb.terminator().kind {

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.