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 BorrowedContent {
26 target_place: Place<'tcx>,
29 },
30
31 InteriorOfTypeWithDestructor { container_ty: Ty<'tcx> },
36
37 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#[derive(Debug)]
72enum GroupedMoveError<'tcx> {
73 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 MovesFromValue {
85 original_path: Place<'tcx>,
86 span: Span,
87 move_from: MovePathIndex,
88 kind: IllegalMoveOriginKind<'tcx>,
89 binds_to: Vec<Local>,
90 },
91 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 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 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 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 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 LookupResult::Exact(_) => {
218 let LookupResult::Parent(Some(mpi)) =
219 self.move_data.rev_lookup.find(move_from.as_ref())
220 else {
221 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 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 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 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 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 let CaptureBy::Value { .. } = closure.capture_clause else { return };
339 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 return;
371 }
372
373 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 break;
401 }
402 }
403 if !suggested {
404 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 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 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 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 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 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 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 param.ty_span
677 }
678 hir::Node::LetStmt(stmt) => match (stmt.ty, stmt.init) {
679 (Some(ty), _) => ty.span,
681 (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 !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 struct BindingFinder<'tcx> {
764 typeck_results: &'tcx ty::TypeckResults<'tcx>,
765 tcx: TyCtxt<'tcx>,
766 pat_span: Span,
768 binding_spans: Vec<Span>,
770 found_pat: bool,
772 ref_pat: Option<&'tcx hir::Pat<'tcx>>,
774 has_adjustments: bool,
776 ref_pat_for_binding: Vec<(Span, Option<&'tcx hir::Pat<'tcx>>)>,
778 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 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 let parent_ref_pat = self.ref_pat;
806 if let hir::PatKind::Ref(..) = p.kind {
807 self.ref_pat = Some(p);
808 self.cannot_remove.extend(parent_ref_pat.map(|r| r.hir_id));
811 if self.has_adjustments {
812 self.cannot_remove.insert(p.hir_id);
814 self.has_adjustments = false;
816 }
817 }
818
819 if let hir::PatKind::Binding(_, _, ident, _) = p.kind {
820 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 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 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}