compiler/rustc_borrowck/src/diagnostics/move_errors.rs RUST 1,164 lines View on github.com → Search inside
1use rustc_abi::FieldIdx;2use rustc_data_structures::fx::FxHashSet;3use rustc_errors::{Applicability, Diag};4use rustc_hir::intravisit::Visitor;5use rustc_hir::{self as hir, CaptureBy, ExprKind, HirId, Node};6use rustc_middle::bug;7use rustc_middle::mir::*;8use rustc_middle::ty::{self, Ty, TyCtxt};9use rustc_mir_dataflow::move_paths::{LookupResult, MovePathIndex};10use rustc_span::def_id::DefId;11use rustc_span::{BytePos, ExpnKind, MacroKind, Span, sym};12use rustc_trait_selection::error_reporting::traits::FindExprBySpan;13use rustc_trait_selection::infer::InferCtxtExt;14use tracing::debug;1516use crate::MirBorrowckCtxt;17use crate::diagnostics::{18    BorrowedContentSource, CapturedMessageOpt, CloneSuggestion, DescribePlaceOpt, UseSpans,19};20use crate::prefixes::PrefixSet;2122#[derive(Debug)]23pub(crate) enum IllegalMoveOriginKind<'tcx> {24    /// Illegal move due to attempt to move from behind a reference.25    BorrowedContent {26        /// The place the reference refers to: if erroneous code was trying to27        /// move from `(*x).f` this will be `*x`.28        target_place: Place<'tcx>,29    },3031    /// Illegal move due to attempt to move from field of an ADT that32    /// implements `Drop`. Rust maintains invariant that all `Drop`33    /// ADT's remain fully-initialized so that user-defined destructor34    /// can safely read from all of the ADT's fields.35    InteriorOfTypeWithDestructor { container_ty: Ty<'tcx> },3637    /// Illegal move due to attempt to move out of a slice or array.38    InteriorOfSliceOrArray { ty: Ty<'tcx>, is_index: bool },39}4041#[derive(Debug)]42pub(crate) struct MoveError<'tcx> {43    place: Place<'tcx>,44    location: Location,45    kind: IllegalMoveOriginKind<'tcx>,46}4748impl<'tcx> MoveError<'tcx> {49    pub(crate) fn new(50        place: Place<'tcx>,51        location: Location,52        kind: IllegalMoveOriginKind<'tcx>,53    ) -> Self {54        MoveError { place, location, kind }55    }56}5758// Often when desugaring a pattern match we may have many individual moves in59// MIR that are all part of one operation from the user's point-of-view. For60// example:61//62// let (x, y) = foo()63//64// would move x from the 0 field of some temporary, and y from the 1 field. We65// group such errors together for cleaner error reporting.66//67// Errors are kept separate if they are from places with different parent move68// paths. For example, this generates two errors:69//70// let (&x, &y) = (&String::new(), &String::new());71#[derive(Debug)]72enum GroupedMoveError<'tcx> {73    // Place expression can't be moved from,74    // e.g., match x[0] { s => (), } where x: &[String]75    MovesFromPlace {76        original_path: Place<'tcx>,77        span: Span,78        move_from: Place<'tcx>,79        kind: IllegalMoveOriginKind<'tcx>,80        binds_to: Vec<Local>,81    },82    // Part of a value expression can't be moved from,83    // e.g., match &String::new() { &x => (), }84    MovesFromValue {85        original_path: Place<'tcx>,86        span: Span,87        move_from: MovePathIndex,88        kind: IllegalMoveOriginKind<'tcx>,89        binds_to: Vec<Local>,90    },91    // Everything that isn't from pattern matching.92    OtherIllegalMove {93        original_path: Place<'tcx>,94        use_spans: UseSpans<'tcx>,95        kind: IllegalMoveOriginKind<'tcx>,96    },97}9899impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {100    pub(crate) fn report_move_errors(&mut self) {101        let grouped_errors = self.group_move_errors();102        for error in grouped_errors {103            self.report(error);104        }105    }106107    fn group_move_errors(&mut self) -> Vec<GroupedMoveError<'tcx>> {108        let mut grouped_errors = Vec::new();109        let errors = std::mem::take(&mut self.move_errors);110        for error in errors {111            self.append_to_grouped_errors(&mut grouped_errors, error);112        }113        grouped_errors114    }115116    fn append_to_grouped_errors(117        &self,118        grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,119        MoveError { place: original_path, location, kind }: MoveError<'tcx>,120    ) {121        // Note: that the only time we assign a place isn't a temporary122        // to a user variable is when initializing it.123        // If that ever stops being the case, then the ever initialized124        // flow could be used.125        if let Some(StatementKind::Assign(box (place, Rvalue::Use(Operand::Move(move_from), _)))) =126            self.body.basic_blocks[location.block]127                .statements128                .get(location.statement_index)129                .map(|stmt| &stmt.kind)130            && let Some(local) = place.as_local()131        {132            let local_decl = &self.body.local_decls[local];133            // opt_match_place is the134            // match_span is the span of the expression being matched on135            // match *x.y { ... }        match_place is Some(*x.y)136            //       ^^^^                match_span is the span of *x.y137            //138            // opt_match_place is None for let [mut] x = ... statements,139            // whether or not the right-hand side is a place expression140            if let LocalInfo::User(BindingForm::Var(VarBindingForm {141                opt_match_place: Some((opt_match_place, match_span)),142                ..143            })) = *local_decl.local_info()144            {145                let stmt_source_info = self.body.source_info(location);146                self.append_binding_error(147                    grouped_errors,148                    kind,149                    original_path,150                    *move_from,151                    local,152                    opt_match_place,153                    match_span,154                    stmt_source_info.span,155                );156                return;157            }158        }159160        let move_spans = self.move_spans(original_path.as_ref(), location);161        grouped_errors.push(GroupedMoveError::OtherIllegalMove {162            use_spans: move_spans,163            original_path,164            kind,165        });166    }167168    fn append_binding_error(169        &self,170        grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,171        kind: IllegalMoveOriginKind<'tcx>,172        original_path: Place<'tcx>,173        move_from: Place<'tcx>,174        bind_to: Local,175        match_place: Option<Place<'tcx>>,176        match_span: Span,177        statement_span: Span,178    ) {179        debug!(?match_place, ?match_span, "append_binding_error");180181        let from_simple_let = match_place.is_none();182        let match_place = match_place.unwrap_or(move_from);183184        match self.move_data.rev_lookup.find(match_place.as_ref()) {185            // Error with the match place186            LookupResult::Parent(_) => {187                for ge in &mut *grouped_errors {188                    if let GroupedMoveError::MovesFromPlace { span, binds_to, .. } = ge189                        && match_span == *span190                    {191                        debug!("appending local({bind_to:?}) to list");192                        if !binds_to.is_empty() {193                            binds_to.push(bind_to);194                        }195                        return;196                    }197                }198                debug!("found a new move error location");199200                // Don't need to point to x in let x = ... .201                let (binds_to, span) = if from_simple_let {202                    (vec![], statement_span)203                } else {204                    (vec![bind_to], match_span)205                };206                grouped_errors.push(GroupedMoveError::MovesFromPlace {207                    span,208                    move_from,209                    original_path,210                    kind,211                    binds_to,212                });213            }214            // Error with the pattern215            LookupResult::Exact(_) => {216                let LookupResult::Parent(Some(mpi)) =217                    self.move_data.rev_lookup.find(move_from.as_ref())218                else {219                    // move_from should be a projection from match_place.220                    unreachable!("Probably not unreachable...");221                };222                for ge in &mut *grouped_errors {223                    if let GroupedMoveError::MovesFromValue {224                        span,225                        move_from: other_mpi,226                        binds_to,227                        ..228                    } = ge229                    {230                        if match_span == *span && mpi == *other_mpi {231                            debug!("appending local({bind_to:?}) to list");232                            binds_to.push(bind_to);233                            return;234                        }235                    }236                }237                debug!("found a new move error location");238                grouped_errors.push(GroupedMoveError::MovesFromValue {239                    span: match_span,240                    move_from: mpi,241                    original_path,242                    kind,243                    binds_to: vec![bind_to],244                });245            }246        };247    }248249    fn report(&mut self, error: GroupedMoveError<'tcx>) {250        let (span, use_spans, original_path, kind) = match error {251            GroupedMoveError::MovesFromPlace { span, original_path, ref kind, .. }252            | GroupedMoveError::MovesFromValue { span, original_path, ref kind, .. } => {253                (span, None, original_path, kind)254            }255            GroupedMoveError::OtherIllegalMove { use_spans, original_path, ref kind } => {256                (use_spans.args_or_use(), Some(use_spans), original_path, kind)257            }258        };259        debug!(260            "report: original_path={:?} span={:?}, kind={:?} \261             original_path.is_upvar_field_projection={:?}",262            original_path,263            span,264            kind,265            self.is_upvar_field_projection(original_path.as_ref())266        );267        if self.has_ambiguous_copy(original_path.ty(self.body, self.infcx.tcx).ty) {268            // If the type may implement Copy, skip the error.269            // It's an error with the Copy implementation (e.g. duplicate Copy) rather than borrow check270            self.dcx()271                .span_delayed_bug(span, "Type may implement copy, but there is no other error.");272            return;273        }274275        let mut has_clone_suggestion = CloneSuggestion::NotEmitted;276        let mut err = match kind {277            &IllegalMoveOriginKind::BorrowedContent { target_place } => {278                let (diag, clone_sugg) = self.report_cannot_move_from_borrowed_content(279                    original_path,280                    target_place,281                    span,282                    use_spans,283                );284                has_clone_suggestion = clone_sugg;285                diag286            }287            &IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => {288                self.cannot_move_out_of_interior_of_drop(span, ty)289            }290            &IllegalMoveOriginKind::InteriorOfSliceOrArray { ty, is_index } => {291                self.cannot_move_out_of_interior_noncopy(span, ty, Some(is_index))292            }293        };294295        self.add_move_hints(error, &mut err, span, has_clone_suggestion);296        self.buffer_error(err);297    }298299    fn has_ambiguous_copy(&mut self, ty: Ty<'tcx>) -> bool {300        let Some(copy_def_id) = self.infcx.tcx.lang_items().copy_trait() else { return false };301302        // Avoid bogus move errors because of an incoherent `Copy` impl.303        self.infcx.type_implements_trait(copy_def_id, [ty], self.infcx.param_env).may_apply()304            && self.infcx.tcx.ensure_result().coherent_trait(copy_def_id).is_err()305    }306307    fn report_cannot_move_from_static(&mut self, place: Place<'tcx>, span: Span) -> Diag<'infcx> {308        let description = if place.projection.len() == 1 {309            format!("static item {}", self.describe_any_place(place.as_ref()))310        } else {311            let base_static = PlaceRef { local: place.local, projection: &[ProjectionElem::Deref] };312313            format!(314                "{} as {} is a static item",315                self.describe_any_place(place.as_ref()),316                self.describe_any_place(base_static),317            )318        };319320        self.cannot_move_out_of(span, &description)321    }322323    pub(in crate::diagnostics) fn suggest_clone_of_captured_var_in_move_closure(324        &self,325        err: &mut Diag<'_>,326        upvar_name: &str,327        use_spans: Option<UseSpans<'tcx>>,328    ) {329        let tcx = self.infcx.tcx;330        let Some(use_spans) = use_spans else { return };331        // We only care about the case where a closure captured a binding.332        let UseSpans::ClosureUse { args_span, .. } = use_spans else { return };333        let Some(body_id) = tcx.hir_node(self.mir_hir_id()).body_id() else { return };334        // Find the closure that captured the binding.335        let mut expr_finder = FindExprBySpan::new(args_span, tcx);336        expr_finder.include_closures = true;337        expr_finder.visit_expr(tcx.hir_body(body_id).value);338        let Some(closure_expr) = expr_finder.result else { return };339        let ExprKind::Closure(closure) = closure_expr.kind else { return };340        // We'll only suggest cloning the binding if it's a `move` closure.341        let CaptureBy::Value { .. } = closure.capture_clause else { return };342        // Find the expression within the closure where the binding is consumed.343        let mut suggested = false;344        let use_span = use_spans.var_or_use();345        let mut expr_finder = FindExprBySpan::new(use_span, tcx);346        expr_finder.include_closures = true;347        expr_finder.visit_expr(tcx.hir_body(body_id).value);348        let Some(use_expr) = expr_finder.result else { return };349        let parent = tcx.parent_hir_node(use_expr.hir_id);350        if let Node::Expr(expr) = parent351            && let ExprKind::Assign(lhs, ..) = expr.kind352            && lhs.hir_id == use_expr.hir_id353        {354            // Cloning the value being assigned makes no sense:355            //356            // error[E0507]: cannot move out of `var`, a captured variable in an `FnMut` closure357            //   --> $DIR/option-content-move2.rs:11:9358            //    |359            // LL |     let mut var = None;360            //    |         ------- captured outer variable361            // LL |     func(|| {362            //    |          -- captured by this `FnMut` closure363            // LL |         // Shouldn't suggest `move ||.as_ref()` here364            // LL |         move || {365            //    |         ^^^^^^^ `var` is moved here366            // LL |367            // LL |             var = Some(NotCopyable);368            //    |             ---369            //    |             |370            //    |             variable moved due to use in closure371            //    |             move occurs because `var` has type `Option<NotCopyable>`, which does not implement the `Copy` trait372            //    |373            return;374        }375376        // Search for an appropriate place for the structured `.clone()` suggestion to be applied.377        // If we encounter a statement before the borrow error, we insert a statement there.378        for (_, node) in tcx.hir_parent_iter(closure_expr.hir_id) {379            if let Node::Stmt(stmt) = node {380                let padding = tcx381                    .sess382                    .source_map()383                    .indentation_before(stmt.span)384                    .unwrap_or_else(|| "    ".to_string());385                err.multipart_suggestion(386                    "consider cloning the value before moving it into the closure",387                    vec![388                        (389                            stmt.span.shrink_to_lo(),390                            format!("let value = {upvar_name}.clone();\n{padding}"),391                        ),392                        (use_span, "value".to_string()),393                    ],394                    Applicability::MachineApplicable,395                );396                suggested = true;397                break;398            } else if let Node::Expr(expr) = node399                && let ExprKind::Closure(_) = expr.kind400            {401                // We want to suggest cloning only on the first closure, not402                // subsequent ones (like `ui/suggestions/option-content-move2.rs`).403                break;404            }405        }406        if !suggested {407            // If we couldn't find a statement for us to insert a new `.clone()` statement before,408            // we have a bare expression, so we suggest the creation of a new block inline to go409            // from `move || val` to `{ let value = val.clone(); move || value }`.410            let padding = tcx411                .sess412                .source_map()413                .indentation_before(closure_expr.span)414                .unwrap_or_else(|| "    ".to_string());415            err.multipart_suggestion(416                "consider cloning the value before moving it into the closure",417                vec![418                    (419                        closure_expr.span.shrink_to_lo(),420                        format!("{{\n{padding}let value = {upvar_name}.clone();\n{padding}"),421                    ),422                    (use_spans.var_or_use(), "value".to_string()),423                    (closure_expr.span.shrink_to_hi(), format!("\n{padding}}}")),424                ],425                Applicability::MachineApplicable,426            );427        }428    }429430    fn report_cannot_move_from_borrowed_content(431        &mut self,432        move_place: Place<'tcx>,433        deref_target_place: Place<'tcx>,434        span: Span,435        use_spans: Option<UseSpans<'tcx>>,436    ) -> (Diag<'infcx>, CloneSuggestion) {437        let tcx = self.infcx.tcx;438        // Inspect the type of the content behind the439        // borrow to provide feedback about why this440        // was a move rather than a copy.441        let ty = deref_target_place.ty(self.body, tcx).ty;442        let upvar_field = self443            .prefixes(move_place.as_ref(), PrefixSet::All)444            .find_map(|p| self.is_upvar_field_projection(p));445446        let deref_base = match deref_target_place.projection.as_ref() {447            [proj_base @ .., ProjectionElem::Deref] => {448                PlaceRef { local: deref_target_place.local, projection: proj_base }449            }450            _ => bug!("deref_target_place is not a deref projection"),451        };452453        if let PlaceRef { local, projection: [] } = deref_base {454            let decl = &self.body.local_decls[local];455            let local_name = self.local_name(local).map(|sym| format!("`{sym}`"));456            if decl.is_ref_for_guard() {457                return (458                    self.cannot_move_out_of(459                        span,460                        &format!(461                            "{} in pattern guard",462                            local_name.as_deref().unwrap_or("the place")463                        ),464                    )465                    .with_note(466                        "variables bound in patterns cannot be moved from \467                         until after the end of the pattern guard",468                    ),469                    CloneSuggestion::NotEmitted,470                );471            } else if decl.is_ref_to_static() {472                return (473                    self.report_cannot_move_from_static(move_place, span),474                    CloneSuggestion::NotEmitted,475                );476            }477        }478479        debug!("report: ty={:?}", ty);480        let mut err = match ty.kind() {481            ty::Array(..) | ty::Slice(..) => {482                self.cannot_move_out_of_interior_noncopy(span, ty, None)483            }484            ty::Closure(def_id, closure_args)485                if def_id.as_local() == Some(self.mir_def_id())486                    && let Some(upvar_field) = upvar_field =>487            {488                self.report_closure_move_error(489                    span,490                    move_place,491                    *def_id,492                    closure_args.as_closure().kind_ty(),493                    upvar_field,494                    ty::Asyncness::No,495                )496            }497            ty::CoroutineClosure(def_id, closure_args)498                if def_id.as_local() == Some(self.mir_def_id())499                    && let Some(upvar_field) = upvar_field500                    && self501                        .get_closure_bound_clause_span(*def_id, ty::Asyncness::Yes)502                        .is_some() =>503            {504                self.report_closure_move_error(505                    span,506                    move_place,507                    *def_id,508                    closure_args.as_coroutine_closure().kind_ty(),509                    upvar_field,510                    ty::Asyncness::Yes,511                )512            }513            _ => {514                let source = self.borrowed_content_source(deref_base);515                let move_place_ref = move_place.as_ref();516                match (517                    self.describe_place_with_options(518                        move_place_ref,519                        DescribePlaceOpt {520                            including_downcast: false,521                            including_tuple_field: false,522                        },523                    ),524                    self.describe_name(move_place_ref),525                    source.describe_for_named_place(),526                ) {527                    (Some(place_desc), Some(name), Some(source_desc)) => self.cannot_move_out_of(528                        span,529                        &format!("`{place_desc}` as enum variant `{name}` which is behind a {source_desc}"),530                    ),531                    (Some(place_desc), Some(name), None) => self.cannot_move_out_of(532                        span,533                        &format!("`{place_desc}` as enum variant `{name}`"),534                    ),535                    (Some(place_desc), _, Some(source_desc)) => self.cannot_move_out_of(536                        span,537                        &format!("`{place_desc}` which is behind a {source_desc}"),538                    ),539                    (_, _, _) => self.cannot_move_out_of(540                        span,541                        &source.describe_for_unnamed_place(tcx),542                    ),543                }544            }545        };546        let msg_opt = CapturedMessageOpt {547            is_partial_move: false,548            is_loop_message: false,549            is_move_msg: false,550            is_loop_move: false,551            has_suggest_reborrow: false,552            maybe_reinitialized_locations_is_empty: true,553        };554        let suggested_cloning = if let Some(use_spans) = use_spans {555            self.explain_captures(&mut err, span, span, use_spans, move_place, msg_opt)556        } else {557            CloneSuggestion::NotEmitted558        };559        (err, suggested_cloning)560    }561562    fn report_closure_move_error(563        &self,564        span: Span,565        move_place: Place<'tcx>,566        def_id: DefId,567        closure_kind_ty: Ty<'tcx>,568        upvar_field: FieldIdx,569        asyncness: ty::Asyncness,570    ) -> Diag<'infcx> {571        let tcx = self.infcx.tcx;572573        let closure_kind = match closure_kind_ty.to_opt_closure_kind() {574            Some(kind @ (ty::ClosureKind::Fn | ty::ClosureKind::FnMut)) => kind,575            Some(ty::ClosureKind::FnOnce) => {576                bug!("closure kind does not match first argument type")577            }578            None => bug!("closure kind not inferred by borrowck"),579        };580581        let async_prefix = if asyncness.is_async() { "Async" } else { "" };582        let capture_description =583            format!("captured variable in an `{async_prefix}{closure_kind}` closure");584585        let upvar = &self.upvars[upvar_field.index()];586        let upvar_hir_id = upvar.get_root_variable();587        let upvar_name = upvar.to_string(tcx);588        let upvar_span = tcx.hir_span(upvar_hir_id);589590        let place_name = self.describe_any_place(move_place.as_ref());591592        let place_description = if self.is_upvar_field_projection(move_place.as_ref()).is_some() {593            format!("{place_name}, a {capture_description}")594        } else {595            format!("{place_name}, as `{upvar_name}` is a {capture_description}")596        };597598        debug!(?closure_kind_ty, ?closure_kind, ?place_description);599600        let closure_span = tcx.def_span(def_id);601602        let help_msg = format!(603            "`{async_prefix}Fn` and `{async_prefix}FnMut` closures require captured values to \604             be able to be consumed multiple times, but `{async_prefix}FnOnce` closures may \605             consume them only once"606        );607608        let mut err = self609            .cannot_move_out_of(span, &place_description)610            .with_span_label(upvar_span, "captured outer variable")611            .with_span_label(612                closure_span,613                format!("captured by this `{async_prefix}{closure_kind}` closure"),614            );615616        if let Some(bound_span) = self.get_closure_bound_clause_span(def_id, asyncness) {617            err.span_help(bound_span, help_msg);618        } else if !asyncness.is_async() {619            // For sync closures, always emit the help message even without a span.620            // For async closures, we only enter this branch if we found a valid span621            // (due to the match guard), so no fallback is needed.622            err.help(help_msg);623        }624625        err626    }627628    fn get_closure_bound_clause_span(629        &self,630        def_id: DefId,631        asyncness: ty::Asyncness,632    ) -> Option<Span> {633        let tcx = self.infcx.tcx;634        let typeck_result = tcx.typeck(self.mir_def_id());635        // Check whether the closure is an argument to a call, if so,636        // get the instantiated where-bounds of that call.637        let closure_hir_id = tcx.local_def_id_to_hir_id(def_id.expect_local());638        let hir::Node::Expr(parent) = tcx.parent_hir_node(closure_hir_id) else { return None };639640        let predicates = match parent.kind {641            hir::ExprKind::Call(callee, _) => {642                let ty = typeck_result.node_type_opt(callee.hir_id)?;643                let ty::FnDef(fn_def_id, args) = *ty.kind() else { return None };644                tcx.predicates_of(fn_def_id).instantiate(tcx, args)645            }646            hir::ExprKind::MethodCall(..) => {647                let (_, method) = typeck_result.type_dependent_def(parent.hir_id)?;648                let args = typeck_result.node_args(parent.hir_id);649                tcx.predicates_of(method).instantiate(tcx, args)650            }651            _ => return None,652        };653654        // Check whether one of the where-bounds requires the closure to impl `Fn[Mut]`655        // or `AsyncFn[Mut]`.656        for (pred, span) in predicates.predicates.iter().zip(predicates.spans.iter()) {657            let pred = pred.skip_norm_wip();658            let dominated_by_fn_trait = self659                .closure_clause_kind(pred, def_id, asyncness)660                .is_some_and(|kind| matches!(kind, ty::ClosureKind::Fn | ty::ClosureKind::FnMut));661            if dominated_by_fn_trait {662                // Found `<TyOfCapturingClosure as FnMut>` or663                // `<TyOfCapturingClosure as AsyncFnMut>`.664                // We point at the bound that coerced the closure, which could be changed665                // to `FnOnce()` or `AsyncFnOnce()` to avoid the move error.666                return Some(*span);667            }668        }669        None670    }671672    /// If `pred` is a trait clause binding the closure `def_id` to `Fn`/`FnMut`/`FnOnce`673    /// (or their async equivalents based on `asyncness`), returns the corresponding674    /// `ClosureKind`. Otherwise returns `None`.675    fn closure_clause_kind(676        &self,677        pred: ty::Clause<'tcx>,678        def_id: DefId,679        asyncness: ty::Asyncness,680    ) -> Option<ty::ClosureKind> {681        let tcx = self.infcx.tcx;682        let clause = pred.as_trait_clause()?;683        let kind = match asyncness {684            ty::Asyncness::Yes => tcx.async_fn_trait_kind_from_def_id(clause.def_id()),685            ty::Asyncness::No => tcx.fn_trait_kind_from_def_id(clause.def_id()),686        }?;687        match clause.self_ty().skip_binder().kind() {688            ty::Closure(id, _) | ty::CoroutineClosure(id, _) if *id == def_id => Some(kind),689            _ => None,690        }691    }692693    /// Suggest cloning via UFCS when a move occurs through a custom `Deref` impl.694    ///695    /// A simple `.clone()` on a type like `MyBox<Vec<i32>>` would clone the wrapper,696    /// not the inner `Vec<i32>`. Instead, we suggest `<Vec<i32> as Clone>::clone(&val)`.697    fn suggest_cloning_through_overloaded_deref(698        &self,699        err: &mut Diag<'_>,700        ty: Ty<'tcx>,701        span: Span,702    ) -> CloneSuggestion {703        let tcx = self.infcx.tcx;704        let Some(clone_trait) = tcx.lang_items().clone_trait() else {705            return CloneSuggestion::NotEmitted;706        };707        let Some(errors) =708            self.infcx.type_implements_trait_shallow(clone_trait, ty, self.infcx.param_env)709        else {710            return CloneSuggestion::NotEmitted;711        };712713        if !errors.is_empty() {714            return CloneSuggestion::NotEmitted;715        }716        let sugg = vec![717            (span.shrink_to_lo(), format!("<{ty} as Clone>::clone(&")),718            (span.shrink_to_hi(), ")".to_string()),719        ];720        err.multipart_suggestion(721            "you can `clone` the value and consume it, but this might not be \722             your desired behavior",723            sugg,724            Applicability::MaybeIncorrect,725        );726        CloneSuggestion::Emitted727    }728729    fn add_move_hints(730        &self,731        error: GroupedMoveError<'tcx>,732        err: &mut Diag<'_>,733        span: Span,734        has_clone_suggestion: CloneSuggestion,735    ) {736        match error {737            GroupedMoveError::MovesFromPlace { mut binds_to, move_from, .. } => {738                self.add_borrow_suggestions(err, span, !binds_to.is_empty());739                if binds_to.is_empty() {740                    let place_ty = move_from.ty(self.body, self.infcx.tcx).ty;741                    let place_desc = match self.describe_place(move_from.as_ref()) {742                        Some(desc) => format!("`{desc}`"),743                        None => "value".to_string(),744                    };745746                    if let Some(expr) = self.find_expr(span) {747                        self.suggest_cloning(err, move_from.as_ref(), place_ty, expr, None);748                    }749750                    err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {751                        is_partial_move: false,752                        ty: place_ty,753                        place: &place_desc,754                        span,755                    });756                } else {757                    binds_to.sort();758                    binds_to.dedup();759760                    self.add_move_error_details(err, &binds_to, &[]);761                }762            }763            GroupedMoveError::MovesFromValue { mut binds_to, .. } => {764                binds_to.sort();765                binds_to.dedup();766                let desugar_spans = self.add_move_error_suggestions(err, &binds_to);767                self.add_move_error_details(err, &binds_to, &desugar_spans);768            }769            // No binding. Nothing to suggest.770            GroupedMoveError::OtherIllegalMove { ref original_path, use_spans, .. } => {771                let mut use_span = use_spans.var_or_use();772                let place_ty = original_path.ty(self.body, self.infcx.tcx).ty;773                let place_desc = match self.describe_place(original_path.as_ref()) {774                    Some(desc) => format!("`{desc}`"),775                    None => "value".to_string(),776                };777778                if has_clone_suggestion == CloneSuggestion::NotEmitted {779                    // Check if the move is directly through a custom Deref impl780                    // (e.g. `*my_box` where MyBox implements Deref).781                    // A simple `.clone()` would clone the wrapper type rather than782                    // the inner value, so we need a UFCS suggestion instead.783                    //784                    // However, if there are further projections after the deref785                    // (e.g. `(*rc).field`), the value accessed is already the inner786                    // type and a simple `.clone()` works correctly.787                    let needs_ufcs = original_path.projection.last()788                        == Some(&ProjectionElem::Deref)789                        && original_path.iter_projections().any(|(place, elem)| {790                            matches!(elem, ProjectionElem::Deref)791                                && matches!(792                                    self.borrowed_content_source(place),793                                    BorrowedContentSource::OverloadedDeref(_)794                                        | BorrowedContentSource::OverloadedIndex(_)795                                )796                        });797798                    let emitted_ufcs = if needs_ufcs {799                        self.suggest_cloning_through_overloaded_deref(err, place_ty, use_span)800                    } else {801                        CloneSuggestion::NotEmitted802                    };803804                    if emitted_ufcs == CloneSuggestion::NotEmitted {805                        if let Some(expr) = self.find_expr(use_span) {806                            self.suggest_cloning(807                                err,808                                original_path.as_ref(),809                                place_ty,810                                expr,811                                Some(use_spans),812                            );813                        }814                    }815                }816817                if let Some(upvar_field) = self818                    .prefixes(original_path.as_ref(), PrefixSet::All)819                    .find_map(|p| self.is_upvar_field_projection(p))820                {821                    // Look for the introduction of the original binding being moved.822                    let upvar = &self.upvars[upvar_field.index()];823                    let upvar_hir_id = upvar.get_root_variable();824                    use_span = match self.infcx.tcx.parent_hir_node(upvar_hir_id) {825                        hir::Node::Param(param) => {826                            // Instead of pointing at the path where we access the value within a827                            // closure, we point at the type of the outer `fn` argument.828                            param.ty_span829                        }830                        hir::Node::LetStmt(stmt) => match (stmt.ty, stmt.init) {831                            // We point at the type of the outer let-binding.832                            (Some(ty), _) => ty.span,833                            // We point at the initializer of the outer let-binding, but only if it834                            // isn't something that spans multiple lines, like a closure, as the835                            // ASCII art gets messy.836                            (None, Some(init))837                                if !self.infcx.tcx.sess.source_map().is_multiline(init.span) =>838                            {839                                init.span840                            }841                            _ => use_span,842                        },843                        _ => use_span,844                    };845                }846847                err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {848                    is_partial_move: false,849                    ty: place_ty,850                    place: &place_desc,851                    span: use_span,852                });853854                let mut pointed_at_span = false;855                use_spans.args_subdiag(err, |args_span| {856                    if args_span == span || args_span == use_span {857                        pointed_at_span = true;858                    }859                    crate::session_diagnostics::CaptureArgLabel::MoveOutPlace {860                        place: place_desc.clone(),861                        args_span,862                    }863                });864                if !pointed_at_span && use_span != span {865                    err.subdiagnostic(crate::session_diagnostics::CaptureArgLabel::MoveOutPlace {866                        place: place_desc,867                        args_span: span,868                    });869                }870871                self.add_note_for_packed_struct_derive(err, original_path.local);872            }873        }874    }875876    fn add_borrow_suggestions(877        &self,878        err: &mut Diag<'_>,879        span: Span,880        is_destructuring_pattern_move: bool,881    ) {882        match self.infcx.tcx.sess.source_map().span_to_snippet(span) {883            Ok(snippet) if snippet.starts_with('*') => {884                let sp = span.with_lo(span.lo() + BytePos(1));885                let inner = self.find_expr(sp);886                let mut is_raw_ptr = false;887                let mut is_ref = false;888                let mut is_destructuring_assignment = false;889                let mut is_nested_deref = false;890                if let Some(inner) = inner {891                    is_nested_deref =892                        matches!(inner.kind, hir::ExprKind::Unary(hir::UnOp::Deref, _));893                    let typck_result = self.infcx.tcx.typeck(self.mir_def_id());894                    if let Some(inner_type) = typck_result.node_type_opt(inner.hir_id) {895                        if matches!(inner_type.kind(), ty::RawPtr(..)) {896                            is_raw_ptr = true;897                        } else if matches!(inner_type.kind(), ty::Ref(..)) {898                            is_ref = true;899                        }900                    }901                    is_destructuring_assignment =902                        self.infcx.tcx.hir_parent_iter(inner.hir_id).any(|(_, node)| {903                            matches!(904                                node,905                                hir::Node::LetStmt(&hir::LetStmt {906                                    source: hir::LocalSource::AssignDesugar,907                                    ..908                                })909                            )910                        });911                }912                // If the `inner` is a raw pointer, do not suggest removing the "*", see #126863913                // FIXME: need to check whether the assigned object can be a raw pointer, see `tests/ui/borrowck/issue-20801.rs`.914                if is_raw_ptr {915                    return;916                }917918                if !is_destructuring_pattern_move || is_ref {919                    err.span_suggestion_verbose(920                        span.with_hi(span.lo() + BytePos(1)),921                        "consider removing the dereference here",922                        String::new(),923                        Applicability::MaybeIncorrect,924                    );925                } else if !is_destructuring_assignment && !is_nested_deref {926                    err.span_suggestion_verbose(927                        span.shrink_to_lo(),928                        "consider borrowing here",929                        '&',930                        Applicability::MaybeIncorrect,931                    );932                } else {933                    err.span_help(934                        span,935                        "destructuring assignment cannot borrow from this expression; consider using a `let` binding instead",936                    );937                }938            }939            _ => {940                err.span_suggestion_verbose(941                    span.shrink_to_lo(),942                    "consider borrowing here",943                    '&',944                    Applicability::MaybeIncorrect,945                );946            }947        }948    }949950    fn add_move_error_suggestions(&self, err: &mut Diag<'_>, binds_to: &[Local]) -> Vec<Span> {951        /// A HIR visitor to associate each binding with a `&` or `&mut` that could be removed to952        /// make it bind by reference instead (if possible)953        struct BindingFinder<'tcx> {954            typeck_results: &'tcx ty::TypeckResults<'tcx>,955            tcx: TyCtxt<'tcx>,956            /// Input: the span of the pattern we're finding bindings in957            pat_span: Span,958            /// Input: the spans of the bindings we're providing suggestions for959            binding_spans: Vec<Span>,960            /// Internal state: have we reached the pattern we're finding bindings in?961            found_pat: bool,962            /// Internal state: the innermost `&` or `&mut` "above" the visitor963            ref_pat: Option<&'tcx hir::Pat<'tcx>>,964            /// Internal state: could removing a `&` give bindings unexpected types?965            has_adjustments: bool,966            /// Output: for each input binding, the `&` or `&mut` to remove to make it by-ref967            ref_pat_for_binding: Vec<(Span, Option<&'tcx hir::Pat<'tcx>>)>,968            /// Output: ref patterns that can't be removed straightforwardly969            cannot_remove: FxHashSet<HirId>,970            /// Output: binding spans from destructuring assignment desugaring971            desugar_binding_spans: Vec<Span>,972        }973        impl<'tcx> Visitor<'tcx> for BindingFinder<'tcx> {974            type NestedFilter = rustc_middle::hir::nested_filter::OnlyBodies;975976            fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {977                self.tcx978            }979980            fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) -> Self::Result {981                // Don't walk into const patterns or anything else that might confuse this982                if !self.found_pat {983                    hir::intravisit::walk_expr(self, ex)984                }985            }986987            fn visit_pat(&mut self, p: &'tcx hir::Pat<'tcx>) {988                if p.span == self.pat_span {989                    self.found_pat = true;990                }991992                let parent_has_adjustments = self.has_adjustments;993                self.has_adjustments |=994                    self.typeck_results.pat_adjustments().contains_key(p.hir_id);995996                // Track the innermost `&` or `&mut` enclosing bindings, to suggest removing it.997                let parent_ref_pat = self.ref_pat;998                if let hir::PatKind::Ref(..) = p.kind {999                    self.ref_pat = Some(p);1000                    // To avoid edition-dependent logic to figure out how many refs this `&` can1001                    // peel off, simply don't remove the "parent" `&`.1002                    self.cannot_remove.extend(parent_ref_pat.map(|r| r.hir_id));1003                    if self.has_adjustments {1004                        // Removing this `&` could give child bindings unexpected types, so don't.1005                        self.cannot_remove.insert(p.hir_id);1006                        // As long the `&` stays, child patterns' types should be as expected.1007                        self.has_adjustments = false;1008                    }1009                }10101011                if let hir::PatKind::Binding(_, _, ident, _) = p.kind {1012                    // Skip synthetic bindings from destructuring assignment desugaring1013                    // These have name `lhs` and their parent is a `LetStmt` with1014                    // `LocalSource::AssignDesugar`1015                    let dominated_by_desugar_assign = ident.name == sym::lhs1016                        && self.tcx.hir_parent_iter(p.hir_id).any(|(_, node)| {1017                            matches!(1018                                node,1019                                hir::Node::LetStmt(&hir::LetStmt {1020                                    source: hir::LocalSource::AssignDesugar,1021                                    ..1022                                })1023                            )1024                        });10251026                    if dominated_by_desugar_assign {1027                        if let Some(&bind_sp) =1028                            self.binding_spans.iter().find(|bind_sp| bind_sp.contains(ident.span))1029                        {1030                            self.desugar_binding_spans.push(bind_sp);1031                        }1032                    } else {1033                        // the spans in `binding_spans` encompass both the ident and binding mode1034                        if let Some(&bind_sp) =1035                            self.binding_spans.iter().find(|bind_sp| bind_sp.contains(ident.span))1036                        {1037                            self.ref_pat_for_binding.push((bind_sp, self.ref_pat));1038                        } else {1039                            // we've encountered a binding that we're not reporting a move error for.1040                            // we don't want to change its type, so don't remove the surrounding `&`.1041                            if let Some(ref_pat) = self.ref_pat {1042                                self.cannot_remove.insert(ref_pat.hir_id);1043                            }1044                        }1045                    }1046                }10471048                hir::intravisit::walk_pat(self, p);1049                self.ref_pat = parent_ref_pat;1050                self.has_adjustments = parent_has_adjustments;1051            }1052        }1053        let mut pat_span = None;1054        let mut binding_spans = Vec::new();1055        for local in binds_to {1056            let bind_to = &self.body.local_decls[*local];1057            if let LocalInfo::User(BindingForm::Var(VarBindingForm { pat_span: pat_sp, .. })) =1058                *bind_to.local_info()1059            {1060                pat_span = Some(pat_sp);1061                binding_spans.push(bind_to.source_info.span);1062            }1063        }1064        let Some(pat_span) = pat_span else { return Vec::new() };10651066        let tcx = self.infcx.tcx;1067        let Some(body) = tcx.hir_maybe_body_owned_by(self.mir_def_id()) else { return Vec::new() };1068        let typeck_results = self.infcx.tcx.typeck(self.mir_def_id());1069        let mut finder = BindingFinder {1070            typeck_results,1071            tcx,1072            pat_span,1073            binding_spans,1074            found_pat: false,1075            ref_pat: None,1076            has_adjustments: false,1077            ref_pat_for_binding: Vec::new(),1078            cannot_remove: FxHashSet::default(),1079            desugar_binding_spans: Vec::new(),1080        };1081        finder.visit_body(body);10821083        let mut suggestions = Vec::new();1084        for (binding_span, opt_ref_pat) in finder.ref_pat_for_binding {1085            if let Some(ref_pat) = opt_ref_pat1086                && !finder.cannot_remove.contains(&ref_pat.hir_id)1087                && let hir::PatKind::Ref(subpat, pinned, mutbl) = ref_pat.kind1088                && let Some(ref_span) = ref_pat.span.trim_end(subpat.span)1089            {1090                let pinned_str = if pinned.is_pinned() { "pinned " } else { "" };1091                let mutable_str = if mutbl.is_mut() { "mutable " } else { "" };1092                let msg = format!("consider removing the {pinned_str}{mutable_str}borrow");1093                suggestions.push((ref_span, msg, "".to_string()));1094            } else {1095                let msg = "consider borrowing the pattern binding".to_string();1096                suggestions.push((binding_span.shrink_to_lo(), msg, "ref ".to_string()));1097            }1098        }1099        suggestions.sort_unstable_by_key(|&(span, _, _)| span);1100        suggestions.dedup_by_key(|&mut (span, _, _)| span);1101        for (span, msg, suggestion) in suggestions {1102            err.span_suggestion_verbose(span, msg, suggestion, Applicability::MachineApplicable);1103        }1104        finder.desugar_binding_spans1105    }11061107    fn add_move_error_details(1108        &self,1109        err: &mut Diag<'_>,1110        binds_to: &[Local],1111        desugar_spans: &[Span],1112    ) {1113        for (j, local) in binds_to.iter().enumerate() {1114            let bind_to = &self.body.local_decls[*local];1115            let binding_span = bind_to.source_info.span;11161117            if binds_to.len() == 1 {1118                let place_desc = self.local_name(*local).map(|sym| format!("`{sym}`"));11191120                err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::LabelMovedHere {1121                    ty: bind_to.ty,1122                    place: place_desc.as_deref().unwrap_or("the place"),1123                    span: binding_span,1124                });11251126                if !desugar_spans.contains(&binding_span)1127                    && let Some(expr) = self.find_expr(binding_span)1128                {1129                    let local_place: PlaceRef<'tcx> = (*local).into();1130                    self.suggest_cloning(err, local_place, bind_to.ty, expr, None);1131                }1132            } else if j == 0 {1133                err.span_label(binding_span, "data moved here");1134            } else {1135                err.span_label(binding_span, "...and here");1136            }1137        }11381139        if binds_to.len() > 1 {1140            err.note(1141                "move occurs because these variables have types that don't implement the `Copy` \1142                 trait",1143            );1144        }1145    }11461147    /// Adds an explanatory note if the move error occurs in a derive macro1148    /// expansion of a packed struct.1149    /// Such errors happen because derive macro expansions shy away from taking1150    /// references to the struct's fields since doing so would be undefined behaviour1151    fn add_note_for_packed_struct_derive(&self, err: &mut Diag<'_>, local: Local) {1152        let local_place: PlaceRef<'tcx> = local.into();1153        let local_ty = local_place.ty(self.body.local_decls(), self.infcx.tcx).ty.peel_refs();11541155        if let Some(adt) = local_ty.ty_adt_def()1156            && adt.repr().packed()1157            && let ExpnKind::Macro(MacroKind::Derive, name) =1158                self.body.span.ctxt().outer_expn_data().kind1159        {1160            err.note(format!("`#[derive({name})]` triggers a move because taking references to the fields of a packed struct is undefined behaviour"));1161        }1162    }1163}

Code quality findings 24

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
// e.g., match x[0] { s => (), } where x: &[String]
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
self.body.basic_blocks[location.block]
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 local_decl = &self.body.local_decls[local];
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
// opt_match_place is None for let [mut] x = ... statements,
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
// error[E0507]: cannot move out of `var`, a captured variable in an `FnMut` closure
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 decl = &self.body.local_decls[local];
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 upvar = &self.upvars[upvar_field.index()];
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
// Check whether one of the where-bounds requires the closure to impl `Fn[Mut]`
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
// or `AsyncFn[Mut]`.
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 upvar = &self.upvars[upvar_field.index()];
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 bind_to = &self.body.local_decls[*local];
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 bind_to = &self.body.local_decls[*local];
Info: Wildcard imports (`use some::path::*;`) can obscure the origin of names and lead to conflicts. Prefer importing specific items explicitly.
info maintainability wildcard-import
use rustc_middle::mir::*;
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info performance push-without-reserve
binds_to.push(bind_to);
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info performance push-without-reserve
binds_to.push(bind_to);
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info performance clone-in-loop
// Search for an appropriate place for the structured `.clone()` suggestion to be applied.
Performance Info: Calling .to_string() (especially on &str) allocates a new String. If done repeatedly in loops, consider alternatives like working with &str or using crates like `itoa`/`ryu` for number-to-string conversion.
info performance to-string-in-loop
.unwrap_or_else(|| " ".to_string());
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 deref_base = match deref_target_place.projection.as_ref() {
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info performance push-without-reserve
binding_spans.push(bind_to.source_info.span);
Performance Info: Calling .to_string() (especially on &str) allocates a new String. If done repeatedly in loops, consider alternatives like working with &str or using crates like `itoa`/`ryu` for number-to-string conversion.
info performance to-string-in-loop
suggestions.push((ref_span, msg, "".to_string()));
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info performance push-without-reserve
suggestions.push((ref_span, msg, "".to_string()));
Performance Info: Calling .to_string() (especially on &str) allocates a new String. If done repeatedly in loops, consider alternatives like working with &str or using crates like `itoa`/`ryu` for number-to-string conversion.
info performance to-string-in-loop
let msg = "consider borrowing the pattern binding".to_string();
Performance Info: Calling .to_string() (especially on &str) allocates a new String. If done repeatedly in loops, consider alternatives like working with &str or using crates like `itoa`/`ryu` for number-to-string conversion.
info performance to-string-in-loop
suggestions.push((binding_span.shrink_to_lo(), msg, "ref ".to_string()));
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info performance push-without-reserve
suggestions.push((binding_span.shrink_to_lo(), msg, "ref ".to_string()));

Get this view in your editor

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