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