]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_borrowck/src/diagnostics/move_errors.rs
Auto merge of #94761 - Dylan-DPC:rollup-v4emqsy, r=Dylan-DPC
[rust.git] / compiler / rustc_borrowck / src / diagnostics / move_errors.rs
1 use rustc_const_eval::util::CallDesugaringKind;
2 use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed};
3 use rustc_infer::infer::TyCtxtInferExt;
4 use rustc_middle::mir::*;
5 use rustc_middle::ty;
6 use rustc_mir_dataflow::move_paths::{
7     IllegalMoveOrigin, IllegalMoveOriginKind, LookupResult, MoveError, MovePathIndex,
8 };
9 use rustc_span::{sym, Span, DUMMY_SP};
10 use rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions;
11
12 use crate::diagnostics::{CallKind, UseSpans};
13 use crate::prefixes::PrefixSet;
14 use crate::MirBorrowckCtxt;
15
16 // Often when desugaring a pattern match we may have many individual moves in
17 // MIR that are all part of one operation from the user's point-of-view. For
18 // example:
19 //
20 // let (x, y) = foo()
21 //
22 // would move x from the 0 field of some temporary, and y from the 1 field. We
23 // group such errors together for cleaner error reporting.
24 //
25 // Errors are kept separate if they are from places with different parent move
26 // paths. For example, this generates two errors:
27 //
28 // let (&x, &y) = (&String::new(), &String::new());
29 #[derive(Debug)]
30 enum GroupedMoveError<'tcx> {
31     // Place expression can't be moved from,
32     // e.g., match x[0] { s => (), } where x: &[String]
33     MovesFromPlace {
34         original_path: Place<'tcx>,
35         span: Span,
36         move_from: Place<'tcx>,
37         kind: IllegalMoveOriginKind<'tcx>,
38         binds_to: Vec<Local>,
39     },
40     // Part of a value expression can't be moved from,
41     // e.g., match &String::new() { &x => (), }
42     MovesFromValue {
43         original_path: Place<'tcx>,
44         span: Span,
45         move_from: MovePathIndex,
46         kind: IllegalMoveOriginKind<'tcx>,
47         binds_to: Vec<Local>,
48     },
49     // Everything that isn't from pattern matching.
50     OtherIllegalMove {
51         original_path: Place<'tcx>,
52         use_spans: UseSpans<'tcx>,
53         kind: IllegalMoveOriginKind<'tcx>,
54     },
55 }
56
57 impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
58     pub(crate) fn report_move_errors(&mut self, move_errors: Vec<(Place<'tcx>, MoveError<'tcx>)>) {
59         let grouped_errors = self.group_move_errors(move_errors);
60         for error in grouped_errors {
61             self.report(error);
62         }
63     }
64
65     fn group_move_errors(
66         &self,
67         errors: Vec<(Place<'tcx>, MoveError<'tcx>)>,
68     ) -> Vec<GroupedMoveError<'tcx>> {
69         let mut grouped_errors = Vec::new();
70         for (original_path, error) in errors {
71             self.append_to_grouped_errors(&mut grouped_errors, original_path, error);
72         }
73         grouped_errors
74     }
75
76     fn append_to_grouped_errors(
77         &self,
78         grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,
79         original_path: Place<'tcx>,
80         error: MoveError<'tcx>,
81     ) {
82         match error {
83             MoveError::UnionMove { .. } => {
84                 unimplemented!("don't know how to report union move errors yet.")
85             }
86             MoveError::IllegalMove { cannot_move_out_of: IllegalMoveOrigin { location, kind } } => {
87                 // Note: that the only time we assign a place isn't a temporary
88                 // to a user variable is when initializing it.
89                 // If that ever stops being the case, then the ever initialized
90                 // flow could be used.
91                 if let Some(StatementKind::Assign(box (
92                     place,
93                     Rvalue::Use(Operand::Move(move_from)),
94                 ))) = self.body.basic_blocks()[location.block]
95                     .statements
96                     .get(location.statement_index)
97                     .map(|stmt| &stmt.kind)
98                 {
99                     if let Some(local) = place.as_local() {
100                         let local_decl = &self.body.local_decls[local];
101                         // opt_match_place is the
102                         // match_span is the span of the expression being matched on
103                         // match *x.y { ... }        match_place is Some(*x.y)
104                         //       ^^^^                match_span is the span of *x.y
105                         //
106                         // opt_match_place is None for let [mut] x = ... statements,
107                         // whether or not the right-hand side is a place expression
108                         if let Some(box LocalInfo::User(ClearCrossCrate::Set(BindingForm::Var(
109                             VarBindingForm {
110                                 opt_match_place: Some((opt_match_place, match_span)),
111                                 binding_mode: _,
112                                 opt_ty_info: _,
113                                 pat_span: _,
114                             },
115                         )))) = local_decl.local_info
116                         {
117                             let stmt_source_info = self.body.source_info(location);
118                             self.append_binding_error(
119                                 grouped_errors,
120                                 kind,
121                                 original_path,
122                                 *move_from,
123                                 local,
124                                 opt_match_place,
125                                 match_span,
126                                 stmt_source_info.span,
127                             );
128                             return;
129                         }
130                     }
131                 }
132
133                 let move_spans = self.move_spans(original_path.as_ref(), location);
134                 grouped_errors.push(GroupedMoveError::OtherIllegalMove {
135                     use_spans: move_spans,
136                     original_path,
137                     kind,
138                 });
139             }
140         }
141     }
142
143     fn append_binding_error(
144         &self,
145         grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,
146         kind: IllegalMoveOriginKind<'tcx>,
147         original_path: Place<'tcx>,
148         move_from: Place<'tcx>,
149         bind_to: Local,
150         match_place: Option<Place<'tcx>>,
151         match_span: Span,
152         statement_span: Span,
153     ) {
154         debug!("append_binding_error(match_place={:?}, match_span={:?})", match_place, match_span);
155
156         let from_simple_let = match_place.is_none();
157         let match_place = match_place.unwrap_or(move_from);
158
159         match self.move_data.rev_lookup.find(match_place.as_ref()) {
160             // Error with the match place
161             LookupResult::Parent(_) => {
162                 for ge in &mut *grouped_errors {
163                     if let GroupedMoveError::MovesFromPlace { span, binds_to, .. } = ge {
164                         if match_span == *span {
165                             debug!("appending local({:?}) to list", bind_to);
166                             if !binds_to.is_empty() {
167                                 binds_to.push(bind_to);
168                             }
169                             return;
170                         }
171                     }
172                 }
173                 debug!("found a new move error location");
174
175                 // Don't need to point to x in let x = ... .
176                 let (binds_to, span) = if from_simple_let {
177                     (vec![], statement_span)
178                 } else {
179                     (vec![bind_to], match_span)
180                 };
181                 grouped_errors.push(GroupedMoveError::MovesFromPlace {
182                     span,
183                     move_from,
184                     original_path,
185                     kind,
186                     binds_to,
187                 });
188             }
189             // Error with the pattern
190             LookupResult::Exact(_) => {
191                 let LookupResult::Parent(Some(mpi)) = self.move_data.rev_lookup.find(move_from.as_ref()) else {
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
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         self.buffer_error(err);
268     }
269
270     fn report_cannot_move_from_static(
271         &mut self,
272         place: Place<'tcx>,
273         span: Span,
274     ) -> DiagnosticBuilder<'a, ErrorGuaranteed> {
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
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, ErrorGuaranteed> {
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:
414                 CallKind::Normal { desugaring: Some((CallDesugaringKind::ForLoopIntoIter, _)), .. },
415             ..
416         }) = use_spans
417         {
418             let suggest = match self.infcx.tcx.get_diagnostic_item(sym::IntoIterator) {
419                 Some(def_id) => self.infcx.tcx.infer_ctxt().enter(|infcx| {
420                     type_known_to_meet_bound_modulo_regions(
421                         &infcx,
422                         self.param_env,
423                         infcx
424                             .tcx
425                             .mk_imm_ref(infcx.tcx.lifetimes.re_erased, infcx.tcx.erase_regions(ty)),
426                         def_id,
427                         DUMMY_SP,
428                     )
429                 }),
430                 _ => false,
431             };
432             if suggest {
433                 err.span_suggestion_verbose(
434                     span.shrink_to_lo(),
435                     &format!("consider iterating over a slice of the `{}`'s content", ty),
436                     "&".to_string(),
437                     Applicability::MaybeIncorrect,
438                 );
439             }
440         }
441         err
442     }
443
444     fn add_move_hints(&self, error: GroupedMoveError<'tcx>, err: &mut Diagnostic, span: Span) {
445         match error {
446             GroupedMoveError::MovesFromPlace { mut binds_to, move_from, .. } => {
447                 if let Ok(snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(span) {
448                     err.span_suggestion(
449                         span,
450                         "consider borrowing here",
451                         format!("&{}", snippet),
452                         Applicability::Unspecified,
453                     );
454                 }
455
456                 if binds_to.is_empty() {
457                     let place_ty = move_from.ty(self.body, self.infcx.tcx).ty;
458                     let place_desc = match self.describe_place(move_from.as_ref()) {
459                         Some(desc) => format!("`{}`", desc),
460                         None => "value".to_string(),
461                     };
462
463                     self.note_type_does_not_implement_copy(
464                         err,
465                         &place_desc,
466                         place_ty,
467                         Some(span),
468                         "",
469                     );
470                 } else {
471                     binds_to.sort();
472                     binds_to.dedup();
473
474                     self.add_move_error_details(err, &binds_to);
475                 }
476             }
477             GroupedMoveError::MovesFromValue { mut binds_to, .. } => {
478                 binds_to.sort();
479                 binds_to.dedup();
480                 self.add_move_error_suggestions(err, &binds_to);
481                 self.add_move_error_details(err, &binds_to);
482             }
483             // No binding. Nothing to suggest.
484             GroupedMoveError::OtherIllegalMove { ref original_path, use_spans, .. } => {
485                 let span = use_spans.var_or_use();
486                 let place_ty = original_path.ty(self.body, self.infcx.tcx).ty;
487                 let place_desc = match self.describe_place(original_path.as_ref()) {
488                     Some(desc) => format!("`{}`", desc),
489                     None => "value".to_string(),
490                 };
491                 self.note_type_does_not_implement_copy(err, &place_desc, place_ty, Some(span), "");
492
493                 use_spans.args_span_label(err, format!("move out of {} occurs here", place_desc));
494                 use_spans.var_span_label(
495                     err,
496                     format!("move occurs due to use{}", use_spans.describe()),
497                     "moved",
498                 );
499             }
500         }
501     }
502
503     fn add_move_error_suggestions(&self, err: &mut Diagnostic, binds_to: &[Local]) {
504         let mut suggestions: Vec<(Span, &str, String)> = Vec::new();
505         for local in binds_to {
506             let bind_to = &self.body.local_decls[*local];
507             if let Some(box LocalInfo::User(ClearCrossCrate::Set(BindingForm::Var(
508                 VarBindingForm { pat_span, .. },
509             )))) = bind_to.local_info
510             {
511                 if let Ok(pat_snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(pat_span)
512                 {
513                     if let Some(stripped) = pat_snippet.strip_prefix('&') {
514                         let pat_snippet = stripped.trim_start();
515                         let (suggestion, to_remove) = if pat_snippet.starts_with("mut")
516                             && pat_snippet["mut".len()..].starts_with(rustc_lexer::is_whitespace)
517                         {
518                             (pat_snippet["mut".len()..].trim_start(), "&mut")
519                         } else {
520                             (pat_snippet, "&")
521                         };
522                         suggestions.push((pat_span, to_remove, suggestion.to_owned()));
523                     }
524                 }
525             }
526         }
527         suggestions.sort_unstable_by_key(|&(span, _, _)| span);
528         suggestions.dedup_by_key(|&mut (span, _, _)| span);
529         for (span, to_remove, suggestion) in suggestions {
530             err.span_suggestion(
531                 span,
532                 &format!("consider removing the `{}`", to_remove),
533                 suggestion,
534                 Applicability::MachineApplicable,
535             );
536         }
537     }
538
539     fn add_move_error_details(&self, err: &mut Diagnostic, binds_to: &[Local]) {
540         for (j, local) in binds_to.iter().enumerate() {
541             let bind_to = &self.body.local_decls[*local];
542             let binding_span = bind_to.source_info.span;
543
544             if j == 0 {
545                 err.span_label(binding_span, "data moved here");
546             } else {
547                 err.span_label(binding_span, "...and here");
548             }
549
550             if binds_to.len() == 1 {
551                 self.note_type_does_not_implement_copy(
552                     err,
553                     &format!("`{}`", self.local_names[*local].unwrap()),
554                     bind_to.ty,
555                     Some(binding_span),
556                     "",
557                 );
558             }
559         }
560
561         if binds_to.len() > 1 {
562             err.note(
563                 "move occurs because these variables have types that \
564                       don't implement the `Copy` trait",
565             );
566         }
567     }
568 }