• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed};
2 use rustc_middle::mir::*;
3 use rustc_middle::ty;
4 use rustc_mir_dataflow::move_paths::{
5     IllegalMoveOrigin, IllegalMoveOriginKind, LookupResult, MoveError, MovePathIndex,
6 };
7 use rustc_span::{BytePos, Span};
8 
9 use crate::diagnostics::CapturedMessageOpt;
10 use crate::diagnostics::{DescribePlaceOpt, UseSpans};
11 use crate::prefixes::PrefixSet;
12 use crate::MirBorrowckCtxt;
13 
14 // Often when desugaring a pattern match we may have many individual moves in
15 // MIR that are all part of one operation from the user's point-of-view. For
16 // example:
17 //
18 // let (x, y) = foo()
19 //
20 // would move x from the 0 field of some temporary, and y from the 1 field. We
21 // group such errors together for cleaner error reporting.
22 //
23 // Errors are kept separate if they are from places with different parent move
24 // paths. For example, this generates two errors:
25 //
26 // let (&x, &y) = (&String::new(), &String::new());
27 #[derive(Debug)]
28 enum GroupedMoveError<'tcx> {
29     // Place expression can't be moved from,
30     // e.g., match x[0] { s => (), } where x: &[String]
31     MovesFromPlace {
32         original_path: Place<'tcx>,
33         span: Span,
34         move_from: Place<'tcx>,
35         kind: IllegalMoveOriginKind<'tcx>,
36         binds_to: Vec<Local>,
37     },
38     // Part of a value expression can't be moved from,
39     // e.g., match &String::new() { &x => (), }
40     MovesFromValue {
41         original_path: Place<'tcx>,
42         span: Span,
43         move_from: MovePathIndex,
44         kind: IllegalMoveOriginKind<'tcx>,
45         binds_to: Vec<Local>,
46     },
47     // Everything that isn't from pattern matching.
48     OtherIllegalMove {
49         original_path: Place<'tcx>,
50         use_spans: UseSpans<'tcx>,
51         kind: IllegalMoveOriginKind<'tcx>,
52     },
53 }
54 
55 impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
report_move_errors(&mut self, move_errors: Vec<(Place<'tcx>, MoveError<'tcx>)>)56     pub(crate) fn report_move_errors(&mut self, move_errors: Vec<(Place<'tcx>, MoveError<'tcx>)>) {
57         let grouped_errors = self.group_move_errors(move_errors);
58         for error in grouped_errors {
59             self.report(error);
60         }
61     }
62 
group_move_errors( &self, errors: Vec<(Place<'tcx>, MoveError<'tcx>)>, ) -> Vec<GroupedMoveError<'tcx>>63     fn group_move_errors(
64         &self,
65         errors: Vec<(Place<'tcx>, MoveError<'tcx>)>,
66     ) -> Vec<GroupedMoveError<'tcx>> {
67         let mut grouped_errors = Vec::new();
68         for (original_path, error) in errors {
69             self.append_to_grouped_errors(&mut grouped_errors, original_path, error);
70         }
71         grouped_errors
72     }
73 
append_to_grouped_errors( &self, grouped_errors: &mut Vec<GroupedMoveError<'tcx>>, original_path: Place<'tcx>, error: MoveError<'tcx>, )74     fn append_to_grouped_errors(
75         &self,
76         grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,
77         original_path: Place<'tcx>,
78         error: MoveError<'tcx>,
79     ) {
80         match error {
81             MoveError::UnionMove { .. } => {
82                 unimplemented!("don't know how to report union move errors yet.")
83             }
84             MoveError::IllegalMove { cannot_move_out_of: IllegalMoveOrigin { location, kind } } => {
85                 // Note: that the only time we assign a place isn't a temporary
86                 // to a user variable is when initializing it.
87                 // If that ever stops being the case, then the ever initialized
88                 // flow could be used.
89                 if let Some(StatementKind::Assign(box (
90                     place,
91                     Rvalue::Use(Operand::Move(move_from)),
92                 ))) = self.body.basic_blocks[location.block]
93                     .statements
94                     .get(location.statement_index)
95                     .map(|stmt| &stmt.kind)
96                 {
97                     if let Some(local) = place.as_local() {
98                         let local_decl = &self.body.local_decls[local];
99                         // opt_match_place is the
100                         // match_span is the span of the expression being matched on
101                         // match *x.y { ... }        match_place is Some(*x.y)
102                         //       ^^^^                match_span is the span of *x.y
103                         //
104                         // opt_match_place is None for let [mut] x = ... statements,
105                         // whether or not the right-hand side is a place expression
106                         if let LocalInfo::User(BindingForm::Var(VarBindingForm {
107                             opt_match_place: Some((opt_match_place, match_span)),
108                             binding_mode: _,
109                             opt_ty_info: _,
110                             pat_span: _,
111                         })) = *local_decl.local_info()
112                         {
113                             let stmt_source_info = self.body.source_info(location);
114                             self.append_binding_error(
115                                 grouped_errors,
116                                 kind,
117                                 original_path,
118                                 *move_from,
119                                 local,
120                                 opt_match_place,
121                                 match_span,
122                                 stmt_source_info.span,
123                             );
124                             return;
125                         }
126                     }
127                 }
128 
129                 let move_spans = self.move_spans(original_path.as_ref(), location);
130                 grouped_errors.push(GroupedMoveError::OtherIllegalMove {
131                     use_spans: move_spans,
132                     original_path,
133                     kind,
134                 });
135             }
136         }
137     }
138 
append_binding_error( &self, grouped_errors: &mut Vec<GroupedMoveError<'tcx>>, kind: IllegalMoveOriginKind<'tcx>, original_path: Place<'tcx>, move_from: Place<'tcx>, bind_to: Local, match_place: Option<Place<'tcx>>, match_span: Span, statement_span: Span, )139     fn append_binding_error(
140         &self,
141         grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,
142         kind: IllegalMoveOriginKind<'tcx>,
143         original_path: Place<'tcx>,
144         move_from: Place<'tcx>,
145         bind_to: Local,
146         match_place: Option<Place<'tcx>>,
147         match_span: Span,
148         statement_span: Span,
149     ) {
150         debug!(?match_place, ?match_span, "append_binding_error");
151 
152         let from_simple_let = match_place.is_none();
153         let match_place = match_place.unwrap_or(move_from);
154 
155         match self.move_data.rev_lookup.find(match_place.as_ref()) {
156             // Error with the match place
157             LookupResult::Parent(_) => {
158                 for ge in &mut *grouped_errors {
159                     if let GroupedMoveError::MovesFromPlace { span, binds_to, .. } = ge
160                         && match_span == *span
161                     {
162                         debug!("appending local({bind_to:?}) to list");
163                         if !binds_to.is_empty() {
164                             binds_to.push(bind_to);
165                         }
166                         return;
167                     }
168                 }
169                 debug!("found a new move error location");
170 
171                 // Don't need to point to x in let x = ... .
172                 let (binds_to, span) = if from_simple_let {
173                     (vec![], statement_span)
174                 } else {
175                     (vec![bind_to], match_span)
176                 };
177                 grouped_errors.push(GroupedMoveError::MovesFromPlace {
178                     span,
179                     move_from,
180                     original_path,
181                     kind,
182                     binds_to,
183                 });
184             }
185             // Error with the pattern
186             LookupResult::Exact(_) => {
187                 let LookupResult::Parent(Some(mpi)) = self.move_data.rev_lookup.find(move_from.as_ref()) else {
188                     // move_from should be a projection from match_place.
189                     unreachable!("Probably not unreachable...");
190                 };
191                 for ge in &mut *grouped_errors {
192                     if let GroupedMoveError::MovesFromValue {
193                         span,
194                         move_from: other_mpi,
195                         binds_to,
196                         ..
197                     } = ge
198                     {
199                         if match_span == *span && mpi == *other_mpi {
200                             debug!("appending local({bind_to:?}) to list");
201                             binds_to.push(bind_to);
202                             return;
203                         }
204                     }
205                 }
206                 debug!("found a new move error location");
207                 grouped_errors.push(GroupedMoveError::MovesFromValue {
208                     span: match_span,
209                     move_from: mpi,
210                     original_path,
211                     kind,
212                     binds_to: vec![bind_to],
213                 });
214             }
215         };
216     }
217 
report(&mut self, error: GroupedMoveError<'tcx>)218     fn report(&mut self, error: GroupedMoveError<'tcx>) {
219         let (mut err, err_span) = {
220             let (span, use_spans, original_path, kind) = match error {
221                 GroupedMoveError::MovesFromPlace { span, original_path, ref kind, .. }
222                 | GroupedMoveError::MovesFromValue { span, original_path, ref kind, .. } => {
223                     (span, None, original_path, kind)
224                 }
225                 GroupedMoveError::OtherIllegalMove { use_spans, original_path, ref kind } => {
226                     (use_spans.args_or_use(), Some(use_spans), original_path, kind)
227                 }
228             };
229             debug!(
230                 "report: original_path={:?} span={:?}, kind={:?} \
231                    original_path.is_upvar_field_projection={:?}",
232                 original_path,
233                 span,
234                 kind,
235                 self.is_upvar_field_projection(original_path.as_ref())
236             );
237             (
238                 match kind {
239                     &IllegalMoveOriginKind::BorrowedContent { target_place } => self
240                         .report_cannot_move_from_borrowed_content(
241                             original_path,
242                             target_place,
243                             span,
244                             use_spans,
245                         ),
246                     &IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => {
247                         self.cannot_move_out_of_interior_of_drop(span, ty)
248                     }
249                     &IllegalMoveOriginKind::InteriorOfSliceOrArray { ty, is_index } => {
250                         self.cannot_move_out_of_interior_noncopy(span, ty, Some(is_index))
251                     }
252                 },
253                 span,
254             )
255         };
256 
257         self.add_move_hints(error, &mut err, err_span);
258         self.buffer_error(err);
259     }
260 
report_cannot_move_from_static( &mut self, place: Place<'tcx>, span: Span, ) -> DiagnosticBuilder<'a, ErrorGuaranteed>261     fn report_cannot_move_from_static(
262         &mut self,
263         place: Place<'tcx>,
264         span: Span,
265     ) -> DiagnosticBuilder<'a, ErrorGuaranteed> {
266         let description = if place.projection.len() == 1 {
267             format!("static item {}", self.describe_any_place(place.as_ref()))
268         } else {
269             let base_static = PlaceRef { local: place.local, projection: &[ProjectionElem::Deref] };
270 
271             format!(
272                 "{} as {} is a static item",
273                 self.describe_any_place(place.as_ref()),
274                 self.describe_any_place(base_static),
275             )
276         };
277 
278         self.cannot_move_out_of(span, &description)
279     }
280 
report_cannot_move_from_borrowed_content( &mut self, move_place: Place<'tcx>, deref_target_place: Place<'tcx>, span: Span, use_spans: Option<UseSpans<'tcx>>, ) -> DiagnosticBuilder<'a, ErrorGuaranteed>281     fn report_cannot_move_from_borrowed_content(
282         &mut self,
283         move_place: Place<'tcx>,
284         deref_target_place: Place<'tcx>,
285         span: Span,
286         use_spans: Option<UseSpans<'tcx>>,
287     ) -> DiagnosticBuilder<'a, ErrorGuaranteed> {
288         // Inspect the type of the content behind the
289         // borrow to provide feedback about why this
290         // was a move rather than a copy.
291         let ty = deref_target_place.ty(self.body, self.infcx.tcx).ty;
292         let upvar_field = self
293             .prefixes(move_place.as_ref(), PrefixSet::All)
294             .find_map(|p| self.is_upvar_field_projection(p));
295 
296         let deref_base = match deref_target_place.projection.as_ref() {
297             [proj_base @ .., ProjectionElem::Deref] => {
298                 PlaceRef { local: deref_target_place.local, projection: &proj_base }
299             }
300             _ => bug!("deref_target_place is not a deref projection"),
301         };
302 
303         if let PlaceRef { local, projection: [] } = deref_base {
304             let decl = &self.body.local_decls[local];
305             if decl.is_ref_for_guard() {
306                 let mut err = self.cannot_move_out_of(
307                     span,
308                     &format!("`{}` in pattern guard", self.local_names[local].unwrap()),
309                 );
310                 err.note(
311                     "variables bound in patterns cannot be moved from \
312                      until after the end of the pattern guard",
313                 );
314                 return err;
315             } else if decl.is_ref_to_static() {
316                 return self.report_cannot_move_from_static(move_place, span);
317             }
318         }
319 
320         debug!("report: ty={:?}", ty);
321         let mut err = match ty.kind() {
322             ty::Array(..) | ty::Slice(..) => {
323                 self.cannot_move_out_of_interior_noncopy(span, ty, None)
324             }
325             ty::Closure(def_id, closure_substs)
326                 if def_id.as_local() == Some(self.mir_def_id()) && upvar_field.is_some() =>
327             {
328                 let closure_kind_ty = closure_substs.as_closure().kind_ty();
329                 let closure_kind = match closure_kind_ty.to_opt_closure_kind() {
330                     Some(kind @ (ty::ClosureKind::Fn | ty::ClosureKind::FnMut)) => kind,
331                     Some(ty::ClosureKind::FnOnce) => {
332                         bug!("closure kind does not match first argument type")
333                     }
334                     None => bug!("closure kind not inferred by borrowck"),
335                 };
336                 let capture_description =
337                     format!("captured variable in an `{closure_kind}` closure");
338 
339                 let upvar = &self.upvars[upvar_field.unwrap().index()];
340                 let upvar_hir_id = upvar.place.get_root_variable();
341                 let upvar_name = upvar.place.to_string(self.infcx.tcx);
342                 let upvar_span = self.infcx.tcx.hir().span(upvar_hir_id);
343 
344                 let place_name = self.describe_any_place(move_place.as_ref());
345 
346                 let place_description =
347                     if self.is_upvar_field_projection(move_place.as_ref()).is_some() {
348                         format!("{place_name}, a {capture_description}")
349                     } else {
350                         format!("{place_name}, as `{upvar_name}` is a {capture_description}")
351                     };
352 
353                 debug!(
354                     "report: closure_kind_ty={:?} closure_kind={:?} place_description={:?}",
355                     closure_kind_ty, closure_kind, place_description,
356                 );
357 
358                 let mut diag = self.cannot_move_out_of(span, &place_description);
359 
360                 diag.span_label(upvar_span, "captured outer variable");
361                 diag.span_label(
362                     self.infcx.tcx.def_span(def_id),
363                     format!("captured by this `{closure_kind}` closure"),
364                 );
365 
366                 diag
367             }
368             _ => {
369                 let source = self.borrowed_content_source(deref_base);
370                 let move_place_ref = move_place.as_ref();
371                 match (
372                     self.describe_place_with_options(
373                         move_place_ref,
374                         DescribePlaceOpt {
375                             including_downcast: false,
376                             including_tuple_field: false,
377                         },
378                     ),
379                     self.describe_name(move_place_ref),
380                     source.describe_for_named_place(),
381                 ) {
382                     (Some(place_desc), Some(name), Some(source_desc)) => self.cannot_move_out_of(
383                         span,
384                         &format!("`{place_desc}` as enum variant `{name}` which is behind a {source_desc}"),
385                     ),
386                     (Some(place_desc), Some(name), None) => self.cannot_move_out_of(
387                         span,
388                         &format!("`{place_desc}` as enum variant `{name}`"),
389                     ),
390                     (Some(place_desc), _, Some(source_desc)) => self.cannot_move_out_of(
391                         span,
392                         &format!("`{place_desc}` which is behind a {source_desc}"),
393                     ),
394                     (_, _, _) => self.cannot_move_out_of(
395                         span,
396                         &source.describe_for_unnamed_place(self.infcx.tcx),
397                     ),
398                 }
399             }
400         };
401         let msg_opt = CapturedMessageOpt {
402             is_partial_move: false,
403             is_loop_message: false,
404             is_move_msg: false,
405             is_loop_move: false,
406             maybe_reinitialized_locations_is_empty: true,
407         };
408         if let Some(use_spans) = use_spans {
409             self.explain_captures(&mut err, span, span, use_spans, move_place, msg_opt);
410         }
411         err
412     }
413 
add_move_hints(&self, error: GroupedMoveError<'tcx>, err: &mut Diagnostic, span: Span)414     fn add_move_hints(&self, error: GroupedMoveError<'tcx>, err: &mut Diagnostic, span: Span) {
415         match error {
416             GroupedMoveError::MovesFromPlace { mut binds_to, move_from, .. } => {
417                 self.add_borrow_suggestions(err, span);
418                 if binds_to.is_empty() {
419                     let place_ty = move_from.ty(self.body, self.infcx.tcx).ty;
420                     let place_desc = match self.describe_place(move_from.as_ref()) {
421                         Some(desc) => format!("`{desc}`"),
422                         None => "value".to_string(),
423                     };
424 
425                     err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {
426                         is_partial_move: false,
427                         ty: place_ty,
428                         place: &place_desc,
429                         span,
430                     });
431                 } else {
432                     binds_to.sort();
433                     binds_to.dedup();
434 
435                     self.add_move_error_details(err, &binds_to);
436                 }
437             }
438             GroupedMoveError::MovesFromValue { mut binds_to, .. } => {
439                 binds_to.sort();
440                 binds_to.dedup();
441                 self.add_move_error_suggestions(err, &binds_to);
442                 self.add_move_error_details(err, &binds_to);
443             }
444             // No binding. Nothing to suggest.
445             GroupedMoveError::OtherIllegalMove { ref original_path, use_spans, .. } => {
446                 let span = use_spans.var_or_use();
447                 let place_ty = original_path.ty(self.body, self.infcx.tcx).ty;
448                 let place_desc = match self.describe_place(original_path.as_ref()) {
449                     Some(desc) => format!("`{desc}`"),
450                     None => "value".to_string(),
451                 };
452                 err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {
453                     is_partial_move: false,
454                     ty: place_ty,
455                     place: &place_desc,
456                     span,
457                 });
458 
459                 use_spans.args_subdiag(err, |args_span| {
460                     crate::session_diagnostics::CaptureArgLabel::MoveOutPlace {
461                         place: place_desc,
462                         args_span,
463                     }
464                 });
465             }
466         }
467     }
468 
add_borrow_suggestions(&self, err: &mut Diagnostic, span: Span)469     fn add_borrow_suggestions(&self, err: &mut Diagnostic, span: Span) {
470         match self.infcx.tcx.sess.source_map().span_to_snippet(span) {
471             Ok(snippet) if snippet.starts_with('*') => {
472                 err.span_suggestion_verbose(
473                     span.with_hi(span.lo() + BytePos(1)),
474                     "consider removing the dereference here",
475                     String::new(),
476                     Applicability::MaybeIncorrect,
477                 );
478             }
479             _ => {
480                 err.span_suggestion_verbose(
481                     span.shrink_to_lo(),
482                     "consider borrowing here",
483                     '&',
484                     Applicability::MaybeIncorrect,
485                 );
486             }
487         }
488     }
489 
add_move_error_suggestions(&self, err: &mut Diagnostic, binds_to: &[Local])490     fn add_move_error_suggestions(&self, err: &mut Diagnostic, binds_to: &[Local]) {
491         let mut suggestions: Vec<(Span, String, String)> = Vec::new();
492         for local in binds_to {
493             let bind_to = &self.body.local_decls[*local];
494             if let LocalInfo::User(BindingForm::Var(VarBindingForm { pat_span, .. })) =
495                 *bind_to.local_info()
496             {
497                 let Ok(pat_snippet) =
498                     self.infcx.tcx.sess.source_map().span_to_snippet(pat_span) else { continue; };
499                 let Some(stripped) = pat_snippet.strip_prefix('&') else {
500                     suggestions.push((
501                         bind_to.source_info.span.shrink_to_lo(),
502                         "consider borrowing the pattern binding".to_string(),
503                         "ref ".to_string(),
504                     ));
505                     continue;
506                 };
507                 let inner_pat_snippet = stripped.trim_start();
508                 let (pat_span, suggestion, to_remove) = if inner_pat_snippet.starts_with("mut")
509                     && inner_pat_snippet["mut".len()..].starts_with(rustc_lexer::is_whitespace)
510                 {
511                     let inner_pat_snippet = inner_pat_snippet["mut".len()..].trim_start();
512                     let pat_span = pat_span.with_hi(
513                         pat_span.lo()
514                             + BytePos((pat_snippet.len() - inner_pat_snippet.len()) as u32),
515                     );
516                     (pat_span, String::new(), "mutable borrow")
517                 } else {
518                     let pat_span = pat_span.with_hi(
519                         pat_span.lo()
520                             + BytePos(
521                                 (pat_snippet.len() - inner_pat_snippet.trim_start().len()) as u32,
522                             ),
523                     );
524                     (pat_span, String::new(), "borrow")
525                 };
526                 suggestions.push((
527                     pat_span,
528                     format!("consider removing the {to_remove}"),
529                     suggestion.to_string(),
530                 ));
531             }
532         }
533         suggestions.sort_unstable_by_key(|&(span, _, _)| span);
534         suggestions.dedup_by_key(|&mut (span, _, _)| span);
535         for (span, msg, suggestion) in suggestions {
536             err.span_suggestion_verbose(span, msg, suggestion, Applicability::MachineApplicable);
537         }
538     }
539 
add_move_error_details(&self, err: &mut Diagnostic, binds_to: &[Local])540     fn add_move_error_details(&self, err: &mut Diagnostic, binds_to: &[Local]) {
541         for (j, local) in binds_to.iter().enumerate() {
542             let bind_to = &self.body.local_decls[*local];
543             let binding_span = bind_to.source_info.span;
544 
545             if j == 0 {
546                 err.span_label(binding_span, "data moved here");
547             } else {
548                 err.span_label(binding_span, "...and here");
549             }
550 
551             if binds_to.len() == 1 {
552                 let place_desc = &format!("`{}`", self.local_names[*local].unwrap());
553                 err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {
554                     is_partial_move: false,
555                     ty: bind_to.ty,
556                     place: &place_desc,
557                     span: binding_span,
558                 });
559             }
560         }
561 
562         if binds_to.len() > 1 {
563             err.note(
564                 "move occurs because these variables have types that don't implement the `Copy` \
565                  trait",
566             );
567         }
568     }
569 }
570