From 698f8c2f01ea549d77d7dc3338a12e04c11057b9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:02:58 +0200 Subject: Adding upstream version 1.64.0+dfsg1. Signed-off-by: Daniel Baumann --- .../rustc_borrowck/src/diagnostics/move_errors.rs | 529 +++++++++++++++++++++ 1 file changed, 529 insertions(+) create mode 100644 compiler/rustc_borrowck/src/diagnostics/move_errors.rs (limited to 'compiler/rustc_borrowck/src/diagnostics/move_errors.rs') diff --git a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs new file mode 100644 index 000000000..cb3cd479a --- /dev/null +++ b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs @@ -0,0 +1,529 @@ +use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed}; +use rustc_middle::mir::*; +use rustc_middle::ty; +use rustc_mir_dataflow::move_paths::{ + IllegalMoveOrigin, IllegalMoveOriginKind, LookupResult, MoveError, MovePathIndex, +}; +use rustc_span::Span; + +use crate::diagnostics::{DescribePlaceOpt, UseSpans}; +use crate::prefixes::PrefixSet; +use crate::MirBorrowckCtxt; + +// Often when desugaring a pattern match we may have many individual moves in +// MIR that are all part of one operation from the user's point-of-view. For +// example: +// +// let (x, y) = foo() +// +// would move x from the 0 field of some temporary, and y from the 1 field. We +// group such errors together for cleaner error reporting. +// +// Errors are kept separate if they are from places with different parent move +// paths. For example, this generates two errors: +// +// let (&x, &y) = (&String::new(), &String::new()); +#[derive(Debug)] +enum GroupedMoveError<'tcx> { + // Place expression can't be moved from, + // e.g., match x[0] { s => (), } where x: &[String] + MovesFromPlace { + original_path: Place<'tcx>, + span: Span, + move_from: Place<'tcx>, + kind: IllegalMoveOriginKind<'tcx>, + binds_to: Vec, + }, + // Part of a value expression can't be moved from, + // e.g., match &String::new() { &x => (), } + MovesFromValue { + original_path: Place<'tcx>, + span: Span, + move_from: MovePathIndex, + kind: IllegalMoveOriginKind<'tcx>, + binds_to: Vec, + }, + // Everything that isn't from pattern matching. + OtherIllegalMove { + original_path: Place<'tcx>, + use_spans: UseSpans<'tcx>, + kind: IllegalMoveOriginKind<'tcx>, + }, +} + +impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { + pub(crate) fn report_move_errors(&mut self, move_errors: Vec<(Place<'tcx>, MoveError<'tcx>)>) { + let grouped_errors = self.group_move_errors(move_errors); + for error in grouped_errors { + self.report(error); + } + } + + fn group_move_errors( + &self, + errors: Vec<(Place<'tcx>, MoveError<'tcx>)>, + ) -> Vec> { + let mut grouped_errors = Vec::new(); + for (original_path, error) in errors { + self.append_to_grouped_errors(&mut grouped_errors, original_path, error); + } + grouped_errors + } + + fn append_to_grouped_errors( + &self, + grouped_errors: &mut Vec>, + original_path: Place<'tcx>, + error: MoveError<'tcx>, + ) { + match error { + MoveError::UnionMove { .. } => { + unimplemented!("don't know how to report union move errors yet.") + } + MoveError::IllegalMove { cannot_move_out_of: IllegalMoveOrigin { location, kind } } => { + // Note: that the only time we assign a place isn't a temporary + // to a user variable is when initializing it. + // If that ever stops being the case, then the ever initialized + // flow could be used. + if let Some(StatementKind::Assign(box ( + place, + Rvalue::Use(Operand::Move(move_from)), + ))) = self.body.basic_blocks()[location.block] + .statements + .get(location.statement_index) + .map(|stmt| &stmt.kind) + { + if let Some(local) = place.as_local() { + let local_decl = &self.body.local_decls[local]; + // opt_match_place is the + // match_span is the span of the expression being matched on + // match *x.y { ... } match_place is Some(*x.y) + // ^^^^ match_span is the span of *x.y + // + // opt_match_place is None for let [mut] x = ... statements, + // whether or not the right-hand side is a place expression + if let Some(box LocalInfo::User(ClearCrossCrate::Set(BindingForm::Var( + VarBindingForm { + opt_match_place: Some((opt_match_place, match_span)), + binding_mode: _, + opt_ty_info: _, + pat_span: _, + }, + )))) = local_decl.local_info + { + let stmt_source_info = self.body.source_info(location); + self.append_binding_error( + grouped_errors, + kind, + original_path, + *move_from, + local, + opt_match_place, + match_span, + stmt_source_info.span, + ); + return; + } + } + } + + let move_spans = self.move_spans(original_path.as_ref(), location); + grouped_errors.push(GroupedMoveError::OtherIllegalMove { + use_spans: move_spans, + original_path, + kind, + }); + } + } + } + + fn append_binding_error( + &self, + grouped_errors: &mut Vec>, + kind: IllegalMoveOriginKind<'tcx>, + original_path: Place<'tcx>, + move_from: Place<'tcx>, + bind_to: Local, + match_place: Option>, + match_span: Span, + statement_span: Span, + ) { + debug!("append_binding_error(match_place={:?}, match_span={:?})", match_place, match_span); + + let from_simple_let = match_place.is_none(); + let match_place = match_place.unwrap_or(move_from); + + match self.move_data.rev_lookup.find(match_place.as_ref()) { + // Error with the match place + LookupResult::Parent(_) => { + for ge in &mut *grouped_errors { + if let GroupedMoveError::MovesFromPlace { span, binds_to, .. } = ge + && match_span == *span + { + debug!("appending local({:?}) to list", bind_to); + if !binds_to.is_empty() { + binds_to.push(bind_to); + } + return; + } + } + debug!("found a new move error location"); + + // Don't need to point to x in let x = ... . + let (binds_to, span) = if from_simple_let { + (vec![], statement_span) + } else { + (vec![bind_to], match_span) + }; + grouped_errors.push(GroupedMoveError::MovesFromPlace { + span, + move_from, + original_path, + kind, + binds_to, + }); + } + // Error with the pattern + LookupResult::Exact(_) => { + let LookupResult::Parent(Some(mpi)) = self.move_data.rev_lookup.find(move_from.as_ref()) else { + // move_from should be a projection from match_place. + unreachable!("Probably not unreachable..."); + }; + for ge in &mut *grouped_errors { + if let GroupedMoveError::MovesFromValue { + span, + move_from: other_mpi, + binds_to, + .. + } = ge + { + if match_span == *span && mpi == *other_mpi { + debug!("appending local({:?}) to list", bind_to); + binds_to.push(bind_to); + return; + } + } + } + debug!("found a new move error location"); + grouped_errors.push(GroupedMoveError::MovesFromValue { + span: match_span, + move_from: mpi, + original_path, + kind, + binds_to: vec![bind_to], + }); + } + }; + } + + fn report(&mut self, error: GroupedMoveError<'tcx>) { + let (mut err, err_span) = { + let (span, use_spans, original_path, kind) = match error { + GroupedMoveError::MovesFromPlace { span, original_path, ref kind, .. } + | GroupedMoveError::MovesFromValue { span, original_path, ref kind, .. } => { + (span, None, original_path, kind) + } + GroupedMoveError::OtherIllegalMove { use_spans, original_path, ref kind } => { + (use_spans.args_or_use(), Some(use_spans), original_path, kind) + } + }; + debug!( + "report: original_path={:?} span={:?}, kind={:?} \ + original_path.is_upvar_field_projection={:?}", + original_path, + span, + kind, + self.is_upvar_field_projection(original_path.as_ref()) + ); + ( + match kind { + &IllegalMoveOriginKind::BorrowedContent { target_place } => self + .report_cannot_move_from_borrowed_content( + original_path, + target_place, + span, + use_spans, + ), + &IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => { + self.cannot_move_out_of_interior_of_drop(span, ty) + } + &IllegalMoveOriginKind::InteriorOfSliceOrArray { ty, is_index } => { + self.cannot_move_out_of_interior_noncopy(span, ty, Some(is_index)) + } + }, + span, + ) + }; + + self.add_move_hints(error, &mut err, err_span); + self.buffer_error(err); + } + + fn report_cannot_move_from_static( + &mut self, + place: Place<'tcx>, + span: Span, + ) -> DiagnosticBuilder<'a, ErrorGuaranteed> { + let description = if place.projection.len() == 1 { + format!("static item {}", self.describe_any_place(place.as_ref())) + } else { + let base_static = PlaceRef { local: place.local, projection: &[ProjectionElem::Deref] }; + + format!( + "{} as {} is a static item", + self.describe_any_place(place.as_ref()), + self.describe_any_place(base_static), + ) + }; + + self.cannot_move_out_of(span, &description) + } + + fn report_cannot_move_from_borrowed_content( + &mut self, + move_place: Place<'tcx>, + deref_target_place: Place<'tcx>, + span: Span, + use_spans: Option>, + ) -> DiagnosticBuilder<'a, ErrorGuaranteed> { + // Inspect the type of the content behind the + // borrow to provide feedback about why this + // was a move rather than a copy. + let ty = deref_target_place.ty(self.body, self.infcx.tcx).ty; + let upvar_field = self + .prefixes(move_place.as_ref(), PrefixSet::All) + .find_map(|p| self.is_upvar_field_projection(p)); + + let deref_base = match deref_target_place.projection.as_ref() { + [proj_base @ .., ProjectionElem::Deref] => { + PlaceRef { local: deref_target_place.local, projection: &proj_base } + } + _ => bug!("deref_target_place is not a deref projection"), + }; + + if let PlaceRef { local, projection: [] } = deref_base { + let decl = &self.body.local_decls[local]; + if decl.is_ref_for_guard() { + let mut err = self.cannot_move_out_of( + span, + &format!("`{}` in pattern guard", self.local_names[local].unwrap()), + ); + err.note( + "variables bound in patterns cannot be moved from \ + until after the end of the pattern guard", + ); + return err; + } else if decl.is_ref_to_static() { + return self.report_cannot_move_from_static(move_place, span); + } + } + + debug!("report: ty={:?}", ty); + let mut err = match ty.kind() { + ty::Array(..) | ty::Slice(..) => { + self.cannot_move_out_of_interior_noncopy(span, ty, None) + } + ty::Closure(def_id, closure_substs) + if def_id.as_local() == Some(self.mir_def_id()) && upvar_field.is_some() => + { + let closure_kind_ty = closure_substs.as_closure().kind_ty(); + let closure_kind = match closure_kind_ty.to_opt_closure_kind() { + Some(kind @ (ty::ClosureKind::Fn | ty::ClosureKind::FnMut)) => kind, + Some(ty::ClosureKind::FnOnce) => { + bug!("closure kind does not match first argument type") + } + None => bug!("closure kind not inferred by borrowck"), + }; + let capture_description = + format!("captured variable in an `{closure_kind}` closure"); + + let upvar = &self.upvars[upvar_field.unwrap().index()]; + let upvar_hir_id = upvar.place.get_root_variable(); + let upvar_name = upvar.place.to_string(self.infcx.tcx); + let upvar_span = self.infcx.tcx.hir().span(upvar_hir_id); + + let place_name = self.describe_any_place(move_place.as_ref()); + + let place_description = + if self.is_upvar_field_projection(move_place.as_ref()).is_some() { + format!("{place_name}, a {capture_description}") + } else { + format!("{place_name}, as `{upvar_name}` is a {capture_description}") + }; + + debug!( + "report: closure_kind_ty={:?} closure_kind={:?} place_description={:?}", + closure_kind_ty, closure_kind, place_description, + ); + + let mut diag = self.cannot_move_out_of(span, &place_description); + + diag.span_label(upvar_span, "captured outer variable"); + diag.span_label( + self.body.span, + format!("captured by this `{closure_kind}` closure"), + ); + + diag + } + _ => { + let source = self.borrowed_content_source(deref_base); + let move_place_ref = move_place.as_ref(); + match ( + self.describe_place_with_options( + move_place_ref, + DescribePlaceOpt { + including_downcast: false, + including_tuple_field: false, + }, + ), + self.describe_name(move_place_ref), + source.describe_for_named_place(), + ) { + (Some(place_desc), Some(name), Some(source_desc)) => self.cannot_move_out_of( + span, + &format!("`{place_desc}` as enum variant `{name}` which is behind a {source_desc}"), + ), + (Some(place_desc), Some(name), None) => self.cannot_move_out_of( + span, + &format!("`{place_desc}` as enum variant `{name}`"), + ), + (Some(place_desc), _, Some(source_desc)) => self.cannot_move_out_of( + span, + &format!("`{place_desc}` which is behind a {source_desc}"), + ), + (_, _, _) => self.cannot_move_out_of( + span, + &source.describe_for_unnamed_place(self.infcx.tcx), + ), + } + } + }; + if let Some(use_spans) = use_spans { + self.explain_captures( + &mut err, span, span, use_spans, move_place, None, "", "", "", false, true, + ); + } + err + } + + fn add_move_hints(&self, error: GroupedMoveError<'tcx>, err: &mut Diagnostic, span: Span) { + match error { + GroupedMoveError::MovesFromPlace { mut binds_to, move_from, .. } => { + if let Ok(snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(span) { + err.span_suggestion( + span, + "consider borrowing here", + format!("&{snippet}"), + Applicability::Unspecified, + ); + } + + if binds_to.is_empty() { + let place_ty = move_from.ty(self.body, self.infcx.tcx).ty; + let place_desc = match self.describe_place(move_from.as_ref()) { + Some(desc) => format!("`{desc}`"), + None => "value".to_string(), + }; + + self.note_type_does_not_implement_copy( + err, + &place_desc, + place_ty, + Some(span), + "", + ); + } else { + binds_to.sort(); + binds_to.dedup(); + + self.add_move_error_details(err, &binds_to); + } + } + GroupedMoveError::MovesFromValue { mut binds_to, .. } => { + binds_to.sort(); + binds_to.dedup(); + self.add_move_error_suggestions(err, &binds_to); + self.add_move_error_details(err, &binds_to); + } + // No binding. Nothing to suggest. + GroupedMoveError::OtherIllegalMove { ref original_path, use_spans, .. } => { + let span = use_spans.var_or_use(); + let place_ty = original_path.ty(self.body, self.infcx.tcx).ty; + let place_desc = match self.describe_place(original_path.as_ref()) { + Some(desc) => format!("`{desc}`"), + None => "value".to_string(), + }; + self.note_type_does_not_implement_copy(err, &place_desc, place_ty, Some(span), ""); + + use_spans.args_span_label(err, format!("move out of {place_desc} occurs here")); + } + } + } + + fn add_move_error_suggestions(&self, err: &mut Diagnostic, binds_to: &[Local]) { + let mut suggestions: Vec<(Span, &str, String)> = Vec::new(); + for local in binds_to { + let bind_to = &self.body.local_decls[*local]; + if let Some(box LocalInfo::User(ClearCrossCrate::Set(BindingForm::Var( + VarBindingForm { pat_span, .. }, + )))) = bind_to.local_info + { + if let Ok(pat_snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(pat_span) + { + if let Some(stripped) = pat_snippet.strip_prefix('&') { + let pat_snippet = stripped.trim_start(); + let (suggestion, to_remove) = if pat_snippet.starts_with("mut") + && pat_snippet["mut".len()..].starts_with(rustc_lexer::is_whitespace) + { + (pat_snippet["mut".len()..].trim_start(), "&mut") + } else { + (pat_snippet, "&") + }; + suggestions.push((pat_span, to_remove, suggestion.to_owned())); + } + } + } + } + suggestions.sort_unstable_by_key(|&(span, _, _)| span); + suggestions.dedup_by_key(|&mut (span, _, _)| span); + for (span, to_remove, suggestion) in suggestions { + err.span_suggestion( + span, + &format!("consider removing the `{to_remove}`"), + suggestion, + Applicability::MachineApplicable, + ); + } + } + + fn add_move_error_details(&self, err: &mut Diagnostic, binds_to: &[Local]) { + for (j, local) in binds_to.iter().enumerate() { + let bind_to = &self.body.local_decls[*local]; + let binding_span = bind_to.source_info.span; + + if j == 0 { + err.span_label(binding_span, "data moved here"); + } else { + err.span_label(binding_span, "...and here"); + } + + if binds_to.len() == 1 { + self.note_type_does_not_implement_copy( + err, + &format!("`{}`", self.local_names[*local].unwrap()), + bind_to.ty, + Some(binding_span), + "", + ); + } + } + + if binds_to.len() > 1 { + err.note( + "move occurs because these variables have types that \ + don't implement the `Copy` trait", + ); + } + } +} -- cgit v1.2.3