From 07a04862287b0f3a74a71fa5a45c982bb2e526b9 Mon Sep 17 00:00:00 2001 From: Mark Mansi Date: Wed, 27 Nov 2019 11:39:21 -0600 Subject: create new borrow_check::diagnostics module and move stuff there --- src/librustc_mir/borrow_check/conflict_errors.rs | 2156 -------------------- .../borrow_check/diagnostics/conflict_errors.rs | 2156 ++++++++++++++++++++ src/librustc_mir/borrow_check/diagnostics/mod.rs | 916 +++++++++ .../borrow_check/diagnostics/move_errors.rs | 587 ++++++ .../borrow_check/diagnostics/mutability_errors.rs | 656 ++++++ src/librustc_mir/borrow_check/error_reporting.rs | 916 --------- src/librustc_mir/borrow_check/move_errors.rs | 587 ------ src/librustc_mir/borrow_check/mutability_errors.rs | 656 ------ 8 files changed, 4315 insertions(+), 4315 deletions(-) delete mode 100644 src/librustc_mir/borrow_check/conflict_errors.rs create mode 100644 src/librustc_mir/borrow_check/diagnostics/conflict_errors.rs create mode 100644 src/librustc_mir/borrow_check/diagnostics/mod.rs create mode 100644 src/librustc_mir/borrow_check/diagnostics/move_errors.rs create mode 100644 src/librustc_mir/borrow_check/diagnostics/mutability_errors.rs delete mode 100644 src/librustc_mir/borrow_check/error_reporting.rs delete mode 100644 src/librustc_mir/borrow_check/move_errors.rs delete mode 100644 src/librustc_mir/borrow_check/mutability_errors.rs diff --git a/src/librustc_mir/borrow_check/conflict_errors.rs b/src/librustc_mir/borrow_check/conflict_errors.rs deleted file mode 100644 index b1e327cdb0e..00000000000 --- a/src/librustc_mir/borrow_check/conflict_errors.rs +++ /dev/null @@ -1,2156 +0,0 @@ -use rustc::hir; -use rustc::hir::def_id::DefId; -use rustc::hir::{AsyncGeneratorKind, GeneratorKind}; -use rustc::mir::{ - self, AggregateKind, BindingForm, BorrowKind, ClearCrossCrate, ConstraintCategory, - FakeReadCause, Local, LocalDecl, LocalInfo, LocalKind, Location, Operand, Place, PlaceBase, - PlaceRef, ProjectionElem, Rvalue, Statement, StatementKind, TerminatorKind, VarBindingForm, -}; -use rustc::ty::{self, Ty}; -use rustc::traits::error_reporting::suggest_constraining_type_param; -use rustc_data_structures::fx::FxHashSet; -use rustc_index::vec::Idx; -use rustc_errors::{Applicability, DiagnosticBuilder}; -use syntax_pos::Span; -use syntax::source_map::DesugaringKind; - -use super::nll::explain_borrow::BorrowExplanation; -use super::nll::region_infer::{RegionName, RegionNameSource}; -use super::prefixes::IsPrefixOf; -use super::WriteKind; -use super::borrow_set::BorrowData; -use super::MirBorrowckCtxt; -use super::{InitializationRequiringAction, PrefixSet}; -use super::error_reporting::{IncludingDowncast, UseSpans}; -use crate::dataflow::drop_flag_effects; -use crate::dataflow::indexes::{MovePathIndex, MoveOutIndex}; -use crate::util::borrowck_errors; - -#[derive(Debug)] -struct MoveSite { - /// Index of the "move out" that we found. The `MoveData` can - /// then tell us where the move occurred. - moi: MoveOutIndex, - - /// `true` if we traversed a back edge while walking from the point - /// of error to the move site. - traversed_back_edge: bool -} - -/// Which case a StorageDeadOrDrop is for. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -enum StorageDeadOrDrop<'tcx> { - LocalStorageDead, - BoxedStorageDead, - Destructor(Ty<'tcx>), -} - -impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { - pub(super) fn report_use_of_moved_or_uninitialized( - &mut self, - location: Location, - desired_action: InitializationRequiringAction, - (moved_place, used_place, span): (PlaceRef<'cx, 'tcx>, PlaceRef<'cx, 'tcx>, Span), - mpi: MovePathIndex, - ) { - debug!( - "report_use_of_moved_or_uninitialized: location={:?} desired_action={:?} \ - moved_place={:?} used_place={:?} span={:?} mpi={:?}", - location, desired_action, moved_place, used_place, span, mpi - ); - - let use_spans = self.move_spans(moved_place, location) - .or_else(|| self.borrow_spans(span, location)); - let span = use_spans.args_or_use(); - - let move_site_vec = self.get_moved_indexes(location, mpi); - debug!( - "report_use_of_moved_or_uninitialized: move_site_vec={:?}", - move_site_vec - ); - let move_out_indices: Vec<_> = move_site_vec - .iter() - .map(|move_site| move_site.moi) - .collect(); - - if move_out_indices.is_empty() { - let root_place = self - .prefixes(used_place, PrefixSet::All) - .last() - .unwrap(); - - if !self.uninitialized_error_reported.insert(root_place) { - debug!( - "report_use_of_moved_or_uninitialized place: error about {:?} suppressed", - root_place - ); - return; - } - - let item_msg = match self.describe_place_with_options(used_place, - IncludingDowncast(true)) { - Some(name) => format!("`{}`", name), - None => "value".to_owned(), - }; - let mut err = self.cannot_act_on_uninitialized_variable( - span, - desired_action.as_noun(), - &self.describe_place_with_options(moved_place, IncludingDowncast(true)) - .unwrap_or_else(|| "_".to_owned()), - ); - err.span_label(span, format!("use of possibly-uninitialized {}", item_msg)); - - use_spans.var_span_label( - &mut err, - format!("{} occurs due to use{}", desired_action.as_noun(), use_spans.describe()), - ); - - err.buffer(&mut self.errors_buffer); - } else { - if let Some((reported_place, _)) = self.move_error_reported.get(&move_out_indices) { - if self.prefixes(*reported_place, PrefixSet::All) - .any(|p| p == used_place) - { - debug!( - "report_use_of_moved_or_uninitialized place: error suppressed \ - mois={:?}", - move_out_indices - ); - return; - } - } - - let msg = ""; //FIXME: add "partially " or "collaterally " - - let mut err = self.cannot_act_on_moved_value( - span, - desired_action.as_noun(), - msg, - self.describe_place_with_options(moved_place, IncludingDowncast(true)), - ); - - self.add_moved_or_invoked_closure_note( - location, - used_place, - &mut err, - ); - - let mut is_loop_move = false; - let is_partial_move = move_site_vec.iter().any(|move_site| { - let move_out = self.move_data.moves[(*move_site).moi]; - let moved_place = &self.move_data.move_paths[move_out.path].place; - used_place != moved_place.as_ref() - && used_place.is_prefix_of(moved_place.as_ref()) - }); - for move_site in &move_site_vec { - let move_out = self.move_data.moves[(*move_site).moi]; - let moved_place = &self.move_data.move_paths[move_out.path].place; - - let move_spans = self.move_spans(moved_place.as_ref(), move_out.source); - let move_span = move_spans.args_or_use(); - - let move_msg = if move_spans.for_closure() { - " into closure" - } else { - "" - }; - - if span == move_span { - err.span_label( - span, - format!("value moved{} here, in previous iteration of loop", move_msg), - ); - is_loop_move = true; - } else if move_site.traversed_back_edge { - err.span_label( - move_span, - format!( - "value moved{} here, in previous iteration of loop", - move_msg - ), - ); - } else { - err.span_label(move_span, format!("value moved{} here", move_msg)); - move_spans.var_span_label( - &mut err, - format!("variable moved due to use{}", move_spans.describe()), - ); - } - if Some(DesugaringKind::ForLoop) == move_span.desugaring_kind() { - let sess = self.infcx.tcx.sess; - if let Ok(snippet) = sess.source_map().span_to_snippet(move_span) { - err.span_suggestion( - move_span, - "consider borrowing to avoid moving into the for loop", - format!("&{}", snippet), - Applicability::MaybeIncorrect, - ); - } - } - } - - use_spans.var_span_label( - &mut err, - format!("{} occurs due to use{}", desired_action.as_noun(), use_spans.describe()), - ); - - if !is_loop_move { - err.span_label( - span, - format!( - "value {} here {}", - desired_action.as_verb_in_past_tense(), - if is_partial_move { "after partial move" } else { "after move" }, - ), - ); - } - - let ty = Place::ty_from( - used_place.base, - used_place.projection, - *self.body, - self.infcx.tcx - ).ty; - let needs_note = match ty.kind { - ty::Closure(id, _) => { - let tables = self.infcx.tcx.typeck_tables_of(id); - let hir_id = self.infcx.tcx.hir().as_local_hir_id(id).unwrap(); - - tables.closure_kind_origins().get(hir_id).is_none() - } - _ => true, - }; - - if needs_note { - let mpi = self.move_data.moves[move_out_indices[0]].path; - let place = &self.move_data.move_paths[mpi].place; - - let ty = place.ty(*self.body, self.infcx.tcx).ty; - let opt_name = - self.describe_place_with_options(place.as_ref(), IncludingDowncast(true)); - let note_msg = match opt_name { - Some(ref name) => format!("`{}`", name), - None => "value".to_owned(), - }; - if let ty::Param(param_ty) = ty.kind { - let tcx = self.infcx.tcx; - let generics = tcx.generics_of(self.mir_def_id); - let param = generics.type_param(¶m_ty, tcx); - let generics = tcx.hir().get_generics(self.mir_def_id).unwrap(); - suggest_constraining_type_param( - generics, - &mut err, - ¶m.name.as_str(), - "Copy", - tcx.sess.source_map(), - span, - ); - } - let span = if let Some(local) = place.as_local() { - let decl = &self.body.local_decls[local]; - Some(decl.source_info.span) - } else { - None - }; - self.note_type_does_not_implement_copy( - &mut err, - ¬e_msg, - ty, - span, - ); - } - - if let Some((_, mut old_err)) = self.move_error_reported - .insert(move_out_indices, (used_place, err)) - { - // Cancel the old error so it doesn't ICE. - old_err.cancel(); - } - } - } - - pub(super) fn report_move_out_while_borrowed( - &mut self, - location: Location, - (place, span): (&Place<'tcx>, Span), - borrow: &BorrowData<'tcx>, - ) { - debug!( - "report_move_out_while_borrowed: location={:?} place={:?} span={:?} borrow={:?}", - location, place, span, borrow - ); - let value_msg = match self.describe_place(place.as_ref()) { - Some(name) => format!("`{}`", name), - None => "value".to_owned(), - }; - let borrow_msg = match self.describe_place(borrow.borrowed_place.as_ref()) { - Some(name) => format!("`{}`", name), - None => "value".to_owned(), - }; - - let borrow_spans = self.retrieve_borrow_spans(borrow); - let borrow_span = borrow_spans.args_or_use(); - - let move_spans = self.move_spans(place.as_ref(), location); - let span = move_spans.args_or_use(); - - let mut err = self.cannot_move_when_borrowed( - span, - &self.describe_place(place.as_ref()).unwrap_or_else(|| "_".to_owned()), - ); - err.span_label(borrow_span, format!("borrow of {} occurs here", borrow_msg)); - err.span_label(span, format!("move out of {} occurs here", value_msg)); - - borrow_spans.var_span_label( - &mut err, - format!("borrow occurs due to use{}", borrow_spans.describe()) - ); - - move_spans.var_span_label( - &mut err, - format!("move occurs due to use{}", move_spans.describe()) - ); - - self.explain_why_borrow_contains_point( - location, - borrow, - None, - ).add_explanation_to_diagnostic( - self.infcx.tcx, - &self.body, - &self.local_names, - &mut err, - "", - Some(borrow_span), - ); - err.buffer(&mut self.errors_buffer); - } - - pub(super) fn report_use_while_mutably_borrowed( - &mut self, - location: Location, - (place, _span): (&Place<'tcx>, Span), - borrow: &BorrowData<'tcx>, - ) -> DiagnosticBuilder<'cx> { - let borrow_spans = self.retrieve_borrow_spans(borrow); - let borrow_span = borrow_spans.args_or_use(); - - // Conflicting borrows are reported separately, so only check for move - // captures. - let use_spans = self.move_spans(place.as_ref(), location); - let span = use_spans.var_or_use(); - - let mut err = self.cannot_use_when_mutably_borrowed( - span, - &self.describe_place(place.as_ref()).unwrap_or_else(|| "_".to_owned()), - borrow_span, - &self.describe_place(borrow.borrowed_place.as_ref()) - .unwrap_or_else(|| "_".to_owned()), - ); - - borrow_spans.var_span_label(&mut err, { - let place = &borrow.borrowed_place; - let desc_place = - self.describe_place(place.as_ref()).unwrap_or_else(|| "_".to_owned()); - - format!("borrow occurs due to use of `{}`{}", desc_place, borrow_spans.describe()) - }); - - self.explain_why_borrow_contains_point(location, borrow, None) - .add_explanation_to_diagnostic( - self.infcx.tcx, - &self.body, - &self.local_names, - &mut err, - "", - None, - ); - err - } - - pub(super) fn report_conflicting_borrow( - &mut self, - location: Location, - (place, span): (&Place<'tcx>, Span), - gen_borrow_kind: BorrowKind, - issued_borrow: &BorrowData<'tcx>, - ) -> DiagnosticBuilder<'cx> { - let issued_spans = self.retrieve_borrow_spans(issued_borrow); - let issued_span = issued_spans.args_or_use(); - - let borrow_spans = self.borrow_spans(span, location); - let span = borrow_spans.args_or_use(); - - let container_name = if issued_spans.for_generator() || borrow_spans.for_generator() { - "generator" - } else { - "closure" - }; - - let (desc_place, msg_place, msg_borrow, union_type_name) = - self.describe_place_for_conflicting_borrow(place, &issued_borrow.borrowed_place); - - let explanation = self.explain_why_borrow_contains_point(location, issued_borrow, None); - let second_borrow_desc = if explanation.is_explained() { - "second " - } else { - "" - }; - - // FIXME: supply non-"" `opt_via` when appropriate - let first_borrow_desc; - let mut err = match ( - gen_borrow_kind, - issued_borrow.kind, - ) { - (BorrowKind::Shared, BorrowKind::Mut { .. }) => { - first_borrow_desc = "mutable "; - self.cannot_reborrow_already_borrowed( - span, - &desc_place, - &msg_place, - "immutable", - issued_span, - "it", - "mutable", - &msg_borrow, - None, - ) - } - (BorrowKind::Mut { .. }, BorrowKind::Shared) => { - first_borrow_desc = "immutable "; - self.cannot_reborrow_already_borrowed( - span, - &desc_place, - &msg_place, - "mutable", - issued_span, - "it", - "immutable", - &msg_borrow, - None, - ) - } - - (BorrowKind::Mut { .. }, BorrowKind::Mut { .. }) => { - first_borrow_desc = "first "; - self.cannot_mutably_borrow_multiply( - span, - &desc_place, - &msg_place, - issued_span, - &msg_borrow, - None, - ) - } - - (BorrowKind::Unique, BorrowKind::Unique) => { - first_borrow_desc = "first "; - self.cannot_uniquely_borrow_by_two_closures( - span, - &desc_place, - issued_span, - None, - ) - } - - (BorrowKind::Mut { .. }, BorrowKind::Shallow) - | (BorrowKind::Unique, BorrowKind::Shallow) => { - if let Some(immutable_section_description) = self.classify_immutable_section( - &issued_borrow.assigned_place, - ) { - let mut err = self.cannot_mutate_in_immutable_section( - span, - issued_span, - &desc_place, - immutable_section_description, - "mutably borrow", - ); - borrow_spans.var_span_label( - &mut err, - format!( - "borrow occurs due to use of `{}`{}", - desc_place, - borrow_spans.describe(), - ), - ); - - return err; - } else { - first_borrow_desc = "immutable "; - self.cannot_reborrow_already_borrowed( - span, - &desc_place, - &msg_place, - "mutable", - issued_span, - "it", - "immutable", - &msg_borrow, - None, - ) - } - } - - (BorrowKind::Unique, _) => { - first_borrow_desc = "first "; - self.cannot_uniquely_borrow_by_one_closure( - span, - container_name, - &desc_place, - "", - issued_span, - "it", - "", - None, - ) - }, - - (BorrowKind::Shared, BorrowKind::Unique) => { - first_borrow_desc = "first "; - self.cannot_reborrow_already_uniquely_borrowed( - span, - container_name, - &desc_place, - "", - "immutable", - issued_span, - "", - None, - second_borrow_desc, - ) - } - - (BorrowKind::Mut { .. }, BorrowKind::Unique) => { - first_borrow_desc = "first "; - self.cannot_reborrow_already_uniquely_borrowed( - span, - container_name, - &desc_place, - "", - "mutable", - issued_span, - "", - None, - second_borrow_desc, - ) - } - - (BorrowKind::Shared, BorrowKind::Shared) - | (BorrowKind::Shared, BorrowKind::Shallow) - | (BorrowKind::Shallow, BorrowKind::Mut { .. }) - | (BorrowKind::Shallow, BorrowKind::Unique) - | (BorrowKind::Shallow, BorrowKind::Shared) - | (BorrowKind::Shallow, BorrowKind::Shallow) => unreachable!(), - }; - - if issued_spans == borrow_spans { - borrow_spans.var_span_label( - &mut err, - format!("borrows occur due to use of `{}`{}", desc_place, borrow_spans.describe()), - ); - } else { - let borrow_place = &issued_borrow.borrowed_place; - let borrow_place_desc = self.describe_place(borrow_place.as_ref()) - .unwrap_or_else(|| "_".to_owned()); - issued_spans.var_span_label( - &mut err, - format!( - "first borrow occurs due to use of `{}`{}", - borrow_place_desc, - issued_spans.describe(), - ), - ); - - borrow_spans.var_span_label( - &mut err, - format!( - "second borrow occurs due to use of `{}`{}", - desc_place, - borrow_spans.describe(), - ), - ); - } - - if union_type_name != "" { - err.note(&format!( - "`{}` is a field of the union `{}`, so it overlaps the field `{}`", - msg_place, union_type_name, msg_borrow, - )); - } - - explanation.add_explanation_to_diagnostic( - self.infcx.tcx, - &self.body, - &self.local_names, - &mut err, - first_borrow_desc, - None, - ); - - err - } - - /// Returns the description of the root place for a conflicting borrow and the full - /// descriptions of the places that caused the conflict. - /// - /// In the simplest case, where there are no unions involved, if a mutable borrow of `x` is - /// attempted while a shared borrow is live, then this function will return: - /// - /// ("x", "", "") - /// - /// In the simple union case, if a mutable borrow of a union field `x.z` is attempted while - /// a shared borrow of another field `x.y`, then this function will return: - /// - /// ("x", "x.z", "x.y") - /// - /// In the more complex union case, where the union is a field of a struct, then if a mutable - /// borrow of a union field in a struct `x.u.z` is attempted while a shared borrow of - /// another field `x.u.y`, then this function will return: - /// - /// ("x.u", "x.u.z", "x.u.y") - /// - /// This is used when creating error messages like below: - /// - /// > cannot borrow `a.u` (via `a.u.z.c`) as immutable because it is also borrowed as - /// > mutable (via `a.u.s.b`) [E0502] - pub(super) fn describe_place_for_conflicting_borrow( - &self, - first_borrowed_place: &Place<'tcx>, - second_borrowed_place: &Place<'tcx>, - ) -> (String, String, String, String) { - // Define a small closure that we can use to check if the type of a place - // is a union. - let union_ty = |place_base, place_projection| { - let ty = Place::ty_from( - place_base, - place_projection, - *self.body, - self.infcx.tcx - ).ty; - ty.ty_adt_def().filter(|adt| adt.is_union()).map(|_| ty) - }; - let describe_place = |place| self.describe_place(place).unwrap_or_else(|| "_".to_owned()); - - // Start with an empty tuple, so we can use the functions on `Option` to reduce some - // code duplication (particularly around returning an empty description in the failure - // case). - Some(()) - .filter(|_| { - // If we have a conflicting borrow of the same place, then we don't want to add - // an extraneous "via x.y" to our diagnostics, so filter out this case. - first_borrowed_place != second_borrowed_place - }) - .and_then(|_| { - // We're going to want to traverse the first borrowed place to see if we can find - // field access to a union. If we find that, then we will keep the place of the - // union being accessed and the field that was being accessed so we can check the - // second borrowed place for the same union and a access to a different field. - let Place { - base, - projection, - } = first_borrowed_place; - - let mut cursor = projection.as_ref(); - while let [proj_base @ .., elem] = cursor { - cursor = proj_base; - - match elem { - ProjectionElem::Field(field, _) if union_ty(base, proj_base).is_some() => { - return Some((PlaceRef { - base: base, - projection: proj_base, - }, field)); - }, - _ => {}, - } - } - None - }) - .and_then(|(target_base, target_field)| { - // With the place of a union and a field access into it, we traverse the second - // borrowed place and look for a access to a different field of the same union. - let Place { - base, - projection, - } = second_borrowed_place; - - let mut cursor = projection.as_ref(); - while let [proj_base @ .., elem] = cursor { - cursor = proj_base; - - if let ProjectionElem::Field(field, _) = elem { - if let Some(union_ty) = union_ty(base, proj_base) { - if field != target_field - && base == target_base.base - && proj_base == target_base.projection { - // FIXME when we avoid clone reuse describe_place closure - let describe_base_place = self.describe_place(PlaceRef { - base: base, - projection: proj_base, - }).unwrap_or_else(|| "_".to_owned()); - - return Some(( - describe_base_place, - describe_place(first_borrowed_place.as_ref()), - describe_place(second_borrowed_place.as_ref()), - union_ty.to_string(), - )); - } - } - } - } - None - }) - .unwrap_or_else(|| { - // If we didn't find a field access into a union, or both places match, then - // only return the description of the first place. - ( - describe_place(first_borrowed_place.as_ref()), - "".to_string(), - "".to_string(), - "".to_string(), - ) - }) - } - - /// Reports StorageDeadOrDrop of `place` conflicts with `borrow`. - /// - /// This means that some data referenced by `borrow` needs to live - /// past the point where the StorageDeadOrDrop of `place` occurs. - /// This is usually interpreted as meaning that `place` has too - /// short a lifetime. (But sometimes it is more useful to report - /// it as a more direct conflict between the execution of a - /// `Drop::drop` with an aliasing borrow.) - pub(super) fn report_borrowed_value_does_not_live_long_enough( - &mut self, - location: Location, - borrow: &BorrowData<'tcx>, - place_span: (&Place<'tcx>, Span), - kind: Option, - ) { - debug!( - "report_borrowed_value_does_not_live_long_enough(\ - {:?}, {:?}, {:?}, {:?}\ - )", - location, borrow, place_span, kind - ); - - let drop_span = place_span.1; - let root_place = self.prefixes(borrow.borrowed_place.as_ref(), PrefixSet::All) - .last() - .unwrap(); - - let borrow_spans = self.retrieve_borrow_spans(borrow); - let borrow_span = borrow_spans.var_or_use(); - - assert!(root_place.projection.is_empty()); - let proper_span = match root_place.base { - PlaceBase::Local(local) => self.body.local_decls[*local].source_info.span, - _ => drop_span, - }; - - let root_place_projection = self.infcx.tcx.intern_place_elems(root_place.projection); - - if self.access_place_error_reported - .contains(&(Place { - base: root_place.base.clone(), - projection: root_place_projection, - }, borrow_span)) - { - debug!( - "suppressing access_place error when borrow doesn't live long enough for {:?}", - borrow_span - ); - return; - } - - self.access_place_error_reported - .insert((Place { - base: root_place.base.clone(), - projection: root_place_projection, - }, borrow_span)); - - if let PlaceBase::Local(local) = borrow.borrowed_place.base { - if self.body.local_decls[local].is_ref_to_thread_local() { - let err = self.report_thread_local_value_does_not_live_long_enough( - drop_span, - borrow_span, - ); - err.buffer(&mut self.errors_buffer); - return; - } - }; - - if let StorageDeadOrDrop::Destructor(dropped_ty) = - self.classify_drop_access_kind(borrow.borrowed_place.as_ref()) - { - // If a borrow of path `B` conflicts with drop of `D` (and - // we're not in the uninteresting case where `B` is a - // prefix of `D`), then report this as a more interesting - // destructor conflict. - if !borrow.borrowed_place.as_ref().is_prefix_of(place_span.0.as_ref()) { - self.report_borrow_conflicts_with_destructor( - location, borrow, place_span, kind, dropped_ty, - ); - return; - } - } - - let place_desc = self.describe_place(borrow.borrowed_place.as_ref()); - - let kind_place = kind.filter(|_| place_desc.is_some()).map(|k| (k, place_span.0)); - let explanation = self.explain_why_borrow_contains_point(location, &borrow, kind_place); - - debug!( - "report_borrowed_value_does_not_live_long_enough(place_desc: {:?}, explanation: {:?})", - place_desc, - explanation - ); - let err = match (place_desc, explanation) { - // If the outlives constraint comes from inside the closure, - // for example: - // - // let x = 0; - // let y = &x; - // Box::new(|| y) as Box &'static i32> - // - // then just use the normal error. The closure isn't escaping - // and `move` will not help here. - ( - Some(ref name), - BorrowExplanation::MustBeValidFor { - category: category @ ConstraintCategory::Return, - from_closure: false, - ref region_name, - span, - .. - }, - ) - | ( - Some(ref name), - BorrowExplanation::MustBeValidFor { - category: category @ ConstraintCategory::CallArgument, - from_closure: false, - ref region_name, - span, - .. - }, - ) if borrow_spans.for_closure() => self.report_escaping_closure_capture( - borrow_spans, - borrow_span, - region_name, - category, - span, - &format!("`{}`", name), - ), - ( - Some(ref name), - BorrowExplanation::MustBeValidFor { - category: category @ ConstraintCategory::OpaqueType, - from_closure: false, - ref region_name, - span, - .. - }, - - ) if borrow_spans.for_generator() => self.report_escaping_closure_capture( - borrow_spans, - borrow_span, - region_name, - category, - span, - &format!("`{}`", name), - ), - ( - ref name, - BorrowExplanation::MustBeValidFor { - category: ConstraintCategory::Assignment, - from_closure: false, - region_name: RegionName { - source: RegionNameSource::AnonRegionFromUpvar(upvar_span, ref upvar_name), - .. - }, - span, - .. - }, - ) => self.report_escaping_data(borrow_span, name, upvar_span, upvar_name, span), - (Some(name), explanation) => self.report_local_value_does_not_live_long_enough( - location, - &name, - &borrow, - drop_span, - borrow_spans, - explanation, - ), - (None, explanation) => self.report_temporary_value_does_not_live_long_enough( - location, - &borrow, - drop_span, - borrow_spans, - proper_span, - explanation, - ), - }; - - err.buffer(&mut self.errors_buffer); - } - - fn report_local_value_does_not_live_long_enough( - &mut self, - location: Location, - name: &str, - borrow: &BorrowData<'tcx>, - drop_span: Span, - borrow_spans: UseSpans, - explanation: BorrowExplanation, - ) -> DiagnosticBuilder<'cx> { - debug!( - "report_local_value_does_not_live_long_enough(\ - {:?}, {:?}, {:?}, {:?}, {:?}\ - )", - location, name, borrow, drop_span, borrow_spans - ); - - let borrow_span = borrow_spans.var_or_use(); - if let BorrowExplanation::MustBeValidFor { - category, - span, - ref opt_place_desc, - from_closure: false, - .. - } = explanation { - if let Some(diag) = self.try_report_cannot_return_reference_to_local( - borrow, - borrow_span, - span, - category, - opt_place_desc.as_ref(), - ) { - return diag; - } - } - - let mut err = self.path_does_not_live_long_enough( - borrow_span, - &format!("`{}`", name), - ); - - if let Some(annotation) = self.annotate_argument_and_return_for_borrow(borrow) { - let region_name = annotation.emit(self, &mut err); - - err.span_label( - borrow_span, - format!("`{}` would have to be valid for `{}`...", name, region_name), - ); - - if let Some(fn_hir_id) = self.infcx.tcx.hir().as_local_hir_id(self.mir_def_id) { - err.span_label( - drop_span, - format!( - "...but `{}` will be dropped here, when the function `{}` returns", - name, - self.infcx.tcx.hir().name(fn_hir_id), - ), - ); - - err.note( - "functions cannot return a borrow to data owned within the function's scope, \ - functions can only return borrows to data passed as arguments", - ); - err.note( - "to learn more, visit ", - ); - } else { - err.span_label( - drop_span, - format!("...but `{}` dropped here while still borrowed", name), - ); - } - - if let BorrowExplanation::MustBeValidFor { .. } = explanation { - } else { - explanation.add_explanation_to_diagnostic( - self.infcx.tcx, - &self.body, - &self.local_names, - &mut err, - "", - None, - ); - } - } else { - err.span_label(borrow_span, "borrowed value does not live long enough"); - err.span_label( - drop_span, - format!("`{}` dropped here while still borrowed", name), - ); - - let within = if borrow_spans.for_generator() { - " by generator" - } else { - "" - }; - - borrow_spans.args_span_label( - &mut err, - format!("value captured here{}", within), - ); - - explanation.add_explanation_to_diagnostic( - self.infcx.tcx, &self.body, &self.local_names, &mut err, "", None); - } - - err - } - - fn report_borrow_conflicts_with_destructor( - &mut self, - location: Location, - borrow: &BorrowData<'tcx>, - (place, drop_span): (&Place<'tcx>, Span), - kind: Option, - dropped_ty: Ty<'tcx>, - ) { - debug!( - "report_borrow_conflicts_with_destructor(\ - {:?}, {:?}, ({:?}, {:?}), {:?}\ - )", - location, borrow, place, drop_span, kind, - ); - - let borrow_spans = self.retrieve_borrow_spans(borrow); - let borrow_span = borrow_spans.var_or_use(); - - let mut err = self.cannot_borrow_across_destructor(borrow_span); - - let what_was_dropped = match self.describe_place(place.as_ref()) { - Some(name) => format!("`{}`", name), - None => String::from("temporary value"), - }; - - let label = match self.describe_place(borrow.borrowed_place.as_ref()) { - Some(borrowed) => format!( - "here, drop of {D} needs exclusive access to `{B}`, \ - because the type `{T}` implements the `Drop` trait", - D = what_was_dropped, - T = dropped_ty, - B = borrowed - ), - None => format!( - "here is drop of {D}; whose type `{T}` implements the `Drop` trait", - D = what_was_dropped, - T = dropped_ty - ), - }; - err.span_label(drop_span, label); - - // Only give this note and suggestion if they could be relevant. - let explanation = - self.explain_why_borrow_contains_point(location, borrow, kind.map(|k| (k, place))); - match explanation { - BorrowExplanation::UsedLater { .. } - | BorrowExplanation::UsedLaterWhenDropped { .. } => { - err.note("consider using a `let` binding to create a longer lived value"); - } - _ => {} - } - - explanation.add_explanation_to_diagnostic( - self.infcx.tcx, - &self.body, - &self.local_names, - &mut err, - "", - None, - ); - - err.buffer(&mut self.errors_buffer); - } - - fn report_thread_local_value_does_not_live_long_enough( - &mut self, - drop_span: Span, - borrow_span: Span, - ) -> DiagnosticBuilder<'cx> { - debug!( - "report_thread_local_value_does_not_live_long_enough(\ - {:?}, {:?}\ - )", - drop_span, borrow_span - ); - - let mut err = self.thread_local_value_does_not_live_long_enough(borrow_span); - - err.span_label( - borrow_span, - "thread-local variables cannot be borrowed beyond the end of the function", - ); - err.span_label(drop_span, "end of enclosing function is here"); - - err - } - - fn report_temporary_value_does_not_live_long_enough( - &mut self, - location: Location, - borrow: &BorrowData<'tcx>, - drop_span: Span, - borrow_spans: UseSpans, - proper_span: Span, - explanation: BorrowExplanation, - ) -> DiagnosticBuilder<'cx> { - debug!( - "report_temporary_value_does_not_live_long_enough(\ - {:?}, {:?}, {:?}, {:?}\ - )", - location, borrow, drop_span, proper_span - ); - - if let BorrowExplanation::MustBeValidFor { - category, - span, - from_closure: false, - .. - } = explanation { - if let Some(diag) = self.try_report_cannot_return_reference_to_local( - borrow, - proper_span, - span, - category, - None, - ) { - return diag; - } - } - - let mut err = self.temporary_value_borrowed_for_too_long(proper_span); - err.span_label( - proper_span, - "creates a temporary which is freed while still in use", - ); - err.span_label( - drop_span, - "temporary value is freed at the end of this statement", - ); - - match explanation { - BorrowExplanation::UsedLater(..) - | BorrowExplanation::UsedLaterInLoop(..) - | BorrowExplanation::UsedLaterWhenDropped { .. } => { - // Only give this note and suggestion if it could be relevant. - err.note("consider using a `let` binding to create a longer lived value"); - } - _ => {} - } - explanation.add_explanation_to_diagnostic( - self.infcx.tcx, - &self.body, - &self.local_names, - &mut err, - "", - None, - ); - - let within = if borrow_spans.for_generator() { - " by generator" - } else { - "" - }; - - borrow_spans.args_span_label( - &mut err, - format!("value captured here{}", within), - ); - - err - } - - fn try_report_cannot_return_reference_to_local( - &self, - borrow: &BorrowData<'tcx>, - borrow_span: Span, - return_span: Span, - category: ConstraintCategory, - opt_place_desc: Option<&String>, - ) -> Option> { - let return_kind = match category { - ConstraintCategory::Return => "return", - ConstraintCategory::Yield => "yield", - _ => return None, - }; - - // FIXME use a better heuristic than Spans - let reference_desc - = if return_span == self.body.source_info(borrow.reserve_location).span { - "reference to" - } else { - "value referencing" - }; - - let (place_desc, note) = if let Some(place_desc) = opt_place_desc { - let local_kind = if let Some(local) = borrow.borrowed_place.as_local() { - match self.body.local_kind(local) { - LocalKind::ReturnPointer - | LocalKind::Temp => bug!("temporary or return pointer with a name"), - LocalKind::Var => "local variable ", - LocalKind::Arg - if !self.upvars.is_empty() - && local == Local::new(1) => { - "variable captured by `move` " - } - LocalKind::Arg => { - "function parameter " - } - } - } else { - "local data " - }; - ( - format!("{}`{}`", local_kind, place_desc), - format!("`{}` is borrowed here", place_desc), - ) - } else { - let root_place = self.prefixes(borrow.borrowed_place.as_ref(), - PrefixSet::All) - .last() - .unwrap(); - let local = if let PlaceRef { - base: PlaceBase::Local(local), - projection: [], - } = root_place { - local - } else { - bug!("try_report_cannot_return_reference_to_local: not a local") - }; - match self.body.local_kind(*local) { - LocalKind::ReturnPointer | LocalKind::Temp => ( - "temporary value".to_string(), - "temporary value created here".to_string(), - ), - LocalKind::Arg => ( - "function parameter".to_string(), - "function parameter borrowed here".to_string(), - ), - LocalKind::Var => ( - "local binding".to_string(), - "local binding introduced here".to_string(), - ), - } - }; - - let mut err = self.cannot_return_reference_to_local( - return_span, - return_kind, - reference_desc, - &place_desc, - ); - - if return_span != borrow_span { - err.span_label(borrow_span, note); - } - - Some(err) - } - - fn report_escaping_closure_capture( - &mut self, - use_span: UseSpans, - var_span: Span, - fr_name: &RegionName, - category: ConstraintCategory, - constraint_span: Span, - captured_var: &str, - ) -> DiagnosticBuilder<'cx> { - let tcx = self.infcx.tcx; - let args_span = use_span.args_or_use(); - let mut err = self.cannot_capture_in_long_lived_closure( - args_span, - captured_var, - var_span, - ); - - let suggestion = match tcx.sess.source_map().span_to_snippet(args_span) { - Ok(mut string) => { - if string.starts_with("async ") { - string.insert_str(6, "move "); - } else if string.starts_with("async|") { - string.insert_str(5, " move"); - } else { - string.insert_str(0, "move "); - }; - string - }, - Err(_) => "move || ".to_string() - }; - let kind = match use_span.generator_kind() { - Some(generator_kind) => match generator_kind { - GeneratorKind::Async(async_kind) => match async_kind { - AsyncGeneratorKind::Block => "async block", - AsyncGeneratorKind::Closure => "async closure", - _ => bug!("async block/closure expected, but async funtion found."), - }, - GeneratorKind::Gen => "generator", - } - None => "closure", - }; - err.span_suggestion( - args_span, - &format!( - "to force the {} to take ownership of {} (and any \ - other referenced variables), use the `move` keyword", - kind, - captured_var - ), - suggestion, - Applicability::MachineApplicable, - ); - - let msg = match category { - ConstraintCategory::Return => "closure is returned here".to_string(), - ConstraintCategory::OpaqueType => "generator is returned here".to_string(), - ConstraintCategory::CallArgument => { - fr_name.highlight_region_name(&mut err); - format!("function requires argument type to outlive `{}`", fr_name) - } - _ => bug!("report_escaping_closure_capture called with unexpected constraint \ - category: `{:?}`", category), - }; - err.span_note(constraint_span, &msg); - err - } - - fn report_escaping_data( - &mut self, - borrow_span: Span, - name: &Option, - upvar_span: Span, - upvar_name: &str, - escape_span: Span, - ) -> DiagnosticBuilder<'cx> { - let tcx = self.infcx.tcx; - - let escapes_from = if tcx.is_closure(self.mir_def_id) { - let tables = tcx.typeck_tables_of(self.mir_def_id); - let mir_hir_id = tcx.hir().def_index_to_hir_id(self.mir_def_id.index); - match tables.node_type(mir_hir_id).kind { - ty::Closure(..) => "closure", - ty::Generator(..) => "generator", - _ => bug!("Closure body doesn't have a closure or generator type"), - } - } else { - "function" - }; - - let mut err = borrowck_errors::borrowed_data_escapes_closure( - tcx, - escape_span, - escapes_from, - ); - - err.span_label( - upvar_span, - format!( - "`{}` is declared here, outside of the {} body", - upvar_name, escapes_from - ), - ); - - err.span_label( - borrow_span, - format!( - "borrow is only valid in the {} body", - escapes_from - ), - ); - - if let Some(name) = name { - err.span_label( - escape_span, - format!("reference to `{}` escapes the {} body here", name, escapes_from), - ); - } else { - err.span_label( - escape_span, - format!("reference escapes the {} body here", escapes_from), - ); - } - - err - } - - fn get_moved_indexes(&mut self, location: Location, mpi: MovePathIndex) -> Vec { - let mut stack = Vec::new(); - stack.extend(self.body.predecessor_locations(location).map(|predecessor| { - let is_back_edge = location.dominates(predecessor, &self.dominators); - (predecessor, is_back_edge) - })); - - let mut visited = FxHashSet::default(); - let mut result = vec![]; - - 'dfs: while let Some((location, is_back_edge)) = stack.pop() { - debug!( - "report_use_of_moved_or_uninitialized: (current_location={:?}, back_edge={})", - location, is_back_edge - ); - - if !visited.insert(location) { - continue; - } - - // check for moves - let stmt_kind = self.body[location.block] - .statements - .get(location.statement_index) - .map(|s| &s.kind); - if let Some(StatementKind::StorageDead(..)) = stmt_kind { - // this analysis only tries to find moves explicitly - // written by the user, so we ignore the move-outs - // created by `StorageDead` and at the beginning - // of a function. - } else { - // If we are found a use of a.b.c which was in error, then we want to look for - // moves not only of a.b.c but also a.b and a. - // - // Note that the moves data already includes "parent" paths, so we don't have to - // worry about the other case: that is, if there is a move of a.b.c, it is already - // marked as a move of a.b and a as well, so we will generate the correct errors - // there. - let mut mpis = vec![mpi]; - let move_paths = &self.move_data.move_paths; - mpis.extend(move_paths[mpi].parents(move_paths)); - - for moi in &self.move_data.loc_map[location] { - debug!("report_use_of_moved_or_uninitialized: moi={:?}", moi); - if mpis.contains(&self.move_data.moves[*moi].path) { - debug!("report_use_of_moved_or_uninitialized: found"); - result.push(MoveSite { - moi: *moi, - traversed_back_edge: is_back_edge, - }); - - // Strictly speaking, we could continue our DFS here. There may be - // other moves that can reach the point of error. But it is kind of - // confusing to highlight them. - // - // Example: - // - // ``` - // let a = vec![]; - // let b = a; - // let c = a; - // drop(a); // <-- current point of error - // ``` - // - // Because we stop the DFS here, we only highlight `let c = a`, - // and not `let b = a`. We will of course also report an error at - // `let c = a` which highlights `let b = a` as the move. - continue 'dfs; - } - } - } - - // check for inits - let mut any_match = false; - drop_flag_effects::for_location_inits( - self.infcx.tcx, - &self.body, - self.move_data, - location, - |m| { - if m == mpi { - any_match = true; - } - }, - ); - if any_match { - continue 'dfs; - } - - stack.extend(self.body.predecessor_locations(location).map(|predecessor| { - let back_edge = location.dominates(predecessor, &self.dominators); - (predecessor, is_back_edge || back_edge) - })); - } - - result - } - - pub(super) fn report_illegal_mutation_of_borrowed( - &mut self, - location: Location, - (place, span): (&Place<'tcx>, Span), - loan: &BorrowData<'tcx>, - ) { - let loan_spans = self.retrieve_borrow_spans(loan); - let loan_span = loan_spans.args_or_use(); - - if loan.kind == BorrowKind::Shallow { - if let Some(section) = self.classify_immutable_section(&loan.assigned_place) { - let mut err = self.cannot_mutate_in_immutable_section( - span, - loan_span, - &self.describe_place(place.as_ref()).unwrap_or_else(|| "_".to_owned()), - section, - "assign", - ); - loan_spans.var_span_label( - &mut err, - format!("borrow occurs due to use{}", loan_spans.describe()), - ); - - err.buffer(&mut self.errors_buffer); - - return; - } - } - - let mut err = self.cannot_assign_to_borrowed( - span, - loan_span, - &self.describe_place(place.as_ref()).unwrap_or_else(|| "_".to_owned()), - ); - - loan_spans.var_span_label( - &mut err, - format!("borrow occurs due to use{}", loan_spans.describe()), - ); - - self.explain_why_borrow_contains_point(location, loan, None) - .add_explanation_to_diagnostic( - self.infcx.tcx, - &self.body, - &self.local_names, - &mut err, - "", - None, - ); - - err.buffer(&mut self.errors_buffer); - } - - /// Reports an illegal reassignment; for example, an assignment to - /// (part of) a non-`mut` local that occurs potentially after that - /// local has already been initialized. `place` is the path being - /// assigned; `err_place` is a place providing a reason why - /// `place` is not mutable (e.g., the non-`mut` local `x` in an - /// assignment to `x.f`). - pub(super) fn report_illegal_reassignment( - &mut self, - _location: Location, - (place, span): (&Place<'tcx>, Span), - assigned_span: Span, - err_place: &Place<'tcx>, - ) { - let (from_arg, local_decl, local_name) = match err_place.as_local() { - Some(local) => ( - self.body.local_kind(local) == LocalKind::Arg, - Some(&self.body.local_decls[local]), - self.local_names[local], - ), - None => (false, None, None), - }; - - // If root local is initialized immediately (everything apart from let - // PATTERN;) then make the error refer to that local, rather than the - // place being assigned later. - let (place_description, assigned_span) = match local_decl { - Some(LocalDecl { - local_info: LocalInfo::User(ClearCrossCrate::Clear), - .. - }) - | Some(LocalDecl { - local_info: LocalInfo::User(ClearCrossCrate::Set(BindingForm::Var(VarBindingForm { - opt_match_place: None, - .. - }))), - .. - }) - | Some(LocalDecl { - local_info: LocalInfo::StaticRef { .. }, - .. - }) - | Some(LocalDecl { - local_info: LocalInfo::Other, - .. - }) - | None => (self.describe_place(place.as_ref()), assigned_span), - Some(decl) => (self.describe_place(err_place.as_ref()), decl.source_info.span), - }; - - let mut err = self.cannot_reassign_immutable( - span, - place_description.as_ref().map(AsRef::as_ref).unwrap_or("_"), - from_arg, - ); - let msg = if from_arg { - "cannot assign to immutable argument" - } else { - "cannot assign twice to immutable variable" - }; - if span != assigned_span { - if !from_arg { - let value_msg = match place_description { - Some(name) => format!("`{}`", name), - None => "value".to_owned(), - }; - err.span_label(assigned_span, format!("first assignment to {}", value_msg)); - } - } - if let Some(decl) = local_decl { - if let Some(name) = local_name { - if decl.can_be_made_mutable() { - err.span_suggestion( - decl.source_info.span, - "make this binding mutable", - format!("mut {}", name), - Applicability::MachineApplicable, - ); - } - } - } - err.span_label(span, msg); - err.buffer(&mut self.errors_buffer); - } - - fn classify_drop_access_kind(&self, place: PlaceRef<'cx, 'tcx>) -> StorageDeadOrDrop<'tcx> { - let tcx = self.infcx.tcx; - match place.projection { - [] => { - StorageDeadOrDrop::LocalStorageDead - } - [proj_base @ .., elem] => { - // FIXME(spastorino) make this iterate - let base_access = self.classify_drop_access_kind(PlaceRef { - base: place.base, - projection: proj_base, - }); - match elem { - ProjectionElem::Deref => match base_access { - StorageDeadOrDrop::LocalStorageDead - | StorageDeadOrDrop::BoxedStorageDead => { - assert!( - Place::ty_from( - &place.base, - proj_base, - *self.body, - tcx - ).ty.is_box(), - "Drop of value behind a reference or raw pointer" - ); - StorageDeadOrDrop::BoxedStorageDead - } - StorageDeadOrDrop::Destructor(_) => base_access, - }, - ProjectionElem::Field(..) | ProjectionElem::Downcast(..) => { - let base_ty = Place::ty_from( - &place.base, - proj_base, - *self.body, - tcx - ).ty; - match base_ty.kind { - ty::Adt(def, _) if def.has_dtor(tcx) => { - // Report the outermost adt with a destructor - match base_access { - StorageDeadOrDrop::Destructor(_) => base_access, - StorageDeadOrDrop::LocalStorageDead - | StorageDeadOrDrop::BoxedStorageDead => { - StorageDeadOrDrop::Destructor(base_ty) - } - } - } - _ => base_access, - } - } - - ProjectionElem::ConstantIndex { .. } - | ProjectionElem::Subslice { .. } - | ProjectionElem::Index(_) => base_access, - } - } - } - } - - /// Describe the reason for the fake borrow that was assigned to `place`. - fn classify_immutable_section(&self, place: &Place<'tcx>) -> Option<&'static str> { - use rustc::mir::visit::Visitor; - struct FakeReadCauseFinder<'a, 'tcx> { - place: &'a Place<'tcx>, - cause: Option, - } - impl<'tcx> Visitor<'tcx> for FakeReadCauseFinder<'_, 'tcx> { - fn visit_statement(&mut self, statement: &Statement<'tcx>, _: Location) { - match statement { - Statement { - kind: StatementKind::FakeRead(cause, box ref place), - .. - } if *place == *self.place => { - self.cause = Some(*cause); - } - _ => (), - } - } - } - let mut visitor = FakeReadCauseFinder { place, cause: None }; - visitor.visit_body(self.body); - match visitor.cause { - Some(FakeReadCause::ForMatchGuard) => Some("match guard"), - Some(FakeReadCause::ForIndex) => Some("indexing expression"), - _ => None, - } - } - - /// Annotate argument and return type of function and closure with (synthesized) lifetime for - /// borrow of local value that does not live long enough. - fn annotate_argument_and_return_for_borrow( - &self, - borrow: &BorrowData<'tcx>, - ) -> Option> { - // Define a fallback for when we can't match a closure. - let fallback = || { - let is_closure = self.infcx.tcx.is_closure(self.mir_def_id); - if is_closure { - None - } else { - let ty = self.infcx.tcx.type_of(self.mir_def_id); - match ty.kind { - ty::FnDef(_, _) | ty::FnPtr(_) => self.annotate_fn_sig( - self.mir_def_id, - self.infcx.tcx.fn_sig(self.mir_def_id), - ), - _ => None, - } - } - }; - - // In order to determine whether we need to annotate, we need to check whether the reserve - // place was an assignment into a temporary. - // - // If it was, we check whether or not that temporary is eventually assigned into the return - // place. If it was, we can add annotations about the function's return type and arguments - // and it'll make sense. - let location = borrow.reserve_location; - debug!( - "annotate_argument_and_return_for_borrow: location={:?}", - location - ); - if let Some(&Statement { kind: StatementKind::Assign(box(ref reservation, _)), ..}) - = &self.body[location.block].statements.get(location.statement_index) - { - debug!( - "annotate_argument_and_return_for_borrow: reservation={:?}", - reservation - ); - // Check that the initial assignment of the reserve location is into a temporary. - let mut target = match reservation.as_local() { - Some(local) if self.body.local_kind(local) == LocalKind::Temp => local, - _ => return None, - }; - - // Next, look through the rest of the block, checking if we are assigning the - // `target` (that is, the place that contains our borrow) to anything. - let mut annotated_closure = None; - for stmt in &self.body[location.block].statements[location.statement_index + 1..] - { - debug!( - "annotate_argument_and_return_for_borrow: target={:?} stmt={:?}", - target, stmt - ); - if let StatementKind::Assign(box(place, rvalue)) = &stmt.kind { - if let Some(assigned_to) = place.as_local() { - debug!( - "annotate_argument_and_return_for_borrow: assigned_to={:?} \ - rvalue={:?}", - assigned_to, rvalue - ); - // Check if our `target` was captured by a closure. - if let Rvalue::Aggregate( - box AggregateKind::Closure(def_id, substs), - operands, - ) = rvalue - { - for operand in operands { - let assigned_from = match operand { - Operand::Copy(assigned_from) | Operand::Move(assigned_from) => { - assigned_from - } - _ => continue, - }; - debug!( - "annotate_argument_and_return_for_borrow: assigned_from={:?}", - assigned_from - ); - - // Find the local from the operand. - let assigned_from_local = match assigned_from.local_or_deref_local() - { - Some(local) => local, - None => continue, - }; - - if assigned_from_local != target { - continue; - } - - // If a closure captured our `target` and then assigned - // into a place then we should annotate the closure in - // case it ends up being assigned into the return place. - annotated_closure = self.annotate_fn_sig( - *def_id, - self.infcx.closure_sig(*def_id, *substs), - ); - debug!( - "annotate_argument_and_return_for_borrow: \ - annotated_closure={:?} assigned_from_local={:?} \ - assigned_to={:?}", - annotated_closure, assigned_from_local, assigned_to - ); - - if assigned_to == mir::RETURN_PLACE { - // If it was assigned directly into the return place, then - // return now. - return annotated_closure; - } else { - // Otherwise, update the target. - target = assigned_to; - } - } - - // If none of our closure's operands matched, then skip to the next - // statement. - continue; - } - - // Otherwise, look at other types of assignment. - let assigned_from = match rvalue { - Rvalue::Ref(_, _, assigned_from) => assigned_from, - Rvalue::Use(operand) => match operand { - Operand::Copy(assigned_from) | Operand::Move(assigned_from) => { - assigned_from - } - _ => continue, - }, - _ => continue, - }; - debug!( - "annotate_argument_and_return_for_borrow: \ - assigned_from={:?}", - assigned_from, - ); - - // Find the local from the rvalue. - let assigned_from_local = match assigned_from.local_or_deref_local() { - Some(local) => local, - None => continue, - }; - debug!( - "annotate_argument_and_return_for_borrow: \ - assigned_from_local={:?}", - assigned_from_local, - ); - - // Check if our local matches the target - if so, we've assigned our - // borrow to a new place. - if assigned_from_local != target { - continue; - } - - // If we assigned our `target` into a new place, then we should - // check if it was the return place. - debug!( - "annotate_argument_and_return_for_borrow: \ - assigned_from_local={:?} assigned_to={:?}", - assigned_from_local, assigned_to - ); - if assigned_to == mir::RETURN_PLACE { - // If it was then return the annotated closure if there was one, - // else, annotate this function. - return annotated_closure.or_else(fallback); - } - - // If we didn't assign into the return place, then we just update - // the target. - target = assigned_to; - } - } - } - - // Check the terminator if we didn't find anything in the statements. - let terminator = &self.body[location.block].terminator(); - debug!( - "annotate_argument_and_return_for_borrow: target={:?} terminator={:?}", - target, terminator - ); - if let TerminatorKind::Call { - destination: Some((place, _)), - args, - .. - } = &terminator.kind - { - if let Some(assigned_to) = place.as_local() { - debug!( - "annotate_argument_and_return_for_borrow: assigned_to={:?} args={:?}", - assigned_to, args - ); - for operand in args { - let assigned_from = match operand { - Operand::Copy(assigned_from) | Operand::Move(assigned_from) => { - assigned_from - } - _ => continue, - }; - debug!( - "annotate_argument_and_return_for_borrow: assigned_from={:?}", - assigned_from, - ); - - if let Some(assigned_from_local) = assigned_from.local_or_deref_local() { - debug!( - "annotate_argument_and_return_for_borrow: assigned_from_local={:?}", - assigned_from_local, - ); - - if assigned_to == mir::RETURN_PLACE && assigned_from_local == target { - return annotated_closure.or_else(fallback); - } - } - } - } - } - } - - // If we haven't found an assignment into the return place, then we need not add - // any annotations. - debug!("annotate_argument_and_return_for_borrow: none found"); - None - } - - /// Annotate the first argument and return type of a function signature if they are - /// references. - fn annotate_fn_sig( - &self, - did: DefId, - sig: ty::PolyFnSig<'tcx>, - ) -> Option> { - debug!("annotate_fn_sig: did={:?} sig={:?}", did, sig); - let is_closure = self.infcx.tcx.is_closure(did); - let fn_hir_id = self.infcx.tcx.hir().as_local_hir_id(did)?; - let fn_decl = self.infcx.tcx.hir().fn_decl_by_hir_id(fn_hir_id)?; - - // We need to work out which arguments to highlight. We do this by looking - // at the return type, where there are three cases: - // - // 1. If there are named arguments, then we should highlight the return type and - // highlight any of the arguments that are also references with that lifetime. - // If there are no arguments that have the same lifetime as the return type, - // then don't highlight anything. - // 2. The return type is a reference with an anonymous lifetime. If this is - // the case, then we can take advantage of (and teach) the lifetime elision - // rules. - // - // We know that an error is being reported. So the arguments and return type - // must satisfy the elision rules. Therefore, if there is a single argument - // then that means the return type and first (and only) argument have the same - // lifetime and the borrow isn't meeting that, we can highlight the argument - // and return type. - // - // If there are multiple arguments then the first argument must be self (else - // it would not satisfy the elision rules), so we can highlight self and the - // return type. - // 3. The return type is not a reference. In this case, we don't highlight - // anything. - let return_ty = sig.output(); - match return_ty.skip_binder().kind { - ty::Ref(return_region, _, _) if return_region.has_name() && !is_closure => { - // This is case 1 from above, return type is a named reference so we need to - // search for relevant arguments. - let mut arguments = Vec::new(); - for (index, argument) in sig.inputs().skip_binder().iter().enumerate() { - if let ty::Ref(argument_region, _, _) = argument.kind { - if argument_region == return_region { - // Need to use the `rustc::ty` types to compare against the - // `return_region`. Then use the `rustc::hir` type to get only - // the lifetime span. - if let hir::TyKind::Rptr(lifetime, _) = &fn_decl.inputs[index].kind { - // With access to the lifetime, we can get - // the span of it. - arguments.push((*argument, lifetime.span)); - } else { - bug!("ty type is a ref but hir type is not"); - } - } - } - } - - // We need to have arguments. This shouldn't happen, but it's worth checking. - if arguments.is_empty() { - return None; - } - - // We use a mix of the HIR and the Ty types to get information - // as the HIR doesn't have full types for closure arguments. - let return_ty = *sig.output().skip_binder(); - let mut return_span = fn_decl.output.span(); - if let hir::FunctionRetTy::Return(ty) = &fn_decl.output { - if let hir::TyKind::Rptr(lifetime, _) = ty.kind { - return_span = lifetime.span; - } - } - - Some(AnnotatedBorrowFnSignature::NamedFunction { - arguments, - return_ty, - return_span, - }) - } - ty::Ref(_, _, _) if is_closure => { - // This is case 2 from above but only for closures, return type is anonymous - // reference so we select - // the first argument. - let argument_span = fn_decl.inputs.first()?.span; - let argument_ty = sig.inputs().skip_binder().first()?; - - // Closure arguments are wrapped in a tuple, so we need to get the first - // from that. - if let ty::Tuple(elems) = argument_ty.kind { - let argument_ty = elems.first()?.expect_ty(); - if let ty::Ref(_, _, _) = argument_ty.kind { - return Some(AnnotatedBorrowFnSignature::Closure { - argument_ty, - argument_span, - }); - } - } - - None - } - ty::Ref(_, _, _) => { - // This is also case 2 from above but for functions, return type is still an - // anonymous reference so we select the first argument. - let argument_span = fn_decl.inputs.first()?.span; - let argument_ty = sig.inputs().skip_binder().first()?; - - let return_span = fn_decl.output.span(); - let return_ty = *sig.output().skip_binder(); - - // We expect the first argument to be a reference. - match argument_ty.kind { - ty::Ref(_, _, _) => {} - _ => return None, - } - - Some(AnnotatedBorrowFnSignature::AnonymousFunction { - argument_ty, - argument_span, - return_ty, - return_span, - }) - } - _ => { - // This is case 3 from above, return type is not a reference so don't highlight - // anything. - None - } - } - } -} - -#[derive(Debug)] -enum AnnotatedBorrowFnSignature<'tcx> { - NamedFunction { - arguments: Vec<(Ty<'tcx>, Span)>, - return_ty: Ty<'tcx>, - return_span: Span, - }, - AnonymousFunction { - argument_ty: Ty<'tcx>, - argument_span: Span, - return_ty: Ty<'tcx>, - return_span: Span, - }, - Closure { - argument_ty: Ty<'tcx>, - argument_span: Span, - }, -} - -impl<'tcx> AnnotatedBorrowFnSignature<'tcx> { - /// Annotate the provided diagnostic with information about borrow from the fn signature that - /// helps explain. - pub(super) fn emit( - &self, - cx: &mut MirBorrowckCtxt<'_, 'tcx>, - diag: &mut DiagnosticBuilder<'_>, - ) -> String { - match self { - AnnotatedBorrowFnSignature::Closure { - argument_ty, - argument_span, - } => { - diag.span_label( - *argument_span, - format!("has type `{}`", cx.get_name_for_ty(argument_ty, 0)), - ); - - cx.get_region_name_for_ty(argument_ty, 0) - } - AnnotatedBorrowFnSignature::AnonymousFunction { - argument_ty, - argument_span, - return_ty, - return_span, - } => { - let argument_ty_name = cx.get_name_for_ty(argument_ty, 0); - diag.span_label(*argument_span, format!("has type `{}`", argument_ty_name)); - - let return_ty_name = cx.get_name_for_ty(return_ty, 0); - let types_equal = return_ty_name == argument_ty_name; - diag.span_label( - *return_span, - format!( - "{}has type `{}`", - if types_equal { "also " } else { "" }, - return_ty_name, - ), - ); - - diag.note( - "argument and return type have the same lifetime due to lifetime elision rules", - ); - diag.note( - "to learn more, visit ", - ); - - cx.get_region_name_for_ty(return_ty, 0) - } - AnnotatedBorrowFnSignature::NamedFunction { - arguments, - return_ty, - return_span, - } => { - // Region of return type and arguments checked to be the same earlier. - let region_name = cx.get_region_name_for_ty(return_ty, 0); - for (_, argument_span) in arguments { - diag.span_label(*argument_span, format!("has lifetime `{}`", region_name)); - } - - diag.span_label( - *return_span, - format!("also has lifetime `{}`", region_name,), - ); - - diag.help(&format!( - "use data from the highlighted arguments which match the `{}` lifetime of \ - the return type", - region_name, - )); - - region_name - } - } - } -} diff --git a/src/librustc_mir/borrow_check/diagnostics/conflict_errors.rs b/src/librustc_mir/borrow_check/diagnostics/conflict_errors.rs new file mode 100644 index 00000000000..b1e327cdb0e --- /dev/null +++ b/src/librustc_mir/borrow_check/diagnostics/conflict_errors.rs @@ -0,0 +1,2156 @@ +use rustc::hir; +use rustc::hir::def_id::DefId; +use rustc::hir::{AsyncGeneratorKind, GeneratorKind}; +use rustc::mir::{ + self, AggregateKind, BindingForm, BorrowKind, ClearCrossCrate, ConstraintCategory, + FakeReadCause, Local, LocalDecl, LocalInfo, LocalKind, Location, Operand, Place, PlaceBase, + PlaceRef, ProjectionElem, Rvalue, Statement, StatementKind, TerminatorKind, VarBindingForm, +}; +use rustc::ty::{self, Ty}; +use rustc::traits::error_reporting::suggest_constraining_type_param; +use rustc_data_structures::fx::FxHashSet; +use rustc_index::vec::Idx; +use rustc_errors::{Applicability, DiagnosticBuilder}; +use syntax_pos::Span; +use syntax::source_map::DesugaringKind; + +use super::nll::explain_borrow::BorrowExplanation; +use super::nll::region_infer::{RegionName, RegionNameSource}; +use super::prefixes::IsPrefixOf; +use super::WriteKind; +use super::borrow_set::BorrowData; +use super::MirBorrowckCtxt; +use super::{InitializationRequiringAction, PrefixSet}; +use super::error_reporting::{IncludingDowncast, UseSpans}; +use crate::dataflow::drop_flag_effects; +use crate::dataflow::indexes::{MovePathIndex, MoveOutIndex}; +use crate::util::borrowck_errors; + +#[derive(Debug)] +struct MoveSite { + /// Index of the "move out" that we found. The `MoveData` can + /// then tell us where the move occurred. + moi: MoveOutIndex, + + /// `true` if we traversed a back edge while walking from the point + /// of error to the move site. + traversed_back_edge: bool +} + +/// Which case a StorageDeadOrDrop is for. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +enum StorageDeadOrDrop<'tcx> { + LocalStorageDead, + BoxedStorageDead, + Destructor(Ty<'tcx>), +} + +impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { + pub(super) fn report_use_of_moved_or_uninitialized( + &mut self, + location: Location, + desired_action: InitializationRequiringAction, + (moved_place, used_place, span): (PlaceRef<'cx, 'tcx>, PlaceRef<'cx, 'tcx>, Span), + mpi: MovePathIndex, + ) { + debug!( + "report_use_of_moved_or_uninitialized: location={:?} desired_action={:?} \ + moved_place={:?} used_place={:?} span={:?} mpi={:?}", + location, desired_action, moved_place, used_place, span, mpi + ); + + let use_spans = self.move_spans(moved_place, location) + .or_else(|| self.borrow_spans(span, location)); + let span = use_spans.args_or_use(); + + let move_site_vec = self.get_moved_indexes(location, mpi); + debug!( + "report_use_of_moved_or_uninitialized: move_site_vec={:?}", + move_site_vec + ); + let move_out_indices: Vec<_> = move_site_vec + .iter() + .map(|move_site| move_site.moi) + .collect(); + + if move_out_indices.is_empty() { + let root_place = self + .prefixes(used_place, PrefixSet::All) + .last() + .unwrap(); + + if !self.uninitialized_error_reported.insert(root_place) { + debug!( + "report_use_of_moved_or_uninitialized place: error about {:?} suppressed", + root_place + ); + return; + } + + let item_msg = match self.describe_place_with_options(used_place, + IncludingDowncast(true)) { + Some(name) => format!("`{}`", name), + None => "value".to_owned(), + }; + let mut err = self.cannot_act_on_uninitialized_variable( + span, + desired_action.as_noun(), + &self.describe_place_with_options(moved_place, IncludingDowncast(true)) + .unwrap_or_else(|| "_".to_owned()), + ); + err.span_label(span, format!("use of possibly-uninitialized {}", item_msg)); + + use_spans.var_span_label( + &mut err, + format!("{} occurs due to use{}", desired_action.as_noun(), use_spans.describe()), + ); + + err.buffer(&mut self.errors_buffer); + } else { + if let Some((reported_place, _)) = self.move_error_reported.get(&move_out_indices) { + if self.prefixes(*reported_place, PrefixSet::All) + .any(|p| p == used_place) + { + debug!( + "report_use_of_moved_or_uninitialized place: error suppressed \ + mois={:?}", + move_out_indices + ); + return; + } + } + + let msg = ""; //FIXME: add "partially " or "collaterally " + + let mut err = self.cannot_act_on_moved_value( + span, + desired_action.as_noun(), + msg, + self.describe_place_with_options(moved_place, IncludingDowncast(true)), + ); + + self.add_moved_or_invoked_closure_note( + location, + used_place, + &mut err, + ); + + let mut is_loop_move = false; + let is_partial_move = move_site_vec.iter().any(|move_site| { + let move_out = self.move_data.moves[(*move_site).moi]; + let moved_place = &self.move_data.move_paths[move_out.path].place; + used_place != moved_place.as_ref() + && used_place.is_prefix_of(moved_place.as_ref()) + }); + for move_site in &move_site_vec { + let move_out = self.move_data.moves[(*move_site).moi]; + let moved_place = &self.move_data.move_paths[move_out.path].place; + + let move_spans = self.move_spans(moved_place.as_ref(), move_out.source); + let move_span = move_spans.args_or_use(); + + let move_msg = if move_spans.for_closure() { + " into closure" + } else { + "" + }; + + if span == move_span { + err.span_label( + span, + format!("value moved{} here, in previous iteration of loop", move_msg), + ); + is_loop_move = true; + } else if move_site.traversed_back_edge { + err.span_label( + move_span, + format!( + "value moved{} here, in previous iteration of loop", + move_msg + ), + ); + } else { + err.span_label(move_span, format!("value moved{} here", move_msg)); + move_spans.var_span_label( + &mut err, + format!("variable moved due to use{}", move_spans.describe()), + ); + } + if Some(DesugaringKind::ForLoop) == move_span.desugaring_kind() { + let sess = self.infcx.tcx.sess; + if let Ok(snippet) = sess.source_map().span_to_snippet(move_span) { + err.span_suggestion( + move_span, + "consider borrowing to avoid moving into the for loop", + format!("&{}", snippet), + Applicability::MaybeIncorrect, + ); + } + } + } + + use_spans.var_span_label( + &mut err, + format!("{} occurs due to use{}", desired_action.as_noun(), use_spans.describe()), + ); + + if !is_loop_move { + err.span_label( + span, + format!( + "value {} here {}", + desired_action.as_verb_in_past_tense(), + if is_partial_move { "after partial move" } else { "after move" }, + ), + ); + } + + let ty = Place::ty_from( + used_place.base, + used_place.projection, + *self.body, + self.infcx.tcx + ).ty; + let needs_note = match ty.kind { + ty::Closure(id, _) => { + let tables = self.infcx.tcx.typeck_tables_of(id); + let hir_id = self.infcx.tcx.hir().as_local_hir_id(id).unwrap(); + + tables.closure_kind_origins().get(hir_id).is_none() + } + _ => true, + }; + + if needs_note { + let mpi = self.move_data.moves[move_out_indices[0]].path; + let place = &self.move_data.move_paths[mpi].place; + + let ty = place.ty(*self.body, self.infcx.tcx).ty; + let opt_name = + self.describe_place_with_options(place.as_ref(), IncludingDowncast(true)); + let note_msg = match opt_name { + Some(ref name) => format!("`{}`", name), + None => "value".to_owned(), + }; + if let ty::Param(param_ty) = ty.kind { + let tcx = self.infcx.tcx; + let generics = tcx.generics_of(self.mir_def_id); + let param = generics.type_param(¶m_ty, tcx); + let generics = tcx.hir().get_generics(self.mir_def_id).unwrap(); + suggest_constraining_type_param( + generics, + &mut err, + ¶m.name.as_str(), + "Copy", + tcx.sess.source_map(), + span, + ); + } + let span = if let Some(local) = place.as_local() { + let decl = &self.body.local_decls[local]; + Some(decl.source_info.span) + } else { + None + }; + self.note_type_does_not_implement_copy( + &mut err, + ¬e_msg, + ty, + span, + ); + } + + if let Some((_, mut old_err)) = self.move_error_reported + .insert(move_out_indices, (used_place, err)) + { + // Cancel the old error so it doesn't ICE. + old_err.cancel(); + } + } + } + + pub(super) fn report_move_out_while_borrowed( + &mut self, + location: Location, + (place, span): (&Place<'tcx>, Span), + borrow: &BorrowData<'tcx>, + ) { + debug!( + "report_move_out_while_borrowed: location={:?} place={:?} span={:?} borrow={:?}", + location, place, span, borrow + ); + let value_msg = match self.describe_place(place.as_ref()) { + Some(name) => format!("`{}`", name), + None => "value".to_owned(), + }; + let borrow_msg = match self.describe_place(borrow.borrowed_place.as_ref()) { + Some(name) => format!("`{}`", name), + None => "value".to_owned(), + }; + + let borrow_spans = self.retrieve_borrow_spans(borrow); + let borrow_span = borrow_spans.args_or_use(); + + let move_spans = self.move_spans(place.as_ref(), location); + let span = move_spans.args_or_use(); + + let mut err = self.cannot_move_when_borrowed( + span, + &self.describe_place(place.as_ref()).unwrap_or_else(|| "_".to_owned()), + ); + err.span_label(borrow_span, format!("borrow of {} occurs here", borrow_msg)); + err.span_label(span, format!("move out of {} occurs here", value_msg)); + + borrow_spans.var_span_label( + &mut err, + format!("borrow occurs due to use{}", borrow_spans.describe()) + ); + + move_spans.var_span_label( + &mut err, + format!("move occurs due to use{}", move_spans.describe()) + ); + + self.explain_why_borrow_contains_point( + location, + borrow, + None, + ).add_explanation_to_diagnostic( + self.infcx.tcx, + &self.body, + &self.local_names, + &mut err, + "", + Some(borrow_span), + ); + err.buffer(&mut self.errors_buffer); + } + + pub(super) fn report_use_while_mutably_borrowed( + &mut self, + location: Location, + (place, _span): (&Place<'tcx>, Span), + borrow: &BorrowData<'tcx>, + ) -> DiagnosticBuilder<'cx> { + let borrow_spans = self.retrieve_borrow_spans(borrow); + let borrow_span = borrow_spans.args_or_use(); + + // Conflicting borrows are reported separately, so only check for move + // captures. + let use_spans = self.move_spans(place.as_ref(), location); + let span = use_spans.var_or_use(); + + let mut err = self.cannot_use_when_mutably_borrowed( + span, + &self.describe_place(place.as_ref()).unwrap_or_else(|| "_".to_owned()), + borrow_span, + &self.describe_place(borrow.borrowed_place.as_ref()) + .unwrap_or_else(|| "_".to_owned()), + ); + + borrow_spans.var_span_label(&mut err, { + let place = &borrow.borrowed_place; + let desc_place = + self.describe_place(place.as_ref()).unwrap_or_else(|| "_".to_owned()); + + format!("borrow occurs due to use of `{}`{}", desc_place, borrow_spans.describe()) + }); + + self.explain_why_borrow_contains_point(location, borrow, None) + .add_explanation_to_diagnostic( + self.infcx.tcx, + &self.body, + &self.local_names, + &mut err, + "", + None, + ); + err + } + + pub(super) fn report_conflicting_borrow( + &mut self, + location: Location, + (place, span): (&Place<'tcx>, Span), + gen_borrow_kind: BorrowKind, + issued_borrow: &BorrowData<'tcx>, + ) -> DiagnosticBuilder<'cx> { + let issued_spans = self.retrieve_borrow_spans(issued_borrow); + let issued_span = issued_spans.args_or_use(); + + let borrow_spans = self.borrow_spans(span, location); + let span = borrow_spans.args_or_use(); + + let container_name = if issued_spans.for_generator() || borrow_spans.for_generator() { + "generator" + } else { + "closure" + }; + + let (desc_place, msg_place, msg_borrow, union_type_name) = + self.describe_place_for_conflicting_borrow(place, &issued_borrow.borrowed_place); + + let explanation = self.explain_why_borrow_contains_point(location, issued_borrow, None); + let second_borrow_desc = if explanation.is_explained() { + "second " + } else { + "" + }; + + // FIXME: supply non-"" `opt_via` when appropriate + let first_borrow_desc; + let mut err = match ( + gen_borrow_kind, + issued_borrow.kind, + ) { + (BorrowKind::Shared, BorrowKind::Mut { .. }) => { + first_borrow_desc = "mutable "; + self.cannot_reborrow_already_borrowed( + span, + &desc_place, + &msg_place, + "immutable", + issued_span, + "it", + "mutable", + &msg_borrow, + None, + ) + } + (BorrowKind::Mut { .. }, BorrowKind::Shared) => { + first_borrow_desc = "immutable "; + self.cannot_reborrow_already_borrowed( + span, + &desc_place, + &msg_place, + "mutable", + issued_span, + "it", + "immutable", + &msg_borrow, + None, + ) + } + + (BorrowKind::Mut { .. }, BorrowKind::Mut { .. }) => { + first_borrow_desc = "first "; + self.cannot_mutably_borrow_multiply( + span, + &desc_place, + &msg_place, + issued_span, + &msg_borrow, + None, + ) + } + + (BorrowKind::Unique, BorrowKind::Unique) => { + first_borrow_desc = "first "; + self.cannot_uniquely_borrow_by_two_closures( + span, + &desc_place, + issued_span, + None, + ) + } + + (BorrowKind::Mut { .. }, BorrowKind::Shallow) + | (BorrowKind::Unique, BorrowKind::Shallow) => { + if let Some(immutable_section_description) = self.classify_immutable_section( + &issued_borrow.assigned_place, + ) { + let mut err = self.cannot_mutate_in_immutable_section( + span, + issued_span, + &desc_place, + immutable_section_description, + "mutably borrow", + ); + borrow_spans.var_span_label( + &mut err, + format!( + "borrow occurs due to use of `{}`{}", + desc_place, + borrow_spans.describe(), + ), + ); + + return err; + } else { + first_borrow_desc = "immutable "; + self.cannot_reborrow_already_borrowed( + span, + &desc_place, + &msg_place, + "mutable", + issued_span, + "it", + "immutable", + &msg_borrow, + None, + ) + } + } + + (BorrowKind::Unique, _) => { + first_borrow_desc = "first "; + self.cannot_uniquely_borrow_by_one_closure( + span, + container_name, + &desc_place, + "", + issued_span, + "it", + "", + None, + ) + }, + + (BorrowKind::Shared, BorrowKind::Unique) => { + first_borrow_desc = "first "; + self.cannot_reborrow_already_uniquely_borrowed( + span, + container_name, + &desc_place, + "", + "immutable", + issued_span, + "", + None, + second_borrow_desc, + ) + } + + (BorrowKind::Mut { .. }, BorrowKind::Unique) => { + first_borrow_desc = "first "; + self.cannot_reborrow_already_uniquely_borrowed( + span, + container_name, + &desc_place, + "", + "mutable", + issued_span, + "", + None, + second_borrow_desc, + ) + } + + (BorrowKind::Shared, BorrowKind::Shared) + | (BorrowKind::Shared, BorrowKind::Shallow) + | (BorrowKind::Shallow, BorrowKind::Mut { .. }) + | (BorrowKind::Shallow, BorrowKind::Unique) + | (BorrowKind::Shallow, BorrowKind::Shared) + | (BorrowKind::Shallow, BorrowKind::Shallow) => unreachable!(), + }; + + if issued_spans == borrow_spans { + borrow_spans.var_span_label( + &mut err, + format!("borrows occur due to use of `{}`{}", desc_place, borrow_spans.describe()), + ); + } else { + let borrow_place = &issued_borrow.borrowed_place; + let borrow_place_desc = self.describe_place(borrow_place.as_ref()) + .unwrap_or_else(|| "_".to_owned()); + issued_spans.var_span_label( + &mut err, + format!( + "first borrow occurs due to use of `{}`{}", + borrow_place_desc, + issued_spans.describe(), + ), + ); + + borrow_spans.var_span_label( + &mut err, + format!( + "second borrow occurs due to use of `{}`{}", + desc_place, + borrow_spans.describe(), + ), + ); + } + + if union_type_name != "" { + err.note(&format!( + "`{}` is a field of the union `{}`, so it overlaps the field `{}`", + msg_place, union_type_name, msg_borrow, + )); + } + + explanation.add_explanation_to_diagnostic( + self.infcx.tcx, + &self.body, + &self.local_names, + &mut err, + first_borrow_desc, + None, + ); + + err + } + + /// Returns the description of the root place for a conflicting borrow and the full + /// descriptions of the places that caused the conflict. + /// + /// In the simplest case, where there are no unions involved, if a mutable borrow of `x` is + /// attempted while a shared borrow is live, then this function will return: + /// + /// ("x", "", "") + /// + /// In the simple union case, if a mutable borrow of a union field `x.z` is attempted while + /// a shared borrow of another field `x.y`, then this function will return: + /// + /// ("x", "x.z", "x.y") + /// + /// In the more complex union case, where the union is a field of a struct, then if a mutable + /// borrow of a union field in a struct `x.u.z` is attempted while a shared borrow of + /// another field `x.u.y`, then this function will return: + /// + /// ("x.u", "x.u.z", "x.u.y") + /// + /// This is used when creating error messages like below: + /// + /// > cannot borrow `a.u` (via `a.u.z.c`) as immutable because it is also borrowed as + /// > mutable (via `a.u.s.b`) [E0502] + pub(super) fn describe_place_for_conflicting_borrow( + &self, + first_borrowed_place: &Place<'tcx>, + second_borrowed_place: &Place<'tcx>, + ) -> (String, String, String, String) { + // Define a small closure that we can use to check if the type of a place + // is a union. + let union_ty = |place_base, place_projection| { + let ty = Place::ty_from( + place_base, + place_projection, + *self.body, + self.infcx.tcx + ).ty; + ty.ty_adt_def().filter(|adt| adt.is_union()).map(|_| ty) + }; + let describe_place = |place| self.describe_place(place).unwrap_or_else(|| "_".to_owned()); + + // Start with an empty tuple, so we can use the functions on `Option` to reduce some + // code duplication (particularly around returning an empty description in the failure + // case). + Some(()) + .filter(|_| { + // If we have a conflicting borrow of the same place, then we don't want to add + // an extraneous "via x.y" to our diagnostics, so filter out this case. + first_borrowed_place != second_borrowed_place + }) + .and_then(|_| { + // We're going to want to traverse the first borrowed place to see if we can find + // field access to a union. If we find that, then we will keep the place of the + // union being accessed and the field that was being accessed so we can check the + // second borrowed place for the same union and a access to a different field. + let Place { + base, + projection, + } = first_borrowed_place; + + let mut cursor = projection.as_ref(); + while let [proj_base @ .., elem] = cursor { + cursor = proj_base; + + match elem { + ProjectionElem::Field(field, _) if union_ty(base, proj_base).is_some() => { + return Some((PlaceRef { + base: base, + projection: proj_base, + }, field)); + }, + _ => {}, + } + } + None + }) + .and_then(|(target_base, target_field)| { + // With the place of a union and a field access into it, we traverse the second + // borrowed place and look for a access to a different field of the same union. + let Place { + base, + projection, + } = second_borrowed_place; + + let mut cursor = projection.as_ref(); + while let [proj_base @ .., elem] = cursor { + cursor = proj_base; + + if let ProjectionElem::Field(field, _) = elem { + if let Some(union_ty) = union_ty(base, proj_base) { + if field != target_field + && base == target_base.base + && proj_base == target_base.projection { + // FIXME when we avoid clone reuse describe_place closure + let describe_base_place = self.describe_place(PlaceRef { + base: base, + projection: proj_base, + }).unwrap_or_else(|| "_".to_owned()); + + return Some(( + describe_base_place, + describe_place(first_borrowed_place.as_ref()), + describe_place(second_borrowed_place.as_ref()), + union_ty.to_string(), + )); + } + } + } + } + None + }) + .unwrap_or_else(|| { + // If we didn't find a field access into a union, or both places match, then + // only return the description of the first place. + ( + describe_place(first_borrowed_place.as_ref()), + "".to_string(), + "".to_string(), + "".to_string(), + ) + }) + } + + /// Reports StorageDeadOrDrop of `place` conflicts with `borrow`. + /// + /// This means that some data referenced by `borrow` needs to live + /// past the point where the StorageDeadOrDrop of `place` occurs. + /// This is usually interpreted as meaning that `place` has too + /// short a lifetime. (But sometimes it is more useful to report + /// it as a more direct conflict between the execution of a + /// `Drop::drop` with an aliasing borrow.) + pub(super) fn report_borrowed_value_does_not_live_long_enough( + &mut self, + location: Location, + borrow: &BorrowData<'tcx>, + place_span: (&Place<'tcx>, Span), + kind: Option, + ) { + debug!( + "report_borrowed_value_does_not_live_long_enough(\ + {:?}, {:?}, {:?}, {:?}\ + )", + location, borrow, place_span, kind + ); + + let drop_span = place_span.1; + let root_place = self.prefixes(borrow.borrowed_place.as_ref(), PrefixSet::All) + .last() + .unwrap(); + + let borrow_spans = self.retrieve_borrow_spans(borrow); + let borrow_span = borrow_spans.var_or_use(); + + assert!(root_place.projection.is_empty()); + let proper_span = match root_place.base { + PlaceBase::Local(local) => self.body.local_decls[*local].source_info.span, + _ => drop_span, + }; + + let root_place_projection = self.infcx.tcx.intern_place_elems(root_place.projection); + + if self.access_place_error_reported + .contains(&(Place { + base: root_place.base.clone(), + projection: root_place_projection, + }, borrow_span)) + { + debug!( + "suppressing access_place error when borrow doesn't live long enough for {:?}", + borrow_span + ); + return; + } + + self.access_place_error_reported + .insert((Place { + base: root_place.base.clone(), + projection: root_place_projection, + }, borrow_span)); + + if let PlaceBase::Local(local) = borrow.borrowed_place.base { + if self.body.local_decls[local].is_ref_to_thread_local() { + let err = self.report_thread_local_value_does_not_live_long_enough( + drop_span, + borrow_span, + ); + err.buffer(&mut self.errors_buffer); + return; + } + }; + + if let StorageDeadOrDrop::Destructor(dropped_ty) = + self.classify_drop_access_kind(borrow.borrowed_place.as_ref()) + { + // If a borrow of path `B` conflicts with drop of `D` (and + // we're not in the uninteresting case where `B` is a + // prefix of `D`), then report this as a more interesting + // destructor conflict. + if !borrow.borrowed_place.as_ref().is_prefix_of(place_span.0.as_ref()) { + self.report_borrow_conflicts_with_destructor( + location, borrow, place_span, kind, dropped_ty, + ); + return; + } + } + + let place_desc = self.describe_place(borrow.borrowed_place.as_ref()); + + let kind_place = kind.filter(|_| place_desc.is_some()).map(|k| (k, place_span.0)); + let explanation = self.explain_why_borrow_contains_point(location, &borrow, kind_place); + + debug!( + "report_borrowed_value_does_not_live_long_enough(place_desc: {:?}, explanation: {:?})", + place_desc, + explanation + ); + let err = match (place_desc, explanation) { + // If the outlives constraint comes from inside the closure, + // for example: + // + // let x = 0; + // let y = &x; + // Box::new(|| y) as Box &'static i32> + // + // then just use the normal error. The closure isn't escaping + // and `move` will not help here. + ( + Some(ref name), + BorrowExplanation::MustBeValidFor { + category: category @ ConstraintCategory::Return, + from_closure: false, + ref region_name, + span, + .. + }, + ) + | ( + Some(ref name), + BorrowExplanation::MustBeValidFor { + category: category @ ConstraintCategory::CallArgument, + from_closure: false, + ref region_name, + span, + .. + }, + ) if borrow_spans.for_closure() => self.report_escaping_closure_capture( + borrow_spans, + borrow_span, + region_name, + category, + span, + &format!("`{}`", name), + ), + ( + Some(ref name), + BorrowExplanation::MustBeValidFor { + category: category @ ConstraintCategory::OpaqueType, + from_closure: false, + ref region_name, + span, + .. + }, + + ) if borrow_spans.for_generator() => self.report_escaping_closure_capture( + borrow_spans, + borrow_span, + region_name, + category, + span, + &format!("`{}`", name), + ), + ( + ref name, + BorrowExplanation::MustBeValidFor { + category: ConstraintCategory::Assignment, + from_closure: false, + region_name: RegionName { + source: RegionNameSource::AnonRegionFromUpvar(upvar_span, ref upvar_name), + .. + }, + span, + .. + }, + ) => self.report_escaping_data(borrow_span, name, upvar_span, upvar_name, span), + (Some(name), explanation) => self.report_local_value_does_not_live_long_enough( + location, + &name, + &borrow, + drop_span, + borrow_spans, + explanation, + ), + (None, explanation) => self.report_temporary_value_does_not_live_long_enough( + location, + &borrow, + drop_span, + borrow_spans, + proper_span, + explanation, + ), + }; + + err.buffer(&mut self.errors_buffer); + } + + fn report_local_value_does_not_live_long_enough( + &mut self, + location: Location, + name: &str, + borrow: &BorrowData<'tcx>, + drop_span: Span, + borrow_spans: UseSpans, + explanation: BorrowExplanation, + ) -> DiagnosticBuilder<'cx> { + debug!( + "report_local_value_does_not_live_long_enough(\ + {:?}, {:?}, {:?}, {:?}, {:?}\ + )", + location, name, borrow, drop_span, borrow_spans + ); + + let borrow_span = borrow_spans.var_or_use(); + if let BorrowExplanation::MustBeValidFor { + category, + span, + ref opt_place_desc, + from_closure: false, + .. + } = explanation { + if let Some(diag) = self.try_report_cannot_return_reference_to_local( + borrow, + borrow_span, + span, + category, + opt_place_desc.as_ref(), + ) { + return diag; + } + } + + let mut err = self.path_does_not_live_long_enough( + borrow_span, + &format!("`{}`", name), + ); + + if let Some(annotation) = self.annotate_argument_and_return_for_borrow(borrow) { + let region_name = annotation.emit(self, &mut err); + + err.span_label( + borrow_span, + format!("`{}` would have to be valid for `{}`...", name, region_name), + ); + + if let Some(fn_hir_id) = self.infcx.tcx.hir().as_local_hir_id(self.mir_def_id) { + err.span_label( + drop_span, + format!( + "...but `{}` will be dropped here, when the function `{}` returns", + name, + self.infcx.tcx.hir().name(fn_hir_id), + ), + ); + + err.note( + "functions cannot return a borrow to data owned within the function's scope, \ + functions can only return borrows to data passed as arguments", + ); + err.note( + "to learn more, visit ", + ); + } else { + err.span_label( + drop_span, + format!("...but `{}` dropped here while still borrowed", name), + ); + } + + if let BorrowExplanation::MustBeValidFor { .. } = explanation { + } else { + explanation.add_explanation_to_diagnostic( + self.infcx.tcx, + &self.body, + &self.local_names, + &mut err, + "", + None, + ); + } + } else { + err.span_label(borrow_span, "borrowed value does not live long enough"); + err.span_label( + drop_span, + format!("`{}` dropped here while still borrowed", name), + ); + + let within = if borrow_spans.for_generator() { + " by generator" + } else { + "" + }; + + borrow_spans.args_span_label( + &mut err, + format!("value captured here{}", within), + ); + + explanation.add_explanation_to_diagnostic( + self.infcx.tcx, &self.body, &self.local_names, &mut err, "", None); + } + + err + } + + fn report_borrow_conflicts_with_destructor( + &mut self, + location: Location, + borrow: &BorrowData<'tcx>, + (place, drop_span): (&Place<'tcx>, Span), + kind: Option, + dropped_ty: Ty<'tcx>, + ) { + debug!( + "report_borrow_conflicts_with_destructor(\ + {:?}, {:?}, ({:?}, {:?}), {:?}\ + )", + location, borrow, place, drop_span, kind, + ); + + let borrow_spans = self.retrieve_borrow_spans(borrow); + let borrow_span = borrow_spans.var_or_use(); + + let mut err = self.cannot_borrow_across_destructor(borrow_span); + + let what_was_dropped = match self.describe_place(place.as_ref()) { + Some(name) => format!("`{}`", name), + None => String::from("temporary value"), + }; + + let label = match self.describe_place(borrow.borrowed_place.as_ref()) { + Some(borrowed) => format!( + "here, drop of {D} needs exclusive access to `{B}`, \ + because the type `{T}` implements the `Drop` trait", + D = what_was_dropped, + T = dropped_ty, + B = borrowed + ), + None => format!( + "here is drop of {D}; whose type `{T}` implements the `Drop` trait", + D = what_was_dropped, + T = dropped_ty + ), + }; + err.span_label(drop_span, label); + + // Only give this note and suggestion if they could be relevant. + let explanation = + self.explain_why_borrow_contains_point(location, borrow, kind.map(|k| (k, place))); + match explanation { + BorrowExplanation::UsedLater { .. } + | BorrowExplanation::UsedLaterWhenDropped { .. } => { + err.note("consider using a `let` binding to create a longer lived value"); + } + _ => {} + } + + explanation.add_explanation_to_diagnostic( + self.infcx.tcx, + &self.body, + &self.local_names, + &mut err, + "", + None, + ); + + err.buffer(&mut self.errors_buffer); + } + + fn report_thread_local_value_does_not_live_long_enough( + &mut self, + drop_span: Span, + borrow_span: Span, + ) -> DiagnosticBuilder<'cx> { + debug!( + "report_thread_local_value_does_not_live_long_enough(\ + {:?}, {:?}\ + )", + drop_span, borrow_span + ); + + let mut err = self.thread_local_value_does_not_live_long_enough(borrow_span); + + err.span_label( + borrow_span, + "thread-local variables cannot be borrowed beyond the end of the function", + ); + err.span_label(drop_span, "end of enclosing function is here"); + + err + } + + fn report_temporary_value_does_not_live_long_enough( + &mut self, + location: Location, + borrow: &BorrowData<'tcx>, + drop_span: Span, + borrow_spans: UseSpans, + proper_span: Span, + explanation: BorrowExplanation, + ) -> DiagnosticBuilder<'cx> { + debug!( + "report_temporary_value_does_not_live_long_enough(\ + {:?}, {:?}, {:?}, {:?}\ + )", + location, borrow, drop_span, proper_span + ); + + if let BorrowExplanation::MustBeValidFor { + category, + span, + from_closure: false, + .. + } = explanation { + if let Some(diag) = self.try_report_cannot_return_reference_to_local( + borrow, + proper_span, + span, + category, + None, + ) { + return diag; + } + } + + let mut err = self.temporary_value_borrowed_for_too_long(proper_span); + err.span_label( + proper_span, + "creates a temporary which is freed while still in use", + ); + err.span_label( + drop_span, + "temporary value is freed at the end of this statement", + ); + + match explanation { + BorrowExplanation::UsedLater(..) + | BorrowExplanation::UsedLaterInLoop(..) + | BorrowExplanation::UsedLaterWhenDropped { .. } => { + // Only give this note and suggestion if it could be relevant. + err.note("consider using a `let` binding to create a longer lived value"); + } + _ => {} + } + explanation.add_explanation_to_diagnostic( + self.infcx.tcx, + &self.body, + &self.local_names, + &mut err, + "", + None, + ); + + let within = if borrow_spans.for_generator() { + " by generator" + } else { + "" + }; + + borrow_spans.args_span_label( + &mut err, + format!("value captured here{}", within), + ); + + err + } + + fn try_report_cannot_return_reference_to_local( + &self, + borrow: &BorrowData<'tcx>, + borrow_span: Span, + return_span: Span, + category: ConstraintCategory, + opt_place_desc: Option<&String>, + ) -> Option> { + let return_kind = match category { + ConstraintCategory::Return => "return", + ConstraintCategory::Yield => "yield", + _ => return None, + }; + + // FIXME use a better heuristic than Spans + let reference_desc + = if return_span == self.body.source_info(borrow.reserve_location).span { + "reference to" + } else { + "value referencing" + }; + + let (place_desc, note) = if let Some(place_desc) = opt_place_desc { + let local_kind = if let Some(local) = borrow.borrowed_place.as_local() { + match self.body.local_kind(local) { + LocalKind::ReturnPointer + | LocalKind::Temp => bug!("temporary or return pointer with a name"), + LocalKind::Var => "local variable ", + LocalKind::Arg + if !self.upvars.is_empty() + && local == Local::new(1) => { + "variable captured by `move` " + } + LocalKind::Arg => { + "function parameter " + } + } + } else { + "local data " + }; + ( + format!("{}`{}`", local_kind, place_desc), + format!("`{}` is borrowed here", place_desc), + ) + } else { + let root_place = self.prefixes(borrow.borrowed_place.as_ref(), + PrefixSet::All) + .last() + .unwrap(); + let local = if let PlaceRef { + base: PlaceBase::Local(local), + projection: [], + } = root_place { + local + } else { + bug!("try_report_cannot_return_reference_to_local: not a local") + }; + match self.body.local_kind(*local) { + LocalKind::ReturnPointer | LocalKind::Temp => ( + "temporary value".to_string(), + "temporary value created here".to_string(), + ), + LocalKind::Arg => ( + "function parameter".to_string(), + "function parameter borrowed here".to_string(), + ), + LocalKind::Var => ( + "local binding".to_string(), + "local binding introduced here".to_string(), + ), + } + }; + + let mut err = self.cannot_return_reference_to_local( + return_span, + return_kind, + reference_desc, + &place_desc, + ); + + if return_span != borrow_span { + err.span_label(borrow_span, note); + } + + Some(err) + } + + fn report_escaping_closure_capture( + &mut self, + use_span: UseSpans, + var_span: Span, + fr_name: &RegionName, + category: ConstraintCategory, + constraint_span: Span, + captured_var: &str, + ) -> DiagnosticBuilder<'cx> { + let tcx = self.infcx.tcx; + let args_span = use_span.args_or_use(); + let mut err = self.cannot_capture_in_long_lived_closure( + args_span, + captured_var, + var_span, + ); + + let suggestion = match tcx.sess.source_map().span_to_snippet(args_span) { + Ok(mut string) => { + if string.starts_with("async ") { + string.insert_str(6, "move "); + } else if string.starts_with("async|") { + string.insert_str(5, " move"); + } else { + string.insert_str(0, "move "); + }; + string + }, + Err(_) => "move || ".to_string() + }; + let kind = match use_span.generator_kind() { + Some(generator_kind) => match generator_kind { + GeneratorKind::Async(async_kind) => match async_kind { + AsyncGeneratorKind::Block => "async block", + AsyncGeneratorKind::Closure => "async closure", + _ => bug!("async block/closure expected, but async funtion found."), + }, + GeneratorKind::Gen => "generator", + } + None => "closure", + }; + err.span_suggestion( + args_span, + &format!( + "to force the {} to take ownership of {} (and any \ + other referenced variables), use the `move` keyword", + kind, + captured_var + ), + suggestion, + Applicability::MachineApplicable, + ); + + let msg = match category { + ConstraintCategory::Return => "closure is returned here".to_string(), + ConstraintCategory::OpaqueType => "generator is returned here".to_string(), + ConstraintCategory::CallArgument => { + fr_name.highlight_region_name(&mut err); + format!("function requires argument type to outlive `{}`", fr_name) + } + _ => bug!("report_escaping_closure_capture called with unexpected constraint \ + category: `{:?}`", category), + }; + err.span_note(constraint_span, &msg); + err + } + + fn report_escaping_data( + &mut self, + borrow_span: Span, + name: &Option, + upvar_span: Span, + upvar_name: &str, + escape_span: Span, + ) -> DiagnosticBuilder<'cx> { + let tcx = self.infcx.tcx; + + let escapes_from = if tcx.is_closure(self.mir_def_id) { + let tables = tcx.typeck_tables_of(self.mir_def_id); + let mir_hir_id = tcx.hir().def_index_to_hir_id(self.mir_def_id.index); + match tables.node_type(mir_hir_id).kind { + ty::Closure(..) => "closure", + ty::Generator(..) => "generator", + _ => bug!("Closure body doesn't have a closure or generator type"), + } + } else { + "function" + }; + + let mut err = borrowck_errors::borrowed_data_escapes_closure( + tcx, + escape_span, + escapes_from, + ); + + err.span_label( + upvar_span, + format!( + "`{}` is declared here, outside of the {} body", + upvar_name, escapes_from + ), + ); + + err.span_label( + borrow_span, + format!( + "borrow is only valid in the {} body", + escapes_from + ), + ); + + if let Some(name) = name { + err.span_label( + escape_span, + format!("reference to `{}` escapes the {} body here", name, escapes_from), + ); + } else { + err.span_label( + escape_span, + format!("reference escapes the {} body here", escapes_from), + ); + } + + err + } + + fn get_moved_indexes(&mut self, location: Location, mpi: MovePathIndex) -> Vec { + let mut stack = Vec::new(); + stack.extend(self.body.predecessor_locations(location).map(|predecessor| { + let is_back_edge = location.dominates(predecessor, &self.dominators); + (predecessor, is_back_edge) + })); + + let mut visited = FxHashSet::default(); + let mut result = vec![]; + + 'dfs: while let Some((location, is_back_edge)) = stack.pop() { + debug!( + "report_use_of_moved_or_uninitialized: (current_location={:?}, back_edge={})", + location, is_back_edge + ); + + if !visited.insert(location) { + continue; + } + + // check for moves + let stmt_kind = self.body[location.block] + .statements + .get(location.statement_index) + .map(|s| &s.kind); + if let Some(StatementKind::StorageDead(..)) = stmt_kind { + // this analysis only tries to find moves explicitly + // written by the user, so we ignore the move-outs + // created by `StorageDead` and at the beginning + // of a function. + } else { + // If we are found a use of a.b.c which was in error, then we want to look for + // moves not only of a.b.c but also a.b and a. + // + // Note that the moves data already includes "parent" paths, so we don't have to + // worry about the other case: that is, if there is a move of a.b.c, it is already + // marked as a move of a.b and a as well, so we will generate the correct errors + // there. + let mut mpis = vec![mpi]; + let move_paths = &self.move_data.move_paths; + mpis.extend(move_paths[mpi].parents(move_paths)); + + for moi in &self.move_data.loc_map[location] { + debug!("report_use_of_moved_or_uninitialized: moi={:?}", moi); + if mpis.contains(&self.move_data.moves[*moi].path) { + debug!("report_use_of_moved_or_uninitialized: found"); + result.push(MoveSite { + moi: *moi, + traversed_back_edge: is_back_edge, + }); + + // Strictly speaking, we could continue our DFS here. There may be + // other moves that can reach the point of error. But it is kind of + // confusing to highlight them. + // + // Example: + // + // ``` + // let a = vec![]; + // let b = a; + // let c = a; + // drop(a); // <-- current point of error + // ``` + // + // Because we stop the DFS here, we only highlight `let c = a`, + // and not `let b = a`. We will of course also report an error at + // `let c = a` which highlights `let b = a` as the move. + continue 'dfs; + } + } + } + + // check for inits + let mut any_match = false; + drop_flag_effects::for_location_inits( + self.infcx.tcx, + &self.body, + self.move_data, + location, + |m| { + if m == mpi { + any_match = true; + } + }, + ); + if any_match { + continue 'dfs; + } + + stack.extend(self.body.predecessor_locations(location).map(|predecessor| { + let back_edge = location.dominates(predecessor, &self.dominators); + (predecessor, is_back_edge || back_edge) + })); + } + + result + } + + pub(super) fn report_illegal_mutation_of_borrowed( + &mut self, + location: Location, + (place, span): (&Place<'tcx>, Span), + loan: &BorrowData<'tcx>, + ) { + let loan_spans = self.retrieve_borrow_spans(loan); + let loan_span = loan_spans.args_or_use(); + + if loan.kind == BorrowKind::Shallow { + if let Some(section) = self.classify_immutable_section(&loan.assigned_place) { + let mut err = self.cannot_mutate_in_immutable_section( + span, + loan_span, + &self.describe_place(place.as_ref()).unwrap_or_else(|| "_".to_owned()), + section, + "assign", + ); + loan_spans.var_span_label( + &mut err, + format!("borrow occurs due to use{}", loan_spans.describe()), + ); + + err.buffer(&mut self.errors_buffer); + + return; + } + } + + let mut err = self.cannot_assign_to_borrowed( + span, + loan_span, + &self.describe_place(place.as_ref()).unwrap_or_else(|| "_".to_owned()), + ); + + loan_spans.var_span_label( + &mut err, + format!("borrow occurs due to use{}", loan_spans.describe()), + ); + + self.explain_why_borrow_contains_point(location, loan, None) + .add_explanation_to_diagnostic( + self.infcx.tcx, + &self.body, + &self.local_names, + &mut err, + "", + None, + ); + + err.buffer(&mut self.errors_buffer); + } + + /// Reports an illegal reassignment; for example, an assignment to + /// (part of) a non-`mut` local that occurs potentially after that + /// local has already been initialized. `place` is the path being + /// assigned; `err_place` is a place providing a reason why + /// `place` is not mutable (e.g., the non-`mut` local `x` in an + /// assignment to `x.f`). + pub(super) fn report_illegal_reassignment( + &mut self, + _location: Location, + (place, span): (&Place<'tcx>, Span), + assigned_span: Span, + err_place: &Place<'tcx>, + ) { + let (from_arg, local_decl, local_name) = match err_place.as_local() { + Some(local) => ( + self.body.local_kind(local) == LocalKind::Arg, + Some(&self.body.local_decls[local]), + self.local_names[local], + ), + None => (false, None, None), + }; + + // If root local is initialized immediately (everything apart from let + // PATTERN;) then make the error refer to that local, rather than the + // place being assigned later. + let (place_description, assigned_span) = match local_decl { + Some(LocalDecl { + local_info: LocalInfo::User(ClearCrossCrate::Clear), + .. + }) + | Some(LocalDecl { + local_info: LocalInfo::User(ClearCrossCrate::Set(BindingForm::Var(VarBindingForm { + opt_match_place: None, + .. + }))), + .. + }) + | Some(LocalDecl { + local_info: LocalInfo::StaticRef { .. }, + .. + }) + | Some(LocalDecl { + local_info: LocalInfo::Other, + .. + }) + | None => (self.describe_place(place.as_ref()), assigned_span), + Some(decl) => (self.describe_place(err_place.as_ref()), decl.source_info.span), + }; + + let mut err = self.cannot_reassign_immutable( + span, + place_description.as_ref().map(AsRef::as_ref).unwrap_or("_"), + from_arg, + ); + let msg = if from_arg { + "cannot assign to immutable argument" + } else { + "cannot assign twice to immutable variable" + }; + if span != assigned_span { + if !from_arg { + let value_msg = match place_description { + Some(name) => format!("`{}`", name), + None => "value".to_owned(), + }; + err.span_label(assigned_span, format!("first assignment to {}", value_msg)); + } + } + if let Some(decl) = local_decl { + if let Some(name) = local_name { + if decl.can_be_made_mutable() { + err.span_suggestion( + decl.source_info.span, + "make this binding mutable", + format!("mut {}", name), + Applicability::MachineApplicable, + ); + } + } + } + err.span_label(span, msg); + err.buffer(&mut self.errors_buffer); + } + + fn classify_drop_access_kind(&self, place: PlaceRef<'cx, 'tcx>) -> StorageDeadOrDrop<'tcx> { + let tcx = self.infcx.tcx; + match place.projection { + [] => { + StorageDeadOrDrop::LocalStorageDead + } + [proj_base @ .., elem] => { + // FIXME(spastorino) make this iterate + let base_access = self.classify_drop_access_kind(PlaceRef { + base: place.base, + projection: proj_base, + }); + match elem { + ProjectionElem::Deref => match base_access { + StorageDeadOrDrop::LocalStorageDead + | StorageDeadOrDrop::BoxedStorageDead => { + assert!( + Place::ty_from( + &place.base, + proj_base, + *self.body, + tcx + ).ty.is_box(), + "Drop of value behind a reference or raw pointer" + ); + StorageDeadOrDrop::BoxedStorageDead + } + StorageDeadOrDrop::Destructor(_) => base_access, + }, + ProjectionElem::Field(..) | ProjectionElem::Downcast(..) => { + let base_ty = Place::ty_from( + &place.base, + proj_base, + *self.body, + tcx + ).ty; + match base_ty.kind { + ty::Adt(def, _) if def.has_dtor(tcx) => { + // Report the outermost adt with a destructor + match base_access { + StorageDeadOrDrop::Destructor(_) => base_access, + StorageDeadOrDrop::LocalStorageDead + | StorageDeadOrDrop::BoxedStorageDead => { + StorageDeadOrDrop::Destructor(base_ty) + } + } + } + _ => base_access, + } + } + + ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } + | ProjectionElem::Index(_) => base_access, + } + } + } + } + + /// Describe the reason for the fake borrow that was assigned to `place`. + fn classify_immutable_section(&self, place: &Place<'tcx>) -> Option<&'static str> { + use rustc::mir::visit::Visitor; + struct FakeReadCauseFinder<'a, 'tcx> { + place: &'a Place<'tcx>, + cause: Option, + } + impl<'tcx> Visitor<'tcx> for FakeReadCauseFinder<'_, 'tcx> { + fn visit_statement(&mut self, statement: &Statement<'tcx>, _: Location) { + match statement { + Statement { + kind: StatementKind::FakeRead(cause, box ref place), + .. + } if *place == *self.place => { + self.cause = Some(*cause); + } + _ => (), + } + } + } + let mut visitor = FakeReadCauseFinder { place, cause: None }; + visitor.visit_body(self.body); + match visitor.cause { + Some(FakeReadCause::ForMatchGuard) => Some("match guard"), + Some(FakeReadCause::ForIndex) => Some("indexing expression"), + _ => None, + } + } + + /// Annotate argument and return type of function and closure with (synthesized) lifetime for + /// borrow of local value that does not live long enough. + fn annotate_argument_and_return_for_borrow( + &self, + borrow: &BorrowData<'tcx>, + ) -> Option> { + // Define a fallback for when we can't match a closure. + let fallback = || { + let is_closure = self.infcx.tcx.is_closure(self.mir_def_id); + if is_closure { + None + } else { + let ty = self.infcx.tcx.type_of(self.mir_def_id); + match ty.kind { + ty::FnDef(_, _) | ty::FnPtr(_) => self.annotate_fn_sig( + self.mir_def_id, + self.infcx.tcx.fn_sig(self.mir_def_id), + ), + _ => None, + } + } + }; + + // In order to determine whether we need to annotate, we need to check whether the reserve + // place was an assignment into a temporary. + // + // If it was, we check whether or not that temporary is eventually assigned into the return + // place. If it was, we can add annotations about the function's return type and arguments + // and it'll make sense. + let location = borrow.reserve_location; + debug!( + "annotate_argument_and_return_for_borrow: location={:?}", + location + ); + if let Some(&Statement { kind: StatementKind::Assign(box(ref reservation, _)), ..}) + = &self.body[location.block].statements.get(location.statement_index) + { + debug!( + "annotate_argument_and_return_for_borrow: reservation={:?}", + reservation + ); + // Check that the initial assignment of the reserve location is into a temporary. + let mut target = match reservation.as_local() { + Some(local) if self.body.local_kind(local) == LocalKind::Temp => local, + _ => return None, + }; + + // Next, look through the rest of the block, checking if we are assigning the + // `target` (that is, the place that contains our borrow) to anything. + let mut annotated_closure = None; + for stmt in &self.body[location.block].statements[location.statement_index + 1..] + { + debug!( + "annotate_argument_and_return_for_borrow: target={:?} stmt={:?}", + target, stmt + ); + if let StatementKind::Assign(box(place, rvalue)) = &stmt.kind { + if let Some(assigned_to) = place.as_local() { + debug!( + "annotate_argument_and_return_for_borrow: assigned_to={:?} \ + rvalue={:?}", + assigned_to, rvalue + ); + // Check if our `target` was captured by a closure. + if let Rvalue::Aggregate( + box AggregateKind::Closure(def_id, substs), + operands, + ) = rvalue + { + for operand in operands { + let assigned_from = match operand { + Operand::Copy(assigned_from) | Operand::Move(assigned_from) => { + assigned_from + } + _ => continue, + }; + debug!( + "annotate_argument_and_return_for_borrow: assigned_from={:?}", + assigned_from + ); + + // Find the local from the operand. + let assigned_from_local = match assigned_from.local_or_deref_local() + { + Some(local) => local, + None => continue, + }; + + if assigned_from_local != target { + continue; + } + + // If a closure captured our `target` and then assigned + // into a place then we should annotate the closure in + // case it ends up being assigned into the return place. + annotated_closure = self.annotate_fn_sig( + *def_id, + self.infcx.closure_sig(*def_id, *substs), + ); + debug!( + "annotate_argument_and_return_for_borrow: \ + annotated_closure={:?} assigned_from_local={:?} \ + assigned_to={:?}", + annotated_closure, assigned_from_local, assigned_to + ); + + if assigned_to == mir::RETURN_PLACE { + // If it was assigned directly into the return place, then + // return now. + return annotated_closure; + } else { + // Otherwise, update the target. + target = assigned_to; + } + } + + // If none of our closure's operands matched, then skip to the next + // statement. + continue; + } + + // Otherwise, look at other types of assignment. + let assigned_from = match rvalue { + Rvalue::Ref(_, _, assigned_from) => assigned_from, + Rvalue::Use(operand) => match operand { + Operand::Copy(assigned_from) | Operand::Move(assigned_from) => { + assigned_from + } + _ => continue, + }, + _ => continue, + }; + debug!( + "annotate_argument_and_return_for_borrow: \ + assigned_from={:?}", + assigned_from, + ); + + // Find the local from the rvalue. + let assigned_from_local = match assigned_from.local_or_deref_local() { + Some(local) => local, + None => continue, + }; + debug!( + "annotate_argument_and_return_for_borrow: \ + assigned_from_local={:?}", + assigned_from_local, + ); + + // Check if our local matches the target - if so, we've assigned our + // borrow to a new place. + if assigned_from_local != target { + continue; + } + + // If we assigned our `target` into a new place, then we should + // check if it was the return place. + debug!( + "annotate_argument_and_return_for_borrow: \ + assigned_from_local={:?} assigned_to={:?}", + assigned_from_local, assigned_to + ); + if assigned_to == mir::RETURN_PLACE { + // If it was then return the annotated closure if there was one, + // else, annotate this function. + return annotated_closure.or_else(fallback); + } + + // If we didn't assign into the return place, then we just update + // the target. + target = assigned_to; + } + } + } + + // Check the terminator if we didn't find anything in the statements. + let terminator = &self.body[location.block].terminator(); + debug!( + "annotate_argument_and_return_for_borrow: target={:?} terminator={:?}", + target, terminator + ); + if let TerminatorKind::Call { + destination: Some((place, _)), + args, + .. + } = &terminator.kind + { + if let Some(assigned_to) = place.as_local() { + debug!( + "annotate_argument_and_return_for_borrow: assigned_to={:?} args={:?}", + assigned_to, args + ); + for operand in args { + let assigned_from = match operand { + Operand::Copy(assigned_from) | Operand::Move(assigned_from) => { + assigned_from + } + _ => continue, + }; + debug!( + "annotate_argument_and_return_for_borrow: assigned_from={:?}", + assigned_from, + ); + + if let Some(assigned_from_local) = assigned_from.local_or_deref_local() { + debug!( + "annotate_argument_and_return_for_borrow: assigned_from_local={:?}", + assigned_from_local, + ); + + if assigned_to == mir::RETURN_PLACE && assigned_from_local == target { + return annotated_closure.or_else(fallback); + } + } + } + } + } + } + + // If we haven't found an assignment into the return place, then we need not add + // any annotations. + debug!("annotate_argument_and_return_for_borrow: none found"); + None + } + + /// Annotate the first argument and return type of a function signature if they are + /// references. + fn annotate_fn_sig( + &self, + did: DefId, + sig: ty::PolyFnSig<'tcx>, + ) -> Option> { + debug!("annotate_fn_sig: did={:?} sig={:?}", did, sig); + let is_closure = self.infcx.tcx.is_closure(did); + let fn_hir_id = self.infcx.tcx.hir().as_local_hir_id(did)?; + let fn_decl = self.infcx.tcx.hir().fn_decl_by_hir_id(fn_hir_id)?; + + // We need to work out which arguments to highlight. We do this by looking + // at the return type, where there are three cases: + // + // 1. If there are named arguments, then we should highlight the return type and + // highlight any of the arguments that are also references with that lifetime. + // If there are no arguments that have the same lifetime as the return type, + // then don't highlight anything. + // 2. The return type is a reference with an anonymous lifetime. If this is + // the case, then we can take advantage of (and teach) the lifetime elision + // rules. + // + // We know that an error is being reported. So the arguments and return type + // must satisfy the elision rules. Therefore, if there is a single argument + // then that means the return type and first (and only) argument have the same + // lifetime and the borrow isn't meeting that, we can highlight the argument + // and return type. + // + // If there are multiple arguments then the first argument must be self (else + // it would not satisfy the elision rules), so we can highlight self and the + // return type. + // 3. The return type is not a reference. In this case, we don't highlight + // anything. + let return_ty = sig.output(); + match return_ty.skip_binder().kind { + ty::Ref(return_region, _, _) if return_region.has_name() && !is_closure => { + // This is case 1 from above, return type is a named reference so we need to + // search for relevant arguments. + let mut arguments = Vec::new(); + for (index, argument) in sig.inputs().skip_binder().iter().enumerate() { + if let ty::Ref(argument_region, _, _) = argument.kind { + if argument_region == return_region { + // Need to use the `rustc::ty` types to compare against the + // `return_region`. Then use the `rustc::hir` type to get only + // the lifetime span. + if let hir::TyKind::Rptr(lifetime, _) = &fn_decl.inputs[index].kind { + // With access to the lifetime, we can get + // the span of it. + arguments.push((*argument, lifetime.span)); + } else { + bug!("ty type is a ref but hir type is not"); + } + } + } + } + + // We need to have arguments. This shouldn't happen, but it's worth checking. + if arguments.is_empty() { + return None; + } + + // We use a mix of the HIR and the Ty types to get information + // as the HIR doesn't have full types for closure arguments. + let return_ty = *sig.output().skip_binder(); + let mut return_span = fn_decl.output.span(); + if let hir::FunctionRetTy::Return(ty) = &fn_decl.output { + if let hir::TyKind::Rptr(lifetime, _) = ty.kind { + return_span = lifetime.span; + } + } + + Some(AnnotatedBorrowFnSignature::NamedFunction { + arguments, + return_ty, + return_span, + }) + } + ty::Ref(_, _, _) if is_closure => { + // This is case 2 from above but only for closures, return type is anonymous + // reference so we select + // the first argument. + let argument_span = fn_decl.inputs.first()?.span; + let argument_ty = sig.inputs().skip_binder().first()?; + + // Closure arguments are wrapped in a tuple, so we need to get the first + // from that. + if let ty::Tuple(elems) = argument_ty.kind { + let argument_ty = elems.first()?.expect_ty(); + if let ty::Ref(_, _, _) = argument_ty.kind { + return Some(AnnotatedBorrowFnSignature::Closure { + argument_ty, + argument_span, + }); + } + } + + None + } + ty::Ref(_, _, _) => { + // This is also case 2 from above but for functions, return type is still an + // anonymous reference so we select the first argument. + let argument_span = fn_decl.inputs.first()?.span; + let argument_ty = sig.inputs().skip_binder().first()?; + + let return_span = fn_decl.output.span(); + let return_ty = *sig.output().skip_binder(); + + // We expect the first argument to be a reference. + match argument_ty.kind { + ty::Ref(_, _, _) => {} + _ => return None, + } + + Some(AnnotatedBorrowFnSignature::AnonymousFunction { + argument_ty, + argument_span, + return_ty, + return_span, + }) + } + _ => { + // This is case 3 from above, return type is not a reference so don't highlight + // anything. + None + } + } + } +} + +#[derive(Debug)] +enum AnnotatedBorrowFnSignature<'tcx> { + NamedFunction { + arguments: Vec<(Ty<'tcx>, Span)>, + return_ty: Ty<'tcx>, + return_span: Span, + }, + AnonymousFunction { + argument_ty: Ty<'tcx>, + argument_span: Span, + return_ty: Ty<'tcx>, + return_span: Span, + }, + Closure { + argument_ty: Ty<'tcx>, + argument_span: Span, + }, +} + +impl<'tcx> AnnotatedBorrowFnSignature<'tcx> { + /// Annotate the provided diagnostic with information about borrow from the fn signature that + /// helps explain. + pub(super) fn emit( + &self, + cx: &mut MirBorrowckCtxt<'_, 'tcx>, + diag: &mut DiagnosticBuilder<'_>, + ) -> String { + match self { + AnnotatedBorrowFnSignature::Closure { + argument_ty, + argument_span, + } => { + diag.span_label( + *argument_span, + format!("has type `{}`", cx.get_name_for_ty(argument_ty, 0)), + ); + + cx.get_region_name_for_ty(argument_ty, 0) + } + AnnotatedBorrowFnSignature::AnonymousFunction { + argument_ty, + argument_span, + return_ty, + return_span, + } => { + let argument_ty_name = cx.get_name_for_ty(argument_ty, 0); + diag.span_label(*argument_span, format!("has type `{}`", argument_ty_name)); + + let return_ty_name = cx.get_name_for_ty(return_ty, 0); + let types_equal = return_ty_name == argument_ty_name; + diag.span_label( + *return_span, + format!( + "{}has type `{}`", + if types_equal { "also " } else { "" }, + return_ty_name, + ), + ); + + diag.note( + "argument and return type have the same lifetime due to lifetime elision rules", + ); + diag.note( + "to learn more, visit ", + ); + + cx.get_region_name_for_ty(return_ty, 0) + } + AnnotatedBorrowFnSignature::NamedFunction { + arguments, + return_ty, + return_span, + } => { + // Region of return type and arguments checked to be the same earlier. + let region_name = cx.get_region_name_for_ty(return_ty, 0); + for (_, argument_span) in arguments { + diag.span_label(*argument_span, format!("has lifetime `{}`", region_name)); + } + + diag.span_label( + *return_span, + format!("also has lifetime `{}`", region_name,), + ); + + diag.help(&format!( + "use data from the highlighted arguments which match the `{}` lifetime of \ + the return type", + region_name, + )); + + region_name + } + } + } +} diff --git a/src/librustc_mir/borrow_check/diagnostics/mod.rs b/src/librustc_mir/borrow_check/diagnostics/mod.rs new file mode 100644 index 00000000000..9953d280743 --- /dev/null +++ b/src/librustc_mir/borrow_check/diagnostics/mod.rs @@ -0,0 +1,916 @@ +use rustc::hir; +use rustc::hir::def::Namespace; +use rustc::hir::def_id::DefId; +use rustc::hir::GeneratorKind; +use rustc::mir::{ + AggregateKind, Constant, Field, Local, LocalInfo, LocalKind, Location, Operand, + Place, PlaceBase, PlaceRef, ProjectionElem, Rvalue, Statement, StatementKind, + Static, StaticKind, Terminator, TerminatorKind, +}; +use rustc::ty::{self, DefIdTree, Ty, TyCtxt}; +use rustc::ty::layout::VariantIdx; +use rustc::ty::print::Print; +use rustc_errors::DiagnosticBuilder; +use syntax_pos::Span; + +use super::borrow_set::BorrowData; +use super::MirBorrowckCtxt; +use crate::dataflow::move_paths::{InitLocation, LookupResult}; + +pub(super) struct IncludingDowncast(pub(super) bool); + +impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { + /// Adds a suggestion when a closure is invoked twice with a moved variable or when a closure + /// is moved after being invoked. + /// + /// ```text + /// note: closure cannot be invoked more than once because it moves the variable `dict` out of + /// its environment + /// --> $DIR/issue-42065.rs:16:29 + /// | + /// LL | for (key, value) in dict { + /// | ^^^^ + /// ``` + pub(super) fn add_moved_or_invoked_closure_note( + &self, + location: Location, + place: PlaceRef<'cx, 'tcx>, + diag: &mut DiagnosticBuilder<'_>, + ) { + debug!("add_moved_or_invoked_closure_note: location={:?} place={:?}", location, place); + let mut target = place.local_or_deref_local(); + for stmt in &self.body[location.block].statements[location.statement_index..] { + debug!("add_moved_or_invoked_closure_note: stmt={:?} target={:?}", stmt, target); + if let StatementKind::Assign(box(into, Rvalue::Use(from))) = &stmt.kind { + debug!("add_fnonce_closure_note: into={:?} from={:?}", into, from); + match from { + Operand::Copy(ref place) | + Operand::Move(ref place) if target == place.local_or_deref_local() => + target = into.local_or_deref_local(), + _ => {}, + } + } + } + + // Check if we are attempting to call a closure after it has been invoked. + let terminator = self.body[location.block].terminator(); + debug!("add_moved_or_invoked_closure_note: terminator={:?}", terminator); + if let TerminatorKind::Call { + func: Operand::Constant(box Constant { + literal: ty::Const { + ty: &ty::TyS { kind: ty::FnDef(id, _), .. }, + .. + }, + .. + }), + args, + .. + } = &terminator.kind { + debug!("add_moved_or_invoked_closure_note: id={:?}", id); + if self.infcx.tcx.parent(id) == self.infcx.tcx.lang_items().fn_once_trait() { + let closure = match args.first() { + Some(Operand::Copy(ref place)) | + Some(Operand::Move(ref place)) if target == place.local_or_deref_local() => + place.local_or_deref_local().unwrap(), + _ => return, + }; + + debug!("add_moved_or_invoked_closure_note: closure={:?}", closure); + if let ty::Closure(did, _) = self.body.local_decls[closure].ty.kind { + let hir_id = self.infcx.tcx.hir().as_local_hir_id(did).unwrap(); + + if let Some((span, name)) = self.infcx.tcx.typeck_tables_of(did) + .closure_kind_origins() + .get(hir_id) + { + diag.span_note( + *span, + &format!( + "closure cannot be invoked more than once because it moves the \ + variable `{}` out of its environment", + name, + ), + ); + return; + } + } + } + } + + // Check if we are just moving a closure after it has been invoked. + if let Some(target) = target { + if let ty::Closure(did, _) = self.body.local_decls[target].ty.kind { + let hir_id = self.infcx.tcx.hir().as_local_hir_id(did).unwrap(); + + if let Some((span, name)) = self.infcx.tcx.typeck_tables_of(did) + .closure_kind_origins() + .get(hir_id) + { + diag.span_note( + *span, + &format!( + "closure cannot be moved more than once as it is not `Copy` due to \ + moving the variable `{}` out of its environment", + name + ), + ); + } + } + } + } + + /// End-user visible description of `place` if one can be found. If the + /// place is a temporary for instance, None will be returned. + pub(super) fn describe_place(&self, place_ref: PlaceRef<'cx, 'tcx>) -> Option { + self.describe_place_with_options(place_ref, IncludingDowncast(false)) + } + + /// End-user visible description of `place` if one can be found. If the + /// place is a temporary for instance, None will be returned. + /// `IncludingDowncast` parameter makes the function return `Err` if `ProjectionElem` is + /// `Downcast` and `IncludingDowncast` is true + pub(super) fn describe_place_with_options( + &self, + place: PlaceRef<'cx, 'tcx>, + including_downcast: IncludingDowncast, + ) -> Option { + let mut buf = String::new(); + match self.append_place_to_string(place, &mut buf, false, &including_downcast) { + Ok(()) => Some(buf), + Err(()) => None, + } + } + + /// Appends end-user visible description of `place` to `buf`. + fn append_place_to_string( + &self, + place: PlaceRef<'cx, 'tcx>, + buf: &mut String, + mut autoderef: bool, + including_downcast: &IncludingDowncast, + ) -> Result<(), ()> { + match place { + PlaceRef { + base: PlaceBase::Local(local), + projection: [], + } => { + self.append_local_to_string(*local, buf)?; + } + PlaceRef { + base: + PlaceBase::Static(box Static { + kind: StaticKind::Promoted(..), + .. + }), + projection: [], + } => { + buf.push_str("promoted"); + } + PlaceRef { + base: + PlaceBase::Static(box Static { + kind: StaticKind::Static, + def_id, + .. + }), + projection: [], + } => { + buf.push_str(&self.infcx.tcx.item_name(*def_id).to_string()); + } + PlaceRef { + base: &PlaceBase::Local(local), + projection: [ProjectionElem::Deref] + } if self.body.local_decls[local].is_ref_for_guard() => { + self.append_place_to_string( + PlaceRef { + base: &PlaceBase::Local(local), + projection: &[], + }, + buf, + autoderef, + &including_downcast, + )?; + }, + PlaceRef { + base: &PlaceBase::Local(local), + projection: [ProjectionElem::Deref] + } if self.body.local_decls[local].is_ref_to_static() => { + let local_info = &self.body.local_decls[local].local_info; + if let LocalInfo::StaticRef { def_id, .. } = *local_info { + buf.push_str(&self.infcx.tcx.item_name(def_id).as_str()); + } else { + unreachable!(); + } + }, + PlaceRef { + base, + projection: [proj_base @ .., elem], + } => { + match elem { + ProjectionElem::Deref => { + let upvar_field_projection = + self.is_upvar_field_projection(place); + if let Some(field) = upvar_field_projection { + let var_index = field.index(); + let name = self.upvars[var_index].name.to_string(); + if self.upvars[var_index].by_ref { + buf.push_str(&name); + } else { + buf.push_str(&format!("*{}", &name)); + } + } else { + if autoderef { + // FIXME turn this recursion into iteration + self.append_place_to_string( + PlaceRef { + base, + projection: proj_base, + }, + buf, + autoderef, + &including_downcast, + )?; + } else { + match (proj_base, base) { + _ => { + buf.push_str(&"*"); + self.append_place_to_string( + PlaceRef { + base, + projection: proj_base, + }, + buf, + autoderef, + &including_downcast, + )?; + } + } + } + } + } + ProjectionElem::Downcast(..) => { + self.append_place_to_string( + PlaceRef { + base, + projection: proj_base, + }, + buf, + autoderef, + &including_downcast, + )?; + if including_downcast.0 { + return Err(()); + } + } + ProjectionElem::Field(field, _ty) => { + autoderef = true; + + let upvar_field_projection = + self.is_upvar_field_projection(place); + if let Some(field) = upvar_field_projection { + let var_index = field.index(); + let name = self.upvars[var_index].name.to_string(); + buf.push_str(&name); + } else { + let field_name = self.describe_field(PlaceRef { + base, + projection: proj_base, + }, *field); + self.append_place_to_string( + PlaceRef { + base, + projection: proj_base, + }, + buf, + autoderef, + &including_downcast, + )?; + buf.push_str(&format!(".{}", field_name)); + } + } + ProjectionElem::Index(index) => { + autoderef = true; + + self.append_place_to_string( + PlaceRef { + base, + projection: proj_base, + }, + buf, + autoderef, + &including_downcast, + )?; + buf.push_str("["); + if self.append_local_to_string(*index, buf).is_err() { + buf.push_str("_"); + } + buf.push_str("]"); + } + ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. } => { + autoderef = true; + // Since it isn't possible to borrow an element on a particular index and + // then use another while the borrow is held, don't output indices details + // to avoid confusing the end-user + self.append_place_to_string( + PlaceRef { + base, + projection: proj_base, + }, + buf, + autoderef, + &including_downcast, + )?; + buf.push_str(&"[..]"); + } + }; + } + } + + Ok(()) + } + + /// Appends end-user visible description of the `local` place to `buf`. If `local` doesn't have + /// a name, or its name was generated by the compiler, then `Err` is returned + fn append_local_to_string(&self, local: Local, buf: &mut String) -> Result<(), ()> { + let decl = &self.body.local_decls[local]; + match self.local_names[local] { + Some(name) if !decl.from_compiler_desugaring() => { + buf.push_str(&name.as_str()); + Ok(()) + } + _ => Err(()), + } + } + + /// End-user visible description of the `field`nth field of `base` + fn describe_field(&self, place: PlaceRef<'cx, 'tcx>, field: Field) -> String { + // FIXME Place2 Make this work iteratively + match place { + PlaceRef { + base: PlaceBase::Local(local), + projection: [], + } => { + let local = &self.body.local_decls[*local]; + self.describe_field_from_ty(&local.ty, field, None) + } + PlaceRef { + base: PlaceBase::Static(static_), + projection: [], + } => + self.describe_field_from_ty(&static_.ty, field, None), + PlaceRef { + base, + projection: [proj_base @ .., elem], + } => match elem { + ProjectionElem::Deref => { + self.describe_field(PlaceRef { + base, + projection: proj_base, + }, field) + } + ProjectionElem::Downcast(_, variant_index) => { + let base_ty = Place::ty_from( + place.base, + place.projection, + *self.body, + self.infcx.tcx).ty; + self.describe_field_from_ty(&base_ty, field, Some(*variant_index)) + } + ProjectionElem::Field(_, field_type) => { + self.describe_field_from_ty(&field_type, field, None) + } + ProjectionElem::Index(..) + | ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } => { + self.describe_field(PlaceRef { + base, + projection: proj_base, + }, field) + } + }, + } + } + + /// End-user visible description of the `field_index`nth field of `ty` + fn describe_field_from_ty( + &self, + ty: Ty<'_>, + field: Field, + variant_index: Option + ) -> String { + if ty.is_box() { + // If the type is a box, the field is described from the boxed type + self.describe_field_from_ty(&ty.boxed_ty(), field, variant_index) + } else { + match ty.kind { + ty::Adt(def, _) => { + let variant = if let Some(idx) = variant_index { + assert!(def.is_enum()); + &def.variants[idx] + } else { + def.non_enum_variant() + }; + variant.fields[field.index()] + .ident + .to_string() + }, + ty::Tuple(_) => field.index().to_string(), + ty::Ref(_, ty, _) | ty::RawPtr(ty::TypeAndMut { ty, .. }) => { + self.describe_field_from_ty(&ty, field, variant_index) + } + ty::Array(ty, _) | ty::Slice(ty) => + self.describe_field_from_ty(&ty, field, variant_index), + ty::Closure(def_id, _) | ty::Generator(def_id, _, _) => { + // `tcx.upvars(def_id)` returns an `Option`, which is `None` in case + // the closure comes from another crate. But in that case we wouldn't + // be borrowck'ing it, so we can just unwrap: + let (&var_id, _) = self.infcx.tcx.upvars(def_id).unwrap() + .get_index(field.index()).unwrap(); + + self.infcx.tcx.hir().name(var_id).to_string() + } + _ => { + // Might need a revision when the fields in trait RFC is implemented + // (https://github.com/rust-lang/rfcs/pull/1546) + bug!( + "End-user description not implemented for field access on `{:?}`", + ty + ); + } + } + } + } + + /// Add a note that a type does not implement `Copy` + pub(super) fn note_type_does_not_implement_copy( + &self, + err: &mut DiagnosticBuilder<'a>, + place_desc: &str, + ty: Ty<'tcx>, + span: Option, + ) { + let message = format!( + "move occurs because {} has type `{}`, which does not implement the `Copy` trait", + place_desc, + ty, + ); + if let Some(span) = span { + err.span_label(span, message); + } else { + err.note(&message); + } + } + + pub(super) fn borrowed_content_source( + &self, + deref_base: PlaceRef<'cx, 'tcx>, + ) -> BorrowedContentSource<'tcx> { + let tcx = self.infcx.tcx; + + // Look up the provided place and work out the move path index for it, + // we'll use this to check whether it was originally from an overloaded + // operator. + match self.move_data.rev_lookup.find(deref_base) { + LookupResult::Exact(mpi) | LookupResult::Parent(Some(mpi)) => { + debug!("borrowed_content_source: mpi={:?}", mpi); + + for i in &self.move_data.init_path_map[mpi] { + let init = &self.move_data.inits[*i]; + debug!("borrowed_content_source: init={:?}", init); + // We're only interested in statements that initialized a value, not the + // initializations from arguments. + let loc = match init.location { + InitLocation::Statement(stmt) => stmt, + _ => continue, + }; + + let bbd = &self.body[loc.block]; + let is_terminator = bbd.statements.len() == loc.statement_index; + debug!( + "borrowed_content_source: loc={:?} is_terminator={:?}", + loc, + is_terminator, + ); + if !is_terminator { + continue; + } else if let Some(Terminator { + kind: TerminatorKind::Call { + ref func, + from_hir_call: false, + .. + }, + .. + }) = bbd.terminator { + if let Some(source) = BorrowedContentSource::from_call( + func.ty(*self.body, tcx), + tcx + ) { + return source; + } + } + } + } + // Base is a `static` so won't be from an overloaded operator + _ => (), + }; + + // If we didn't find an overloaded deref or index, then assume it's a + // built in deref and check the type of the base. + let base_ty = Place::ty_from( + deref_base.base, + deref_base.projection, + *self.body, + tcx + ).ty; + if base_ty.is_unsafe_ptr() { + BorrowedContentSource::DerefRawPointer + } else if base_ty.is_mutable_ptr() { + BorrowedContentSource::DerefMutableRef + } else { + BorrowedContentSource::DerefSharedRef + } + } +} + +impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { + /// Return the name of the provided `Ty` (that must be a reference) with a synthesized lifetime + /// name where required. + pub(super) fn get_name_for_ty(&self, ty: Ty<'tcx>, counter: usize) -> String { + let mut s = String::new(); + let mut printer = ty::print::FmtPrinter::new(self.infcx.tcx, &mut s, Namespace::TypeNS); + + // We need to add synthesized lifetimes where appropriate. We do + // this by hooking into the pretty printer and telling it to label the + // lifetimes without names with the value `'0`. + match ty.kind { + ty::Ref(ty::RegionKind::ReLateBound(_, br), _, _) + | ty::Ref( + ty::RegionKind::RePlaceholder(ty::PlaceholderRegion { name: br, .. }), + _, + _, + ) => printer.region_highlight_mode.highlighting_bound_region(*br, counter), + _ => {} + } + + let _ = ty.print(printer); + s + } + + /// Returns the name of the provided `Ty` (that must be a reference)'s region with a + /// synthesized lifetime name where required. + pub(super) fn get_region_name_for_ty(&self, ty: Ty<'tcx>, counter: usize) -> String { + let mut s = String::new(); + let mut printer = ty::print::FmtPrinter::new(self.infcx.tcx, &mut s, Namespace::TypeNS); + + let region = match ty.kind { + ty::Ref(region, _, _) => { + match region { + ty::RegionKind::ReLateBound(_, br) + | ty::RegionKind::RePlaceholder(ty::PlaceholderRegion { name: br, .. }) => { + printer.region_highlight_mode.highlighting_bound_region(*br, counter) + } + _ => {} + } + + region + } + _ => bug!("ty for annotation of borrow region is not a reference"), + }; + + let _ = region.print(printer); + s + } +} + +// The span(s) associated to a use of a place. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub(super) enum UseSpans { + // The access is caused by capturing a variable for a closure. + ClosureUse { + // This is true if the captured variable was from a generator. + generator_kind: Option, + // The span of the args of the closure, including the `move` keyword if + // it's present. + args_span: Span, + // The span of the first use of the captured variable inside the closure. + var_span: Span, + }, + // This access has a single span associated to it: common case. + OtherUse(Span), +} + +impl UseSpans { + pub(super) fn args_or_use(self) -> Span { + match self { + UseSpans::ClosureUse { + args_span: span, .. + } + | UseSpans::OtherUse(span) => span, + } + } + + pub(super) fn var_or_use(self) -> Span { + match self { + UseSpans::ClosureUse { var_span: span, .. } | UseSpans::OtherUse(span) => span, + } + } + + pub(super) fn generator_kind(self) -> Option { + match self { + UseSpans::ClosureUse { generator_kind, .. } => generator_kind, + _ => None, + } + } + + // Add a span label to the arguments of the closure, if it exists. + pub(super) fn args_span_label( + self, + err: &mut DiagnosticBuilder<'_>, + message: impl Into, + ) { + if let UseSpans::ClosureUse { args_span, .. } = self { + err.span_label(args_span, message); + } + } + + // Add a span label to the use of the captured variable, if it exists. + pub(super) fn var_span_label( + self, + err: &mut DiagnosticBuilder<'_>, + message: impl Into, + ) { + if let UseSpans::ClosureUse { var_span, .. } = self { + err.span_label(var_span, message); + } + } + + /// Returns `false` if this place is not used in a closure. + pub(super) fn for_closure(&self) -> bool { + match *self { + UseSpans::ClosureUse { generator_kind, .. } => generator_kind.is_none(), + _ => false, + } + } + + /// Returns `false` if this place is not used in a generator. + pub(super) fn for_generator(&self) -> bool { + match *self { + UseSpans::ClosureUse { generator_kind, .. } => generator_kind.is_some(), + _ => false, + } + } + + /// Describe the span associated with a use of a place. + pub(super) fn describe(&self) -> String { + match *self { + UseSpans::ClosureUse { generator_kind, .. } => if generator_kind.is_some() { + " in generator".to_string() + } else { + " in closure".to_string() + }, + _ => "".to_string(), + } + } + + pub(super) fn or_else(self, if_other: F) -> Self + where + F: FnOnce() -> Self, + { + match self { + closure @ UseSpans::ClosureUse { .. } => closure, + UseSpans::OtherUse(_) => if_other(), + } + } +} + +pub(super) enum BorrowedContentSource<'tcx> { + DerefRawPointer, + DerefMutableRef, + DerefSharedRef, + OverloadedDeref(Ty<'tcx>), + OverloadedIndex(Ty<'tcx>), +} + +impl BorrowedContentSource<'tcx> { + pub(super) fn describe_for_unnamed_place(&self) -> String { + match *self { + BorrowedContentSource::DerefRawPointer => format!("a raw pointer"), + BorrowedContentSource::DerefSharedRef => format!("a shared reference"), + BorrowedContentSource::DerefMutableRef => { + format!("a mutable reference") + } + BorrowedContentSource::OverloadedDeref(ty) => { + if ty.is_rc() { + format!("an `Rc`") + } else if ty.is_arc() { + format!("an `Arc`") + } else { + format!("dereference of `{}`", ty) + } + } + BorrowedContentSource::OverloadedIndex(ty) => format!("index of `{}`", ty), + } + } + + pub(super) fn describe_for_named_place(&self) -> Option<&'static str> { + match *self { + BorrowedContentSource::DerefRawPointer => Some("raw pointer"), + BorrowedContentSource::DerefSharedRef => Some("shared reference"), + BorrowedContentSource::DerefMutableRef => Some("mutable reference"), + // Overloaded deref and index operators should be evaluated into a + // temporary. So we don't need a description here. + BorrowedContentSource::OverloadedDeref(_) + | BorrowedContentSource::OverloadedIndex(_) => None + } + } + + pub(super) fn describe_for_immutable_place(&self) -> String { + match *self { + BorrowedContentSource::DerefRawPointer => format!("a `*const` pointer"), + BorrowedContentSource::DerefSharedRef => format!("a `&` reference"), + BorrowedContentSource::DerefMutableRef => { + bug!("describe_for_immutable_place: DerefMutableRef isn't immutable") + }, + BorrowedContentSource::OverloadedDeref(ty) => { + if ty.is_rc() { + format!("an `Rc`") + } else if ty.is_arc() { + format!("an `Arc`") + } else { + format!("a dereference of `{}`", ty) + } + } + BorrowedContentSource::OverloadedIndex(ty) => format!("an index of `{}`", ty), + } + } + + fn from_call(func: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Option { + match func.kind { + ty::FnDef(def_id, substs) => { + let trait_id = tcx.trait_of_item(def_id)?; + + let lang_items = tcx.lang_items(); + if Some(trait_id) == lang_items.deref_trait() + || Some(trait_id) == lang_items.deref_mut_trait() + { + Some(BorrowedContentSource::OverloadedDeref(substs.type_at(0))) + } else if Some(trait_id) == lang_items.index_trait() + || Some(trait_id) == lang_items.index_mut_trait() + { + Some(BorrowedContentSource::OverloadedIndex(substs.type_at(0))) + } else { + None + } + } + _ => None, + } + } +} + +impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { + /// Finds the spans associated to a move or copy of move_place at location. + pub(super) fn move_spans( + &self, + moved_place: PlaceRef<'cx, 'tcx>, // Could also be an upvar. + location: Location, + ) -> UseSpans { + use self::UseSpans::*; + + let stmt = match self.body[location.block].statements.get(location.statement_index) { + Some(stmt) => stmt, + None => return OtherUse(self.body.source_info(location).span), + }; + + debug!("move_spans: moved_place={:?} location={:?} stmt={:?}", moved_place, location, stmt); + if let StatementKind::Assign( + box(_, Rvalue::Aggregate(ref kind, ref places)) + ) = stmt.kind { + let def_id = match kind { + box AggregateKind::Closure(def_id, _) + | box AggregateKind::Generator(def_id, _, _) => def_id, + _ => return OtherUse(stmt.source_info.span), + }; + + debug!( + "move_spans: def_id={:?} places={:?}", + def_id, places + ); + if let Some((args_span, generator_kind, var_span)) + = self.closure_span(*def_id, moved_place, places) { + return ClosureUse { + generator_kind, + args_span, + var_span, + }; + } + } + + OtherUse(stmt.source_info.span) + } + + /// Finds the span of arguments of a closure (within `maybe_closure_span`) + /// and its usage of the local assigned at `location`. + /// This is done by searching in statements succeeding `location` + /// and originating from `maybe_closure_span`. + pub(super) fn borrow_spans(&self, use_span: Span, location: Location) -> UseSpans { + use self::UseSpans::*; + debug!("borrow_spans: use_span={:?} location={:?}", use_span, location); + + let target = match self.body[location.block] + .statements + .get(location.statement_index) + { + Some(&Statement { + kind: StatementKind::Assign(box(ref place, _)), + .. + }) => { + if let Some(local) = place.as_local() { + local + } else { + return OtherUse(use_span); + } + } + _ => return OtherUse(use_span), + }; + + if self.body.local_kind(target) != LocalKind::Temp { + // operands are always temporaries. + return OtherUse(use_span); + } + + for stmt in &self.body[location.block].statements[location.statement_index + 1..] { + if let StatementKind::Assign( + box(_, Rvalue::Aggregate(ref kind, ref places)) + ) = stmt.kind { + let (def_id, is_generator) = match kind { + box AggregateKind::Closure(def_id, _) => (def_id, false), + box AggregateKind::Generator(def_id, _, _) => (def_id, true), + _ => continue, + }; + + debug!( + "borrow_spans: def_id={:?} is_generator={:?} places={:?}", + def_id, is_generator, places + ); + if let Some((args_span, generator_kind, var_span)) = self.closure_span( + *def_id, Place::from(target).as_ref(), places + ) { + return ClosureUse { + generator_kind, + args_span, + var_span, + }; + } else { + return OtherUse(use_span); + } + } + + if use_span != stmt.source_info.span { + break; + } + } + + OtherUse(use_span) + } + + /// Finds the span of a captured variable within a closure or generator. + fn closure_span( + &self, + def_id: DefId, + target_place: PlaceRef<'cx, 'tcx>, + places: &Vec>, + ) -> Option<(Span, Option, Span)> { + debug!( + "closure_span: def_id={:?} target_place={:?} places={:?}", + def_id, target_place, places + ); + let hir_id = self.infcx.tcx.hir().as_local_hir_id(def_id)?; + let expr = &self.infcx.tcx.hir().expect_expr(hir_id).kind; + debug!("closure_span: hir_id={:?} expr={:?}", hir_id, expr); + if let hir::ExprKind::Closure( + .., body_id, args_span, _ + ) = expr { + for (upvar, place) in self.infcx.tcx.upvars(def_id)?.values().zip(places) { + match place { + Operand::Copy(place) | + Operand::Move(place) if target_place == place.as_ref() => { + debug!("closure_span: found captured local {:?}", place); + let body = self.infcx.tcx.hir().body(*body_id); + let generator_kind = body.generator_kind(); + return Some((*args_span, generator_kind, upvar.span)); + }, + _ => {} + } + } + + } + None + } + + /// Helper to retrieve span(s) of given borrow from the current MIR + /// representation + pub(super) fn retrieve_borrow_spans(&self, borrow: &BorrowData<'_>) -> UseSpans { + let span = self.body.source_info(borrow.reserve_location).span; + self.borrow_spans(span, borrow.reserve_location) + } +} diff --git a/src/librustc_mir/borrow_check/diagnostics/move_errors.rs b/src/librustc_mir/borrow_check/diagnostics/move_errors.rs new file mode 100644 index 00000000000..fd779767d7a --- /dev/null +++ b/src/librustc_mir/borrow_check/diagnostics/move_errors.rs @@ -0,0 +1,587 @@ +use rustc::mir::*; +use rustc::ty; +use rustc_errors::{DiagnosticBuilder,Applicability}; +use syntax_pos::Span; + +use crate::borrow_check::MirBorrowckCtxt; +use crate::borrow_check::prefixes::PrefixSet; +use crate::borrow_check::error_reporting::UseSpans; +use crate::dataflow::move_paths::{ + IllegalMoveOrigin, IllegalMoveOriginKind, + LookupResult, MoveError, MovePathIndex, +}; + +// 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, + 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 LocalInfo::User(ClearCrossCrate::Set(BindingForm::Var( + VarBindingForm { + opt_match_place: Some((ref 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.as_ref().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 { + if 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: match_place.clone(), + original_path, + kind, + binds_to, + }); + } + // Error with the pattern + LookupResult::Exact(_) => { + let mpi = match self.move_data.rev_lookup.find(move_from.as_ref()) { + LookupResult::Parent(Some(mpi)) => mpi, + // 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, original_path, kind): (Span, &Place<'tcx>, &IllegalMoveOriginKind<'_>) = + match error { + GroupedMoveError::MovesFromPlace { span, ref original_path, ref kind, .. } | + GroupedMoveError::MovesFromValue { span, ref original_path, ref kind, .. } => { + (span, original_path, kind) + } + GroupedMoveError::OtherIllegalMove { + use_spans, + ref original_path, + ref kind + } => { + (use_spans.args_or_use(), 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::Static => { + unreachable!(); + } + IllegalMoveOriginKind::BorrowedContent { target_place } => { + self.report_cannot_move_from_borrowed_content( + original_path, + target_place, + span, + ) + } + 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); + err.buffer(&mut self.errors_buffer); + } + + fn report_cannot_move_from_static( + &mut self, + place: &Place<'tcx>, + span: Span + ) -> DiagnosticBuilder<'a> { + let description = if place.projection.len() == 1 { + format!("static item `{}`", self.describe_place(place.as_ref()).unwrap()) + } else { + let base_static = PlaceRef { + base: &place.base, + projection: &[ProjectionElem::Deref], + }; + + format!( + "`{:?}` as `{:?}` is a static item", + self.describe_place(place.as_ref()).unwrap(), + self.describe_place(base_static).unwrap(), + ) + }; + + 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, + ) -> DiagnosticBuilder<'a> { + // 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() { + &[ref proj_base @ .., ProjectionElem::Deref] => { + PlaceRef { + base: &deref_target_place.base, + projection: &proj_base, + } + } + _ => bug!("deref_target_place is not a deref projection"), + }; + + if let PlaceRef { + base: PlaceBase::Local(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 == self.mir_def_id && upvar_field.is_some() + => { + let closure_kind_ty = closure_substs + .as_closure().kind_ty(def_id, self.infcx.tcx); + let closure_kind = closure_kind_ty.to_opt_closure_kind(); + let capture_description = match closure_kind { + Some(ty::ClosureKind::Fn) => { + "captured variable in an `Fn` closure" + } + Some(ty::ClosureKind::FnMut) => { + "captured variable in an `FnMut` closure" + } + Some(ty::ClosureKind::FnOnce) => { + bug!("closure kind does not match first argument type") + } + None => bug!("closure kind not inferred by borrowck"), + }; + + let upvar = &self.upvars[upvar_field.unwrap().index()]; + let upvar_hir_id = upvar.var_hir_id; + let upvar_name = upvar.name; + let upvar_span = self.infcx.tcx.hir().span(upvar_hir_id); + + let place_name = self.describe_place(move_place.as_ref()).unwrap(); + + let place_description = if self + .is_upvar_field_projection(move_place.as_ref()) + .is_some() + { + format!("`{}`, a {}", place_name, capture_description) + } else { + format!( + "`{}`, as `{}` is a {}", + place_name, + upvar_name, + 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 + } + _ => { + let source = self.borrowed_content_source(deref_base); + match ( + self.describe_place(move_place.as_ref()), + source.describe_for_named_place(), + ) { + (Some(place_desc), Some(source_desc)) => { + self.cannot_move_out_of( + span, + &format!("`{}` which is behind a {}", place_desc, source_desc), + ) + } + (_, _) => { + self.cannot_move_out_of( + span, + &source.describe_for_unnamed_place(), + ) + } + } + }, + }; + let move_ty = format!( + "{:?}", + move_place.ty(*self.body, self.infcx.tcx).ty, + ); + if let Ok(snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(span) { + let is_option = move_ty.starts_with("std::option::Option"); + let is_result = move_ty.starts_with("std::result::Result"); + if is_option || is_result { + err.span_suggestion( + span, + &format!("consider borrowing the `{}`'s content", if is_option { + "Option" + } else { + "Result" + }), + format!("{}.as_ref()", snippet), + Applicability::MaybeIncorrect, + ); + } + } + err + } + + fn add_move_hints( + &self, + error: GroupedMoveError<'tcx>, + err: &mut DiagnosticBuilder<'a>, + 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 => format!("value"), + }; + + 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 => format!("value"), + }; + self.note_type_does_not_implement_copy( + err, + &place_desc, + place_ty, + Some(span), + ); + + use_spans.args_span_label(err, format!("move out of {} occurs here", place_desc)); + use_spans.var_span_label( + err, + format!("move occurs due to use{}", use_spans.describe()), + ); + }, + } + } + + fn add_move_error_suggestions( + &self, + err: &mut DiagnosticBuilder<'a>, + 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 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 pat_snippet.starts_with('&') { + let pat_snippet = pat_snippet[1..].trim_start(); + let suggestion; + let to_remove; + if pat_snippet.starts_with("mut") + && pat_snippet["mut".len()..].starts_with(rustc_lexer::is_whitespace) + { + suggestion = pat_snippet["mut".len()..].trim_start(); + to_remove = "&mut"; + } else { + suggestion = pat_snippet; + to_remove = "&"; + } + 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 DiagnosticBuilder<'a>, + binds_to: &[Local], + ) { + for (j, local) in binds_to.into_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", + ); + } + } +} diff --git a/src/librustc_mir/borrow_check/diagnostics/mutability_errors.rs b/src/librustc_mir/borrow_check/diagnostics/mutability_errors.rs new file mode 100644 index 00000000000..98a7b101d56 --- /dev/null +++ b/src/librustc_mir/borrow_check/diagnostics/mutability_errors.rs @@ -0,0 +1,656 @@ +use rustc::hir; +use rustc::hir::Node; +use rustc::mir::{self, ClearCrossCrate, Local, LocalInfo, Location, ReadOnlyBodyCache}; +use rustc::mir::{Mutability, Place, PlaceRef, PlaceBase, ProjectionElem}; +use rustc::ty::{self, Ty, TyCtxt}; +use rustc_index::vec::Idx; +use syntax_pos::Span; +use syntax_pos::symbol::kw; + +use crate::borrow_check::MirBorrowckCtxt; +use crate::borrow_check::error_reporting::BorrowedContentSource; +use crate::util::collect_writes::FindAssignments; +use rustc_errors::Applicability; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(super) enum AccessKind { + MutableBorrow, + Mutate, +} + +impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { + pub(super) fn report_mutability_error( + &mut self, + access_place: &Place<'tcx>, + span: Span, + the_place_err: PlaceRef<'cx, 'tcx>, + error_access: AccessKind, + location: Location, + ) { + debug!( + "report_mutability_error(\ + access_place={:?}, span={:?}, the_place_err={:?}, error_access={:?}, location={:?},\ + )", + access_place, span, the_place_err, error_access, location, + ); + + let mut err; + let item_msg; + let reason; + let mut opt_source = None; + let access_place_desc = self.describe_place(access_place.as_ref()); + debug!("report_mutability_error: access_place_desc={:?}", access_place_desc); + + match the_place_err { + PlaceRef { + base: PlaceBase::Local(local), + projection: [], + } => { + item_msg = format!("`{}`", access_place_desc.unwrap()); + if access_place.as_local().is_some() { + reason = ", as it is not declared as mutable".to_string(); + } else { + let name = self.local_names[*local] + .expect("immutable unnamed local"); + reason = format!(", as `{}` is not declared as mutable", name); + } + } + + PlaceRef { + base: _, + projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)], + } => { + debug_assert!(is_closure_or_generator( + Place::ty_from( + &the_place_err.base, + proj_base, + *self.body, + self.infcx.tcx + ).ty)); + + item_msg = format!("`{}`", access_place_desc.unwrap()); + if self.is_upvar_field_projection(access_place.as_ref()).is_some() { + reason = ", as it is not declared as mutable".to_string(); + } else { + let name = self.upvars[upvar_index.index()].name; + reason = format!(", as `{}` is not declared as mutable", name); + } + } + + PlaceRef { + base: &PlaceBase::Local(local), + projection: [ProjectionElem::Deref], + } if self.body.local_decls[local].is_ref_for_guard() => { + item_msg = format!("`{}`", access_place_desc.unwrap()); + reason = ", as it is immutable for the pattern guard".to_string(); + } + PlaceRef { + base: &PlaceBase::Local(local), + projection: [ProjectionElem::Deref], + } if self.body.local_decls[local].is_ref_to_static() => { + if access_place.projection.len() == 1 { + item_msg = format!("immutable static item `{}`", access_place_desc.unwrap()); + reason = String::new(); + } else { + item_msg = format!("`{}`", access_place_desc.unwrap()); + let local_info = &self.body.local_decls[local].local_info; + if let LocalInfo::StaticRef { def_id, .. } = *local_info { + let static_name = &self.infcx.tcx.item_name(def_id); + reason = format!(", as `{}` is an immutable static item", static_name); + } else { + bug!("is_ref_to_static return true, but not ref to static?"); + } + } + } + PlaceRef { + base: _, + projection: [proj_base @ .., ProjectionElem::Deref], + } => { + if the_place_err.base == &PlaceBase::Local(Local::new(1)) && + proj_base.is_empty() && + !self.upvars.is_empty() { + item_msg = format!("`{}`", access_place_desc.unwrap()); + debug_assert!(self.body.local_decls[Local::new(1)].ty.is_region_ptr()); + debug_assert!(is_closure_or_generator( + Place::ty_from( + the_place_err.base, + the_place_err.projection, + *self.body, + self.infcx.tcx + ) + .ty + )); + + reason = + if self.is_upvar_field_projection(access_place.as_ref()).is_some() { + ", as it is a captured variable in a `Fn` closure".to_string() + } else { + ", as `Fn` closures cannot mutate their captured variables".to_string() + } + } else { + let source = self.borrowed_content_source(PlaceRef { + base: the_place_err.base, + projection: proj_base, + }); + let pointer_type = source.describe_for_immutable_place(); + opt_source = Some(source); + if let Some(desc) = access_place_desc { + item_msg = format!("`{}`", desc); + reason = match error_access { + AccessKind::Mutate => format!(" which is behind {}", pointer_type), + AccessKind::MutableBorrow => { + format!(", as it is behind {}", pointer_type) + } + } + } else { + item_msg = format!("data in {}", pointer_type); + reason = String::new(); + } + } + } + + PlaceRef { + base: PlaceBase::Static(_), + .. + } + | PlaceRef { + base: _, + projection: [.., ProjectionElem::Index(_)], + } + | PlaceRef { + base: _, + projection: [.., ProjectionElem::ConstantIndex { .. }], + } + | PlaceRef { + base: _, + projection: [.., ProjectionElem::Subslice { .. }], + } + | PlaceRef { + base: _, + projection: [.., ProjectionElem::Downcast(..)], + } => bug!("Unexpected immutable place."), + } + + debug!("report_mutability_error: item_msg={:?}, reason={:?}", item_msg, reason); + + // `act` and `acted_on` are strings that let us abstract over + // the verbs used in some diagnostic messages. + let act; + let acted_on; + + let span = match error_access { + AccessKind::Mutate => { + err = self.cannot_assign(span, &(item_msg + &reason)); + act = "assign"; + acted_on = "written"; + span + } + AccessKind::MutableBorrow => { + act = "borrow as mutable"; + acted_on = "borrowed as mutable"; + + let borrow_spans = self.borrow_spans(span, location); + let borrow_span = borrow_spans.args_or_use(); + err = self.cannot_borrow_path_as_mutable_because( + borrow_span, + &item_msg, + &reason, + ); + borrow_spans.var_span_label( + &mut err, + format!( + "mutable borrow occurs due to use of `{}` in closure", + // always Some() if the message is printed. + self.describe_place(access_place.as_ref()).unwrap_or_default(), + ) + ); + borrow_span + } + }; + + debug!("report_mutability_error: act={:?}, acted_on={:?}", act, acted_on); + + match the_place_err { + // Suggest making an existing shared borrow in a struct definition a mutable borrow. + // + // This is applicable when we have a deref of a field access to a deref of a local - + // something like `*((*_1).0`. The local that we get will be a reference to the + // struct we've got a field access of (it must be a reference since there's a deref + // after the field access). + PlaceRef { + base, + projection: [proj_base @ .., + ProjectionElem::Deref, + ProjectionElem::Field(field, _), + ProjectionElem::Deref, + ], + } => { + err.span_label(span, format!("cannot {ACT}", ACT = act)); + + if let Some((span, message)) = annotate_struct_field( + self.infcx.tcx, + Place::ty_from(base, proj_base, *self.body, self.infcx.tcx).ty, + field, + ) { + err.span_suggestion( + span, + "consider changing this to be mutable", + message, + Applicability::MaybeIncorrect, + ); + } + }, + + // Suggest removing a `&mut` from the use of a mutable reference. + PlaceRef { + base: PlaceBase::Local(local), + projection: [], + } if { + self.body.local_decls.get(*local).map(|local_decl| { + if let LocalInfo::User(ClearCrossCrate::Set( + mir::BindingForm::ImplicitSelf(kind) + )) = local_decl.local_info { + // Check if the user variable is a `&mut self` and we can therefore + // suggest removing the `&mut`. + // + // Deliberately fall into this case for all implicit self types, + // so that we don't fall in to the next case with them. + kind == mir::ImplicitSelfKind::MutRef + } else if Some(kw::SelfLower) == self.local_names[*local] { + // Otherwise, check if the name is the self kewyord - in which case + // we have an explicit self. Do the same thing in this case and check + // for a `self: &mut Self` to suggest removing the `&mut`. + if let ty::Ref( + _, _, hir::Mutability::Mutable + ) = local_decl.ty.kind { + true + } else { + false + } + } else { + false + } + }).unwrap_or(false) + } => { + err.span_label(span, format!("cannot {ACT}", ACT = act)); + err.span_label(span, "try removing `&mut` here"); + }, + + // We want to suggest users use `let mut` for local (user + // variable) mutations... + PlaceRef { + base: PlaceBase::Local(local), + projection: [], + } if self.body.local_decls[*local].can_be_made_mutable() => { + // ... but it doesn't make sense to suggest it on + // variables that are `ref x`, `ref mut x`, `&self`, + // or `&mut self` (such variables are simply not + // mutable). + let local_decl = &self.body.local_decls[*local]; + assert_eq!(local_decl.mutability, Mutability::Not); + + err.span_label(span, format!("cannot {ACT}", ACT = act)); + err.span_suggestion( + local_decl.source_info.span, + "consider changing this to be mutable", + format!("mut {}", self.local_names[*local].unwrap()), + Applicability::MachineApplicable, + ); + } + + // Also suggest adding mut for upvars + PlaceRef { + base, + projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)], + } => { + debug_assert!(is_closure_or_generator( + Place::ty_from(base, proj_base, *self.body, self.infcx.tcx).ty + )); + + err.span_label(span, format!("cannot {ACT}", ACT = act)); + + let upvar_hir_id = self.upvars[upvar_index.index()].var_hir_id; + if let Some(Node::Binding(pat)) = self.infcx.tcx.hir().find(upvar_hir_id) + { + if let hir::PatKind::Binding( + hir::BindingAnnotation::Unannotated, + _, + upvar_ident, + _, + ) = pat.kind + { + err.span_suggestion( + upvar_ident.span, + "consider changing this to be mutable", + format!("mut {}", upvar_ident.name), + Applicability::MachineApplicable, + ); + } + } + } + + // complete hack to approximate old AST-borrowck + // diagnostic: if the span starts with a mutable borrow of + // a local variable, then just suggest the user remove it. + PlaceRef { + base: PlaceBase::Local(_), + projection: [], + } if { + if let Ok(snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(span) { + snippet.starts_with("&mut ") + } else { + false + } + } => + { + err.span_label(span, format!("cannot {ACT}", ACT = act)); + err.span_label(span, "try removing `&mut` here"); + } + + PlaceRef { + base: PlaceBase::Local(local), + projection: [ProjectionElem::Deref], + } if self.body.local_decls[*local].is_ref_for_guard() => { + err.span_label(span, format!("cannot {ACT}", ACT = act)); + err.note( + "variables bound in patterns are immutable until the end of the pattern guard", + ); + } + + // We want to point out when a `&` can be readily replaced + // with an `&mut`. + // + // FIXME: can this case be generalized to work for an + // arbitrary base for the projection? + PlaceRef { + base: PlaceBase::Local(local), + projection: [ProjectionElem::Deref], + } if self.body.local_decls[*local].is_user_variable() => + { + let local_decl = &self.body.local_decls[*local]; + let suggestion = match local_decl.local_info { + LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::ImplicitSelf(_))) => { + Some(suggest_ampmut_self(self.infcx.tcx, local_decl)) + } + + LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var( + mir::VarBindingForm { + binding_mode: ty::BindingMode::BindByValue(_), + opt_ty_info, + .. + }, + ))) => Some(suggest_ampmut( + self.infcx.tcx, + self.body, + *local, + local_decl, + opt_ty_info, + )), + + LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var( + mir::VarBindingForm { + binding_mode: ty::BindingMode::BindByReference(_), + .. + }, + ))) => { + let pattern_span = local_decl.source_info.span; + suggest_ref_mut(self.infcx.tcx, pattern_span) + .map(|replacement| (pattern_span, replacement)) + } + + LocalInfo::User(ClearCrossCrate::Clear) => bug!("saw cleared local state"), + + _ => unreachable!(), + }; + + let (pointer_sigil, pointer_desc) = if local_decl.ty.is_region_ptr() { + ("&", "reference") + } else { + ("*const", "pointer") + }; + + if let Some((err_help_span, suggested_code)) = suggestion { + err.span_suggestion( + err_help_span, + &format!("consider changing this to be a mutable {}", pointer_desc), + suggested_code, + Applicability::MachineApplicable, + ); + } + + match self.local_names[*local] { + Some(name) if !local_decl.from_compiler_desugaring() => { + err.span_label( + span, + format!( + "`{NAME}` is a `{SIGIL}` {DESC}, \ + so the data it refers to cannot be {ACTED_ON}", + NAME = name, + SIGIL = pointer_sigil, + DESC = pointer_desc, + ACTED_ON = acted_on + ), + ); + } + _ => { + err.span_label( + span, + format!( + "cannot {ACT} through `{SIGIL}` {DESC}", + ACT = act, + SIGIL = pointer_sigil, + DESC = pointer_desc + ), + ); + } + } + } + + PlaceRef { + base, + projection: [ProjectionElem::Deref], + // FIXME document what is this 1 magic number about + } if *base == PlaceBase::Local(Local::new(1)) && + !self.upvars.is_empty() => + { + err.span_label(span, format!("cannot {ACT}", ACT = act)); + err.span_help( + self.body.span, + "consider changing this to accept closures that implement `FnMut`" + ); + } + + PlaceRef { + base: _, + projection: [.., ProjectionElem::Deref], + } => { + err.span_label(span, format!("cannot {ACT}", ACT = act)); + + match opt_source { + Some(BorrowedContentSource::OverloadedDeref(ty)) => { + err.help( + &format!( + "trait `DerefMut` is required to modify through a dereference, \ + but it is not implemented for `{}`", + ty, + ), + ); + }, + Some(BorrowedContentSource::OverloadedIndex(ty)) => { + err.help( + &format!( + "trait `IndexMut` is required to modify indexed content, \ + but it is not implemented for `{}`", + ty, + ), + ); + } + _ => (), + } + } + + _ => { + err.span_label(span, format!("cannot {ACT}", ACT = act)); + } + } + + err.buffer(&mut self.errors_buffer); + } +} + +fn suggest_ampmut_self<'tcx>( + tcx: TyCtxt<'tcx>, + local_decl: &mir::LocalDecl<'tcx>, +) -> (Span, String) { + let sp = local_decl.source_info.span; + (sp, match tcx.sess.source_map().span_to_snippet(sp) { + Ok(snippet) => { + let lt_pos = snippet.find('\''); + if let Some(lt_pos) = lt_pos { + format!("&{}mut self", &snippet[lt_pos..snippet.len() - 4]) + } else { + "&mut self".to_string() + } + } + _ => "&mut self".to_string() + }) +} + +// When we want to suggest a user change a local variable to be a `&mut`, there +// are three potential "obvious" things to highlight: +// +// let ident [: Type] [= RightHandSideExpression]; +// ^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ +// (1.) (2.) (3.) +// +// We can always fallback on highlighting the first. But chances are good that +// the user experience will be better if we highlight one of the others if possible; +// for example, if the RHS is present and the Type is not, then the type is going to +// be inferred *from* the RHS, which means we should highlight that (and suggest +// that they borrow the RHS mutably). +// +// This implementation attempts to emulate AST-borrowck prioritization +// by trying (3.), then (2.) and finally falling back on (1.). +fn suggest_ampmut<'tcx>( + tcx: TyCtxt<'tcx>, + body: ReadOnlyBodyCache<'_, 'tcx>, + local: Local, + local_decl: &mir::LocalDecl<'tcx>, + opt_ty_info: Option, +) -> (Span, String) { + let locations = body.find_assignments(local); + if !locations.is_empty() { + let assignment_rhs_span = body.source_info(locations[0]).span; + if let Ok(src) = tcx.sess.source_map().span_to_snippet(assignment_rhs_span) { + if let (true, Some(ws_pos)) = ( + src.starts_with("&'"), + src.find(|c: char| -> bool { c.is_whitespace() }), + ) { + let lt_name = &src[1..ws_pos]; + let ty = &src[ws_pos..]; + return (assignment_rhs_span, format!("&{} mut {}", lt_name, ty)); + } else if src.starts_with('&') { + let borrowed_expr = &src[1..]; + return (assignment_rhs_span, format!("&mut {}", borrowed_expr)); + } + } + } + + let highlight_span = match opt_ty_info { + // if this is a variable binding with an explicit type, + // try to highlight that for the suggestion. + Some(ty_span) => ty_span, + + // otherwise, just highlight the span associated with + // the (MIR) LocalDecl. + None => local_decl.source_info.span, + }; + + if let Ok(src) = tcx.sess.source_map().span_to_snippet(highlight_span) { + if let (true, Some(ws_pos)) = ( + src.starts_with("&'"), + src.find(|c: char| -> bool { c.is_whitespace() }), + ) { + let lt_name = &src[1..ws_pos]; + let ty = &src[ws_pos..]; + return (highlight_span, format!("&{} mut{}", lt_name, ty)); + } + } + + let ty_mut = local_decl.ty.builtin_deref(true).unwrap(); + assert_eq!(ty_mut.mutbl, hir::Mutability::Immutable); + (highlight_span, + if local_decl.ty.is_region_ptr() { + format!("&mut {}", ty_mut.ty) + } else { + format!("*mut {}", ty_mut.ty) + }) +} + +fn is_closure_or_generator(ty: Ty<'_>) -> bool { + ty.is_closure() || ty.is_generator() +} + +/// Adds a suggestion to a struct definition given a field access to a local. +/// This function expects the local to be a reference to a struct in order to produce a suggestion. +/// +/// ```text +/// LL | s: &'a String +/// | ---------- use `&'a mut String` here to make mutable +/// ``` +fn annotate_struct_field( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, + field: &mir::Field, +) -> Option<(Span, String)> { + // Expect our local to be a reference to a struct of some kind. + if let ty::Ref(_, ty, _) = ty.kind { + if let ty::Adt(def, _) = ty.kind { + let field = def.all_fields().nth(field.index())?; + // Use the HIR types to construct the diagnostic message. + let hir_id = tcx.hir().as_local_hir_id(field.did)?; + let node = tcx.hir().find(hir_id)?; + // Now we're dealing with the actual struct that we're going to suggest a change to, + // we can expect a field that is an immutable reference to a type. + if let hir::Node::Field(field) = node { + if let hir::TyKind::Rptr(lifetime, hir::MutTy { + mutbl: hir::Mutability::Immutable, + ref ty + }) = field.ty.kind { + // Get the snippets in two parts - the named lifetime (if there is one) and + // type being referenced, that way we can reconstruct the snippet without loss + // of detail. + let type_snippet = tcx.sess.source_map().span_to_snippet(ty.span).ok()?; + let lifetime_snippet = if !lifetime.is_elided() { + format!("{} ", tcx.sess.source_map().span_to_snippet(lifetime.span).ok()?) + } else { + String::new() + }; + + return Some(( + field.ty.span, + format!( + "&{}mut {}", + lifetime_snippet, &*type_snippet, + ), + )); + } + } + } + } + + None +} + +/// If possible, suggest replacing `ref` with `ref mut`. +fn suggest_ref_mut(tcx: TyCtxt<'_>, binding_span: Span) -> Option { + let hi_src = tcx.sess.source_map().span_to_snippet(binding_span).ok()?; + if hi_src.starts_with("ref") + && hi_src["ref".len()..].starts_with(rustc_lexer::is_whitespace) + { + let replacement = format!("ref mut{}", &hi_src["ref".len()..]); + Some(replacement) + } else { + None + } +} diff --git a/src/librustc_mir/borrow_check/error_reporting.rs b/src/librustc_mir/borrow_check/error_reporting.rs deleted file mode 100644 index 9953d280743..00000000000 --- a/src/librustc_mir/borrow_check/error_reporting.rs +++ /dev/null @@ -1,916 +0,0 @@ -use rustc::hir; -use rustc::hir::def::Namespace; -use rustc::hir::def_id::DefId; -use rustc::hir::GeneratorKind; -use rustc::mir::{ - AggregateKind, Constant, Field, Local, LocalInfo, LocalKind, Location, Operand, - Place, PlaceBase, PlaceRef, ProjectionElem, Rvalue, Statement, StatementKind, - Static, StaticKind, Terminator, TerminatorKind, -}; -use rustc::ty::{self, DefIdTree, Ty, TyCtxt}; -use rustc::ty::layout::VariantIdx; -use rustc::ty::print::Print; -use rustc_errors::DiagnosticBuilder; -use syntax_pos::Span; - -use super::borrow_set::BorrowData; -use super::MirBorrowckCtxt; -use crate::dataflow::move_paths::{InitLocation, LookupResult}; - -pub(super) struct IncludingDowncast(pub(super) bool); - -impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { - /// Adds a suggestion when a closure is invoked twice with a moved variable or when a closure - /// is moved after being invoked. - /// - /// ```text - /// note: closure cannot be invoked more than once because it moves the variable `dict` out of - /// its environment - /// --> $DIR/issue-42065.rs:16:29 - /// | - /// LL | for (key, value) in dict { - /// | ^^^^ - /// ``` - pub(super) fn add_moved_or_invoked_closure_note( - &self, - location: Location, - place: PlaceRef<'cx, 'tcx>, - diag: &mut DiagnosticBuilder<'_>, - ) { - debug!("add_moved_or_invoked_closure_note: location={:?} place={:?}", location, place); - let mut target = place.local_or_deref_local(); - for stmt in &self.body[location.block].statements[location.statement_index..] { - debug!("add_moved_or_invoked_closure_note: stmt={:?} target={:?}", stmt, target); - if let StatementKind::Assign(box(into, Rvalue::Use(from))) = &stmt.kind { - debug!("add_fnonce_closure_note: into={:?} from={:?}", into, from); - match from { - Operand::Copy(ref place) | - Operand::Move(ref place) if target == place.local_or_deref_local() => - target = into.local_or_deref_local(), - _ => {}, - } - } - } - - // Check if we are attempting to call a closure after it has been invoked. - let terminator = self.body[location.block].terminator(); - debug!("add_moved_or_invoked_closure_note: terminator={:?}", terminator); - if let TerminatorKind::Call { - func: Operand::Constant(box Constant { - literal: ty::Const { - ty: &ty::TyS { kind: ty::FnDef(id, _), .. }, - .. - }, - .. - }), - args, - .. - } = &terminator.kind { - debug!("add_moved_or_invoked_closure_note: id={:?}", id); - if self.infcx.tcx.parent(id) == self.infcx.tcx.lang_items().fn_once_trait() { - let closure = match args.first() { - Some(Operand::Copy(ref place)) | - Some(Operand::Move(ref place)) if target == place.local_or_deref_local() => - place.local_or_deref_local().unwrap(), - _ => return, - }; - - debug!("add_moved_or_invoked_closure_note: closure={:?}", closure); - if let ty::Closure(did, _) = self.body.local_decls[closure].ty.kind { - let hir_id = self.infcx.tcx.hir().as_local_hir_id(did).unwrap(); - - if let Some((span, name)) = self.infcx.tcx.typeck_tables_of(did) - .closure_kind_origins() - .get(hir_id) - { - diag.span_note( - *span, - &format!( - "closure cannot be invoked more than once because it moves the \ - variable `{}` out of its environment", - name, - ), - ); - return; - } - } - } - } - - // Check if we are just moving a closure after it has been invoked. - if let Some(target) = target { - if let ty::Closure(did, _) = self.body.local_decls[target].ty.kind { - let hir_id = self.infcx.tcx.hir().as_local_hir_id(did).unwrap(); - - if let Some((span, name)) = self.infcx.tcx.typeck_tables_of(did) - .closure_kind_origins() - .get(hir_id) - { - diag.span_note( - *span, - &format!( - "closure cannot be moved more than once as it is not `Copy` due to \ - moving the variable `{}` out of its environment", - name - ), - ); - } - } - } - } - - /// End-user visible description of `place` if one can be found. If the - /// place is a temporary for instance, None will be returned. - pub(super) fn describe_place(&self, place_ref: PlaceRef<'cx, 'tcx>) -> Option { - self.describe_place_with_options(place_ref, IncludingDowncast(false)) - } - - /// End-user visible description of `place` if one can be found. If the - /// place is a temporary for instance, None will be returned. - /// `IncludingDowncast` parameter makes the function return `Err` if `ProjectionElem` is - /// `Downcast` and `IncludingDowncast` is true - pub(super) fn describe_place_with_options( - &self, - place: PlaceRef<'cx, 'tcx>, - including_downcast: IncludingDowncast, - ) -> Option { - let mut buf = String::new(); - match self.append_place_to_string(place, &mut buf, false, &including_downcast) { - Ok(()) => Some(buf), - Err(()) => None, - } - } - - /// Appends end-user visible description of `place` to `buf`. - fn append_place_to_string( - &self, - place: PlaceRef<'cx, 'tcx>, - buf: &mut String, - mut autoderef: bool, - including_downcast: &IncludingDowncast, - ) -> Result<(), ()> { - match place { - PlaceRef { - base: PlaceBase::Local(local), - projection: [], - } => { - self.append_local_to_string(*local, buf)?; - } - PlaceRef { - base: - PlaceBase::Static(box Static { - kind: StaticKind::Promoted(..), - .. - }), - projection: [], - } => { - buf.push_str("promoted"); - } - PlaceRef { - base: - PlaceBase::Static(box Static { - kind: StaticKind::Static, - def_id, - .. - }), - projection: [], - } => { - buf.push_str(&self.infcx.tcx.item_name(*def_id).to_string()); - } - PlaceRef { - base: &PlaceBase::Local(local), - projection: [ProjectionElem::Deref] - } if self.body.local_decls[local].is_ref_for_guard() => { - self.append_place_to_string( - PlaceRef { - base: &PlaceBase::Local(local), - projection: &[], - }, - buf, - autoderef, - &including_downcast, - )?; - }, - PlaceRef { - base: &PlaceBase::Local(local), - projection: [ProjectionElem::Deref] - } if self.body.local_decls[local].is_ref_to_static() => { - let local_info = &self.body.local_decls[local].local_info; - if let LocalInfo::StaticRef { def_id, .. } = *local_info { - buf.push_str(&self.infcx.tcx.item_name(def_id).as_str()); - } else { - unreachable!(); - } - }, - PlaceRef { - base, - projection: [proj_base @ .., elem], - } => { - match elem { - ProjectionElem::Deref => { - let upvar_field_projection = - self.is_upvar_field_projection(place); - if let Some(field) = upvar_field_projection { - let var_index = field.index(); - let name = self.upvars[var_index].name.to_string(); - if self.upvars[var_index].by_ref { - buf.push_str(&name); - } else { - buf.push_str(&format!("*{}", &name)); - } - } else { - if autoderef { - // FIXME turn this recursion into iteration - self.append_place_to_string( - PlaceRef { - base, - projection: proj_base, - }, - buf, - autoderef, - &including_downcast, - )?; - } else { - match (proj_base, base) { - _ => { - buf.push_str(&"*"); - self.append_place_to_string( - PlaceRef { - base, - projection: proj_base, - }, - buf, - autoderef, - &including_downcast, - )?; - } - } - } - } - } - ProjectionElem::Downcast(..) => { - self.append_place_to_string( - PlaceRef { - base, - projection: proj_base, - }, - buf, - autoderef, - &including_downcast, - )?; - if including_downcast.0 { - return Err(()); - } - } - ProjectionElem::Field(field, _ty) => { - autoderef = true; - - let upvar_field_projection = - self.is_upvar_field_projection(place); - if let Some(field) = upvar_field_projection { - let var_index = field.index(); - let name = self.upvars[var_index].name.to_string(); - buf.push_str(&name); - } else { - let field_name = self.describe_field(PlaceRef { - base, - projection: proj_base, - }, *field); - self.append_place_to_string( - PlaceRef { - base, - projection: proj_base, - }, - buf, - autoderef, - &including_downcast, - )?; - buf.push_str(&format!(".{}", field_name)); - } - } - ProjectionElem::Index(index) => { - autoderef = true; - - self.append_place_to_string( - PlaceRef { - base, - projection: proj_base, - }, - buf, - autoderef, - &including_downcast, - )?; - buf.push_str("["); - if self.append_local_to_string(*index, buf).is_err() { - buf.push_str("_"); - } - buf.push_str("]"); - } - ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. } => { - autoderef = true; - // Since it isn't possible to borrow an element on a particular index and - // then use another while the borrow is held, don't output indices details - // to avoid confusing the end-user - self.append_place_to_string( - PlaceRef { - base, - projection: proj_base, - }, - buf, - autoderef, - &including_downcast, - )?; - buf.push_str(&"[..]"); - } - }; - } - } - - Ok(()) - } - - /// Appends end-user visible description of the `local` place to `buf`. If `local` doesn't have - /// a name, or its name was generated by the compiler, then `Err` is returned - fn append_local_to_string(&self, local: Local, buf: &mut String) -> Result<(), ()> { - let decl = &self.body.local_decls[local]; - match self.local_names[local] { - Some(name) if !decl.from_compiler_desugaring() => { - buf.push_str(&name.as_str()); - Ok(()) - } - _ => Err(()), - } - } - - /// End-user visible description of the `field`nth field of `base` - fn describe_field(&self, place: PlaceRef<'cx, 'tcx>, field: Field) -> String { - // FIXME Place2 Make this work iteratively - match place { - PlaceRef { - base: PlaceBase::Local(local), - projection: [], - } => { - let local = &self.body.local_decls[*local]; - self.describe_field_from_ty(&local.ty, field, None) - } - PlaceRef { - base: PlaceBase::Static(static_), - projection: [], - } => - self.describe_field_from_ty(&static_.ty, field, None), - PlaceRef { - base, - projection: [proj_base @ .., elem], - } => match elem { - ProjectionElem::Deref => { - self.describe_field(PlaceRef { - base, - projection: proj_base, - }, field) - } - ProjectionElem::Downcast(_, variant_index) => { - let base_ty = Place::ty_from( - place.base, - place.projection, - *self.body, - self.infcx.tcx).ty; - self.describe_field_from_ty(&base_ty, field, Some(*variant_index)) - } - ProjectionElem::Field(_, field_type) => { - self.describe_field_from_ty(&field_type, field, None) - } - ProjectionElem::Index(..) - | ProjectionElem::ConstantIndex { .. } - | ProjectionElem::Subslice { .. } => { - self.describe_field(PlaceRef { - base, - projection: proj_base, - }, field) - } - }, - } - } - - /// End-user visible description of the `field_index`nth field of `ty` - fn describe_field_from_ty( - &self, - ty: Ty<'_>, - field: Field, - variant_index: Option - ) -> String { - if ty.is_box() { - // If the type is a box, the field is described from the boxed type - self.describe_field_from_ty(&ty.boxed_ty(), field, variant_index) - } else { - match ty.kind { - ty::Adt(def, _) => { - let variant = if let Some(idx) = variant_index { - assert!(def.is_enum()); - &def.variants[idx] - } else { - def.non_enum_variant() - }; - variant.fields[field.index()] - .ident - .to_string() - }, - ty::Tuple(_) => field.index().to_string(), - ty::Ref(_, ty, _) | ty::RawPtr(ty::TypeAndMut { ty, .. }) => { - self.describe_field_from_ty(&ty, field, variant_index) - } - ty::Array(ty, _) | ty::Slice(ty) => - self.describe_field_from_ty(&ty, field, variant_index), - ty::Closure(def_id, _) | ty::Generator(def_id, _, _) => { - // `tcx.upvars(def_id)` returns an `Option`, which is `None` in case - // the closure comes from another crate. But in that case we wouldn't - // be borrowck'ing it, so we can just unwrap: - let (&var_id, _) = self.infcx.tcx.upvars(def_id).unwrap() - .get_index(field.index()).unwrap(); - - self.infcx.tcx.hir().name(var_id).to_string() - } - _ => { - // Might need a revision when the fields in trait RFC is implemented - // (https://github.com/rust-lang/rfcs/pull/1546) - bug!( - "End-user description not implemented for field access on `{:?}`", - ty - ); - } - } - } - } - - /// Add a note that a type does not implement `Copy` - pub(super) fn note_type_does_not_implement_copy( - &self, - err: &mut DiagnosticBuilder<'a>, - place_desc: &str, - ty: Ty<'tcx>, - span: Option, - ) { - let message = format!( - "move occurs because {} has type `{}`, which does not implement the `Copy` trait", - place_desc, - ty, - ); - if let Some(span) = span { - err.span_label(span, message); - } else { - err.note(&message); - } - } - - pub(super) fn borrowed_content_source( - &self, - deref_base: PlaceRef<'cx, 'tcx>, - ) -> BorrowedContentSource<'tcx> { - let tcx = self.infcx.tcx; - - // Look up the provided place and work out the move path index for it, - // we'll use this to check whether it was originally from an overloaded - // operator. - match self.move_data.rev_lookup.find(deref_base) { - LookupResult::Exact(mpi) | LookupResult::Parent(Some(mpi)) => { - debug!("borrowed_content_source: mpi={:?}", mpi); - - for i in &self.move_data.init_path_map[mpi] { - let init = &self.move_data.inits[*i]; - debug!("borrowed_content_source: init={:?}", init); - // We're only interested in statements that initialized a value, not the - // initializations from arguments. - let loc = match init.location { - InitLocation::Statement(stmt) => stmt, - _ => continue, - }; - - let bbd = &self.body[loc.block]; - let is_terminator = bbd.statements.len() == loc.statement_index; - debug!( - "borrowed_content_source: loc={:?} is_terminator={:?}", - loc, - is_terminator, - ); - if !is_terminator { - continue; - } else if let Some(Terminator { - kind: TerminatorKind::Call { - ref func, - from_hir_call: false, - .. - }, - .. - }) = bbd.terminator { - if let Some(source) = BorrowedContentSource::from_call( - func.ty(*self.body, tcx), - tcx - ) { - return source; - } - } - } - } - // Base is a `static` so won't be from an overloaded operator - _ => (), - }; - - // If we didn't find an overloaded deref or index, then assume it's a - // built in deref and check the type of the base. - let base_ty = Place::ty_from( - deref_base.base, - deref_base.projection, - *self.body, - tcx - ).ty; - if base_ty.is_unsafe_ptr() { - BorrowedContentSource::DerefRawPointer - } else if base_ty.is_mutable_ptr() { - BorrowedContentSource::DerefMutableRef - } else { - BorrowedContentSource::DerefSharedRef - } - } -} - -impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { - /// Return the name of the provided `Ty` (that must be a reference) with a synthesized lifetime - /// name where required. - pub(super) fn get_name_for_ty(&self, ty: Ty<'tcx>, counter: usize) -> String { - let mut s = String::new(); - let mut printer = ty::print::FmtPrinter::new(self.infcx.tcx, &mut s, Namespace::TypeNS); - - // We need to add synthesized lifetimes where appropriate. We do - // this by hooking into the pretty printer and telling it to label the - // lifetimes without names with the value `'0`. - match ty.kind { - ty::Ref(ty::RegionKind::ReLateBound(_, br), _, _) - | ty::Ref( - ty::RegionKind::RePlaceholder(ty::PlaceholderRegion { name: br, .. }), - _, - _, - ) => printer.region_highlight_mode.highlighting_bound_region(*br, counter), - _ => {} - } - - let _ = ty.print(printer); - s - } - - /// Returns the name of the provided `Ty` (that must be a reference)'s region with a - /// synthesized lifetime name where required. - pub(super) fn get_region_name_for_ty(&self, ty: Ty<'tcx>, counter: usize) -> String { - let mut s = String::new(); - let mut printer = ty::print::FmtPrinter::new(self.infcx.tcx, &mut s, Namespace::TypeNS); - - let region = match ty.kind { - ty::Ref(region, _, _) => { - match region { - ty::RegionKind::ReLateBound(_, br) - | ty::RegionKind::RePlaceholder(ty::PlaceholderRegion { name: br, .. }) => { - printer.region_highlight_mode.highlighting_bound_region(*br, counter) - } - _ => {} - } - - region - } - _ => bug!("ty for annotation of borrow region is not a reference"), - }; - - let _ = region.print(printer); - s - } -} - -// The span(s) associated to a use of a place. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub(super) enum UseSpans { - // The access is caused by capturing a variable for a closure. - ClosureUse { - // This is true if the captured variable was from a generator. - generator_kind: Option, - // The span of the args of the closure, including the `move` keyword if - // it's present. - args_span: Span, - // The span of the first use of the captured variable inside the closure. - var_span: Span, - }, - // This access has a single span associated to it: common case. - OtherUse(Span), -} - -impl UseSpans { - pub(super) fn args_or_use(self) -> Span { - match self { - UseSpans::ClosureUse { - args_span: span, .. - } - | UseSpans::OtherUse(span) => span, - } - } - - pub(super) fn var_or_use(self) -> Span { - match self { - UseSpans::ClosureUse { var_span: span, .. } | UseSpans::OtherUse(span) => span, - } - } - - pub(super) fn generator_kind(self) -> Option { - match self { - UseSpans::ClosureUse { generator_kind, .. } => generator_kind, - _ => None, - } - } - - // Add a span label to the arguments of the closure, if it exists. - pub(super) fn args_span_label( - self, - err: &mut DiagnosticBuilder<'_>, - message: impl Into, - ) { - if let UseSpans::ClosureUse { args_span, .. } = self { - err.span_label(args_span, message); - } - } - - // Add a span label to the use of the captured variable, if it exists. - pub(super) fn var_span_label( - self, - err: &mut DiagnosticBuilder<'_>, - message: impl Into, - ) { - if let UseSpans::ClosureUse { var_span, .. } = self { - err.span_label(var_span, message); - } - } - - /// Returns `false` if this place is not used in a closure. - pub(super) fn for_closure(&self) -> bool { - match *self { - UseSpans::ClosureUse { generator_kind, .. } => generator_kind.is_none(), - _ => false, - } - } - - /// Returns `false` if this place is not used in a generator. - pub(super) fn for_generator(&self) -> bool { - match *self { - UseSpans::ClosureUse { generator_kind, .. } => generator_kind.is_some(), - _ => false, - } - } - - /// Describe the span associated with a use of a place. - pub(super) fn describe(&self) -> String { - match *self { - UseSpans::ClosureUse { generator_kind, .. } => if generator_kind.is_some() { - " in generator".to_string() - } else { - " in closure".to_string() - }, - _ => "".to_string(), - } - } - - pub(super) fn or_else(self, if_other: F) -> Self - where - F: FnOnce() -> Self, - { - match self { - closure @ UseSpans::ClosureUse { .. } => closure, - UseSpans::OtherUse(_) => if_other(), - } - } -} - -pub(super) enum BorrowedContentSource<'tcx> { - DerefRawPointer, - DerefMutableRef, - DerefSharedRef, - OverloadedDeref(Ty<'tcx>), - OverloadedIndex(Ty<'tcx>), -} - -impl BorrowedContentSource<'tcx> { - pub(super) fn describe_for_unnamed_place(&self) -> String { - match *self { - BorrowedContentSource::DerefRawPointer => format!("a raw pointer"), - BorrowedContentSource::DerefSharedRef => format!("a shared reference"), - BorrowedContentSource::DerefMutableRef => { - format!("a mutable reference") - } - BorrowedContentSource::OverloadedDeref(ty) => { - if ty.is_rc() { - format!("an `Rc`") - } else if ty.is_arc() { - format!("an `Arc`") - } else { - format!("dereference of `{}`", ty) - } - } - BorrowedContentSource::OverloadedIndex(ty) => format!("index of `{}`", ty), - } - } - - pub(super) fn describe_for_named_place(&self) -> Option<&'static str> { - match *self { - BorrowedContentSource::DerefRawPointer => Some("raw pointer"), - BorrowedContentSource::DerefSharedRef => Some("shared reference"), - BorrowedContentSource::DerefMutableRef => Some("mutable reference"), - // Overloaded deref and index operators should be evaluated into a - // temporary. So we don't need a description here. - BorrowedContentSource::OverloadedDeref(_) - | BorrowedContentSource::OverloadedIndex(_) => None - } - } - - pub(super) fn describe_for_immutable_place(&self) -> String { - match *self { - BorrowedContentSource::DerefRawPointer => format!("a `*const` pointer"), - BorrowedContentSource::DerefSharedRef => format!("a `&` reference"), - BorrowedContentSource::DerefMutableRef => { - bug!("describe_for_immutable_place: DerefMutableRef isn't immutable") - }, - BorrowedContentSource::OverloadedDeref(ty) => { - if ty.is_rc() { - format!("an `Rc`") - } else if ty.is_arc() { - format!("an `Arc`") - } else { - format!("a dereference of `{}`", ty) - } - } - BorrowedContentSource::OverloadedIndex(ty) => format!("an index of `{}`", ty), - } - } - - fn from_call(func: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Option { - match func.kind { - ty::FnDef(def_id, substs) => { - let trait_id = tcx.trait_of_item(def_id)?; - - let lang_items = tcx.lang_items(); - if Some(trait_id) == lang_items.deref_trait() - || Some(trait_id) == lang_items.deref_mut_trait() - { - Some(BorrowedContentSource::OverloadedDeref(substs.type_at(0))) - } else if Some(trait_id) == lang_items.index_trait() - || Some(trait_id) == lang_items.index_mut_trait() - { - Some(BorrowedContentSource::OverloadedIndex(substs.type_at(0))) - } else { - None - } - } - _ => None, - } - } -} - -impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { - /// Finds the spans associated to a move or copy of move_place at location. - pub(super) fn move_spans( - &self, - moved_place: PlaceRef<'cx, 'tcx>, // Could also be an upvar. - location: Location, - ) -> UseSpans { - use self::UseSpans::*; - - let stmt = match self.body[location.block].statements.get(location.statement_index) { - Some(stmt) => stmt, - None => return OtherUse(self.body.source_info(location).span), - }; - - debug!("move_spans: moved_place={:?} location={:?} stmt={:?}", moved_place, location, stmt); - if let StatementKind::Assign( - box(_, Rvalue::Aggregate(ref kind, ref places)) - ) = stmt.kind { - let def_id = match kind { - box AggregateKind::Closure(def_id, _) - | box AggregateKind::Generator(def_id, _, _) => def_id, - _ => return OtherUse(stmt.source_info.span), - }; - - debug!( - "move_spans: def_id={:?} places={:?}", - def_id, places - ); - if let Some((args_span, generator_kind, var_span)) - = self.closure_span(*def_id, moved_place, places) { - return ClosureUse { - generator_kind, - args_span, - var_span, - }; - } - } - - OtherUse(stmt.source_info.span) - } - - /// Finds the span of arguments of a closure (within `maybe_closure_span`) - /// and its usage of the local assigned at `location`. - /// This is done by searching in statements succeeding `location` - /// and originating from `maybe_closure_span`. - pub(super) fn borrow_spans(&self, use_span: Span, location: Location) -> UseSpans { - use self::UseSpans::*; - debug!("borrow_spans: use_span={:?} location={:?}", use_span, location); - - let target = match self.body[location.block] - .statements - .get(location.statement_index) - { - Some(&Statement { - kind: StatementKind::Assign(box(ref place, _)), - .. - }) => { - if let Some(local) = place.as_local() { - local - } else { - return OtherUse(use_span); - } - } - _ => return OtherUse(use_span), - }; - - if self.body.local_kind(target) != LocalKind::Temp { - // operands are always temporaries. - return OtherUse(use_span); - } - - for stmt in &self.body[location.block].statements[location.statement_index + 1..] { - if let StatementKind::Assign( - box(_, Rvalue::Aggregate(ref kind, ref places)) - ) = stmt.kind { - let (def_id, is_generator) = match kind { - box AggregateKind::Closure(def_id, _) => (def_id, false), - box AggregateKind::Generator(def_id, _, _) => (def_id, true), - _ => continue, - }; - - debug!( - "borrow_spans: def_id={:?} is_generator={:?} places={:?}", - def_id, is_generator, places - ); - if let Some((args_span, generator_kind, var_span)) = self.closure_span( - *def_id, Place::from(target).as_ref(), places - ) { - return ClosureUse { - generator_kind, - args_span, - var_span, - }; - } else { - return OtherUse(use_span); - } - } - - if use_span != stmt.source_info.span { - break; - } - } - - OtherUse(use_span) - } - - /// Finds the span of a captured variable within a closure or generator. - fn closure_span( - &self, - def_id: DefId, - target_place: PlaceRef<'cx, 'tcx>, - places: &Vec>, - ) -> Option<(Span, Option, Span)> { - debug!( - "closure_span: def_id={:?} target_place={:?} places={:?}", - def_id, target_place, places - ); - let hir_id = self.infcx.tcx.hir().as_local_hir_id(def_id)?; - let expr = &self.infcx.tcx.hir().expect_expr(hir_id).kind; - debug!("closure_span: hir_id={:?} expr={:?}", hir_id, expr); - if let hir::ExprKind::Closure( - .., body_id, args_span, _ - ) = expr { - for (upvar, place) in self.infcx.tcx.upvars(def_id)?.values().zip(places) { - match place { - Operand::Copy(place) | - Operand::Move(place) if target_place == place.as_ref() => { - debug!("closure_span: found captured local {:?}", place); - let body = self.infcx.tcx.hir().body(*body_id); - let generator_kind = body.generator_kind(); - return Some((*args_span, generator_kind, upvar.span)); - }, - _ => {} - } - } - - } - None - } - - /// Helper to retrieve span(s) of given borrow from the current MIR - /// representation - pub(super) fn retrieve_borrow_spans(&self, borrow: &BorrowData<'_>) -> UseSpans { - let span = self.body.source_info(borrow.reserve_location).span; - self.borrow_spans(span, borrow.reserve_location) - } -} diff --git a/src/librustc_mir/borrow_check/move_errors.rs b/src/librustc_mir/borrow_check/move_errors.rs deleted file mode 100644 index fd779767d7a..00000000000 --- a/src/librustc_mir/borrow_check/move_errors.rs +++ /dev/null @@ -1,587 +0,0 @@ -use rustc::mir::*; -use rustc::ty; -use rustc_errors::{DiagnosticBuilder,Applicability}; -use syntax_pos::Span; - -use crate::borrow_check::MirBorrowckCtxt; -use crate::borrow_check::prefixes::PrefixSet; -use crate::borrow_check::error_reporting::UseSpans; -use crate::dataflow::move_paths::{ - IllegalMoveOrigin, IllegalMoveOriginKind, - LookupResult, MoveError, MovePathIndex, -}; - -// 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, - 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 LocalInfo::User(ClearCrossCrate::Set(BindingForm::Var( - VarBindingForm { - opt_match_place: Some((ref 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.as_ref().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 { - if 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: match_place.clone(), - original_path, - kind, - binds_to, - }); - } - // Error with the pattern - LookupResult::Exact(_) => { - let mpi = match self.move_data.rev_lookup.find(move_from.as_ref()) { - LookupResult::Parent(Some(mpi)) => mpi, - // 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, original_path, kind): (Span, &Place<'tcx>, &IllegalMoveOriginKind<'_>) = - match error { - GroupedMoveError::MovesFromPlace { span, ref original_path, ref kind, .. } | - GroupedMoveError::MovesFromValue { span, ref original_path, ref kind, .. } => { - (span, original_path, kind) - } - GroupedMoveError::OtherIllegalMove { - use_spans, - ref original_path, - ref kind - } => { - (use_spans.args_or_use(), 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::Static => { - unreachable!(); - } - IllegalMoveOriginKind::BorrowedContent { target_place } => { - self.report_cannot_move_from_borrowed_content( - original_path, - target_place, - span, - ) - } - 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); - err.buffer(&mut self.errors_buffer); - } - - fn report_cannot_move_from_static( - &mut self, - place: &Place<'tcx>, - span: Span - ) -> DiagnosticBuilder<'a> { - let description = if place.projection.len() == 1 { - format!("static item `{}`", self.describe_place(place.as_ref()).unwrap()) - } else { - let base_static = PlaceRef { - base: &place.base, - projection: &[ProjectionElem::Deref], - }; - - format!( - "`{:?}` as `{:?}` is a static item", - self.describe_place(place.as_ref()).unwrap(), - self.describe_place(base_static).unwrap(), - ) - }; - - 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, - ) -> DiagnosticBuilder<'a> { - // 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() { - &[ref proj_base @ .., ProjectionElem::Deref] => { - PlaceRef { - base: &deref_target_place.base, - projection: &proj_base, - } - } - _ => bug!("deref_target_place is not a deref projection"), - }; - - if let PlaceRef { - base: PlaceBase::Local(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 == self.mir_def_id && upvar_field.is_some() - => { - let closure_kind_ty = closure_substs - .as_closure().kind_ty(def_id, self.infcx.tcx); - let closure_kind = closure_kind_ty.to_opt_closure_kind(); - let capture_description = match closure_kind { - Some(ty::ClosureKind::Fn) => { - "captured variable in an `Fn` closure" - } - Some(ty::ClosureKind::FnMut) => { - "captured variable in an `FnMut` closure" - } - Some(ty::ClosureKind::FnOnce) => { - bug!("closure kind does not match first argument type") - } - None => bug!("closure kind not inferred by borrowck"), - }; - - let upvar = &self.upvars[upvar_field.unwrap().index()]; - let upvar_hir_id = upvar.var_hir_id; - let upvar_name = upvar.name; - let upvar_span = self.infcx.tcx.hir().span(upvar_hir_id); - - let place_name = self.describe_place(move_place.as_ref()).unwrap(); - - let place_description = if self - .is_upvar_field_projection(move_place.as_ref()) - .is_some() - { - format!("`{}`, a {}", place_name, capture_description) - } else { - format!( - "`{}`, as `{}` is a {}", - place_name, - upvar_name, - 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 - } - _ => { - let source = self.borrowed_content_source(deref_base); - match ( - self.describe_place(move_place.as_ref()), - source.describe_for_named_place(), - ) { - (Some(place_desc), Some(source_desc)) => { - self.cannot_move_out_of( - span, - &format!("`{}` which is behind a {}", place_desc, source_desc), - ) - } - (_, _) => { - self.cannot_move_out_of( - span, - &source.describe_for_unnamed_place(), - ) - } - } - }, - }; - let move_ty = format!( - "{:?}", - move_place.ty(*self.body, self.infcx.tcx).ty, - ); - if let Ok(snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(span) { - let is_option = move_ty.starts_with("std::option::Option"); - let is_result = move_ty.starts_with("std::result::Result"); - if is_option || is_result { - err.span_suggestion( - span, - &format!("consider borrowing the `{}`'s content", if is_option { - "Option" - } else { - "Result" - }), - format!("{}.as_ref()", snippet), - Applicability::MaybeIncorrect, - ); - } - } - err - } - - fn add_move_hints( - &self, - error: GroupedMoveError<'tcx>, - err: &mut DiagnosticBuilder<'a>, - 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 => format!("value"), - }; - - 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 => format!("value"), - }; - self.note_type_does_not_implement_copy( - err, - &place_desc, - place_ty, - Some(span), - ); - - use_spans.args_span_label(err, format!("move out of {} occurs here", place_desc)); - use_spans.var_span_label( - err, - format!("move occurs due to use{}", use_spans.describe()), - ); - }, - } - } - - fn add_move_error_suggestions( - &self, - err: &mut DiagnosticBuilder<'a>, - 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 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 pat_snippet.starts_with('&') { - let pat_snippet = pat_snippet[1..].trim_start(); - let suggestion; - let to_remove; - if pat_snippet.starts_with("mut") - && pat_snippet["mut".len()..].starts_with(rustc_lexer::is_whitespace) - { - suggestion = pat_snippet["mut".len()..].trim_start(); - to_remove = "&mut"; - } else { - suggestion = pat_snippet; - to_remove = "&"; - } - 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 DiagnosticBuilder<'a>, - binds_to: &[Local], - ) { - for (j, local) in binds_to.into_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", - ); - } - } -} diff --git a/src/librustc_mir/borrow_check/mutability_errors.rs b/src/librustc_mir/borrow_check/mutability_errors.rs deleted file mode 100644 index 98a7b101d56..00000000000 --- a/src/librustc_mir/borrow_check/mutability_errors.rs +++ /dev/null @@ -1,656 +0,0 @@ -use rustc::hir; -use rustc::hir::Node; -use rustc::mir::{self, ClearCrossCrate, Local, LocalInfo, Location, ReadOnlyBodyCache}; -use rustc::mir::{Mutability, Place, PlaceRef, PlaceBase, ProjectionElem}; -use rustc::ty::{self, Ty, TyCtxt}; -use rustc_index::vec::Idx; -use syntax_pos::Span; -use syntax_pos::symbol::kw; - -use crate::borrow_check::MirBorrowckCtxt; -use crate::borrow_check::error_reporting::BorrowedContentSource; -use crate::util::collect_writes::FindAssignments; -use rustc_errors::Applicability; - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub(super) enum AccessKind { - MutableBorrow, - Mutate, -} - -impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { - pub(super) fn report_mutability_error( - &mut self, - access_place: &Place<'tcx>, - span: Span, - the_place_err: PlaceRef<'cx, 'tcx>, - error_access: AccessKind, - location: Location, - ) { - debug!( - "report_mutability_error(\ - access_place={:?}, span={:?}, the_place_err={:?}, error_access={:?}, location={:?},\ - )", - access_place, span, the_place_err, error_access, location, - ); - - let mut err; - let item_msg; - let reason; - let mut opt_source = None; - let access_place_desc = self.describe_place(access_place.as_ref()); - debug!("report_mutability_error: access_place_desc={:?}", access_place_desc); - - match the_place_err { - PlaceRef { - base: PlaceBase::Local(local), - projection: [], - } => { - item_msg = format!("`{}`", access_place_desc.unwrap()); - if access_place.as_local().is_some() { - reason = ", as it is not declared as mutable".to_string(); - } else { - let name = self.local_names[*local] - .expect("immutable unnamed local"); - reason = format!(", as `{}` is not declared as mutable", name); - } - } - - PlaceRef { - base: _, - projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)], - } => { - debug_assert!(is_closure_or_generator( - Place::ty_from( - &the_place_err.base, - proj_base, - *self.body, - self.infcx.tcx - ).ty)); - - item_msg = format!("`{}`", access_place_desc.unwrap()); - if self.is_upvar_field_projection(access_place.as_ref()).is_some() { - reason = ", as it is not declared as mutable".to_string(); - } else { - let name = self.upvars[upvar_index.index()].name; - reason = format!(", as `{}` is not declared as mutable", name); - } - } - - PlaceRef { - base: &PlaceBase::Local(local), - projection: [ProjectionElem::Deref], - } if self.body.local_decls[local].is_ref_for_guard() => { - item_msg = format!("`{}`", access_place_desc.unwrap()); - reason = ", as it is immutable for the pattern guard".to_string(); - } - PlaceRef { - base: &PlaceBase::Local(local), - projection: [ProjectionElem::Deref], - } if self.body.local_decls[local].is_ref_to_static() => { - if access_place.projection.len() == 1 { - item_msg = format!("immutable static item `{}`", access_place_desc.unwrap()); - reason = String::new(); - } else { - item_msg = format!("`{}`", access_place_desc.unwrap()); - let local_info = &self.body.local_decls[local].local_info; - if let LocalInfo::StaticRef { def_id, .. } = *local_info { - let static_name = &self.infcx.tcx.item_name(def_id); - reason = format!(", as `{}` is an immutable static item", static_name); - } else { - bug!("is_ref_to_static return true, but not ref to static?"); - } - } - } - PlaceRef { - base: _, - projection: [proj_base @ .., ProjectionElem::Deref], - } => { - if the_place_err.base == &PlaceBase::Local(Local::new(1)) && - proj_base.is_empty() && - !self.upvars.is_empty() { - item_msg = format!("`{}`", access_place_desc.unwrap()); - debug_assert!(self.body.local_decls[Local::new(1)].ty.is_region_ptr()); - debug_assert!(is_closure_or_generator( - Place::ty_from( - the_place_err.base, - the_place_err.projection, - *self.body, - self.infcx.tcx - ) - .ty - )); - - reason = - if self.is_upvar_field_projection(access_place.as_ref()).is_some() { - ", as it is a captured variable in a `Fn` closure".to_string() - } else { - ", as `Fn` closures cannot mutate their captured variables".to_string() - } - } else { - let source = self.borrowed_content_source(PlaceRef { - base: the_place_err.base, - projection: proj_base, - }); - let pointer_type = source.describe_for_immutable_place(); - opt_source = Some(source); - if let Some(desc) = access_place_desc { - item_msg = format!("`{}`", desc); - reason = match error_access { - AccessKind::Mutate => format!(" which is behind {}", pointer_type), - AccessKind::MutableBorrow => { - format!(", as it is behind {}", pointer_type) - } - } - } else { - item_msg = format!("data in {}", pointer_type); - reason = String::new(); - } - } - } - - PlaceRef { - base: PlaceBase::Static(_), - .. - } - | PlaceRef { - base: _, - projection: [.., ProjectionElem::Index(_)], - } - | PlaceRef { - base: _, - projection: [.., ProjectionElem::ConstantIndex { .. }], - } - | PlaceRef { - base: _, - projection: [.., ProjectionElem::Subslice { .. }], - } - | PlaceRef { - base: _, - projection: [.., ProjectionElem::Downcast(..)], - } => bug!("Unexpected immutable place."), - } - - debug!("report_mutability_error: item_msg={:?}, reason={:?}", item_msg, reason); - - // `act` and `acted_on` are strings that let us abstract over - // the verbs used in some diagnostic messages. - let act; - let acted_on; - - let span = match error_access { - AccessKind::Mutate => { - err = self.cannot_assign(span, &(item_msg + &reason)); - act = "assign"; - acted_on = "written"; - span - } - AccessKind::MutableBorrow => { - act = "borrow as mutable"; - acted_on = "borrowed as mutable"; - - let borrow_spans = self.borrow_spans(span, location); - let borrow_span = borrow_spans.args_or_use(); - err = self.cannot_borrow_path_as_mutable_because( - borrow_span, - &item_msg, - &reason, - ); - borrow_spans.var_span_label( - &mut err, - format!( - "mutable borrow occurs due to use of `{}` in closure", - // always Some() if the message is printed. - self.describe_place(access_place.as_ref()).unwrap_or_default(), - ) - ); - borrow_span - } - }; - - debug!("report_mutability_error: act={:?}, acted_on={:?}", act, acted_on); - - match the_place_err { - // Suggest making an existing shared borrow in a struct definition a mutable borrow. - // - // This is applicable when we have a deref of a field access to a deref of a local - - // something like `*((*_1).0`. The local that we get will be a reference to the - // struct we've got a field access of (it must be a reference since there's a deref - // after the field access). - PlaceRef { - base, - projection: [proj_base @ .., - ProjectionElem::Deref, - ProjectionElem::Field(field, _), - ProjectionElem::Deref, - ], - } => { - err.span_label(span, format!("cannot {ACT}", ACT = act)); - - if let Some((span, message)) = annotate_struct_field( - self.infcx.tcx, - Place::ty_from(base, proj_base, *self.body, self.infcx.tcx).ty, - field, - ) { - err.span_suggestion( - span, - "consider changing this to be mutable", - message, - Applicability::MaybeIncorrect, - ); - } - }, - - // Suggest removing a `&mut` from the use of a mutable reference. - PlaceRef { - base: PlaceBase::Local(local), - projection: [], - } if { - self.body.local_decls.get(*local).map(|local_decl| { - if let LocalInfo::User(ClearCrossCrate::Set( - mir::BindingForm::ImplicitSelf(kind) - )) = local_decl.local_info { - // Check if the user variable is a `&mut self` and we can therefore - // suggest removing the `&mut`. - // - // Deliberately fall into this case for all implicit self types, - // so that we don't fall in to the next case with them. - kind == mir::ImplicitSelfKind::MutRef - } else if Some(kw::SelfLower) == self.local_names[*local] { - // Otherwise, check if the name is the self kewyord - in which case - // we have an explicit self. Do the same thing in this case and check - // for a `self: &mut Self` to suggest removing the `&mut`. - if let ty::Ref( - _, _, hir::Mutability::Mutable - ) = local_decl.ty.kind { - true - } else { - false - } - } else { - false - } - }).unwrap_or(false) - } => { - err.span_label(span, format!("cannot {ACT}", ACT = act)); - err.span_label(span, "try removing `&mut` here"); - }, - - // We want to suggest users use `let mut` for local (user - // variable) mutations... - PlaceRef { - base: PlaceBase::Local(local), - projection: [], - } if self.body.local_decls[*local].can_be_made_mutable() => { - // ... but it doesn't make sense to suggest it on - // variables that are `ref x`, `ref mut x`, `&self`, - // or `&mut self` (such variables are simply not - // mutable). - let local_decl = &self.body.local_decls[*local]; - assert_eq!(local_decl.mutability, Mutability::Not); - - err.span_label(span, format!("cannot {ACT}", ACT = act)); - err.span_suggestion( - local_decl.source_info.span, - "consider changing this to be mutable", - format!("mut {}", self.local_names[*local].unwrap()), - Applicability::MachineApplicable, - ); - } - - // Also suggest adding mut for upvars - PlaceRef { - base, - projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)], - } => { - debug_assert!(is_closure_or_generator( - Place::ty_from(base, proj_base, *self.body, self.infcx.tcx).ty - )); - - err.span_label(span, format!("cannot {ACT}", ACT = act)); - - let upvar_hir_id = self.upvars[upvar_index.index()].var_hir_id; - if let Some(Node::Binding(pat)) = self.infcx.tcx.hir().find(upvar_hir_id) - { - if let hir::PatKind::Binding( - hir::BindingAnnotation::Unannotated, - _, - upvar_ident, - _, - ) = pat.kind - { - err.span_suggestion( - upvar_ident.span, - "consider changing this to be mutable", - format!("mut {}", upvar_ident.name), - Applicability::MachineApplicable, - ); - } - } - } - - // complete hack to approximate old AST-borrowck - // diagnostic: if the span starts with a mutable borrow of - // a local variable, then just suggest the user remove it. - PlaceRef { - base: PlaceBase::Local(_), - projection: [], - } if { - if let Ok(snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(span) { - snippet.starts_with("&mut ") - } else { - false - } - } => - { - err.span_label(span, format!("cannot {ACT}", ACT = act)); - err.span_label(span, "try removing `&mut` here"); - } - - PlaceRef { - base: PlaceBase::Local(local), - projection: [ProjectionElem::Deref], - } if self.body.local_decls[*local].is_ref_for_guard() => { - err.span_label(span, format!("cannot {ACT}", ACT = act)); - err.note( - "variables bound in patterns are immutable until the end of the pattern guard", - ); - } - - // We want to point out when a `&` can be readily replaced - // with an `&mut`. - // - // FIXME: can this case be generalized to work for an - // arbitrary base for the projection? - PlaceRef { - base: PlaceBase::Local(local), - projection: [ProjectionElem::Deref], - } if self.body.local_decls[*local].is_user_variable() => - { - let local_decl = &self.body.local_decls[*local]; - let suggestion = match local_decl.local_info { - LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::ImplicitSelf(_))) => { - Some(suggest_ampmut_self(self.infcx.tcx, local_decl)) - } - - LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var( - mir::VarBindingForm { - binding_mode: ty::BindingMode::BindByValue(_), - opt_ty_info, - .. - }, - ))) => Some(suggest_ampmut( - self.infcx.tcx, - self.body, - *local, - local_decl, - opt_ty_info, - )), - - LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var( - mir::VarBindingForm { - binding_mode: ty::BindingMode::BindByReference(_), - .. - }, - ))) => { - let pattern_span = local_decl.source_info.span; - suggest_ref_mut(self.infcx.tcx, pattern_span) - .map(|replacement| (pattern_span, replacement)) - } - - LocalInfo::User(ClearCrossCrate::Clear) => bug!("saw cleared local state"), - - _ => unreachable!(), - }; - - let (pointer_sigil, pointer_desc) = if local_decl.ty.is_region_ptr() { - ("&", "reference") - } else { - ("*const", "pointer") - }; - - if let Some((err_help_span, suggested_code)) = suggestion { - err.span_suggestion( - err_help_span, - &format!("consider changing this to be a mutable {}", pointer_desc), - suggested_code, - Applicability::MachineApplicable, - ); - } - - match self.local_names[*local] { - Some(name) if !local_decl.from_compiler_desugaring() => { - err.span_label( - span, - format!( - "`{NAME}` is a `{SIGIL}` {DESC}, \ - so the data it refers to cannot be {ACTED_ON}", - NAME = name, - SIGIL = pointer_sigil, - DESC = pointer_desc, - ACTED_ON = acted_on - ), - ); - } - _ => { - err.span_label( - span, - format!( - "cannot {ACT} through `{SIGIL}` {DESC}", - ACT = act, - SIGIL = pointer_sigil, - DESC = pointer_desc - ), - ); - } - } - } - - PlaceRef { - base, - projection: [ProjectionElem::Deref], - // FIXME document what is this 1 magic number about - } if *base == PlaceBase::Local(Local::new(1)) && - !self.upvars.is_empty() => - { - err.span_label(span, format!("cannot {ACT}", ACT = act)); - err.span_help( - self.body.span, - "consider changing this to accept closures that implement `FnMut`" - ); - } - - PlaceRef { - base: _, - projection: [.., ProjectionElem::Deref], - } => { - err.span_label(span, format!("cannot {ACT}", ACT = act)); - - match opt_source { - Some(BorrowedContentSource::OverloadedDeref(ty)) => { - err.help( - &format!( - "trait `DerefMut` is required to modify through a dereference, \ - but it is not implemented for `{}`", - ty, - ), - ); - }, - Some(BorrowedContentSource::OverloadedIndex(ty)) => { - err.help( - &format!( - "trait `IndexMut` is required to modify indexed content, \ - but it is not implemented for `{}`", - ty, - ), - ); - } - _ => (), - } - } - - _ => { - err.span_label(span, format!("cannot {ACT}", ACT = act)); - } - } - - err.buffer(&mut self.errors_buffer); - } -} - -fn suggest_ampmut_self<'tcx>( - tcx: TyCtxt<'tcx>, - local_decl: &mir::LocalDecl<'tcx>, -) -> (Span, String) { - let sp = local_decl.source_info.span; - (sp, match tcx.sess.source_map().span_to_snippet(sp) { - Ok(snippet) => { - let lt_pos = snippet.find('\''); - if let Some(lt_pos) = lt_pos { - format!("&{}mut self", &snippet[lt_pos..snippet.len() - 4]) - } else { - "&mut self".to_string() - } - } - _ => "&mut self".to_string() - }) -} - -// When we want to suggest a user change a local variable to be a `&mut`, there -// are three potential "obvious" things to highlight: -// -// let ident [: Type] [= RightHandSideExpression]; -// ^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ -// (1.) (2.) (3.) -// -// We can always fallback on highlighting the first. But chances are good that -// the user experience will be better if we highlight one of the others if possible; -// for example, if the RHS is present and the Type is not, then the type is going to -// be inferred *from* the RHS, which means we should highlight that (and suggest -// that they borrow the RHS mutably). -// -// This implementation attempts to emulate AST-borrowck prioritization -// by trying (3.), then (2.) and finally falling back on (1.). -fn suggest_ampmut<'tcx>( - tcx: TyCtxt<'tcx>, - body: ReadOnlyBodyCache<'_, 'tcx>, - local: Local, - local_decl: &mir::LocalDecl<'tcx>, - opt_ty_info: Option, -) -> (Span, String) { - let locations = body.find_assignments(local); - if !locations.is_empty() { - let assignment_rhs_span = body.source_info(locations[0]).span; - if let Ok(src) = tcx.sess.source_map().span_to_snippet(assignment_rhs_span) { - if let (true, Some(ws_pos)) = ( - src.starts_with("&'"), - src.find(|c: char| -> bool { c.is_whitespace() }), - ) { - let lt_name = &src[1..ws_pos]; - let ty = &src[ws_pos..]; - return (assignment_rhs_span, format!("&{} mut {}", lt_name, ty)); - } else if src.starts_with('&') { - let borrowed_expr = &src[1..]; - return (assignment_rhs_span, format!("&mut {}", borrowed_expr)); - } - } - } - - let highlight_span = match opt_ty_info { - // if this is a variable binding with an explicit type, - // try to highlight that for the suggestion. - Some(ty_span) => ty_span, - - // otherwise, just highlight the span associated with - // the (MIR) LocalDecl. - None => local_decl.source_info.span, - }; - - if let Ok(src) = tcx.sess.source_map().span_to_snippet(highlight_span) { - if let (true, Some(ws_pos)) = ( - src.starts_with("&'"), - src.find(|c: char| -> bool { c.is_whitespace() }), - ) { - let lt_name = &src[1..ws_pos]; - let ty = &src[ws_pos..]; - return (highlight_span, format!("&{} mut{}", lt_name, ty)); - } - } - - let ty_mut = local_decl.ty.builtin_deref(true).unwrap(); - assert_eq!(ty_mut.mutbl, hir::Mutability::Immutable); - (highlight_span, - if local_decl.ty.is_region_ptr() { - format!("&mut {}", ty_mut.ty) - } else { - format!("*mut {}", ty_mut.ty) - }) -} - -fn is_closure_or_generator(ty: Ty<'_>) -> bool { - ty.is_closure() || ty.is_generator() -} - -/// Adds a suggestion to a struct definition given a field access to a local. -/// This function expects the local to be a reference to a struct in order to produce a suggestion. -/// -/// ```text -/// LL | s: &'a String -/// | ---------- use `&'a mut String` here to make mutable -/// ``` -fn annotate_struct_field( - tcx: TyCtxt<'tcx>, - ty: Ty<'tcx>, - field: &mir::Field, -) -> Option<(Span, String)> { - // Expect our local to be a reference to a struct of some kind. - if let ty::Ref(_, ty, _) = ty.kind { - if let ty::Adt(def, _) = ty.kind { - let field = def.all_fields().nth(field.index())?; - // Use the HIR types to construct the diagnostic message. - let hir_id = tcx.hir().as_local_hir_id(field.did)?; - let node = tcx.hir().find(hir_id)?; - // Now we're dealing with the actual struct that we're going to suggest a change to, - // we can expect a field that is an immutable reference to a type. - if let hir::Node::Field(field) = node { - if let hir::TyKind::Rptr(lifetime, hir::MutTy { - mutbl: hir::Mutability::Immutable, - ref ty - }) = field.ty.kind { - // Get the snippets in two parts - the named lifetime (if there is one) and - // type being referenced, that way we can reconstruct the snippet without loss - // of detail. - let type_snippet = tcx.sess.source_map().span_to_snippet(ty.span).ok()?; - let lifetime_snippet = if !lifetime.is_elided() { - format!("{} ", tcx.sess.source_map().span_to_snippet(lifetime.span).ok()?) - } else { - String::new() - }; - - return Some(( - field.ty.span, - format!( - "&{}mut {}", - lifetime_snippet, &*type_snippet, - ), - )); - } - } - } - } - - None -} - -/// If possible, suggest replacing `ref` with `ref mut`. -fn suggest_ref_mut(tcx: TyCtxt<'_>, binding_span: Span) -> Option { - let hi_src = tcx.sess.source_map().span_to_snippet(binding_span).ok()?; - if hi_src.starts_with("ref") - && hi_src["ref".len()..].starts_with(rustc_lexer::is_whitespace) - { - let replacement = format!("ref mut{}", &hi_src["ref".len()..]); - Some(replacement) - } else { - None - } -} -- cgit 1.4.1-3-g733a5