rustc_borrowck/diagnostics/
move_errors.rs

1#![allow(rustc::diagnostic_outside_of_impl)]
2#![allow(rustc::untranslatable_diagnostic)]
3
4use rustc_data_structures::fx::FxHashSet;
5use rustc_errors::{Applicability, Diag};
6use rustc_hir::intravisit::Visitor;
7use rustc_hir::{self as hir, CaptureBy, ExprKind, HirId, Node};
8use rustc_middle::bug;
9use rustc_middle::mir::*;
10use rustc_middle::ty::{self, Ty, TyCtxt};
11use rustc_mir_dataflow::move_paths::{LookupResult, MovePathIndex};
12use rustc_span::{BytePos, ExpnKind, MacroKind, Span};
13use rustc_trait_selection::error_reporting::traits::FindExprBySpan;
14use rustc_trait_selection::infer::InferCtxtExt;
15use tracing::debug;
16
17use crate::MirBorrowckCtxt;
18use crate::diagnostics::{CapturedMessageOpt, DescribePlaceOpt, UseSpans};
19use crate::prefixes::PrefixSet;
20
21#[derive(Debug)]
22pub(crate) enum IllegalMoveOriginKind<'tcx> {
23    /// Illegal move due to attempt to move from behind a reference.
24    BorrowedContent {
25        /// The place the reference refers to: if erroneous code was trying to
26        /// move from `(*x).f` this will be `*x`.
27        target_place: Place<'tcx>,
28    },
29
30    /// Illegal move due to attempt to move from field of an ADT that
31    /// implements `Drop`. Rust maintains invariant that all `Drop`
32    /// ADT's remain fully-initialized so that user-defined destructor
33    /// can safely read from all of the ADT's fields.
34    InteriorOfTypeWithDestructor { container_ty: Ty<'tcx> },
35
36    /// Illegal move due to attempt to move out of a slice or array.
37    InteriorOfSliceOrArray { ty: Ty<'tcx>, is_index: bool },
38}
39
40#[derive(Debug)]
41pub(crate) struct MoveError<'tcx> {
42    place: Place<'tcx>,
43    location: Location,
44    kind: IllegalMoveOriginKind<'tcx>,
45}
46
47impl<'tcx> MoveError<'tcx> {
48    pub(crate) fn new(
49        place: Place<'tcx>,
50        location: Location,
51        kind: IllegalMoveOriginKind<'tcx>,
52    ) -> Self {
53        MoveError { place, location, kind }
54    }
55}
56
57// Often when desugaring a pattern match we may have many individual moves in
58// MIR that are all part of one operation from the user's point-of-view. For
59// example:
60//
61// let (x, y) = foo()
62//
63// would move x from the 0 field of some temporary, and y from the 1 field. We
64// group such errors together for cleaner error reporting.
65//
66// Errors are kept separate if they are from places with different parent move
67// paths. For example, this generates two errors:
68//
69// let (&x, &y) = (&String::new(), &String::new());
70#[derive(Debug)]
71enum GroupedMoveError<'tcx> {
72    // Place expression can't be moved from,
73    // e.g., match x[0] { s => (), } where x: &[String]
74    MovesFromPlace {
75        original_path: Place<'tcx>,
76        span: Span,
77        move_from: Place<'tcx>,
78        kind: IllegalMoveOriginKind<'tcx>,
79        binds_to: Vec<Local>,
80    },
81    // Part of a value expression can't be moved from,
82    // e.g., match &String::new() { &x => (), }
83    MovesFromValue {
84        original_path: Place<'tcx>,
85        span: Span,
86        move_from: MovePathIndex,
87        kind: IllegalMoveOriginKind<'tcx>,
88        binds_to: Vec<Local>,
89    },
90    // Everything that isn't from pattern matching.
91    OtherIllegalMove {
92        original_path: Place<'tcx>,
93        use_spans: UseSpans<'tcx>,
94        kind: IllegalMoveOriginKind<'tcx>,
95    },
96}
97
98impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
99    pub(crate) fn report_move_errors(&mut self) {
100        let grouped_errors = self.group_move_errors();
101        for error in grouped_errors {
102            self.report(error);
103        }
104    }
105
106    fn group_move_errors(&mut self) -> Vec<GroupedMoveError<'tcx>> {
107        let mut grouped_errors = Vec::new();
108        let errors = std::mem::take(&mut self.move_errors);
109        for error in errors {
110            self.append_to_grouped_errors(&mut grouped_errors, error);
111        }
112        grouped_errors
113    }
114
115    fn append_to_grouped_errors(
116        &self,
117        grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,
118        MoveError { place: original_path, location, kind }: MoveError<'tcx>,
119    ) {
120        // Note: that the only time we assign a place isn't a temporary
121        // to a user variable is when initializing it.
122        // If that ever stops being the case, then the ever initialized
123        // flow could be used.
124        if let Some(StatementKind::Assign(box (place, Rvalue::Use(Operand::Move(move_from))))) =
125            self.body.basic_blocks[location.block]
126                .statements
127                .get(location.statement_index)
128                .map(|stmt| &stmt.kind)
129            && let Some(local) = place.as_local()
130        {
131            let local_decl = &self.body.local_decls[local];
132            // opt_match_place is the
133            // match_span is the span of the expression being matched on
134            // match *x.y { ... }        match_place is Some(*x.y)
135            //       ^^^^                match_span is the span of *x.y
136            //
137            // opt_match_place is None for let [mut] x = ... statements,
138            // whether or not the right-hand side is a place expression
139            if let LocalInfo::User(BindingForm::Var(VarBindingForm {
140                opt_match_place: Some((opt_match_place, match_span)),
141                binding_mode: _,
142                opt_ty_info: _,
143                pat_span: _,
144            })) = *local_decl.local_info()
145            {
146                let stmt_source_info = self.body.source_info(location);
147                self.append_binding_error(
148                    grouped_errors,
149                    kind,
150                    original_path,
151                    *move_from,
152                    local,
153                    opt_match_place,
154                    match_span,
155                    stmt_source_info.span,
156                );
157                return;
158            }
159        }
160
161        let move_spans = self.move_spans(original_path.as_ref(), location);
162        grouped_errors.push(GroupedMoveError::OtherIllegalMove {
163            use_spans: move_spans,
164            original_path,
165            kind,
166        });
167    }
168
169    fn append_binding_error(
170        &self,
171        grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,
172        kind: IllegalMoveOriginKind<'tcx>,
173        original_path: Place<'tcx>,
174        move_from: Place<'tcx>,
175        bind_to: Local,
176        match_place: Option<Place<'tcx>>,
177        match_span: Span,
178        statement_span: Span,
179    ) {
180        debug!(?match_place, ?match_span, "append_binding_error");
181
182        let from_simple_let = match_place.is_none();
183        let match_place = match_place.unwrap_or(move_from);
184
185        match self.move_data.rev_lookup.find(match_place.as_ref()) {
186            // Error with the match place
187            LookupResult::Parent(_) => {
188                for ge in &mut *grouped_errors {
189                    if let GroupedMoveError::MovesFromPlace { span, binds_to, .. } = ge
190                        && match_span == *span
191                    {
192                        debug!("appending local({bind_to:?}) to list");
193                        if !binds_to.is_empty() {
194                            binds_to.push(bind_to);
195                        }
196                        return;
197                    }
198                }
199                debug!("found a new move error location");
200
201                // Don't need to point to x in let x = ... .
202                let (binds_to, span) = if from_simple_let {
203                    (vec![], statement_span)
204                } else {
205                    (vec![bind_to], match_span)
206                };
207                grouped_errors.push(GroupedMoveError::MovesFromPlace {
208                    span,
209                    move_from,
210                    original_path,
211                    kind,
212                    binds_to,
213                });
214            }
215            // Error with the pattern
216            LookupResult::Exact(_) => {
217                let LookupResult::Parent(Some(mpi)) =
218                    self.move_data.rev_lookup.find(move_from.as_ref())
219                else {
220                    // move_from should be a projection from match_place.
221                    unreachable!("Probably not unreachable...");
222                };
223                for ge in &mut *grouped_errors {
224                    if let GroupedMoveError::MovesFromValue {
225                        span,
226                        move_from: other_mpi,
227                        binds_to,
228                        ..
229                    } = ge
230                    {
231                        if match_span == *span && mpi == *other_mpi {
232                            debug!("appending local({bind_to:?}) to list");
233                            binds_to.push(bind_to);
234                            return;
235                        }
236                    }
237                }
238                debug!("found a new move error location");
239                grouped_errors.push(GroupedMoveError::MovesFromValue {
240                    span: match_span,
241                    move_from: mpi,
242                    original_path,
243                    kind,
244                    binds_to: vec![bind_to],
245                });
246            }
247        };
248    }
249
250    fn report(&mut self, error: GroupedMoveError<'tcx>) {
251        let (span, use_spans, original_path, kind) = match error {
252            GroupedMoveError::MovesFromPlace { span, original_path, ref kind, .. }
253            | GroupedMoveError::MovesFromValue { span, original_path, ref kind, .. } => {
254                (span, None, original_path, kind)
255            }
256            GroupedMoveError::OtherIllegalMove { use_spans, original_path, ref kind } => {
257                (use_spans.args_or_use(), Some(use_spans), original_path, kind)
258            }
259        };
260        debug!(
261            "report: original_path={:?} span={:?}, kind={:?} \
262             original_path.is_upvar_field_projection={:?}",
263            original_path,
264            span,
265            kind,
266            self.is_upvar_field_projection(original_path.as_ref())
267        );
268        if self.has_ambiguous_copy(original_path.ty(self.body, self.infcx.tcx).ty) {
269            // If the type may implement Copy, skip the error.
270            // It's an error with the Copy implementation (e.g. duplicate Copy) rather than borrow check
271            self.dcx()
272                .span_delayed_bug(span, "Type may implement copy, but there is no other error.");
273            return;
274        }
275        let mut err = match kind {
276            &IllegalMoveOriginKind::BorrowedContent { target_place } => self
277                .report_cannot_move_from_borrowed_content(
278                    original_path,
279                    target_place,
280                    span,
281                    use_spans,
282                ),
283            &IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => {
284                self.cannot_move_out_of_interior_of_drop(span, ty)
285            }
286            &IllegalMoveOriginKind::InteriorOfSliceOrArray { ty, is_index } => {
287                self.cannot_move_out_of_interior_noncopy(span, ty, Some(is_index))
288            }
289        };
290
291        self.add_move_hints(error, &mut err, span);
292        self.buffer_error(err);
293    }
294
295    fn has_ambiguous_copy(&mut self, ty: Ty<'tcx>) -> bool {
296        let Some(copy_def_id) = self.infcx.tcx.lang_items().copy_trait() else { return false };
297
298        // Avoid bogus move errors because of an incoherent `Copy` impl.
299        self.infcx.type_implements_trait(copy_def_id, [ty], self.infcx.param_env).may_apply()
300            && self.infcx.tcx.coherent_trait(copy_def_id).is_err()
301    }
302
303    fn report_cannot_move_from_static(&mut self, place: Place<'tcx>, span: Span) -> Diag<'infcx> {
304        let description = if place.projection.len() == 1 {
305            format!("static item {}", self.describe_any_place(place.as_ref()))
306        } else {
307            let base_static = PlaceRef { local: place.local, projection: &[ProjectionElem::Deref] };
308
309            format!(
310                "{} as {} is a static item",
311                self.describe_any_place(place.as_ref()),
312                self.describe_any_place(base_static),
313            )
314        };
315
316        self.cannot_move_out_of(span, &description)
317    }
318
319    pub(in crate::diagnostics) fn suggest_clone_of_captured_var_in_move_closure(
320        &self,
321        err: &mut Diag<'_>,
322        upvar_name: &str,
323        use_spans: Option<UseSpans<'tcx>>,
324    ) {
325        let tcx = self.infcx.tcx;
326        let Some(use_spans) = use_spans else { return };
327        // We only care about the case where a closure captured a binding.
328        let UseSpans::ClosureUse { args_span, .. } = use_spans else { return };
329        let Some(body_id) = tcx.hir_node(self.mir_hir_id()).body_id() else { return };
330        // Find the closure that captured the binding.
331        let mut expr_finder = FindExprBySpan::new(args_span, tcx);
332        expr_finder.include_closures = true;
333        expr_finder.visit_expr(tcx.hir_body(body_id).value);
334        let Some(closure_expr) = expr_finder.result else { return };
335        let ExprKind::Closure(closure) = closure_expr.kind else { return };
336        // We'll only suggest cloning the binding if it's a `move` closure.
337        let CaptureBy::Value { .. } = closure.capture_clause else { return };
338        // Find the expression within the closure where the binding is consumed.
339        let mut suggested = false;
340        let use_span = use_spans.var_or_use();
341        let mut expr_finder = FindExprBySpan::new(use_span, tcx);
342        expr_finder.include_closures = true;
343        expr_finder.visit_expr(tcx.hir_body(body_id).value);
344        let Some(use_expr) = expr_finder.result else { return };
345        let parent = tcx.parent_hir_node(use_expr.hir_id);
346        if let Node::Expr(expr) = parent
347            && let ExprKind::Assign(lhs, ..) = expr.kind
348            && lhs.hir_id == use_expr.hir_id
349        {
350            // Cloning the value being assigned makes no sense:
351            //
352            // error[E0507]: cannot move out of `var`, a captured variable in an `FnMut` closure
353            //   --> $DIR/option-content-move2.rs:11:9
354            //    |
355            // LL |     let mut var = None;
356            //    |         ------- captured outer variable
357            // LL |     func(|| {
358            //    |          -- captured by this `FnMut` closure
359            // LL |         // Shouldn't suggest `move ||.as_ref()` here
360            // LL |         move || {
361            //    |         ^^^^^^^ `var` is moved here
362            // LL |
363            // LL |             var = Some(NotCopyable);
364            //    |             ---
365            //    |             |
366            //    |             variable moved due to use in closure
367            //    |             move occurs because `var` has type `Option<NotCopyable>`, which does not implement the `Copy` trait
368            //    |
369            return;
370        }
371
372        // Search for an appropriate place for the structured `.clone()` suggestion to be applied.
373        // If we encounter a statement before the borrow error, we insert a statement there.
374        for (_, node) in tcx.hir_parent_iter(closure_expr.hir_id) {
375            if let Node::Stmt(stmt) = node {
376                let padding = tcx
377                    .sess
378                    .source_map()
379                    .indentation_before(stmt.span)
380                    .unwrap_or_else(|| "    ".to_string());
381                err.multipart_suggestion_verbose(
382                    "consider cloning the value before moving it into the closure",
383                    vec![
384                        (
385                            stmt.span.shrink_to_lo(),
386                            format!("let value = {upvar_name}.clone();\n{padding}"),
387                        ),
388                        (use_span, "value".to_string()),
389                    ],
390                    Applicability::MachineApplicable,
391                );
392                suggested = true;
393                break;
394            } else if let Node::Expr(expr) = node
395                && let ExprKind::Closure(_) = expr.kind
396            {
397                // We want to suggest cloning only on the first closure, not
398                // subsequent ones (like `ui/suggestions/option-content-move2.rs`).
399                break;
400            }
401        }
402        if !suggested {
403            // If we couldn't find a statement for us to insert a new `.clone()` statement before,
404            // we have a bare expression, so we suggest the creation of a new block inline to go
405            // from `move || val` to `{ let value = val.clone(); move || value }`.
406            let padding = tcx
407                .sess
408                .source_map()
409                .indentation_before(closure_expr.span)
410                .unwrap_or_else(|| "    ".to_string());
411            err.multipart_suggestion_verbose(
412                "consider cloning the value before moving it into the closure",
413                vec![
414                    (
415                        closure_expr.span.shrink_to_lo(),
416                        format!("{{\n{padding}let value = {upvar_name}.clone();\n{padding}"),
417                    ),
418                    (use_spans.var_or_use(), "value".to_string()),
419                    (closure_expr.span.shrink_to_hi(), format!("\n{padding}}}")),
420                ],
421                Applicability::MachineApplicable,
422            );
423        }
424    }
425
426    fn report_cannot_move_from_borrowed_content(
427        &mut self,
428        move_place: Place<'tcx>,
429        deref_target_place: Place<'tcx>,
430        span: Span,
431        use_spans: Option<UseSpans<'tcx>>,
432    ) -> Diag<'infcx> {
433        let tcx = self.infcx.tcx;
434        // Inspect the type of the content behind the
435        // borrow to provide feedback about why this
436        // was a move rather than a copy.
437        let ty = deref_target_place.ty(self.body, tcx).ty;
438        let upvar_field = self
439            .prefixes(move_place.as_ref(), PrefixSet::All)
440            .find_map(|p| self.is_upvar_field_projection(p));
441
442        let deref_base = match deref_target_place.projection.as_ref() {
443            [proj_base @ .., ProjectionElem::Deref] => {
444                PlaceRef { local: deref_target_place.local, projection: proj_base }
445            }
446            _ => bug!("deref_target_place is not a deref projection"),
447        };
448
449        if let PlaceRef { local, projection: [] } = deref_base {
450            let decl = &self.body.local_decls[local];
451            let local_name = self.local_name(local).map(|sym| format!("`{sym}`"));
452            if decl.is_ref_for_guard() {
453                return self
454                    .cannot_move_out_of(
455                        span,
456                        &format!(
457                            "{} in pattern guard",
458                            local_name.as_deref().unwrap_or("the place")
459                        ),
460                    )
461                    .with_note(
462                        "variables bound in patterns cannot be moved from \
463                         until after the end of the pattern guard",
464                    );
465            } else if decl.is_ref_to_static() {
466                return self.report_cannot_move_from_static(move_place, span);
467            }
468        }
469
470        debug!("report: ty={:?}", ty);
471        let mut err = match ty.kind() {
472            ty::Array(..) | ty::Slice(..) => {
473                self.cannot_move_out_of_interior_noncopy(span, ty, None)
474            }
475            ty::Closure(def_id, closure_args)
476                if def_id.as_local() == Some(self.mir_def_id())
477                    && let Some(upvar_field) = upvar_field =>
478            {
479                let closure_kind_ty = closure_args.as_closure().kind_ty();
480                let closure_kind = match closure_kind_ty.to_opt_closure_kind() {
481                    Some(kind @ (ty::ClosureKind::Fn | ty::ClosureKind::FnMut)) => kind,
482                    Some(ty::ClosureKind::FnOnce) => {
483                        bug!("closure kind does not match first argument type")
484                    }
485                    None => bug!("closure kind not inferred by borrowck"),
486                };
487                let capture_description =
488                    format!("captured variable in an `{closure_kind}` closure");
489
490                let upvar = &self.upvars[upvar_field.index()];
491                let upvar_hir_id = upvar.get_root_variable();
492                let upvar_name = upvar.to_string(tcx);
493                let upvar_span = tcx.hir_span(upvar_hir_id);
494
495                let place_name = self.describe_any_place(move_place.as_ref());
496
497                let place_description =
498                    if self.is_upvar_field_projection(move_place.as_ref()).is_some() {
499                        format!("{place_name}, a {capture_description}")
500                    } else {
501                        format!("{place_name}, as `{upvar_name}` is a {capture_description}")
502                    };
503
504                debug!(
505                    "report: closure_kind_ty={:?} closure_kind={:?} place_description={:?}",
506                    closure_kind_ty, closure_kind, place_description,
507                );
508
509                let closure_span = tcx.def_span(def_id);
510                self.cannot_move_out_of(span, &place_description)
511                    .with_span_label(upvar_span, "captured outer variable")
512                    .with_span_label(
513                        closure_span,
514                        format!("captured by this `{closure_kind}` closure"),
515                    )
516            }
517            _ => {
518                let source = self.borrowed_content_source(deref_base);
519                let move_place_ref = move_place.as_ref();
520                match (
521                    self.describe_place_with_options(
522                        move_place_ref,
523                        DescribePlaceOpt {
524                            including_downcast: false,
525                            including_tuple_field: false,
526                        },
527                    ),
528                    self.describe_name(move_place_ref),
529                    source.describe_for_named_place(),
530                ) {
531                    (Some(place_desc), Some(name), Some(source_desc)) => self.cannot_move_out_of(
532                        span,
533                        &format!("`{place_desc}` as enum variant `{name}` which is behind a {source_desc}"),
534                    ),
535                    (Some(place_desc), Some(name), None) => self.cannot_move_out_of(
536                        span,
537                        &format!("`{place_desc}` as enum variant `{name}`"),
538                    ),
539                    (Some(place_desc), _, Some(source_desc)) => self.cannot_move_out_of(
540                        span,
541                        &format!("`{place_desc}` which is behind a {source_desc}"),
542                    ),
543                    (_, _, _) => self.cannot_move_out_of(
544                        span,
545                        &source.describe_for_unnamed_place(tcx),
546                    ),
547                }
548            }
549        };
550        let msg_opt = CapturedMessageOpt {
551            is_partial_move: false,
552            is_loop_message: false,
553            is_move_msg: false,
554            is_loop_move: false,
555            has_suggest_reborrow: false,
556            maybe_reinitialized_locations_is_empty: true,
557        };
558        if let Some(use_spans) = use_spans {
559            self.explain_captures(&mut err, span, span, use_spans, move_place, msg_opt);
560        }
561        err
562    }
563
564    fn add_move_hints(&self, error: GroupedMoveError<'tcx>, err: &mut Diag<'_>, span: Span) {
565        match error {
566            GroupedMoveError::MovesFromPlace { mut binds_to, move_from, .. } => {
567                self.add_borrow_suggestions(err, span);
568                if binds_to.is_empty() {
569                    let place_ty = move_from.ty(self.body, self.infcx.tcx).ty;
570                    let place_desc = match self.describe_place(move_from.as_ref()) {
571                        Some(desc) => format!("`{desc}`"),
572                        None => "value".to_string(),
573                    };
574
575                    if let Some(expr) = self.find_expr(span) {
576                        self.suggest_cloning(err, move_from.as_ref(), place_ty, expr, None);
577                    }
578
579                    err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {
580                        is_partial_move: false,
581                        ty: place_ty,
582                        place: &place_desc,
583                        span,
584                    });
585                } else {
586                    binds_to.sort();
587                    binds_to.dedup();
588
589                    self.add_move_error_details(err, &binds_to);
590                }
591            }
592            GroupedMoveError::MovesFromValue { mut binds_to, .. } => {
593                binds_to.sort();
594                binds_to.dedup();
595                self.add_move_error_suggestions(err, &binds_to);
596                self.add_move_error_details(err, &binds_to);
597            }
598            // No binding. Nothing to suggest.
599            GroupedMoveError::OtherIllegalMove { ref original_path, use_spans, .. } => {
600                let mut use_span = use_spans.var_or_use();
601                let place_ty = original_path.ty(self.body, self.infcx.tcx).ty;
602                let place_desc = match self.describe_place(original_path.as_ref()) {
603                    Some(desc) => format!("`{desc}`"),
604                    None => "value".to_string(),
605                };
606
607                if let Some(expr) = self.find_expr(use_span) {
608                    self.suggest_cloning(
609                        err,
610                        original_path.as_ref(),
611                        place_ty,
612                        expr,
613                        Some(use_spans),
614                    );
615                }
616
617                if let Some(upvar_field) = self
618                    .prefixes(original_path.as_ref(), PrefixSet::All)
619                    .find_map(|p| self.is_upvar_field_projection(p))
620                {
621                    // Look for the introduction of the original binding being moved.
622                    let upvar = &self.upvars[upvar_field.index()];
623                    let upvar_hir_id = upvar.get_root_variable();
624                    use_span = match self.infcx.tcx.parent_hir_node(upvar_hir_id) {
625                        hir::Node::Param(param) => {
626                            // Instead of pointing at the path where we access the value within a
627                            // closure, we point at the type of the outer `fn` argument.
628                            param.ty_span
629                        }
630                        hir::Node::LetStmt(stmt) => match (stmt.ty, stmt.init) {
631                            // We point at the type of the outer let-binding.
632                            (Some(ty), _) => ty.span,
633                            // We point at the initializer of the outer let-binding, but only if it
634                            // isn't something that spans multiple lines, like a closure, as the
635                            // ASCII art gets messy.
636                            (None, Some(init))
637                                if !self.infcx.tcx.sess.source_map().is_multiline(init.span) =>
638                            {
639                                init.span
640                            }
641                            _ => use_span,
642                        },
643                        _ => use_span,
644                    };
645                }
646
647                err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {
648                    is_partial_move: false,
649                    ty: place_ty,
650                    place: &place_desc,
651                    span: use_span,
652                });
653
654                let mut pointed_at_span = false;
655                use_spans.args_subdiag(err, |args_span| {
656                    if args_span == span || args_span == use_span {
657                        pointed_at_span = true;
658                    }
659                    crate::session_diagnostics::CaptureArgLabel::MoveOutPlace {
660                        place: place_desc.clone(),
661                        args_span,
662                    }
663                });
664                if !pointed_at_span && use_span != span {
665                    err.subdiagnostic(crate::session_diagnostics::CaptureArgLabel::MoveOutPlace {
666                        place: place_desc,
667                        args_span: span,
668                    });
669                }
670
671                self.add_note_for_packed_struct_derive(err, original_path.local);
672            }
673        }
674    }
675
676    fn add_borrow_suggestions(&self, err: &mut Diag<'_>, span: Span) {
677        match self.infcx.tcx.sess.source_map().span_to_snippet(span) {
678            Ok(snippet) if snippet.starts_with('*') => {
679                let sp = span.with_lo(span.lo() + BytePos(1));
680                let inner = self.find_expr(sp);
681                let mut is_raw_ptr = false;
682                if let Some(inner) = inner {
683                    let typck_result = self.infcx.tcx.typeck(self.mir_def_id());
684                    if let Some(inner_type) = typck_result.node_type_opt(inner.hir_id) {
685                        if matches!(inner_type.kind(), ty::RawPtr(..)) {
686                            is_raw_ptr = true;
687                        }
688                    }
689                }
690                // If the `inner` is a raw pointer, do not suggest removing the "*", see #126863
691                // FIXME: need to check whether the assigned object can be a raw pointer, see `tests/ui/borrowck/issue-20801.rs`.
692                if !is_raw_ptr {
693                    err.span_suggestion_verbose(
694                        span.with_hi(span.lo() + BytePos(1)),
695                        "consider removing the dereference here",
696                        String::new(),
697                        Applicability::MaybeIncorrect,
698                    );
699                }
700            }
701            _ => {
702                err.span_suggestion_verbose(
703                    span.shrink_to_lo(),
704                    "consider borrowing here",
705                    '&',
706                    Applicability::MaybeIncorrect,
707                );
708            }
709        }
710    }
711
712    fn add_move_error_suggestions(&self, err: &mut Diag<'_>, binds_to: &[Local]) {
713        /// A HIR visitor to associate each binding with a `&` or `&mut` that could be removed to
714        /// make it bind by reference instead (if possible)
715        struct BindingFinder<'tcx> {
716            typeck_results: &'tcx ty::TypeckResults<'tcx>,
717            tcx: TyCtxt<'tcx>,
718            /// Input: the span of the pattern we're finding bindings in
719            pat_span: Span,
720            /// Input: the spans of the bindings we're providing suggestions for
721            binding_spans: Vec<Span>,
722            /// Internal state: have we reached the pattern we're finding bindings in?
723            found_pat: bool,
724            /// Internal state: the innermost `&` or `&mut` "above" the visitor
725            ref_pat: Option<&'tcx hir::Pat<'tcx>>,
726            /// Internal state: could removing a `&` give bindings unexpected types?
727            has_adjustments: bool,
728            /// Output: for each input binding, the `&` or `&mut` to remove to make it by-ref
729            ref_pat_for_binding: Vec<(Span, Option<&'tcx hir::Pat<'tcx>>)>,
730            /// Output: ref patterns that can't be removed straightforwardly
731            cannot_remove: FxHashSet<HirId>,
732        }
733        impl<'tcx> Visitor<'tcx> for BindingFinder<'tcx> {
734            type NestedFilter = rustc_middle::hir::nested_filter::OnlyBodies;
735
736            fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
737                self.tcx
738            }
739
740            fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) -> Self::Result {
741                // Don't walk into const patterns or anything else that might confuse this
742                if !self.found_pat {
743                    hir::intravisit::walk_expr(self, ex)
744                }
745            }
746
747            fn visit_pat(&mut self, p: &'tcx hir::Pat<'tcx>) {
748                if p.span == self.pat_span {
749                    self.found_pat = true;
750                }
751
752                let parent_has_adjustments = self.has_adjustments;
753                self.has_adjustments |=
754                    self.typeck_results.pat_adjustments().contains_key(p.hir_id);
755
756                // Track the innermost `&` or `&mut` enclosing bindings, to suggest removing it.
757                let parent_ref_pat = self.ref_pat;
758                if let hir::PatKind::Ref(..) = p.kind {
759                    self.ref_pat = Some(p);
760                    // To avoid edition-dependent logic to figure out how many refs this `&` can
761                    // peel off, simply don't remove the "parent" `&`.
762                    self.cannot_remove.extend(parent_ref_pat.map(|r| r.hir_id));
763                    if self.has_adjustments {
764                        // Removing this `&` could give child bindings unexpected types, so don't.
765                        self.cannot_remove.insert(p.hir_id);
766                        // As long the `&` stays, child patterns' types should be as expected.
767                        self.has_adjustments = false;
768                    }
769                }
770
771                if let hir::PatKind::Binding(_, _, ident, _) = p.kind {
772                    // the spans in `binding_spans` encompass both the ident and binding mode
773                    if let Some(&bind_sp) =
774                        self.binding_spans.iter().find(|bind_sp| bind_sp.contains(ident.span))
775                    {
776                        self.ref_pat_for_binding.push((bind_sp, self.ref_pat));
777                    } else {
778                        // we've encountered a binding that we're not reporting a move error for.
779                        // we don't want to change its type, so don't remove the surrounding `&`.
780                        if let Some(ref_pat) = self.ref_pat {
781                            self.cannot_remove.insert(ref_pat.hir_id);
782                        }
783                    }
784                }
785
786                hir::intravisit::walk_pat(self, p);
787                self.ref_pat = parent_ref_pat;
788                self.has_adjustments = parent_has_adjustments;
789            }
790        }
791        let mut pat_span = None;
792        let mut binding_spans = Vec::new();
793        for local in binds_to {
794            let bind_to = &self.body.local_decls[*local];
795            if let LocalInfo::User(BindingForm::Var(VarBindingForm { pat_span: pat_sp, .. })) =
796                *bind_to.local_info()
797            {
798                pat_span = Some(pat_sp);
799                binding_spans.push(bind_to.source_info.span);
800            }
801        }
802        let Some(pat_span) = pat_span else { return };
803
804        let tcx = self.infcx.tcx;
805        let Some(body) = tcx.hir_maybe_body_owned_by(self.mir_def_id()) else { return };
806        let typeck_results = self.infcx.tcx.typeck(self.mir_def_id());
807        let mut finder = BindingFinder {
808            typeck_results,
809            tcx,
810            pat_span,
811            binding_spans,
812            found_pat: false,
813            ref_pat: None,
814            has_adjustments: false,
815            ref_pat_for_binding: Vec::new(),
816            cannot_remove: FxHashSet::default(),
817        };
818        finder.visit_body(body);
819
820        let mut suggestions = Vec::new();
821        for (binding_span, opt_ref_pat) in finder.ref_pat_for_binding {
822            if let Some(ref_pat) = opt_ref_pat
823                && !finder.cannot_remove.contains(&ref_pat.hir_id)
824                && let hir::PatKind::Ref(subpat, mutbl) = ref_pat.kind
825                && let Some(ref_span) = ref_pat.span.trim_end(subpat.span)
826            {
827                let mutable_str = if mutbl.is_mut() { "mutable " } else { "" };
828                let msg = format!("consider removing the {mutable_str}borrow");
829                suggestions.push((ref_span, msg, "".to_string()));
830            } else {
831                let msg = "consider borrowing the pattern binding".to_string();
832                suggestions.push((binding_span.shrink_to_lo(), msg, "ref ".to_string()));
833            }
834        }
835        suggestions.sort_unstable_by_key(|&(span, _, _)| span);
836        suggestions.dedup_by_key(|&mut (span, _, _)| span);
837        for (span, msg, suggestion) in suggestions {
838            err.span_suggestion_verbose(span, msg, suggestion, Applicability::MachineApplicable);
839        }
840    }
841
842    fn add_move_error_details(&self, err: &mut Diag<'_>, binds_to: &[Local]) {
843        for (j, local) in binds_to.iter().enumerate() {
844            let bind_to = &self.body.local_decls[*local];
845            let binding_span = bind_to.source_info.span;
846
847            if j == 0 {
848                err.span_label(binding_span, "data moved here");
849            } else {
850                err.span_label(binding_span, "...and here");
851            }
852
853            if binds_to.len() == 1 {
854                let place_desc = self.local_name(*local).map(|sym| format!("`{sym}`"));
855
856                if let Some(expr) = self.find_expr(binding_span) {
857                    let local_place: PlaceRef<'tcx> = (*local).into();
858                    self.suggest_cloning(err, local_place, bind_to.ty, expr, None);
859                }
860
861                err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {
862                    is_partial_move: false,
863                    ty: bind_to.ty,
864                    place: place_desc.as_deref().unwrap_or("the place"),
865                    span: binding_span,
866                });
867            }
868        }
869
870        if binds_to.len() > 1 {
871            err.note(
872                "move occurs because these variables have types that don't implement the `Copy` \
873                 trait",
874            );
875        }
876    }
877
878    /// Adds an explanatory note if the move error occurs in a derive macro
879    /// expansion of a packed struct.
880    /// Such errors happen because derive macro expansions shy away from taking
881    /// references to the struct's fields since doing so would be undefined behaviour
882    fn add_note_for_packed_struct_derive(&self, err: &mut Diag<'_>, local: Local) {
883        let local_place: PlaceRef<'tcx> = local.into();
884        let local_ty = local_place.ty(self.body.local_decls(), self.infcx.tcx).ty.peel_refs();
885
886        if let Some(adt) = local_ty.ty_adt_def()
887            && adt.repr().packed()
888            && let ExpnKind::Macro(MacroKind::Derive, name) =
889                self.body.span.ctxt().outer_expn_data().kind
890        {
891            err.note(format!("`#[derive({name})]` triggers a move because taking references to the fields of a packed struct is undefined behaviour"));
892        }
893    }
894}