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