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 BorrowedContent {
25 target_place: Place<'tcx>,
28 },
29
30 InteriorOfTypeWithDestructor { container_ty: Ty<'tcx> },
35
36 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#[derive(Debug)]
71enum GroupedMoveError<'tcx> {
72 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 MovesFromValue {
84 original_path: Place<'tcx>,
85 span: Span,
86 move_from: MovePathIndex,
87 kind: IllegalMoveOriginKind<'tcx>,
88 binds_to: Vec<Local>,
89 },
90 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 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 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 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 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 LookupResult::Exact(_) => {
220 let LookupResult::Parent(Some(mpi)) =
221 self.move_data.rev_lookup.find(move_from.as_ref())
222 else {
223 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 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 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 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 let Some(captured_ty) = typeck_results.node_type_opt(upvar_hir_id) else { return };
343 if !self.implements_clone(captured_ty) {
344 return;
346 };
347 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 let CaptureBy::Value { .. } = closure.capture_clause else { return };
355 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 return;
387 }
388
389 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 break;
417 }
418 }
419 if !suggested {
420 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 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 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 !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 struct BindingFinder<'tcx> {
692 typeck_results: &'tcx ty::TypeckResults<'tcx>,
693 hir: rustc_middle::hir::map::Map<'tcx>,
694 pat_span: Span,
696 binding_spans: Vec<Span>,
698 found_pat: bool,
700 ref_pat: Option<&'tcx hir::Pat<'tcx>>,
702 has_adjustments: bool,
704 ref_pat_for_binding: Vec<(Span, Option<&'tcx hir::Pat<'tcx>>)>,
706 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 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 let parent_ref_pat = self.ref_pat;
734 if let hir::PatKind::Ref(..) = p.kind {
735 self.ref_pat = Some(p);
736 self.cannot_remove.extend(parent_ref_pat.map(|r| r.hir_id));
739 if self.has_adjustments {
740 self.cannot_remove.insert(p.hir_id);
742 self.has_adjustments = false;
744 }
745 }
746
747 if let hir::PatKind::Binding(_, _, ident, _) = p.kind {
748 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 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 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}