compiler/rustc_borrowck/src/diagnostics/move_errors.rs RUST 1,276 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}9899struct PatternBindingInfo {100    pat_span: Span,101    binding_spans: Vec<Span>,102    has_mutable_by_value_binding: bool,103}104105impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {106    pub(crate) fn report_move_errors(&mut self) {107        let grouped_errors = self.group_move_errors();108        for error in grouped_errors {109            self.report(error);110        }111    }112113    fn group_move_errors(&mut self) -> Vec<GroupedMoveError<'tcx>> {114        let mut grouped_errors = Vec::new();115        let errors = std::mem::take(&mut self.move_errors);116        for error in errors {117            self.append_to_grouped_errors(&mut grouped_errors, error);118        }119        grouped_errors120    }121122    fn append_to_grouped_errors(123        &self,124        grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,125        MoveError { place: original_path, location, kind }: MoveError<'tcx>,126    ) {127        // Note: that the only time we assign a place isn't a temporary128        // to a user variable is when initializing it.129        // If that ever stops being the case, then the ever initialized130        // flow could be used.131        if let Some(StatementKind::Assign((place, Rvalue::Use(Operand::Move(move_from), _)))) =132            self.body.basic_blocks[location.block]133                .statements134                .get(location.statement_index)135                .map(|stmt| &stmt.kind)136            && let Some(local) = place.as_local()137        {138            let local_decl = &self.body.local_decls[local];139            // opt_match_place is the140            // match_span is the span of the expression being matched on141            // match *x.y { ... }        match_place is Some(*x.y)142            //       ^^^^                match_span is the span of *x.y143            //144            // opt_match_place is None for let [mut] x = ... statements,145            // whether or not the right-hand side is a place expression146            if let LocalInfo::User(BindingForm::Var(VarBindingForm {147                opt_match_place: Some((opt_match_place, match_span)),148                ..149            })) = *local_decl.local_info()150            {151                let stmt_source_info = self.body.source_info(location);152                self.append_binding_error(153                    grouped_errors,154                    kind,155                    original_path,156                    *move_from,157                    local,158                    opt_match_place,159                    match_span,160                    stmt_source_info.span,161                );162                return;163            }164        }165166        let move_spans = self.move_spans(original_path.as_ref(), location);167        grouped_errors.push(GroupedMoveError::OtherIllegalMove {168            use_spans: move_spans,169            original_path,170            kind,171        });172    }173174    fn append_binding_error(175        &self,176        grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,177        kind: IllegalMoveOriginKind<'tcx>,178        original_path: Place<'tcx>,179        move_from: Place<'tcx>,180        bind_to: Local,181        match_place: Option<Place<'tcx>>,182        match_span: Span,183        statement_span: Span,184    ) {185        debug!(?match_place, ?match_span, "append_binding_error");186187        let from_simple_let = match_place.is_none();188        let match_place = match_place.unwrap_or(move_from);189190        match self.move_data.rev_lookup.find(match_place.as_ref()) {191            // Error with the match place192            LookupResult::Parent(_) => {193                for ge in &mut *grouped_errors {194                    if let GroupedMoveError::MovesFromPlace { span, binds_to, .. } = ge195                        && match_span == *span196                    {197                        debug!("appending local({bind_to:?}) to list");198                        if !binds_to.is_empty() {199                            binds_to.push(bind_to);200                        }201                        return;202                    }203                }204                debug!("found a new move error location");205206                // Don't need to point to x in let x = ... .207                let (binds_to, span) = if from_simple_let {208                    (vec![], statement_span)209                } else {210                    (vec![bind_to], match_span)211                };212                grouped_errors.push(GroupedMoveError::MovesFromPlace {213                    span,214                    move_from,215                    original_path,216                    kind,217                    binds_to,218                });219            }220            // Error with the pattern221            LookupResult::Exact(_) => {222                let LookupResult::Parent(Some(mpi)) =223                    self.move_data.rev_lookup.find(move_from.as_ref())224                else {225                    // move_from should be a projection from match_place.226                    unreachable!("Probably not unreachable...");227                };228                for ge in &mut *grouped_errors {229                    if let GroupedMoveError::MovesFromValue {230                        span,231                        move_from: other_mpi,232                        binds_to,233                        ..234                    } = ge235                    {236                        if match_span == *span && mpi == *other_mpi {237                            debug!("appending local({bind_to:?}) to list");238                            binds_to.push(bind_to);239                            return;240                        }241                    }242                }243                debug!("found a new move error location");244                grouped_errors.push(GroupedMoveError::MovesFromValue {245                    span: match_span,246                    move_from: mpi,247                    original_path,248                    kind,249                    binds_to: vec![bind_to],250                });251            }252        };253    }254255    fn report(&mut self, error: GroupedMoveError<'tcx>) {256        let (span, use_spans, original_path, kind) = match error {257            GroupedMoveError::MovesFromPlace { span, original_path, ref kind, .. }258            | GroupedMoveError::MovesFromValue { span, original_path, ref kind, .. } => {259                (span, None, original_path, kind)260            }261            GroupedMoveError::OtherIllegalMove { use_spans, original_path, ref kind } => {262                (use_spans.args_or_use(), Some(use_spans), original_path, kind)263            }264        };265        debug!(266            "report: original_path={:?} span={:?}, kind={:?} \267             original_path.is_upvar_field_projection={:?}",268            original_path,269            span,270            kind,271            self.is_upvar_field_projection(original_path.as_ref())272        );273        if self.has_ambiguous_copy(original_path.ty(self.body, self.infcx.tcx).ty) {274            // If the type may implement Copy, skip the error.275            // It's an error with the Copy implementation (e.g. duplicate Copy) rather than borrow check276            self.dcx()277                .span_delayed_bug(span, "Type may implement copy, but there is no other error.");278            return;279        }280281        let mut has_clone_suggestion = CloneSuggestion::NotEmitted;282        let mut err = match kind {283            &IllegalMoveOriginKind::BorrowedContent { target_place } => {284                let (diag, clone_sugg) = self.report_cannot_move_from_borrowed_content(285                    original_path,286                    target_place,287                    span,288                    use_spans,289                );290                has_clone_suggestion = clone_sugg;291                diag292            }293            &IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => {294                self.cannot_move_out_of_interior_of_drop(span, ty)295            }296            &IllegalMoveOriginKind::InteriorOfSliceOrArray { ty, is_index } => {297                self.cannot_move_out_of_interior_noncopy(span, ty, Some(is_index))298            }299        };300301        self.add_move_hints(error, &mut err, span, has_clone_suggestion);302        self.buffer_error(err);303    }304305    fn has_ambiguous_copy(&mut self, ty: Ty<'tcx>) -> bool {306        let Some(copy_def_id) = self.infcx.tcx.lang_items().copy_trait() else { return false };307308        // Avoid bogus move errors because of an incoherent `Copy` impl.309        self.infcx.type_implements_trait(copy_def_id, [ty], self.infcx.param_env).may_apply()310            && self.infcx.tcx.ensure_result().coherent_trait(copy_def_id).is_err()311    }312313    fn report_cannot_move_from_static(&mut self, place: Place<'tcx>, span: Span) -> Diag<'infcx> {314        let description = if place.projection.len() == 1 {315            format!("static item {}", self.describe_any_place(place.as_ref()))316        } else {317            let base_static = PlaceRef { local: place.local, projection: &[ProjectionElem::Deref] };318319            format!(320                "{} as {} is a static item",321                self.describe_any_place(place.as_ref()),322                self.describe_any_place(base_static),323            )324        };325326        self.cannot_move_out_of(span, &description)327    }328329    pub(in crate::diagnostics) fn suggest_clone_of_captured_var_in_move_closure(330        &self,331        err: &mut Diag<'_>,332        upvar_name: &str,333        use_spans: Option<UseSpans<'tcx>>,334    ) {335        let tcx = self.infcx.tcx;336        let Some(use_spans) = use_spans else { return };337        // We only care about the case where a closure captured a binding.338        let UseSpans::ClosureUse { args_span, .. } = use_spans else { return };339        let Some(body_id) = tcx.hir_node(self.mir_hir_id()).body_id() else { return };340        // Find the closure that captured the binding.341        let mut expr_finder = FindExprBySpan::new(args_span, tcx);342        expr_finder.include_closures = true;343        expr_finder.visit_expr(tcx.hir_body(body_id).value);344        let Some(closure_expr) = expr_finder.result else { return };345        let ExprKind::Closure(closure) = closure_expr.kind else { return };346        // We'll only suggest cloning the binding if it's a `move` closure.347        let CaptureBy::Value { .. } = closure.capture_clause else { return };348        // Find the expression within the closure where the binding is consumed.349        let mut suggested = false;350        let use_span = use_spans.var_or_use();351        let mut expr_finder = FindExprBySpan::new(use_span, tcx);352        expr_finder.include_closures = true;353        expr_finder.visit_expr(tcx.hir_body(body_id).value);354        let Some(use_expr) = expr_finder.result else { return };355        let parent = tcx.parent_hir_node(use_expr.hir_id);356        if let Node::Expr(expr) = parent357            && let ExprKind::Assign(lhs, ..) = expr.kind358            && lhs.hir_id == use_expr.hir_id359        {360            // Cloning the value being assigned makes no sense:361            //362            // error[E0507]: cannot move out of `var`, a captured variable in an `FnMut` closure363            //   --> $DIR/option-content-move2.rs:11:9364            //    |365            // LL |     let mut var = None;366            //    |         ------- captured outer variable367            // LL |     func(|| {368            //    |          -- captured by this `FnMut` closure369            // LL |         // Shouldn't suggest `move ||.as_ref()` here370            // LL |         move || {371            //    |         ^^^^^^^ `var` is moved here372            // LL |373            // LL |             var = Some(NotCopyable);374            //    |             ---375            //    |             |376            //    |             variable moved due to use in closure377            //    |             move occurs because `var` has type `Option<NotCopyable>`, which does not implement the `Copy` trait378            //    |379            return;380        }381382        // Search for an appropriate place for the structured `.clone()` suggestion to be applied.383        // If we encounter a statement before the borrow error, we insert a statement there.384        for (_, node) in tcx.hir_parent_iter(closure_expr.hir_id) {385            if let Node::Stmt(stmt) = node {386                let padding = tcx387                    .sess388                    .source_map()389                    .indentation_before(stmt.span)390                    .unwrap_or_else(|| "    ".to_string());391                err.multipart_suggestion(392                    "consider cloning the value before moving it into the closure",393                    vec![394                        (395                            stmt.span.shrink_to_lo(),396                            format!("let value = {upvar_name}.clone();\n{padding}"),397                        ),398                        (use_span, "value".to_string()),399                    ],400                    Applicability::MachineApplicable,401                );402                suggested = true;403                break;404            } else if let Node::Expr(expr) = node405                && let ExprKind::Closure(_) = expr.kind406            {407                // We want to suggest cloning only on the first closure, not408                // subsequent ones (like `ui/suggestions/option-content-move2.rs`).409                break;410            }411        }412        if !suggested {413            // If we couldn't find a statement for us to insert a new `.clone()` statement before,414            // we have a bare expression, so we suggest the creation of a new block inline to go415            // from `move || val` to `{ let value = val.clone(); move || value }`.416            let padding = tcx417                .sess418                .source_map()419                .indentation_before(closure_expr.span)420                .unwrap_or_else(|| "    ".to_string());421            err.multipart_suggestion(422                "consider cloning the value before moving it into the closure",423                vec![424                    (425                        closure_expr.span.shrink_to_lo(),426                        format!("{{\n{padding}let value = {upvar_name}.clone();\n{padding}"),427                    ),428                    (use_spans.var_or_use(), "value".to_string()),429                    (closure_expr.span.shrink_to_hi(), format!("\n{padding}}}")),430                ],431                Applicability::MachineApplicable,432            );433        }434    }435436    fn report_cannot_move_from_borrowed_content(437        &mut self,438        move_place: Place<'tcx>,439        deref_target_place: Place<'tcx>,440        span: Span,441        use_spans: Option<UseSpans<'tcx>>,442    ) -> (Diag<'infcx>, CloneSuggestion) {443        let tcx = self.infcx.tcx;444        // Inspect the type of the content behind the445        // borrow to provide feedback about why this446        // was a move rather than a copy.447        let ty = deref_target_place.ty(self.body, tcx).ty;448        let upvar_field = self449            .prefixes(move_place.as_ref(), PrefixSet::All)450            .find_map(|p| self.is_upvar_field_projection(p));451452        let deref_base = match deref_target_place.projection.as_ref() {453            [proj_base @ .., ProjectionElem::Deref] => {454                PlaceRef { local: deref_target_place.local, projection: proj_base }455            }456            _ => bug!("deref_target_place is not a deref projection"),457        };458459        if let PlaceRef { local, projection: [] } = deref_base {460            let decl = &self.body.local_decls[local];461            let local_name = self.local_name(local).map(|sym| format!("`{sym}`"));462            if decl.is_ref_for_guard() {463                return (464                    self.cannot_move_out_of(465                        span,466                        &format!(467                            "{} in pattern guard",468                            local_name.as_deref().unwrap_or("the place")469                        ),470                    )471                    .with_note(472                        "variables bound in patterns cannot be moved from \473                         until after the end of the pattern guard",474                    ),475                    CloneSuggestion::NotEmitted,476                );477            } else if decl.is_ref_to_static() {478                return (479                    self.report_cannot_move_from_static(move_place, span),480                    CloneSuggestion::NotEmitted,481                );482            }483        }484485        debug!("report: ty={:?}", ty);486        let mut err = match ty.kind() {487            ty::Array(..) | ty::Slice(..) => {488                self.cannot_move_out_of_interior_noncopy(span, ty, None)489            }490            ty::Closure(def_id, closure_args)491                if def_id.as_local() == Some(self.mir_def_id())492                    && let Some(upvar_field) = upvar_field =>493            {494                self.report_closure_move_error(495                    span,496                    move_place,497                    *def_id,498                    closure_args.as_closure().kind_ty(),499                    upvar_field,500                    ty::Asyncness::No,501                )502            }503            ty::CoroutineClosure(def_id, closure_args)504                if def_id.as_local() == Some(self.mir_def_id())505                    && let Some(upvar_field) = upvar_field506                    && self507                        .get_closure_bound_clause_span(*def_id, ty::Asyncness::Yes)508                        .is_some() =>509            {510                self.report_closure_move_error(511                    span,512                    move_place,513                    *def_id,514                    closure_args.as_coroutine_closure().kind_ty(),515                    upvar_field,516                    ty::Asyncness::Yes,517                )518            }519            _ => {520                let source = self.borrowed_content_source(deref_base);521                let move_place_ref = move_place.as_ref();522                match (523                    self.describe_place_with_options(524                        move_place_ref,525                        DescribePlaceOpt {526                            including_downcast: false,527                            including_tuple_field: false,528                        },529                    ),530                    self.describe_name(move_place_ref),531                    source.describe_for_named_place(),532                ) {533                    (Some(place_desc), Some(name), Some(source_desc)) => self.cannot_move_out_of(534                        span,535                        &format!("`{place_desc}` as enum variant `{name}` which is behind a {source_desc}"),536                    ),537                    (Some(place_desc), Some(name), None) => self.cannot_move_out_of(538                        span,539                        &format!("`{place_desc}` as enum variant `{name}`"),540                    ),541                    (Some(place_desc), _, Some(source_desc)) => self.cannot_move_out_of(542                        span,543                        &format!("`{place_desc}` which is behind a {source_desc}"),544                    ),545                    (_, _, _) => self.cannot_move_out_of(546                        span,547                        &source.describe_for_unnamed_place(tcx),548                    ),549                }550            }551        };552        let msg_opt = CapturedMessageOpt {553            is_partial_move: false,554            is_loop_message: false,555            is_move_msg: false,556            is_loop_move: false,557            has_suggest_reborrow: false,558            maybe_reinitialized_locations_is_empty: true,559        };560        let suggested_cloning = if let Some(use_spans) = use_spans {561            self.explain_captures(&mut err, span, span, use_spans, move_place, msg_opt)562        } else {563            CloneSuggestion::NotEmitted564        };565        (err, suggested_cloning)566    }567568    fn report_closure_move_error(569        &self,570        span: Span,571        move_place: Place<'tcx>,572        def_id: DefId,573        closure_kind_ty: Ty<'tcx>,574        upvar_field: FieldIdx,575        asyncness: ty::Asyncness,576    ) -> Diag<'infcx> {577        let tcx = self.infcx.tcx;578579        let closure_kind = match closure_kind_ty.to_opt_closure_kind() {580            Some(kind @ (ty::ClosureKind::Fn | ty::ClosureKind::FnMut)) => kind,581            Some(ty::ClosureKind::FnOnce) => {582                bug!("closure kind does not match first argument type")583            }584            None => bug!("closure kind not inferred by borrowck"),585        };586587        let async_prefix = if asyncness.is_async() { "Async" } else { "" };588        let capture_description =589            format!("captured variable in an `{async_prefix}{closure_kind}` closure");590591        let upvar = &self.upvars[upvar_field.index()];592        let upvar_hir_id = upvar.get_root_variable();593        let upvar_name = upvar.to_string(tcx);594        let upvar_span = tcx.hir_span(upvar_hir_id);595596        let place_name = self.describe_any_place(move_place.as_ref());597598        let place_description = if self.is_upvar_field_projection(move_place.as_ref()).is_some() {599            format!("{place_name}, a {capture_description}")600        } else {601            format!("{place_name}, as `{upvar_name}` is a {capture_description}")602        };603604        debug!(?closure_kind_ty, ?closure_kind, ?place_description);605606        let closure_span = tcx.def_span(def_id);607608        let help_msg = format!(609            "`{async_prefix}Fn` and `{async_prefix}FnMut` closures require captured values to \610             be able to be consumed multiple times, but `{async_prefix}FnOnce` closures may \611             consume them only once"612        );613614        let mut err = self615            .cannot_move_out_of(span, &place_description)616            .with_span_label(upvar_span, "captured outer variable")617            .with_span_label(618                closure_span,619                format!("captured by this `{async_prefix}{closure_kind}` closure"),620            );621622        if let Some(bound_span) = self.get_closure_bound_clause_span(def_id, asyncness) {623            err.span_help(bound_span, help_msg);624        } else if !asyncness.is_async() {625            // For sync closures, always emit the help message even without a span.626            // For async closures, we only enter this branch if we found a valid span627            // (due to the match guard), so no fallback is needed.628            err.help(help_msg);629        }630631        err632    }633634    fn get_closure_bound_clause_span(635        &self,636        def_id: DefId,637        asyncness: ty::Asyncness,638    ) -> Option<Span> {639        let tcx = self.infcx.tcx;640        let typeck_result = tcx.typeck(self.mir_def_id());641        // Check whether the closure is an argument to a call, if so,642        // get the instantiated where-bounds of that call.643        let closure_hir_id = tcx.local_def_id_to_hir_id(def_id.expect_local());644        let hir::Node::Expr(parent) = tcx.parent_hir_node(closure_hir_id) else { return None };645646        let predicates = match parent.kind {647            hir::ExprKind::Call(callee, _) => {648                let ty = typeck_result.node_type_opt(callee.hir_id)?;649                let ty::FnDef(fn_def_id, args) = *ty.kind() else { return None };650                tcx.predicates_of(fn_def_id).instantiate(tcx, args)651            }652            hir::ExprKind::MethodCall(..) => {653                let (_, method) = typeck_result.type_dependent_def(parent.hir_id)?;654                let args = typeck_result.node_args(parent.hir_id);655                tcx.predicates_of(method).instantiate(tcx, args)656            }657            _ => return None,658        };659660        // Check whether one of the where-bounds requires the closure to impl `Fn[Mut]`661        // or `AsyncFn[Mut]`.662        for (pred, span) in predicates.predicates.iter().zip(predicates.spans.iter()) {663            let pred = pred.skip_norm_wip();664            let dominated_by_fn_trait = self665                .closure_clause_kind(pred, def_id, asyncness)666                .is_some_and(|kind| matches!(kind, ty::ClosureKind::Fn | ty::ClosureKind::FnMut));667            if dominated_by_fn_trait {668                // Found `<TyOfCapturingClosure as FnMut>` or669                // `<TyOfCapturingClosure as AsyncFnMut>`.670                // We point at the bound that coerced the closure, which could be changed671                // to `FnOnce()` or `AsyncFnOnce()` to avoid the move error.672                return Some(*span);673            }674        }675        None676    }677678    /// If `pred` is a trait clause binding the closure `def_id` to `Fn`/`FnMut`/`FnOnce`679    /// (or their async equivalents based on `asyncness`), returns the corresponding680    /// `ClosureKind`. Otherwise returns `None`.681    fn closure_clause_kind(682        &self,683        pred: ty::Clause<'tcx>,684        def_id: DefId,685        asyncness: ty::Asyncness,686    ) -> Option<ty::ClosureKind> {687        let tcx = self.infcx.tcx;688        let clause = pred.as_trait_clause()?;689        let kind = match asyncness {690            ty::Asyncness::Yes => tcx.async_fn_trait_kind_from_def_id(clause.def_id()),691            ty::Asyncness::No => tcx.fn_trait_kind_from_def_id(clause.def_id()),692        }?;693        match clause.self_ty().skip_binder().kind() {694            ty::Closure(id, _) | ty::CoroutineClosure(id, _) if *id == def_id => Some(kind),695            _ => None,696        }697    }698699    /// Suggest cloning via UFCS when a move occurs through a custom `Deref` impl.700    ///701    /// A simple `.clone()` on a type like `MyBox<Vec<i32>>` would clone the wrapper,702    /// not the inner `Vec<i32>`. Instead, we suggest `<Vec<i32> as Clone>::clone(&val)`.703    fn suggest_cloning_through_overloaded_deref(704        &self,705        err: &mut Diag<'_>,706        ty: Ty<'tcx>,707        span: Span,708    ) -> CloneSuggestion {709        let tcx = self.infcx.tcx;710        let Some(clone_trait) = tcx.lang_items().clone_trait() else {711            return CloneSuggestion::NotEmitted;712        };713        let Some(errors) =714            self.infcx.type_implements_trait_shallow(clone_trait, ty, self.infcx.param_env)715        else {716            return CloneSuggestion::NotEmitted;717        };718719        if !errors.is_empty() {720            return CloneSuggestion::NotEmitted;721        }722        let sugg = vec![723            (span.shrink_to_lo(), format!("<{ty} as Clone>::clone(&")),724            (span.shrink_to_hi(), ")".to_string()),725        ];726        err.multipart_suggestion(727            "you can `clone` the value and consume it, but this might not be \728             your desired behavior",729            sugg,730            Applicability::MaybeIncorrect,731        );732        CloneSuggestion::Emitted733    }734735    fn add_move_hints(736        &self,737        error: GroupedMoveError<'tcx>,738        err: &mut Diag<'_>,739        span: Span,740        has_clone_suggestion: CloneSuggestion,741    ) {742        match error {743            GroupedMoveError::MovesFromPlace { mut binds_to, move_from, .. } => {744                binds_to.sort();745                binds_to.dedup();746747                if binds_to.is_empty() {748                    self.add_borrow_suggestions(err, span, false);749                    let place_ty = move_from.ty(self.body, self.infcx.tcx).ty;750                    let place_desc = match self.describe_place(move_from.as_ref()) {751                        Some(desc) => format!("`{desc}`"),752                        None => "value".to_string(),753                    };754755                    if let Some(expr) = self.find_expr(span) {756                        self.suggest_cloning(err, move_from.as_ref(), place_ty, expr, None);757                    }758759                    err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {760                        is_partial_move: false,761                        ty: place_ty,762                        place: &place_desc,763                        span,764                    });765                } else {766                    let binding_info = self.pattern_binding_info(&binds_to);767                    let suggest_pattern_binding = binding_info.as_ref().is_some_and(|info| {768                        self.should_suggest_pattern_binding_instead(span, info)769                    });770                    let desugar_spans = if suggest_pattern_binding {771                        self.add_move_error_suggestions(err, &binds_to)772                    } else {773                        if self.should_suggest_borrow_instead(span, binding_info.as_ref()) {774                            self.add_borrow_suggestions(err, span, true);775                        }776                        None777                    };778                    self.add_move_error_details(779                        err,780                        &binds_to,781                        desugar_spans.as_deref().unwrap_or_default(),782                    );783                }784            }785            GroupedMoveError::MovesFromValue { mut binds_to, .. } => {786                binds_to.sort();787                binds_to.dedup();788                let desugar_spans =789                    self.add_move_error_suggestions(err, &binds_to).unwrap_or_default();790                self.add_move_error_details(err, &binds_to, &desugar_spans);791            }792            // No binding. Nothing to suggest.793            GroupedMoveError::OtherIllegalMove { ref original_path, use_spans, .. } => {794                let mut use_span = use_spans.var_or_use();795                let place_ty = original_path.ty(self.body, self.infcx.tcx).ty;796                let place_desc = match self.describe_place(original_path.as_ref()) {797                    Some(desc) => format!("`{desc}`"),798                    None => "value".to_string(),799                };800801                if has_clone_suggestion == CloneSuggestion::NotEmitted {802                    // Check if the move is directly through a custom Deref impl803                    // (e.g. `*my_box` where MyBox implements Deref).804                    // A simple `.clone()` would clone the wrapper type rather than805                    // the inner value, so we need a UFCS suggestion instead.806                    //807                    // However, if there are further projections after the deref808                    // (e.g. `(*rc).field`), the value accessed is already the inner809                    // type and a simple `.clone()` works correctly.810                    let needs_ufcs = original_path.projection.last()811                        == Some(&ProjectionElem::Deref)812                        && original_path.iter_projections().any(|(place, elem)| {813                            matches!(elem, ProjectionElem::Deref)814                                && matches!(815                                    self.borrowed_content_source(place),816                                    BorrowedContentSource::OverloadedDeref(_)817                                        | BorrowedContentSource::OverloadedIndex(_)818                                )819                        });820821                    let emitted_ufcs = if needs_ufcs {822                        self.suggest_cloning_through_overloaded_deref(err, place_ty, use_span)823                    } else {824                        CloneSuggestion::NotEmitted825                    };826827                    if emitted_ufcs == CloneSuggestion::NotEmitted {828                        if let Some(expr) = self.find_expr(use_span) {829                            self.suggest_cloning(830                                err,831                                original_path.as_ref(),832                                place_ty,833                                expr,834                                Some(use_spans),835                            );836                        }837                    }838                }839840                if let Some(upvar_field) = self841                    .prefixes(original_path.as_ref(), PrefixSet::All)842                    .find_map(|p| self.is_upvar_field_projection(p))843                {844                    // Look for the introduction of the original binding being moved.845                    let upvar = &self.upvars[upvar_field.index()];846                    let upvar_hir_id = upvar.get_root_variable();847                    use_span = match self.infcx.tcx.parent_hir_node(upvar_hir_id) {848                        hir::Node::Param(param) => {849                            // Instead of pointing at the path where we access the value within a850                            // closure, we point at the type of the outer `fn` argument.851                            param.ty_span852                        }853                        hir::Node::LetStmt(stmt) => match (stmt.ty, stmt.init) {854                            // We point at the type of the outer let-binding.855                            (Some(ty), _) => ty.span,856                            // We point at the initializer of the outer let-binding, but only if it857                            // isn't something that spans multiple lines, like a closure, as the858                            // ASCII art gets messy.859                            (None, Some(init))860                                if !self.infcx.tcx.sess.source_map().is_multiline(init.span) =>861                            {862                                init.span863                            }864                            _ => use_span,865                        },866                        _ => use_span,867                    };868                }869870                err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {871                    is_partial_move: false,872                    ty: place_ty,873                    place: &place_desc,874                    span: use_span,875                });876877                let mut pointed_at_span = false;878                use_spans.args_subdiag(err, |args_span| {879                    if args_span == span || args_span == use_span {880                        pointed_at_span = true;881                    }882                    crate::session_diagnostics::CaptureArgLabel::MoveOutPlace {883                        place: place_desc.clone(),884                        args_span,885                    }886                });887                if !pointed_at_span && use_span != span {888                    err.subdiagnostic(crate::session_diagnostics::CaptureArgLabel::MoveOutPlace {889                        place: place_desc,890                        args_span: span,891                    });892                }893894                self.add_note_for_packed_struct_derive(err, original_path.local);895            }896        }897    }898899    fn add_borrow_suggestions(900        &self,901        err: &mut Diag<'_>,902        span: Span,903        is_destructuring_pattern_move: bool,904    ) {905        match self.infcx.tcx.sess.source_map().span_to_snippet(span) {906            Ok(snippet) if snippet.starts_with('*') => {907                let sp = span.with_lo(span.lo() + BytePos(1));908                let inner = self.find_expr(sp);909                let mut is_raw_ptr = false;910                let mut is_ref = false;911                let mut is_destructuring_assignment = false;912                let mut is_nested_deref = false;913                if let Some(inner) = inner {914                    is_nested_deref =915                        matches!(inner.kind, hir::ExprKind::Unary(hir::UnOp::Deref, _));916                    let typck_result = self.infcx.tcx.typeck(self.mir_def_id());917                    if let Some(inner_type) = typck_result.node_type_opt(inner.hir_id) {918                        if matches!(inner_type.kind(), ty::RawPtr(..)) {919                            is_raw_ptr = true;920                        } else if matches!(inner_type.kind(), ty::Ref(..)) {921                            is_ref = true;922                        }923                    }924                    is_destructuring_assignment =925                        self.infcx.tcx.hir_parent_iter(inner.hir_id).any(|(_, node)| {926                            matches!(927                                node,928                                hir::Node::LetStmt(&hir::LetStmt {929                                    source: hir::LocalSource::AssignDesugar,930                                    ..931                                })932                            )933                        });934                }935                // If the `inner` is a raw pointer, do not suggest removing the "*", see #126863936                // FIXME: need to check whether the assigned object can be a raw pointer, see `tests/ui/borrowck/issue-20801.rs`.937                if is_raw_ptr {938                    return;939                }940941                if !is_destructuring_pattern_move || is_ref {942                    err.span_suggestion_verbose(943                        span.with_hi(span.lo() + BytePos(1)),944                        "consider removing the dereference here",945                        String::new(),946                        Applicability::MaybeIncorrect,947                    );948                } else if !is_destructuring_assignment && !is_nested_deref {949                    err.span_suggestion_verbose(950                        span.shrink_to_lo(),951                        "consider borrowing here",952                        '&',953                        Applicability::MaybeIncorrect,954                    );955                } else {956                    err.span_help(957                        span,958                        "destructuring assignment cannot borrow from this expression; consider using a `let` binding instead",959                    );960                }961            }962            _ => {963                err.span_suggestion_verbose(964                    span.shrink_to_lo(),965                    "consider borrowing here",966                    '&',967                    Applicability::MaybeIncorrect,968                );969            }970        }971    }972973    fn should_suggest_pattern_binding_instead(974        &self,975        span: Span,976        binding_info: &PatternBindingInfo,977    ) -> bool {978        let Some(expr) = self.find_expr(span) else {979            return false;980        };981982        let typeck_results = self.infcx.tcx.typeck(self.mir_def_id());983        let projection_qualifies = match expr.kind {984            hir::ExprKind::Field(base, ..) => {985                !typeck_results.node_type_opt(base.hir_id).is_some_and(|base_ty| {986                    binding_info.has_mutable_by_value_binding987                        && matches!(base_ty.kind(), ty::Ref(_, _, hir::Mutability::Not))988                })989            }990            hir::ExprKind::Index(base, ..) => typeck_results991                .node_type_opt(base.hir_id)992                .is_some_and(|base_ty| match base_ty.kind() {993                    ty::Ref(_, _, hir::Mutability::Not) | ty::RawPtr(..) => false,994                    ty::Ref(_, _, hir::Mutability::Mut) => {995                        binding_info.has_mutable_by_value_binding996                    }997                    _ => true,998                }),999            _ => false,1000        };1001        if !projection_qualifies {1002            return false;1003        }10041005        let is_single_binding = binding_info.binding_spans.len() == 11006            && binding_info.binding_spans[0] == binding_info.pat_span;1007        !is_single_binding1008    }10091010    fn should_suggest_borrow_instead(1011        &self,1012        span: Span,1013        binding_info: Option<&PatternBindingInfo>,1014    ) -> bool {1015        if !binding_info.is_some_and(|info| info.has_mutable_by_value_binding) {1016            return true;1017        }10181019        let Some(expr) = self.find_expr(span) else {1020            return true;1021        };10221023        let Some(base) = (match expr.kind {1024            hir::ExprKind::Field(base, _) | hir::ExprKind::Index(base, ..) => Some(base),1025            _ => None,1026        }) else {1027            return true;1028        };10291030        !self1031            .infcx1032            .tcx1033            .typeck(self.mir_def_id())1034            .node_type_opt(base.hir_id)1035            .is_some_and(|base_ty| matches!(base_ty.kind(), ty::Ref(_, _, hir::Mutability::Not)))1036    }10371038    fn pattern_binding_info(&self, binds_to: &[Local]) -> Option<PatternBindingInfo> {1039        let mut pat_span = None;1040        let mut binding_spans = Vec::new();1041        let mut has_mutable_by_value_binding = false;1042        for local in binds_to {1043            let bind_to = &self.body.local_decls[*local];1044            if let LocalInfo::User(BindingForm::Var(VarBindingForm {1045                pat_span: pat_sp,1046                binding_mode,1047                ..1048            })) = *bind_to.local_info()1049            {1050                pat_span = Some(pat_sp);1051                binding_spans.push(bind_to.source_info.span);1052                has_mutable_by_value_binding |=1053                    matches!(binding_mode, hir::BindingMode(hir::ByRef::No, hir::Mutability::Mut));1054            }1055        }10561057        Some(PatternBindingInfo {1058            pat_span: pat_span?,1059            binding_spans,1060            has_mutable_by_value_binding,1061        })1062    }10631064    fn add_move_error_suggestions(1065        &self,1066        err: &mut Diag<'_>,1067        binds_to: &[Local],1068    ) -> Option<Vec<Span>> {1069        /// A HIR visitor to associate each binding with a `&` or `&mut` that could be removed to1070        /// make it bind by reference instead (if possible)1071        struct BindingFinder<'tcx> {1072            typeck_results: &'tcx ty::TypeckResults<'tcx>,1073            tcx: TyCtxt<'tcx>,1074            /// Input: the span of the pattern we're finding bindings in1075            pat_span: Span,1076            /// Input: the spans of the bindings we're providing suggestions for1077            binding_spans: Vec<Span>,1078            /// Internal state: have we reached the pattern we're finding bindings in?1079            found_pat: bool,1080            /// Internal state: the innermost `&` or `&mut` "above" the visitor1081            ref_pat: Option<&'tcx hir::Pat<'tcx>>,1082            /// Internal state: could removing a `&` give bindings unexpected types?1083            has_adjustments: bool,1084            /// Output: for each input binding, the `&` or `&mut` to remove to make it by-ref1085            ref_pat_for_binding: Vec<(Span, Option<&'tcx hir::Pat<'tcx>>)>,1086            /// Output: ref patterns that can't be removed straightforwardly1087            cannot_remove: FxHashSet<HirId>,1088            /// Output: binding spans from destructuring assignment desugaring1089            desugar_binding_spans: Vec<Span>,1090        }1091        impl<'tcx> Visitor<'tcx> for BindingFinder<'tcx> {1092            type NestedFilter = rustc_middle::hir::nested_filter::OnlyBodies;10931094            fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {1095                self.tcx1096            }10971098            fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) -> Self::Result {1099                // Don't walk into const patterns or anything else that might confuse this1100                if !self.found_pat {1101                    hir::intravisit::walk_expr(self, ex)1102                }1103            }11041105            fn visit_pat(&mut self, p: &'tcx hir::Pat<'tcx>) {1106                if p.span == self.pat_span {1107                    self.found_pat = true;1108                }11091110                let parent_has_adjustments = self.has_adjustments;1111                self.has_adjustments |=1112                    self.typeck_results.pat_adjustments().contains_key(p.hir_id);11131114                // Track the innermost `&` or `&mut` enclosing bindings, to suggest removing it.1115                let parent_ref_pat = self.ref_pat;1116                if let hir::PatKind::Ref(..) = p.kind {1117                    self.ref_pat = Some(p);1118                    // To avoid edition-dependent logic to figure out how many refs this `&` can1119                    // peel off, simply don't remove the "parent" `&`.1120                    self.cannot_remove.extend(parent_ref_pat.map(|r| r.hir_id));1121                    if self.has_adjustments {1122                        // Removing this `&` could give child bindings unexpected types, so don't.1123                        self.cannot_remove.insert(p.hir_id);1124                        // As long the `&` stays, child patterns' types should be as expected.1125                        self.has_adjustments = false;1126                    }1127                }11281129                if let hir::PatKind::Binding(_, _, ident, _) = p.kind {1130                    // Skip synthetic bindings from destructuring assignment desugaring1131                    // These have name `lhs` and their parent is a `LetStmt` with1132                    // `LocalSource::AssignDesugar`1133                    let dominated_by_desugar_assign = ident.name == sym::lhs1134                        && self.tcx.hir_parent_iter(p.hir_id).any(|(_, node)| {1135                            matches!(1136                                node,1137                                hir::Node::LetStmt(&hir::LetStmt {1138                                    source: hir::LocalSource::AssignDesugar,1139                                    ..1140                                })1141                            )1142                        });11431144                    if dominated_by_desugar_assign {1145                        if let Some(&bind_sp) =1146                            self.binding_spans.iter().find(|bind_sp| bind_sp.contains(ident.span))1147                        {1148                            self.desugar_binding_spans.push(bind_sp);1149                        }1150                    } else {1151                        // the spans in `binding_spans` encompass both the ident and binding mode1152                        if let Some(&bind_sp) =1153                            self.binding_spans.iter().find(|bind_sp| bind_sp.contains(ident.span))1154                        {1155                            self.ref_pat_for_binding.push((bind_sp, self.ref_pat));1156                        } else {1157                            // we've encountered a binding that we're not reporting a move error for.1158                            // we don't want to change its type, so don't remove the surrounding `&`.1159                            if let Some(ref_pat) = self.ref_pat {1160                                self.cannot_remove.insert(ref_pat.hir_id);1161                            }1162                        }1163                    }1164                }11651166                hir::intravisit::walk_pat(self, p);1167                self.ref_pat = parent_ref_pat;1168                self.has_adjustments = parent_has_adjustments;1169            }1170        }1171        let Some(binding_info) = self.pattern_binding_info(binds_to) else {1172            return None;1173        };11741175        let tcx = self.infcx.tcx;1176        let Some(body) = tcx.hir_maybe_body_owned_by(self.mir_def_id()) else {1177            return None;1178        };1179        let typeck_results = self.infcx.tcx.typeck(self.mir_def_id());1180        let mut finder = BindingFinder {1181            typeck_results,1182            tcx,1183            pat_span: binding_info.pat_span,1184            binding_spans: binding_info.binding_spans,1185            found_pat: false,1186            ref_pat: None,1187            has_adjustments: false,1188            ref_pat_for_binding: Vec::new(),1189            cannot_remove: FxHashSet::default(),1190            desugar_binding_spans: Vec::new(),1191        };1192        finder.visit_body(body);11931194        let mut suggestions = Vec::new();1195        for (binding_span, opt_ref_pat) in finder.ref_pat_for_binding {1196            if let Some(ref_pat) = opt_ref_pat1197                && !finder.cannot_remove.contains(&ref_pat.hir_id)1198                && let hir::PatKind::Ref(subpat, pinned, mutbl) = ref_pat.kind1199                && let Some(ref_span) = ref_pat.span.trim_end(subpat.span)1200            {1201                let pinned_str = if pinned.is_pinned() { "pinned " } else { "" };1202                let mutable_str = if mutbl.is_mut() { "mutable " } else { "" };1203                let msg = format!("consider removing the {pinned_str}{mutable_str}borrow");1204                suggestions.push((ref_span, msg, "".to_string()));1205            } else {1206                let msg = "consider borrowing the pattern binding".to_string();1207                suggestions.push((binding_span.shrink_to_lo(), msg, "ref ".to_string()));1208            }1209        }1210        suggestions.sort_unstable_by_key(|&(span, _, _)| span);1211        suggestions.dedup_by_key(|&mut (span, _, _)| span);1212        for (span, msg, suggestion) in suggestions {1213            err.span_suggestion_verbose(span, msg, suggestion, Applicability::MachineApplicable);1214        }12151216        Some(finder.desugar_binding_spans)1217    }12181219    fn add_move_error_details(1220        &self,1221        err: &mut Diag<'_>,1222        binds_to: &[Local],1223        desugar_spans: &[Span],1224    ) {1225        for (j, local) in binds_to.iter().enumerate() {1226            let bind_to = &self.body.local_decls[*local];1227            let binding_span = bind_to.source_info.span;12281229            if binds_to.len() == 1 {1230                let place_desc = self.local_name(*local).map(|sym| format!("`{sym}`"));12311232                err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::LabelMovedHere {1233                    ty: bind_to.ty,1234                    place: place_desc.as_deref().unwrap_or("the place"),1235                    span: binding_span,1236                });12371238                if !desugar_spans.contains(&binding_span)1239                    && let Some(expr) = self.find_expr(binding_span)1240                {1241                    let local_place: PlaceRef<'tcx> = (*local).into();1242                    self.suggest_cloning(err, local_place, bind_to.ty, expr, None);1243                }1244            } else if j == 0 {1245                err.span_label(binding_span, "data moved here");1246            } else {1247                err.span_label(binding_span, "...and here");1248            }1249        }12501251        if binds_to.len() > 1 {1252            err.note(1253                "move occurs because these variables have types that don't implement the `Copy` \1254                 trait",1255            );1256        }1257    }12581259    /// Adds an explanatory note if the move error occurs in a derive macro1260    /// expansion of a packed struct.1261    /// Such errors happen because derive macro expansions shy away from taking1262    /// references to the struct's fields since doing so would be undefined behaviour1263    fn add_note_for_packed_struct_derive(&self, err: &mut Diag<'_>, local: Local) {1264        let local_place: PlaceRef<'tcx> = local.into();1265        let local_ty = local_place.ty(self.body.local_decls(), self.infcx.tcx).ty.peel_refs();12661267        if let Some(adt) = local_ty.ty_adt_def()1268            && adt.repr().packed()1269            && let ExpnKind::Macro(MacroKind::Derive, name) =1270                self.body.span.ctxt().outer_expn_data().kind1271        {1272            err.note(format!("`#[derive({name})]` triggers a move because taking references to the fields of a packed struct is undefined behaviour"));1273        }1274    }1275}

Findings

✓ No findings reported for this file.

Get this view in your editor

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