1 use rustc_errors
::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed}
;
2 use rustc_middle
::mir
::*;
4 use rustc_mir_dataflow
::move_paths
::{
5 IllegalMoveOrigin
, IllegalMoveOriginKind
, LookupResult
, MoveError
, MovePathIndex
,
7 use rustc_span
::{sym, Span}
;
9 use crate::diagnostics
::UseSpans
;
10 use crate::prefixes
::PrefixSet
;
11 use crate::MirBorrowckCtxt
;
13 // Often when desugaring a pattern match we may have many individual moves in
14 // MIR that are all part of one operation from the user's point-of-view. For
19 // would move x from the 0 field of some temporary, and y from the 1 field. We
20 // group such errors together for cleaner error reporting.
22 // Errors are kept separate if they are from places with different parent move
23 // paths. For example, this generates two errors:
25 // let (&x, &y) = (&String::new(), &String::new());
27 enum GroupedMoveError
<'tcx
> {
28 // Place expression can't be moved from,
29 // e.g., match x[0] { s => (), } where x: &[String]
31 original_path
: Place
<'tcx
>,
33 move_from
: Place
<'tcx
>,
34 kind
: IllegalMoveOriginKind
<'tcx
>,
37 // Part of a value expression can't be moved from,
38 // e.g., match &String::new() { &x => (), }
40 original_path
: Place
<'tcx
>,
42 move_from
: MovePathIndex
,
43 kind
: IllegalMoveOriginKind
<'tcx
>,
46 // Everything that isn't from pattern matching.
48 original_path
: Place
<'tcx
>,
49 use_spans
: UseSpans
<'tcx
>,
50 kind
: IllegalMoveOriginKind
<'tcx
>,
54 impl<'a
, 'tcx
> MirBorrowckCtxt
<'a
, 'tcx
> {
55 pub(crate) fn report_move_errors(&mut self, move_errors
: Vec
<(Place
<'tcx
>, MoveError
<'tcx
>)>) {
56 let grouped_errors
= self.group_move_errors(move_errors
);
57 for error
in grouped_errors
{
64 errors
: Vec
<(Place
<'tcx
>, MoveError
<'tcx
>)>,
65 ) -> Vec
<GroupedMoveError
<'tcx
>> {
66 let mut grouped_errors
= Vec
::new();
67 for (original_path
, error
) in errors
{
68 self.append_to_grouped_errors(&mut grouped_errors
, original_path
, error
);
73 fn append_to_grouped_errors(
75 grouped_errors
: &mut Vec
<GroupedMoveError
<'tcx
>>,
76 original_path
: Place
<'tcx
>,
77 error
: MoveError
<'tcx
>,
80 MoveError
::UnionMove { .. }
=> {
81 unimplemented
!("don't know how to report union move errors yet.")
83 MoveError
::IllegalMove { cannot_move_out_of: IllegalMoveOrigin { location, kind }
} => {
84 // Note: that the only time we assign a place isn't a temporary
85 // to a user variable is when initializing it.
86 // If that ever stops being the case, then the ever initialized
87 // flow could be used.
88 if let Some(StatementKind
::Assign(box (
90 Rvalue
::Use(Operand
::Move(move_from
)),
91 ))) = self.body
.basic_blocks()[location
.block
]
93 .get(location
.statement_index
)
94 .map(|stmt
| &stmt
.kind
)
96 if let Some(local
) = place
.as_local() {
97 let local_decl
= &self.body
.local_decls
[local
];
98 // opt_match_place is the
99 // match_span is the span of the expression being matched on
100 // match *x.y { ... } match_place is Some(*x.y)
101 // ^^^^ match_span is the span of *x.y
103 // opt_match_place is None for let [mut] x = ... statements,
104 // whether or not the right-hand side is a place expression
105 if let Some(box LocalInfo
::User(ClearCrossCrate
::Set(BindingForm
::Var(
107 opt_match_place
: Some((opt_match_place
, match_span
)),
112 )))) = local_decl
.local_info
114 let stmt_source_info
= self.body
.source_info(location
);
115 self.append_binding_error(
123 stmt_source_info
.span
,
130 let move_spans
= self.move_spans(original_path
.as_ref(), location
);
131 grouped_errors
.push(GroupedMoveError
::OtherIllegalMove
{
132 use_spans
: move_spans
,
140 fn append_binding_error(
142 grouped_errors
: &mut Vec
<GroupedMoveError
<'tcx
>>,
143 kind
: IllegalMoveOriginKind
<'tcx
>,
144 original_path
: Place
<'tcx
>,
145 move_from
: Place
<'tcx
>,
147 match_place
: Option
<Place
<'tcx
>>,
149 statement_span
: Span
,
151 debug
!("append_binding_error(match_place={:?}, match_span={:?})", match_place
, match_span
);
153 let from_simple_let
= match_place
.is_none();
154 let match_place
= match_place
.unwrap_or(move_from
);
156 match self.move_data
.rev_lookup
.find(match_place
.as_ref()) {
157 // Error with the match place
158 LookupResult
::Parent(_
) => {
159 for ge
in &mut *grouped_errors
{
160 if let GroupedMoveError
::MovesFromPlace { span, binds_to, .. }
= ge
161 && match_span
== *span
163 debug
!("appending local({:?}) to list", bind_to
);
164 if !binds_to
.is_empty() {
165 binds_to
.push(bind_to
);
170 debug
!("found a new move error location");
172 // Don't need to point to x in let x = ... .
173 let (binds_to
, span
) = if from_simple_let
{
174 (vec
![], statement_span
)
176 (vec
![bind_to
], match_span
)
178 grouped_errors
.push(GroupedMoveError
::MovesFromPlace
{
186 // Error with the pattern
187 LookupResult
::Exact(_
) => {
188 let LookupResult
::Parent(Some(mpi
)) = self.move_data
.rev_lookup
.find(move_from
.as_ref()) else {
189 // move_from should be a projection from match_place.
190 unreachable
!("Probably not unreachable...");
192 for ge
in &mut *grouped_errors
{
193 if let GroupedMoveError
::MovesFromValue
{
195 move_from
: other_mpi
,
200 if match_span
== *span
&& mpi
== *other_mpi
{
201 debug
!("appending local({:?}) to list", bind_to
);
202 binds_to
.push(bind_to
);
207 debug
!("found a new move error location");
208 grouped_errors
.push(GroupedMoveError
::MovesFromValue
{
213 binds_to
: vec
![bind_to
],
219 fn report(&mut self, error
: GroupedMoveError
<'tcx
>) {
220 let (mut err
, err_span
) = {
221 let (span
, use_spans
, original_path
, kind
, has_complex_bindings
): (
223 Option
<UseSpans
<'tcx
>>,
225 &IllegalMoveOriginKind
<'_
>,
228 GroupedMoveError
::MovesFromPlace
{
235 | GroupedMoveError
::MovesFromValue
{
241 } => (span
, None
, original_path
, kind
, !binds_to
.is_empty()),
242 GroupedMoveError
::OtherIllegalMove { use_spans, original_path, ref kind }
=> {
243 (use_spans
.args_or_use(), Some(use_spans
), original_path
, kind
, false)
247 "report: original_path={:?} span={:?}, kind={:?} \
248 original_path.is_upvar_field_projection={:?}",
252 self.is_upvar_field_projection(original_path
.as_ref())
256 &IllegalMoveOriginKind
::BorrowedContent { target_place }
=> self
257 .report_cannot_move_from_borrowed_content(
262 has_complex_bindings
,
264 &IllegalMoveOriginKind
::InteriorOfTypeWithDestructor { container_ty: ty }
=> {
265 self.cannot_move_out_of_interior_of_drop(span
, ty
)
267 &IllegalMoveOriginKind
::InteriorOfSliceOrArray { ty, is_index }
=> {
268 self.cannot_move_out_of_interior_noncopy(span
, ty
, Some(is_index
))
275 self.add_move_hints(error
, &mut err
, err_span
);
276 self.buffer_error(err
);
279 fn report_cannot_move_from_static(
283 ) -> DiagnosticBuilder
<'a
, ErrorGuaranteed
> {
284 let description
= if place
.projection
.len() == 1 {
285 format
!("static item {}", self.describe_any_place(place
.as_ref()))
287 let base_static
= PlaceRef { local: place.local, projection: &[ProjectionElem::Deref] }
;
290 "{} as {} is a static item",
291 self.describe_any_place(place
.as_ref()),
292 self.describe_any_place(base_static
),
296 self.cannot_move_out_of(span
, &description
)
299 fn report_cannot_move_from_borrowed_content(
301 move_place
: Place
<'tcx
>,
302 deref_target_place
: Place
<'tcx
>,
304 use_spans
: Option
<UseSpans
<'tcx
>>,
305 has_complex_bindings
: bool
,
306 ) -> DiagnosticBuilder
<'a
, ErrorGuaranteed
> {
307 // Inspect the type of the content behind the
308 // borrow to provide feedback about why this
309 // was a move rather than a copy.
310 let ty
= deref_target_place
.ty(self.body
, self.infcx
.tcx
).ty
;
311 let upvar_field
= self
312 .prefixes(move_place
.as_ref(), PrefixSet
::All
)
313 .find_map(|p
| self.is_upvar_field_projection(p
));
315 let deref_base
= match deref_target_place
.projection
.as_ref() {
316 [proj_base @
.., ProjectionElem
::Deref
] => {
317 PlaceRef { local: deref_target_place.local, projection: &proj_base }
319 _
=> bug
!("deref_target_place is not a deref projection"),
322 if let PlaceRef { local, projection: [] }
= deref_base
{
323 let decl
= &self.body
.local_decls
[local
];
324 if decl
.is_ref_for_guard() {
325 let mut err
= self.cannot_move_out_of(
327 &format
!("`{}` in pattern guard", self.local_names
[local
].unwrap()),
330 "variables bound in patterns cannot be moved from \
331 until after the end of the pattern guard",
334 } else if decl
.is_ref_to_static() {
335 return self.report_cannot_move_from_static(move_place
, span
);
339 debug
!("report: ty={:?}", ty
);
340 let mut err
= match ty
.kind() {
341 ty
::Array(..) | ty
::Slice(..) => {
342 self.cannot_move_out_of_interior_noncopy(span
, ty
, None
)
344 ty
::Closure(def_id
, closure_substs
)
345 if def_id
.as_local() == Some(self.mir_def_id()) && upvar_field
.is_some() =>
347 let closure_kind_ty
= closure_substs
.as_closure().kind_ty();
348 let closure_kind
= match closure_kind_ty
.to_opt_closure_kind() {
349 Some(kind @
(ty
::ClosureKind
::Fn
| ty
::ClosureKind
::FnMut
)) => kind
,
350 Some(ty
::ClosureKind
::FnOnce
) => {
351 bug
!("closure kind does not match first argument type")
353 None
=> bug
!("closure kind not inferred by borrowck"),
355 let capture_description
=
356 format
!("captured variable in an `{closure_kind}` closure");
358 let upvar
= &self.upvars
[upvar_field
.unwrap().index()];
359 let upvar_hir_id
= upvar
.place
.get_root_variable();
360 let upvar_name
= upvar
.place
.to_string(self.infcx
.tcx
);
361 let upvar_span
= self.infcx
.tcx
.hir().span(upvar_hir_id
);
363 let place_name
= self.describe_any_place(move_place
.as_ref());
365 let place_description
=
366 if self.is_upvar_field_projection(move_place
.as_ref()).is_some() {
367 format
!("{place_name}, a {capture_description}")
369 format
!("{place_name}, as `{upvar_name}` is a {capture_description}")
373 "report: closure_kind_ty={:?} closure_kind={:?} place_description={:?}",
374 closure_kind_ty
, closure_kind
, place_description
,
377 let mut diag
= self.cannot_move_out_of(span
, &place_description
);
379 diag
.span_label(upvar_span
, "captured outer variable");
382 format
!("captured by this `{closure_kind}` closure"),
388 let source
= self.borrowed_content_source(deref_base
);
389 match (self.describe_place(move_place
.as_ref()), source
.describe_for_named_place())
391 (Some(place_desc
), Some(source_desc
)) => self.cannot_move_out_of(
393 &format
!("`{place_desc}` which is behind a {source_desc}"),
395 (_
, _
) => self.cannot_move_out_of(
397 &source
.describe_for_unnamed_place(self.infcx
.tcx
),
402 let ty
= move_place
.ty(self.body
, self.infcx
.tcx
).ty
;
403 let def_id
= match *ty
.kind() {
404 ty
::Adt(self_def
, _
) => self_def
.did(),
406 | ty
::FnDef(def_id
, _
)
407 | ty
::Closure(def_id
, _
)
408 | ty
::Generator(def_id
, ..)
409 | ty
::Opaque(def_id
, _
) => def_id
,
412 let diag_name
= self.infcx
.tcx
.get_diagnostic_name(def_id
);
413 if matches
!(diag_name
, Some(sym
::Option
| sym
::Result
))
414 && use_spans
.map_or(true, |v
| !v
.for_closure())
415 && !has_complex_bindings
417 err
.span_suggestion_verbose(
419 &format
!("consider borrowing the `{}`'s content", diag_name
.unwrap()),
421 Applicability
::MaybeIncorrect
,
423 } else if let Some(use_spans
) = use_spans
{
424 self.explain_captures(
425 &mut err
, span
, span
, use_spans
, move_place
, None
, "", "", "", false, true,
431 fn add_move_hints(&self, error
: GroupedMoveError
<'tcx
>, err
: &mut Diagnostic
, span
: Span
) {
433 GroupedMoveError
::MovesFromPlace { mut binds_to, move_from, .. }
=> {
434 if let Ok(snippet
) = self.infcx
.tcx
.sess
.source_map().span_to_snippet(span
) {
437 "consider borrowing here",
438 format
!("&{snippet}"),
439 Applicability
::Unspecified
,
443 if binds_to
.is_empty() {
444 let place_ty
= move_from
.ty(self.body
, self.infcx
.tcx
).ty
;
445 let place_desc
= match self.describe_place(move_from
.as_ref()) {
446 Some(desc
) => format
!("`{desc}`"),
447 None
=> "value".to_string(),
450 self.note_type_does_not_implement_copy(
461 self.add_move_error_details(err
, &binds_to
);
464 GroupedMoveError
::MovesFromValue { mut binds_to, .. }
=> {
467 self.add_move_error_suggestions(err
, &binds_to
);
468 self.add_move_error_details(err
, &binds_to
);
470 // No binding. Nothing to suggest.
471 GroupedMoveError
::OtherIllegalMove { ref original_path, use_spans, .. }
=> {
472 let span
= use_spans
.var_or_use();
473 let place_ty
= original_path
.ty(self.body
, self.infcx
.tcx
).ty
;
474 let place_desc
= match self.describe_place(original_path
.as_ref()) {
475 Some(desc
) => format
!("`{desc}`"),
476 None
=> "value".to_string(),
478 self.note_type_does_not_implement_copy(err
, &place_desc
, place_ty
, Some(span
), "");
480 use_spans
.args_span_label(err
, format
!("move out of {place_desc} occurs here"));
485 fn add_move_error_suggestions(&self, err
: &mut Diagnostic
, binds_to
: &[Local
]) {
486 let mut suggestions
: Vec
<(Span
, &str, String
)> = Vec
::new();
487 for local
in binds_to
{
488 let bind_to
= &self.body
.local_decls
[*local
];
489 if let Some(box LocalInfo
::User(ClearCrossCrate
::Set(BindingForm
::Var(
490 VarBindingForm { pat_span, .. }
,
491 )))) = bind_to
.local_info
493 if let Ok(pat_snippet
) = self.infcx
.tcx
.sess
.source_map().span_to_snippet(pat_span
)
495 if let Some(stripped
) = pat_snippet
.strip_prefix('
&'
) {
496 let pat_snippet
= stripped
.trim_start();
497 let (suggestion
, to_remove
) = if pat_snippet
.starts_with("mut")
498 && pat_snippet
["mut".len()..].starts_with(rustc_lexer
::is_whitespace
)
500 (pat_snippet
["mut".len()..].trim_start(), "&mut")
504 suggestions
.push((pat_span
, to_remove
, suggestion
.to_owned()));
509 suggestions
.sort_unstable_by_key(|&(span
, _
, _
)| span
);
510 suggestions
.dedup_by_key(|&mut (span
, _
, _
)| span
);
511 for (span
, to_remove
, suggestion
) in suggestions
{
514 &format
!("consider removing the `{to_remove}`"),
516 Applicability
::MachineApplicable
,
521 fn add_move_error_details(&self, err
: &mut Diagnostic
, binds_to
: &[Local
]) {
522 for (j
, local
) in binds_to
.iter().enumerate() {
523 let bind_to
= &self.body
.local_decls
[*local
];
524 let binding_span
= bind_to
.source_info
.span
;
527 err
.span_label(binding_span
, "data moved here");
529 err
.span_label(binding_span
, "...and here");
532 if binds_to
.len() == 1 {
533 self.note_type_does_not_implement_copy(
535 &format
!("`{}`", self.local_names
[*local
].unwrap()),
543 if binds_to
.len() > 1 {
545 "move occurs because these variables have types that \
546 don't implement the `Copy` trait",