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