From: Mazdak Farrokhzad Date: Thu, 5 Dec 2019 18:03:21 +0000 (+0100) Subject: Rollup merge of #67055 - lqd:const_qualif, r=oli-obk X-Git-Url: https://git.lizzy.rs/?a=commitdiff_plain;h=a008aff0758268c03496d25686cf74e270cef8ba;hp=2d83b7608070d6ad250e8cd6d9d5a7d4be628dc4;p=rust.git Rollup merge of #67055 - lqd:const_qualif, r=oli-obk Make const-qualification look at more `const fn`s As explained in a lot more detail in #67053 this makes const-qualification not ignore the unstable const fns in libcore. r? @oli-obk cc @ecstatic-morse (Still a bit unsure about the `cfg`s here, for bootstrapping, does that seem correct ?) Fixes #67053. --- diff --git a/Cargo.lock b/Cargo.lock index 5e83513af5b..26727c5c1db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3806,7 +3806,6 @@ dependencies = [ "rustc_errors", "rustc_feature", "rustc_lexer", - "rustc_target", "smallvec 1.0.0", "syntax", "syntax_pos", diff --git a/src/liballoc/rc.rs b/src/liballoc/rc.rs index 2254cde7f49..1ff1c3c834f 100644 --- a/src/liballoc/rc.rs +++ b/src/liballoc/rc.rs @@ -1648,10 +1648,8 @@ pub fn new() -> Weak { /// Returns a raw pointer to the object `T` pointed to by this `Weak`. /// - /// It is up to the caller to ensure that the object is still alive when accessing it through - /// the pointer. - /// - /// The pointer may be [`null`] or be dangling in case the object has already been destroyed. + /// The pointer is valid only if there are some strong references. The pointer may be dangling + /// or even [`null`] otherwise. /// /// # Examples /// @@ -1731,14 +1729,18 @@ pub fn into_raw(self) -> *const T { /// This can be used to safely get a strong reference (by calling [`upgrade`] /// later) or to deallocate the weak count by dropping the `Weak`. /// - /// It takes ownership of one weak count. In case a [`null`] is passed, a dangling [`Weak`] is - /// returned. + /// It takes ownership of one weak count (with the exception of pointers created by [`new`], + /// as these don't have any corresponding weak count). /// /// # Safety /// - /// The pointer must represent one valid weak count. In other words, it must point to `T` which - /// is or *was* managed by an [`Rc`] and the weak count of that [`Rc`] must not have reached - /// 0. It is allowed for the strong count to be 0. + /// The pointer must have originated from the [`into_raw`] (or [`as_raw`], provided there was + /// a corresponding [`forget`] on the `Weak`) and must still own its potential weak reference + /// count. + /// + /// It is allowed for the strong count to be 0 at the time of calling this, but the weak count + /// must be non-zero or the pointer must have originated from a dangling `Weak` (one created + /// by [`new`]). /// /// # Examples /// @@ -1763,11 +1765,13 @@ pub fn into_raw(self) -> *const T { /// assert!(unsafe { Weak::from_raw(raw_2) }.upgrade().is_none()); /// ``` /// - /// [`null`]: ../../std/ptr/fn.null.html /// [`into_raw`]: struct.Weak.html#method.into_raw /// [`upgrade`]: struct.Weak.html#method.upgrade /// [`Rc`]: struct.Rc.html /// [`Weak`]: struct.Weak.html + /// [`as_raw`]: struct.Weak.html#method.as_raw + /// [`new`]: struct.Weak.html#method.new + /// [`forget`]: ../../std/mem/fn.forget.html #[unstable(feature = "weak_into_raw", issue = "60728")] pub unsafe fn from_raw(ptr: *const T) -> Self { if ptr.is_null() { diff --git a/src/liballoc/sync.rs b/src/liballoc/sync.rs index 7bf2ff13615..19b0086fa33 100644 --- a/src/liballoc/sync.rs +++ b/src/liballoc/sync.rs @@ -1324,10 +1324,8 @@ pub fn new() -> Weak { /// Returns a raw pointer to the object `T` pointed to by this `Weak`. /// - /// It is up to the caller to ensure that the object is still alive when accessing it through - /// the pointer. - /// - /// The pointer may be [`null`] or be dangling in case the object has already been destroyed. + /// The pointer is valid only if there are some strong references. The pointer may be dangling + /// or even [`null`] otherwise. /// /// # Examples /// @@ -1408,14 +1406,18 @@ pub fn into_raw(self) -> *const T { /// This can be used to safely get a strong reference (by calling [`upgrade`] /// later) or to deallocate the weak count by dropping the `Weak`. /// - /// It takes ownership of one weak count. In case a [`null`] is passed, a dangling [`Weak`] is - /// returned. + /// It takes ownership of one weak count (with the exception of pointers created by [`new`], + /// as these don't have any corresponding weak count). /// /// # Safety /// - /// The pointer must represent one valid weak count. In other words, it must point to `T` which - /// is or *was* managed by an [`Arc`] and the weak count of that [`Arc`] must not have reached - /// 0. It is allowed for the strong count to be 0. + /// The pointer must have originated from the [`into_raw`] (or [`as_raw'], provided there was + /// a corresponding [`forget`] on the `Weak`) and must still own its potential weak reference + /// count. + /// + /// It is allowed for the strong count to be 0 at the time of calling this, but the weak count + /// must be non-zero or the pointer must have originated from a dangling `Weak` (one created + /// by [`new`]). /// /// # Examples /// @@ -1440,11 +1442,13 @@ pub fn into_raw(self) -> *const T { /// assert!(unsafe { Weak::from_raw(raw_2) }.upgrade().is_none()); /// ``` /// - /// [`null`]: ../../std/ptr/fn.null.html + /// [`as_raw`]: struct.Weak.html#method.as_raw + /// [`new`]: struct.Weak.html#method.new /// [`into_raw`]: struct.Weak.html#method.into_raw /// [`upgrade`]: struct.Weak.html#method.upgrade /// [`Weak`]: struct.Weak.html /// [`Arc`]: struct.Arc.html + /// [`forget`]: ../../std/mem/fn.forget.html #[unstable(feature = "weak_into_raw", issue = "60728")] pub unsafe fn from_raw(ptr: *const T) -> Self { if ptr.is_null() { diff --git a/src/libcore/str/mod.rs b/src/libcore/str/mod.rs index 25b7eec5b33..b2a420f3c43 100644 --- a/src/libcore/str/mod.rs +++ b/src/libcore/str/mod.rs @@ -3371,8 +3371,8 @@ pub fn rsplitn<'a, P>(&'a self, n: usize, pat: P) -> RSplitN<'a, P> /// An iterator over the disjoint matches of a pattern within the given string /// slice. /// - /// The pattern can be any type that implements the Pattern trait. Notable - /// examples are `&str`, [`char`], and closures that determines the split. + /// The pattern can be a `&str`, [`char`], or a closure that determines if + /// a character matches. /// /// # Iterator behavior /// diff --git a/src/libcore/sync/atomic.rs b/src/libcore/sync/atomic.rs index 251d49db062..6e1aac00c7b 100644 --- a/src/libcore/sync/atomic.rs +++ b/src/libcore/sync/atomic.rs @@ -27,7 +27,7 @@ //! //! Atomic variables are safe to share between threads (they implement [`Sync`]) //! but they do not themselves provide the mechanism for sharing and follow the -//! [threading model](../../../std/thread/index.html#the-threading-model) of rust. +//! [threading model](../../../std/thread/index.html#the-threading-model) of Rust. //! The most common way to share an atomic variable is to put it into an [`Arc`][arc] (an //! atomically-reference-counted shared pointer). //! diff --git a/src/librustc/infer/error_reporting/mod.rs b/src/librustc/infer/error_reporting/mod.rs index 5a940f2f80a..58c1498faa9 100644 --- a/src/librustc/infer/error_reporting/mod.rs +++ b/src/librustc/infer/error_reporting/mod.rs @@ -1809,12 +1809,17 @@ fn report_sub_sup_conflict( sub_region, "...", ); - err.note(&format!( - "...so that the {}:\nexpected {}\n found {}", - sup_trace.cause.as_requirement_str(), - sup_expected.content(), - sup_found.content() + err.span_note(sup_trace.cause.span, &format!( + "...so that the {}", + sup_trace.cause.as_requirement_str() )); + + err.note_expected_found( + &"", + sup_expected, + &"", + sup_found + ); err.emit(); return; } diff --git a/src/librustc/infer/error_reporting/note.rs b/src/librustc/infer/error_reporting/note.rs index c1f840ad678..4b933735fc7 100644 --- a/src/librustc/infer/error_reporting/note.rs +++ b/src/librustc/infer/error_reporting/note.rs @@ -13,12 +13,20 @@ pub(super) fn note_region_origin(&self, match *origin { infer::Subtype(ref trace) => { if let Some((expected, found)) = self.values_str(&trace.values) { - let expected = expected.content(); - let found = found.content(); - err.note(&format!("...so that the {}:\nexpected {}\n found {}", - trace.cause.as_requirement_str(), - expected, - found)); + err.span_note( + trace.cause.span, + &format!( + "...so that the {}", + trace.cause.as_requirement_str() + ) + ); + + err.note_expected_found( + &"", + expected, + &"", + found + ); } else { // FIXME: this really should be handled at some earlier stage. Our // handling of region checking when type errors are present is diff --git a/src/librustc/ty/print/pretty.rs b/src/librustc/ty/print/pretty.rs index fff2f06e87b..745f7d0276d 100644 --- a/src/librustc/ty/print/pretty.rs +++ b/src/librustc/ty/print/pretty.rs @@ -1282,6 +1282,9 @@ fn path_append( if !self.empty_path { write!(self, "::")?; } + if ast::Ident::from_str(&name).is_raw_guess() { + write!(self, "r#")?; + } write!(self, "{}", name)?; // FIXME(eddyb) this will print e.g. `{{closure}}#3`, but it diff --git a/src/librustc_codegen_llvm/back/write.rs b/src/librustc_codegen_llvm/back/write.rs index 48bbc130723..a05ba9c78e0 100644 --- a/src/librustc_codegen_llvm/back/write.rs +++ b/src/librustc_codegen_llvm/back/write.rs @@ -588,14 +588,11 @@ extern "C" fn demangle_callback(input_ptr: *const c_char, cursor.position() as size_t } - with_codegen(tm, llmod, config.no_builtins, |cpm| { - let result = - llvm::LLVMRustPrintModule(cpm, llmod, out_c.as_ptr(), demangle_callback); - llvm::LLVMDisposePassManager(cpm); - result.into_result().map_err(|()| { - let msg = format!("failed to write LLVM IR to {}", out.display()); - llvm_err(diag_handler, &msg) - }) + let result = + llvm::LLVMRustPrintModule(llmod, out_c.as_ptr(), demangle_callback); + result.into_result().map_err(|()| { + let msg = format!("failed to write LLVM IR to {}", out.display()); + llvm_err(diag_handler, &msg) })?; } diff --git a/src/librustc_codegen_llvm/llvm/ffi.rs b/src/librustc_codegen_llvm/llvm/ffi.rs index fd31e65c9d3..5362d180d1b 100644 --- a/src/librustc_codegen_llvm/llvm/ffi.rs +++ b/src/librustc_codegen_llvm/llvm/ffi.rs @@ -1727,8 +1727,7 @@ pub fn LLVMRustWriteOutputFile(T: &'a TargetMachine, Output: *const c_char, FileType: FileType) -> LLVMRustResult; - pub fn LLVMRustPrintModule(PM: &PassManager<'a>, - M: &'a Module, + pub fn LLVMRustPrintModule(M: &'a Module, Output: *const c_char, Demangle: extern fn(*const c_char, size_t, 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..d14957b9017 --- /dev/null +++ b/src/librustc_mir/borrow_check/diagnostics/conflict_errors.rs @@ -0,0 +1,2160 @@ +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 crate::dataflow::drop_flag_effects; +use crate::dataflow::indexes::{MovePathIndex, MoveOutIndex}; +use crate::util::borrowck_errors; + +use crate::borrow_check::{ + prefixes::IsPrefixOf, + WriteKind, + borrow_set::BorrowData, + MirBorrowckCtxt, InitializationRequiringAction, PrefixSet +}; + +use super::{ + IncludingDowncast, UseSpans, RegionName, RegionNameSource, + explain_borrow::BorrowExplanation, +}; + +#[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(in crate::borrow_check) 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(in crate::borrow_check) 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(in crate::borrow_check) 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(in crate::borrow_check) 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(in crate::borrow_check) 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(in crate::borrow_check) 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(in crate::borrow_check) 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(in crate::borrow_check) 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(in crate::borrow_check) 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/explain_borrow.rs b/src/librustc_mir/borrow_check/diagnostics/explain_borrow.rs new file mode 100644 index 00000000000..67c3c36e73e --- /dev/null +++ b/src/librustc_mir/borrow_check/diagnostics/explain_borrow.rs @@ -0,0 +1,668 @@ +use std::collections::VecDeque; + +use crate::borrow_check::borrow_set::BorrowData; +use crate::borrow_check::nll::region_infer::Cause; +use crate::borrow_check::nll::ConstraintDescription; +use crate::borrow_check::{MirBorrowckCtxt, WriteKind}; +use rustc::mir::{ + CastKind, ConstraintCategory, FakeReadCause, Local, Location, Body, Operand, Place, Rvalue, + Statement, StatementKind, TerminatorKind, +}; +use rustc::ty::{self, TyCtxt}; +use rustc::ty::adjustment::{PointerCast}; +use rustc_index::vec::IndexVec; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::DiagnosticBuilder; +use syntax_pos::Span; +use syntax_pos::symbol::Symbol; + +use super::{UseSpans, find_use, RegionName}; + +#[derive(Debug)] +pub(in crate::borrow_check) enum BorrowExplanation { + UsedLater(LaterUseKind, Span), + UsedLaterInLoop(LaterUseKind, Span), + UsedLaterWhenDropped { + drop_loc: Location, + dropped_local: Local, + should_note_order: bool, + }, + MustBeValidFor { + category: ConstraintCategory, + from_closure: bool, + span: Span, + region_name: RegionName, + opt_place_desc: Option, + }, + Unexplained, +} + +#[derive(Clone, Copy, Debug)] +pub(in crate::borrow_check) enum LaterUseKind { + TraitCapture, + ClosureCapture, + Call, + FakeLetRead, + Other, +} + +impl BorrowExplanation { + pub(in crate::borrow_check) fn is_explained(&self) -> bool { + match self { + BorrowExplanation::Unexplained => false, + _ => true, + } + } + pub(in crate::borrow_check) fn add_explanation_to_diagnostic<'tcx>( + &self, + tcx: TyCtxt<'tcx>, + body: &Body<'tcx>, + local_names: &IndexVec>, + err: &mut DiagnosticBuilder<'_>, + borrow_desc: &str, + borrow_span: Option, + ) { + match *self { + BorrowExplanation::UsedLater(later_use_kind, var_or_use_span) => { + let message = match later_use_kind { + LaterUseKind::TraitCapture => "captured here by trait object", + LaterUseKind::ClosureCapture => "captured here by closure", + LaterUseKind::Call => "used by call", + LaterUseKind::FakeLetRead => "stored here", + LaterUseKind::Other => "used here", + }; + if !borrow_span.map(|sp| sp.overlaps(var_or_use_span)).unwrap_or(false) { + err.span_label( + var_or_use_span, + format!("{}borrow later {}", borrow_desc, message), + ); + } + } + BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span) => { + let message = match later_use_kind { + LaterUseKind::TraitCapture => { + "borrow captured here by trait object, in later iteration of loop" + } + LaterUseKind::ClosureCapture => { + "borrow captured here by closure, in later iteration of loop" + } + LaterUseKind::Call => "borrow used by call, in later iteration of loop", + LaterUseKind::FakeLetRead => "borrow later stored here", + LaterUseKind::Other => "borrow used here, in later iteration of loop", + }; + err.span_label(var_or_use_span, format!("{}{}", borrow_desc, message)); + } + BorrowExplanation::UsedLaterWhenDropped { + drop_loc, + dropped_local, + should_note_order, + } => { + let local_decl = &body.local_decls[dropped_local]; + let (dtor_desc, type_desc) = match local_decl.ty.kind { + // If type is an ADT that implements Drop, then + // simplify output by reporting just the ADT name. + ty::Adt(adt, _substs) if adt.has_dtor(tcx) && !adt.is_box() => ( + "`Drop` code", + format!("type `{}`", tcx.def_path_str(adt.did)), + ), + + // Otherwise, just report the whole type (and use + // the intentionally fuzzy phrase "destructor") + ty::Closure(..) => ("destructor", "closure".to_owned()), + ty::Generator(..) => ("destructor", "generator".to_owned()), + + _ => ("destructor", format!("type `{}`", local_decl.ty)), + }; + + match local_names[dropped_local] { + Some(local_name) if !local_decl.from_compiler_desugaring() => { + let message = format!( + "{B}borrow might be used here, when `{LOC}` is dropped \ + and runs the {DTOR} for {TYPE}", + B = borrow_desc, + LOC = local_name, + TYPE = type_desc, + DTOR = dtor_desc + ); + err.span_label(body.source_info(drop_loc).span, message); + + if should_note_order { + err.note( + "values in a scope are dropped \ + in the opposite order they are defined", + ); + } + } + _ => { + err.span_label( + local_decl.source_info.span, + format!( + "a temporary with access to the {B}borrow \ + is created here ...", + B = borrow_desc + ), + ); + let message = format!( + "... and the {B}borrow might be used here, \ + when that temporary is dropped \ + and runs the {DTOR} for {TYPE}", + B = borrow_desc, + TYPE = type_desc, + DTOR = dtor_desc + ); + err.span_label(body.source_info(drop_loc).span, message); + + if let Some(info) = &local_decl.is_block_tail { + // FIXME: use span_suggestion instead, highlighting the + // whole block tail expression. + let msg = if info.tail_result_is_ignored { + "The temporary is part of an expression at the end of a block. \ + Consider adding semicolon after the expression so its temporaries \ + are dropped sooner, before the local variables declared by the \ + block are dropped." + } else { + "The temporary is part of an expression at the end of a block. \ + Consider forcing this temporary to be dropped sooner, before \ + the block's local variables are dropped. \ + For example, you could save the expression's value in a new \ + local variable `x` and then make `x` be the expression \ + at the end of the block." + }; + + err.note(msg); + } + } + } + } + BorrowExplanation::MustBeValidFor { + category, + span, + ref region_name, + ref opt_place_desc, + from_closure: _, + } => { + region_name.highlight_region_name(err); + + if let Some(desc) = opt_place_desc { + err.span_label( + span, + format!( + "{}requires that `{}` is borrowed for `{}`", + category.description(), + desc, + region_name, + ), + ); + } else { + err.span_label( + span, + format!( + "{}requires that {}borrow lasts for `{}`", + category.description(), + borrow_desc, + region_name, + ), + ); + }; + } + _ => {} + } + } +} + +impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { + /// Returns structured explanation for *why* the borrow contains the + /// point from `location`. This is key for the "3-point errors" + /// [described in the NLL RFC][d]. + /// + /// # Parameters + /// + /// - `borrow`: the borrow in question + /// - `location`: where the borrow occurs + /// - `kind_place`: if Some, this describes the statement that triggered the error. + /// - first half is the kind of write, if any, being performed + /// - second half is the place being accessed + /// + /// [d]: https://rust-lang.github.io/rfcs/2094-nll.html#leveraging-intuition-framing-errors-in-terms-of-points + pub(in crate::borrow_check) fn explain_why_borrow_contains_point( + &self, + location: Location, + borrow: &BorrowData<'tcx>, + kind_place: Option<(WriteKind, &Place<'tcx>)>, + ) -> BorrowExplanation { + debug!( + "explain_why_borrow_contains_point(location={:?}, borrow={:?}, kind_place={:?})", + location, borrow, kind_place + ); + + let regioncx = &self.nonlexical_regioncx; + let body: &Body<'_> = &self.body; + let tcx = self.infcx.tcx; + + let borrow_region_vid = borrow.region; + debug!( + "explain_why_borrow_contains_point: borrow_region_vid={:?}", + borrow_region_vid + ); + + let region_sub = regioncx.find_sub_region_live_at(borrow_region_vid, location); + debug!( + "explain_why_borrow_contains_point: region_sub={:?}", + region_sub + ); + + match find_use::find(body, regioncx, tcx, region_sub, location) { + Some(Cause::LiveVar(local, location)) => { + let span = body.source_info(location).span; + let spans = self + .move_spans(Place::from(local).as_ref(), location) + .or_else(|| self.borrow_spans(span, location)); + + let borrow_location = location; + if self.is_use_in_later_iteration_of_loop(borrow_location, location) { + let later_use = self.later_use_kind(borrow, spans, location); + BorrowExplanation::UsedLaterInLoop(later_use.0, later_use.1) + } else { + // Check if the location represents a `FakeRead`, and adapt the error + // message to the `FakeReadCause` it is from: in particular, + // the ones inserted in optimized `let var = ` patterns. + let later_use = self.later_use_kind(borrow, spans, location); + BorrowExplanation::UsedLater(later_use.0, later_use.1) + } + } + + Some(Cause::DropVar(local, location)) => { + let mut should_note_order = false; + if self.local_names[local].is_some() { + if let Some((WriteKind::StorageDeadOrDrop, place)) = kind_place { + if let Some(borrowed_local) = place.as_local() { + if self.local_names[borrowed_local].is_some() + && local != borrowed_local + { + should_note_order = true; + } + } + } + } + + BorrowExplanation::UsedLaterWhenDropped { + drop_loc: location, + dropped_local: local, + should_note_order, + } + } + + None => { + if let Some(region) = regioncx.to_error_region_vid(borrow_region_vid) { + let (category, from_closure, span, region_name) = + self.nonlexical_regioncx.free_region_constraint_info( + &self.body, + &self.local_names, + &self.upvars, + self.mir_def_id, + self.infcx, + borrow_region_vid, + region, + ); + if let Some(region_name) = region_name { + let opt_place_desc = + self.describe_place(borrow.borrowed_place.as_ref()); + BorrowExplanation::MustBeValidFor { + category, + from_closure, + span, + region_name, + opt_place_desc, + } + } else { + debug!("explain_why_borrow_contains_point: \ + Could not generate a region name"); + BorrowExplanation::Unexplained + } + } else { + debug!("explain_why_borrow_contains_point: \ + Could not generate an error region vid"); + BorrowExplanation::Unexplained + } + } + } + } + + /// true if `borrow_location` can reach `use_location` by going through a loop and + /// `use_location` is also inside of that loop + fn is_use_in_later_iteration_of_loop( + &self, + borrow_location: Location, + use_location: Location, + ) -> bool { + let back_edge = self.reach_through_backedge(borrow_location, use_location); + back_edge.map_or(false, |back_edge| { + self.can_reach_head_of_loop(use_location, back_edge) + }) + } + + /// Returns the outmost back edge if `from` location can reach `to` location passing through + /// that back edge + fn reach_through_backedge(&self, from: Location, to: Location) -> Option { + let mut visited_locations = FxHashSet::default(); + let mut pending_locations = VecDeque::new(); + visited_locations.insert(from); + pending_locations.push_back(from); + debug!("reach_through_backedge: from={:?} to={:?}", from, to,); + + let mut outmost_back_edge = None; + while let Some(location) = pending_locations.pop_front() { + debug!( + "reach_through_backedge: location={:?} outmost_back_edge={:?} + pending_locations={:?} visited_locations={:?}", + location, outmost_back_edge, pending_locations, visited_locations + ); + + if location == to && outmost_back_edge.is_some() { + // We've managed to reach the use location + debug!("reach_through_backedge: found!"); + return outmost_back_edge; + } + + let block = &self.body.basic_blocks()[location.block]; + + if location.statement_index < block.statements.len() { + let successor = location.successor_within_block(); + if visited_locations.insert(successor) { + pending_locations.push_back(successor); + } + } else { + pending_locations.extend( + block + .terminator() + .successors() + .map(|bb| Location { + statement_index: 0, + block: *bb, + }) + .filter(|s| visited_locations.insert(*s)) + .map(|s| { + if self.is_back_edge(location, s) { + match outmost_back_edge { + None => { + outmost_back_edge = Some(location); + } + + Some(back_edge) + if location.dominates(back_edge, &self.dominators) => + { + outmost_back_edge = Some(location); + } + + Some(_) => {} + } + } + + s + }), + ); + } + } + + None + } + + /// true if `from` location can reach `loop_head` location and `loop_head` dominates all the + /// intermediate nodes + fn can_reach_head_of_loop(&self, from: Location, loop_head: Location) -> bool { + self.find_loop_head_dfs(from, loop_head, &mut FxHashSet::default()) + } + + fn find_loop_head_dfs( + &self, + from: Location, + loop_head: Location, + visited_locations: &mut FxHashSet, + ) -> bool { + visited_locations.insert(from); + + if from == loop_head { + return true; + } + + if loop_head.dominates(from, &self.dominators) { + let block = &self.body.basic_blocks()[from.block]; + + if from.statement_index < block.statements.len() { + let successor = from.successor_within_block(); + + if !visited_locations.contains(&successor) + && self.find_loop_head_dfs(successor, loop_head, visited_locations) + { + return true; + } + } else { + for bb in block.terminator().successors() { + let successor = Location { + statement_index: 0, + block: *bb, + }; + + if !visited_locations.contains(&successor) + && self.find_loop_head_dfs(successor, loop_head, visited_locations) + { + return true; + } + } + } + } + + false + } + + /// True if an edge `source -> target` is a backedge -- in other words, if the target + /// dominates the source. + fn is_back_edge(&self, source: Location, target: Location) -> bool { + target.dominates(source, &self.dominators) + } + + /// Determine how the borrow was later used. + fn later_use_kind( + &self, + borrow: &BorrowData<'tcx>, + use_spans: UseSpans, + location: Location, + ) -> (LaterUseKind, Span) { + match use_spans { + UseSpans::ClosureUse { var_span, .. } => { + // Used in a closure. + (LaterUseKind::ClosureCapture, var_span) + } + UseSpans::OtherUse(span) => { + let block = &self.body.basic_blocks()[location.block]; + + let kind = if let Some(&Statement { + kind: StatementKind::FakeRead(FakeReadCause::ForLet, _), + .. + }) = block.statements.get(location.statement_index) + { + LaterUseKind::FakeLetRead + } else if self.was_captured_by_trait_object(borrow) { + LaterUseKind::TraitCapture + } else if location.statement_index == block.statements.len() { + if let TerminatorKind::Call { + ref func, + from_hir_call: true, + .. + } = block.terminator().kind + { + // Just point to the function, to reduce the chance of overlapping spans. + let function_span = match func { + Operand::Constant(c) => c.span, + Operand::Copy(place) | + Operand::Move(place) => { + if let Some(l) = place.as_local() { + let local_decl = &self.body.local_decls[l]; + if self.local_names[l].is_none() { + local_decl.source_info.span + } else { + span + } + } else { + span + } + } + }; + return (LaterUseKind::Call, function_span); + } else { + LaterUseKind::Other + } + } else { + LaterUseKind::Other + }; + + (kind, span) + } + } + } + + /// Checks if a borrowed value was captured by a trait object. We do this by + /// looking forward in the MIR from the reserve location and checking if we see + /// a unsized cast to a trait object on our data. + fn was_captured_by_trait_object(&self, borrow: &BorrowData<'tcx>) -> bool { + // Start at the reserve location, find the place that we want to see cast to a trait object. + let location = borrow.reserve_location; + let block = &self.body[location.block]; + let stmt = block.statements.get(location.statement_index); + debug!( + "was_captured_by_trait_object: location={:?} stmt={:?}", + location, stmt + ); + + // We make a `queue` vector that has the locations we want to visit. As of writing, this + // will only ever have one item at any given time, but by using a vector, we can pop from + // it which simplifies the termination logic. + let mut queue = vec![location]; + let mut target = if let Some(&Statement { + kind: StatementKind::Assign(box(ref place, _)), + .. + }) = stmt { + if let Some(local) = place.as_local() { + local + } else { + return false; + } + } else { + return false; + }; + + debug!( + "was_captured_by_trait: target={:?} queue={:?}", + target, queue + ); + while let Some(current_location) = queue.pop() { + debug!("was_captured_by_trait: target={:?}", target); + let block = &self.body[current_location.block]; + // We need to check the current location to find out if it is a terminator. + let is_terminator = current_location.statement_index == block.statements.len(); + if !is_terminator { + let stmt = &block.statements[current_location.statement_index]; + debug!("was_captured_by_trait_object: stmt={:?}", stmt); + + // The only kind of statement that we care about is assignments... + if let StatementKind::Assign(box(place, rvalue)) = &stmt.kind { + let into = match place.local_or_deref_local() { + Some(into) => into, + None => { + // Continue at the next location. + queue.push(current_location.successor_within_block()); + continue; + } + }; + + match rvalue { + // If we see a use, we should check whether it is our data, and if so + // update the place that we're looking for to that new place. + Rvalue::Use(operand) => match operand { + Operand::Copy(place) + | Operand::Move(place) => { + if let Some(from) = place.as_local() { + if from == target { + target = into; + } + } + } + _ => {} + }, + // If we see a unsized cast, then if it is our data we should check + // whether it is being cast to a trait object. + Rvalue::Cast( + CastKind::Pointer(PointerCast::Unsize), operand, ty + ) => match operand { + Operand::Copy(place) + | Operand::Move(place) => { + if let Some(from) = place.as_local() { + if from == target { + debug!("was_captured_by_trait_object: ty={:?}", ty); + // Check the type for a trait object. + return match ty.kind { + // `&dyn Trait` + ty::Ref(_, ty, _) if ty.is_trait() => true, + // `Box` + _ if ty.is_box() && ty.boxed_ty().is_trait() => true, + // `dyn Trait` + _ if ty.is_trait() => true, + // Anything else. + _ => false, + }; + } + } + return false; + } + _ => return false, + }, + _ => {} + } + } + + // Continue at the next location. + queue.push(current_location.successor_within_block()); + } else { + // The only thing we need to do for terminators is progress to the next block. + let terminator = block.terminator(); + debug!("was_captured_by_trait_object: terminator={:?}", terminator); + + if let TerminatorKind::Call { + destination: Some((place, block)), + args, + .. + } = &terminator.kind { + if let Some(dest) = place.as_local() { + debug!( + "was_captured_by_trait_object: target={:?} dest={:?} args={:?}", + target, dest, args + ); + // Check if one of the arguments to this function is the target place. + let found_target = args.iter().any(|arg| { + if let Operand::Move(place) = arg { + if let Some(potential) = place.as_local() { + potential == target + } else { + false + } + } else { + false + } + }); + + // If it is, follow this to the next block and update the target. + if found_target { + target = dest; + queue.push(block.start_location()); + } + } + } + } + + debug!("was_captured_by_trait: queue={:?}", queue); + } + + // We didn't find anything and ran out of locations to check. + false + } +} diff --git a/src/librustc_mir/borrow_check/diagnostics/find_use.rs b/src/librustc_mir/borrow_check/diagnostics/find_use.rs new file mode 100644 index 00000000000..7ab069260f9 --- /dev/null +++ b/src/librustc_mir/borrow_check/diagnostics/find_use.rs @@ -0,0 +1,135 @@ +use std::collections::VecDeque; +use std::rc::Rc; + +use crate::borrow_check::nll::region_infer::{Cause, RegionInferenceContext}; +use crate::borrow_check::nll::ToRegionVid; +use crate::util::liveness::{self, DefUse}; +use rustc::mir::visit::{MirVisitable, PlaceContext, Visitor}; +use rustc::mir::{Local, Location, Body}; +use rustc::ty::{RegionVid, TyCtxt}; +use rustc_data_structures::fx::FxHashSet; + +crate fn find<'tcx>( + body: &Body<'tcx>, + regioncx: &Rc>, + tcx: TyCtxt<'tcx>, + region_vid: RegionVid, + start_point: Location, +) -> Option { + let mut uf = UseFinder { + body, + regioncx, + tcx, + region_vid, + start_point, + }; + + uf.find() +} + +struct UseFinder<'cx, 'tcx> { + body: &'cx Body<'tcx>, + regioncx: &'cx Rc>, + tcx: TyCtxt<'tcx>, + region_vid: RegionVid, + start_point: Location, +} + +impl<'cx, 'tcx> UseFinder<'cx, 'tcx> { + fn find(&mut self) -> Option { + let mut queue = VecDeque::new(); + let mut visited = FxHashSet::default(); + + queue.push_back(self.start_point); + while let Some(p) = queue.pop_front() { + if !self.regioncx.region_contains(self.region_vid, p) { + continue; + } + + if !visited.insert(p) { + continue; + } + + let block_data = &self.body[p.block]; + + match self.def_use(p, block_data.visitable(p.statement_index)) { + Some(DefUseResult::Def) => {} + + Some(DefUseResult::UseLive { local }) => { + return Some(Cause::LiveVar(local, p)); + } + + Some(DefUseResult::UseDrop { local }) => { + return Some(Cause::DropVar(local, p)); + } + + None => { + if p.statement_index < block_data.statements.len() { + queue.push_back(p.successor_within_block()); + } else { + queue.extend( + block_data + .terminator() + .successors() + .filter(|&bb| Some(&Some(*bb)) != block_data.terminator().unwind()) + .map(|&bb| Location { + statement_index: 0, + block: bb, + }), + ); + } + } + } + } + + None + } + + fn def_use(&self, location: Location, thing: &dyn MirVisitable<'tcx>) -> Option { + let mut visitor = DefUseVisitor { + body: self.body, + tcx: self.tcx, + region_vid: self.region_vid, + def_use_result: None, + }; + + thing.apply(location, &mut visitor); + + visitor.def_use_result + } +} + +struct DefUseVisitor<'cx, 'tcx> { + body: &'cx Body<'tcx>, + tcx: TyCtxt<'tcx>, + region_vid: RegionVid, + def_use_result: Option, +} + +enum DefUseResult { + Def, + UseLive { local: Local }, + UseDrop { local: Local }, +} + +impl<'cx, 'tcx> Visitor<'tcx> for DefUseVisitor<'cx, 'tcx> { + fn visit_local(&mut self, &local: &Local, context: PlaceContext, _: Location) { + let local_ty = self.body.local_decls[local].ty; + + let mut found_it = false; + self.tcx.for_each_free_region(&local_ty, |r| { + if r.to_region_vid() == self.region_vid { + found_it = true; + } + }); + + if found_it { + self.def_use_result = match liveness::categorize(context) { + Some(DefUse::Def) => Some(DefUseResult::Def), + Some(DefUse::Use) => Some(DefUseResult::UseLive { local }), + Some(DefUse::Drop) => Some(DefUseResult::UseDrop { local }), + None => None, + }; + } + } +} 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..1a76265fbdc --- /dev/null +++ b/src/librustc_mir/borrow_check/diagnostics/mod.rs @@ -0,0 +1,934 @@ +//! Borrow checker diagnostics. + +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}; + +mod find_use; +mod var_name; +mod region_name; +mod outlives_suggestion; + +mod conflict_errors; +mod move_errors; +mod mutability_errors; +mod region_errors; +mod explain_borrow; + +crate use mutability_errors::AccessKind; +crate use region_name::{RegionName, RegionNameSource, RegionErrorNamingCtx}; +crate use region_errors::{ErrorReportingCtx, ErrorConstraintInfo}; +crate use outlives_suggestion::OutlivesSuggestionBuilder; + +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..938836db9ae --- /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::diagnostics::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..6449ae349ab --- /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::diagnostics::BorrowedContentSource; +use crate::util::collect_writes::FindAssignments; +use rustc_errors::Applicability; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) enum AccessKind { + MutableBorrow, + Mutate, +} + +impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { + pub(crate) 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/diagnostics/outlives_suggestion.rs b/src/librustc_mir/borrow_check/diagnostics/outlives_suggestion.rs new file mode 100644 index 00000000000..7aecadae98b --- /dev/null +++ b/src/librustc_mir/borrow_check/diagnostics/outlives_suggestion.rs @@ -0,0 +1,319 @@ +//! Contains utilities for generating suggestions for borrowck errors related to unsatisified +//! outlives constraints. + +use std::collections::BTreeMap; + +use log::debug; +use rustc::{hir::def_id::DefId, infer::InferCtxt, ty::RegionVid}; +use rustc::mir::{Body, Local}; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::{Diagnostic, DiagnosticBuilder}; +use rustc_index::vec::IndexVec; +use syntax_pos::symbol::Symbol; + +use smallvec::SmallVec; + +use crate::borrow_check::nll::region_infer::RegionInferenceContext; + +use super::{ + RegionName, RegionNameSource, ErrorConstraintInfo, ErrorReportingCtx, RegionErrorNamingCtx, +}; + +/// The different things we could suggest. +enum SuggestedConstraint { + /// Outlives(a, [b, c, d, ...]) => 'a: 'b + 'c + 'd + ... + Outlives(RegionName, SmallVec<[RegionName; 2]>), + + /// 'a = 'b + Equal(RegionName, RegionName), + + /// 'a: 'static i.e. 'a = 'static and the user should just use 'static + Static(RegionName), +} + +/// Collects information about outlives constraints that needed to be added for a given MIR node +/// corresponding to a function definition. +/// +/// Adds a help note suggesting adding a where clause with the needed constraints. +pub struct OutlivesSuggestionBuilder<'a> { + /// The MIR DefId of the fn with the lifetime error. + mir_def_id: DefId, + + local_names: &'a IndexVec>, + + /// The list of outlives constraints that need to be added. Specifically, we map each free + /// region to all other regions that it must outlive. I will use the shorthand `fr: + /// outlived_frs`. Not all of these regions will already have names necessarily. Some could be + /// implicit free regions that we inferred. These will need to be given names in the final + /// suggestion message. + constraints_to_add: BTreeMap>, +} + +impl OutlivesSuggestionBuilder<'a> { + /// Create a new builder for the given MIR node representing a fn definition. + crate fn new( + mir_def_id: DefId, + local_names: &'a IndexVec>, + ) -> Self { + OutlivesSuggestionBuilder { + mir_def_id, + local_names, + constraints_to_add: BTreeMap::default(), + } + } + + /// Returns `true` iff the `RegionNameSource` is a valid source for an outlives + /// suggestion. + // + // FIXME: Currently, we only report suggestions if the `RegionNameSource` is an early-bound + // region or a named region, avoiding using regions with synthetic names altogether. This + // allows us to avoid giving impossible suggestions (e.g. adding bounds to closure args). + // We can probably be less conservative, since some inferred free regions are namable (e.g. + // the user can explicitly name them. To do this, we would allow some regions whose names + // come from `MatchedAdtAndSegment`, being careful to filter out bad suggestions, such as + // naming the `'self` lifetime in methods, etc. + fn region_name_is_suggestable(name: &RegionName) -> bool { + match name.source { + RegionNameSource::NamedEarlyBoundRegion(..) + | RegionNameSource::NamedFreeRegion(..) + | RegionNameSource::Static => true, + + // Don't give suggestions for upvars, closure return types, or other unnamable + // regions. + RegionNameSource::SynthesizedFreeEnvRegion(..) + | RegionNameSource::CannotMatchHirTy(..) + | RegionNameSource::MatchedHirTy(..) + | RegionNameSource::MatchedAdtAndSegment(..) + | RegionNameSource::AnonRegionFromUpvar(..) + | RegionNameSource::AnonRegionFromOutput(..) + | RegionNameSource::AnonRegionFromYieldTy(..) + | RegionNameSource::AnonRegionFromAsyncFn(..) => { + debug!("Region {:?} is NOT suggestable", name); + false + } + } + } + + /// Returns a name for the region if it is suggestable. See `region_name_is_suggestable`. + fn region_vid_to_name( + &self, + errctx: &ErrorReportingCtx<'_, '_, '_>, + renctx: &mut RegionErrorNamingCtx, + region: RegionVid, + ) -> Option { + errctx + .region_infcx + .give_region_a_name(errctx, renctx, region) + .filter(Self::region_name_is_suggestable) + } + + /// Compiles a list of all suggestions to be printed in the final big suggestion. + fn compile_all_suggestions<'tcx>( + &self, + body: &Body<'tcx>, + region_infcx: &RegionInferenceContext<'tcx>, + infcx: &InferCtxt<'_, 'tcx>, + renctx: &mut RegionErrorNamingCtx, + ) -> SmallVec<[SuggestedConstraint; 2]> { + let mut suggested = SmallVec::new(); + + // Keep track of variables that we have already suggested unifying so that we don't print + // out silly duplicate messages. + let mut unified_already = FxHashSet::default(); + + let errctx = ErrorReportingCtx { + region_infcx, + infcx, + body, + mir_def_id: self.mir_def_id, + local_names: self.local_names, + + // We should not be suggesting naming upvars, so we pass in a dummy set of upvars that + // should never be used. + upvars: &[], + }; + + for (fr, outlived) in &self.constraints_to_add { + let fr_name = if let Some(fr_name) = self.region_vid_to_name(&errctx, renctx, *fr) { + fr_name + } else { + continue; + }; + + let outlived = outlived + .iter() + // if there is a `None`, we will just omit that constraint + .filter_map(|fr| { + self.region_vid_to_name(&errctx, renctx, *fr).map(|rname| (fr, rname)) + }) + .collect::>(); + + // No suggestable outlived lifetimes. + if outlived.is_empty() { + continue; + } + + // There are three types of suggestions we can make: + // 1) Suggest a bound: 'a: 'b + // 2) Suggest replacing 'a with 'static. If any of `outlived` is `'static`, then we + // should just replace 'a with 'static. + // 3) Suggest unifying 'a with 'b if we have both 'a: 'b and 'b: 'a + + if outlived.iter().any(|(_, outlived_name)| { + if let RegionNameSource::Static = outlived_name.source { + true + } else { + false + } + }) { + suggested.push(SuggestedConstraint::Static(fr_name)); + } else { + // We want to isolate out all lifetimes that should be unified and print out + // separate messages for them. + + let (unified, other): (Vec<_>, Vec<_>) = outlived.into_iter().partition( + // Do we have both 'fr: 'r and 'r: 'fr? + |(r, _)| { + self.constraints_to_add + .get(r) + .map(|r_outlived| r_outlived.as_slice().contains(fr)) + .unwrap_or(false) + }, + ); + + for (r, bound) in unified.into_iter() { + if !unified_already.contains(fr) { + suggested.push(SuggestedConstraint::Equal(fr_name.clone(), bound)); + unified_already.insert(r); + } + } + + if !other.is_empty() { + let other = + other.iter().map(|(_, rname)| rname.clone()).collect::>(); + suggested.push(SuggestedConstraint::Outlives(fr_name, other)) + } + } + } + + suggested + } + + /// Add the outlives constraint `fr: outlived_fr` to the set of constraints we need to suggest. + crate fn collect_constraint(&mut self, fr: RegionVid, outlived_fr: RegionVid) { + debug!("Collected {:?}: {:?}", fr, outlived_fr); + + // Add to set of constraints for final help note. + self.constraints_to_add.entry(fr).or_insert(Vec::new()).push(outlived_fr); + } + + /// Emit an intermediate note on the given `Diagnostic` if the involved regions are + /// suggestable. + crate fn intermediate_suggestion( + &mut self, + errctx: &ErrorReportingCtx<'_, '_, '_>, + errci: &ErrorConstraintInfo, + renctx: &mut RegionErrorNamingCtx, + diag: &mut DiagnosticBuilder<'_>, + ) { + // Emit an intermediate note. + let fr_name = self.region_vid_to_name(errctx, renctx, errci.fr); + let outlived_fr_name = self.region_vid_to_name(errctx, renctx, errci.outlived_fr); + + if let (Some(fr_name), Some(outlived_fr_name)) = (fr_name, outlived_fr_name) { + if let RegionNameSource::Static = outlived_fr_name.source { + diag.help(&format!("consider replacing `{}` with `'static`", fr_name)); + } else { + diag.help(&format!( + "consider adding the following bound: `{}: {}`", + fr_name, outlived_fr_name + )); + } + } + } + + /// If there is a suggestion to emit, add a diagnostic to the buffer. This is the final + /// suggestion including all collected constraints. + crate fn add_suggestion<'tcx>( + &self, + body: &Body<'tcx>, + region_infcx: &RegionInferenceContext<'tcx>, + infcx: &InferCtxt<'_, 'tcx>, + errors_buffer: &mut Vec, + renctx: &mut RegionErrorNamingCtx, + ) { + // No constraints to add? Done. + if self.constraints_to_add.is_empty() { + debug!("No constraints to suggest."); + return; + } + + // If there is only one constraint to suggest, then we already suggested it in the + // intermediate suggestion above. + if self.constraints_to_add.len() == 1 { + debug!("Only 1 suggestion. Skipping."); + return; + } + + // Get all suggestable constraints. + let suggested = self.compile_all_suggestions(body, region_infcx, infcx, renctx); + + // If there are no suggestable constraints... + if suggested.is_empty() { + debug!("Only 1 suggestable constraint. Skipping."); + return; + } + + // If there is exactly one suggestable constraints, then just suggest it. Otherwise, emit a + // list of diagnostics. + let mut diag = if suggested.len() == 1 { + infcx.tcx.sess.diagnostic().struct_help(&match suggested.last().unwrap() { + SuggestedConstraint::Outlives(a, bs) => { + let bs: SmallVec<[String; 2]> = bs.iter().map(|r| format!("{}", r)).collect(); + format!("add bound `{}: {}`", a, bs.join(" + ")) + } + + SuggestedConstraint::Equal(a, b) => { + format!("`{}` and `{}` must be the same: replace one with the other", a, b) + } + SuggestedConstraint::Static(a) => format!("replace `{}` with `'static`", a), + }) + } else { + // Create a new diagnostic. + let mut diag = infcx + .tcx + .sess + .diagnostic() + .struct_help("the following changes may resolve your lifetime errors"); + + // Add suggestions. + for constraint in suggested { + match constraint { + SuggestedConstraint::Outlives(a, bs) => { + let bs: SmallVec<[String; 2]> = + bs.iter().map(|r| format!("{}", r)).collect(); + diag.help(&format!("add bound `{}: {}`", a, bs.join(" + "))); + } + SuggestedConstraint::Equal(a, b) => { + diag.help(&format!( + "`{}` and `{}` must be the same: replace one with the other", + a, b + )); + } + SuggestedConstraint::Static(a) => { + diag.help(&format!("replace `{}` with `'static`", a)); + } + } + } + + diag + }; + + // We want this message to appear after other messages on the mir def. + let mir_span = infcx.tcx.def_span(self.mir_def_id); + diag.sort_span = mir_span.shrink_to_hi(); + + // Buffer the diagnostic + diag.buffer(errors_buffer); + } +} diff --git a/src/librustc_mir/borrow_check/diagnostics/region_errors.rs b/src/librustc_mir/borrow_check/diagnostics/region_errors.rs new file mode 100644 index 00000000000..66f0330fe9b --- /dev/null +++ b/src/librustc_mir/borrow_check/diagnostics/region_errors.rs @@ -0,0 +1,930 @@ +use crate::borrow_check::nll::constraints::OutlivesConstraint; +use crate::borrow_check::nll::region_infer::RegionInferenceContext; +use crate::borrow_check::nll::type_check::Locations; +use crate::borrow_check::nll::universal_regions::DefiningTy; +use crate::borrow_check::nll::ConstraintDescription; +use crate::borrow_check::Upvar; +use crate::util::borrowck_errors; +use rustc::hir::def_id::DefId; +use rustc::infer::error_reporting::nice_region_error::NiceRegionError; +use rustc::infer::InferCtxt; +use rustc::infer::NLLRegionVariableOrigin; +use rustc::mir::{ConstraintCategory, Local, Location, Body}; +use rustc::ty::{self, RegionVid}; +use rustc_index::vec::IndexVec; +use rustc_errors::DiagnosticBuilder; +use std::collections::VecDeque; +use syntax::errors::Applicability; +use syntax::symbol::kw; +use syntax_pos::Span; +use syntax_pos::symbol::Symbol; + +use super::{OutlivesSuggestionBuilder, RegionName, RegionNameSource, RegionErrorNamingCtx}; + +impl ConstraintDescription for ConstraintCategory { + fn description(&self) -> &'static str { + // Must end with a space. Allows for empty names to be provided. + match self { + ConstraintCategory::Assignment => "assignment ", + ConstraintCategory::Return => "returning this value ", + ConstraintCategory::Yield => "yielding this value ", + ConstraintCategory::UseAsConst => "using this value as a constant ", + ConstraintCategory::UseAsStatic => "using this value as a static ", + ConstraintCategory::Cast => "cast ", + ConstraintCategory::CallArgument => "argument ", + ConstraintCategory::TypeAnnotation => "type annotation ", + ConstraintCategory::ClosureBounds => "closure body ", + ConstraintCategory::SizedBound => "proving this value is `Sized` ", + ConstraintCategory::CopyBound => "copying this value ", + ConstraintCategory::OpaqueType => "opaque type ", + ConstraintCategory::Boring + | ConstraintCategory::BoringNoLocation + | ConstraintCategory::Internal => "", + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +enum Trace { + StartRegion, + FromOutlivesConstraint(OutlivesConstraint), + NotVisited, +} + +/// Various pieces of state used when reporting borrow checker errors. +pub struct ErrorReportingCtx<'a, 'b, 'tcx> { + /// The region inference context used for borrow chekcing this MIR body. + pub(super) region_infcx: &'b RegionInferenceContext<'tcx>, + + /// The inference context used for type checking. + pub(super) infcx: &'b InferCtxt<'a, 'tcx>, + + /// The MIR def we are reporting errors on. + pub(super) mir_def_id: DefId, + + /// The MIR body we are reporting errors on (for convenience). + pub(super) body: &'b Body<'tcx>, + + /// User variable names for MIR locals (where applicable). + pub(super) local_names: &'b IndexVec>, + + /// Any upvars for the MIR body we have kept track of during borrow checking. + pub(super) upvars: &'b [Upvar], +} + +/// Information about the various region constraints involved in a borrow checker error. +#[derive(Clone, Debug)] +pub struct ErrorConstraintInfo { + // fr: outlived_fr + pub(super) fr: RegionVid, + pub(super) fr_is_local: bool, + pub(super) outlived_fr: RegionVid, + pub(super) outlived_fr_is_local: bool, + + // Category and span for best blame constraint + pub(super) category: ConstraintCategory, + pub(super) span: Span, +} + +impl<'tcx> RegionInferenceContext<'tcx> { + /// Tries to find the best constraint to blame for the fact that + /// `R: from_region`, where `R` is some region that meets + /// `target_test`. This works by following the constraint graph, + /// creating a constraint path that forces `R` to outlive + /// `from_region`, and then finding the best choices within that + /// path to blame. + fn best_blame_constraint( + &self, + body: &Body<'tcx>, + from_region: RegionVid, + from_region_origin: NLLRegionVariableOrigin, + target_test: impl Fn(RegionVid) -> bool, + ) -> (ConstraintCategory, bool, Span) { + debug!("best_blame_constraint(from_region={:?}, from_region_origin={:?})", + from_region, from_region_origin); + + // Find all paths + let (path, target_region) = + self.find_constraint_paths_between_regions(from_region, target_test) + .unwrap(); + debug!( + "best_blame_constraint: path={:#?}", + path.iter() + .map(|&c| format!( + "{:?} ({:?}: {:?})", + c, + self.constraint_sccs.scc(c.sup), + self.constraint_sccs.scc(c.sub), + )) + .collect::>() + ); + + // Classify each of the constraints along the path. + let mut categorized_path: Vec<(ConstraintCategory, bool, Span)> = path.iter() + .map(|constraint| { + if constraint.category == ConstraintCategory::ClosureBounds { + self.retrieve_closure_constraint_info(body, &constraint) + } else { + (constraint.category, false, constraint.locations.span(body)) + } + }) + .collect(); + debug!( + "best_blame_constraint: categorized_path={:#?}", + categorized_path + ); + + // To find the best span to cite, we first try to look for the + // final constraint that is interesting and where the `sup` is + // not unified with the ultimate target region. The reason + // for this is that we have a chain of constraints that lead + // from the source to the target region, something like: + // + // '0: '1 ('0 is the source) + // '1: '2 + // '2: '3 + // '3: '4 + // '4: '5 + // '5: '6 ('6 is the target) + // + // Some of those regions are unified with `'6` (in the same + // SCC). We want to screen those out. After that point, the + // "closest" constraint we have to the end is going to be the + // most likely to be the point where the value escapes -- but + // we still want to screen for an "interesting" point to + // highlight (e.g., a call site or something). + let target_scc = self.constraint_sccs.scc(target_region); + let mut range = 0..path.len(); + + // As noted above, when reporting an error, there is typically a chain of constraints + // leading from some "source" region which must outlive some "target" region. + // In most cases, we prefer to "blame" the constraints closer to the target -- + // but there is one exception. When constraints arise from higher-ranked subtyping, + // we generally prefer to blame the source value, + // as the "target" in this case tends to be some type annotation that the user gave. + // Therefore, if we find that the region origin is some instantiation + // of a higher-ranked region, we start our search from the "source" point + // rather than the "target", and we also tweak a few other things. + // + // An example might be this bit of Rust code: + // + // ```rust + // let x: fn(&'static ()) = |_| {}; + // let y: for<'a> fn(&'a ()) = x; + // ``` + // + // In MIR, this will be converted into a combination of assignments and type ascriptions. + // In particular, the 'static is imposed through a type ascription: + // + // ```rust + // x = ...; + // AscribeUserType(x, fn(&'static ()) + // y = x; + // ``` + // + // We wind up ultimately with constraints like + // + // ```rust + // !a: 'temp1 // from the `y = x` statement + // 'temp1: 'temp2 + // 'temp2: 'static // from the AscribeUserType + // ``` + // + // and here we prefer to blame the source (the y = x statement). + let blame_source = match from_region_origin { + NLLRegionVariableOrigin::FreeRegion + | NLLRegionVariableOrigin::Existential { from_forall: false } => { + true + } + NLLRegionVariableOrigin::Placeholder(_) + | NLLRegionVariableOrigin::Existential { from_forall: true } => { + false + } + }; + + let find_region = |i: &usize| { + let constraint = path[*i]; + + let constraint_sup_scc = self.constraint_sccs.scc(constraint.sup); + + if blame_source { + match categorized_path[*i].0 { + ConstraintCategory::OpaqueType | ConstraintCategory::Boring | + ConstraintCategory::BoringNoLocation | ConstraintCategory::Internal => false, + ConstraintCategory::TypeAnnotation | ConstraintCategory::Return | + ConstraintCategory::Yield => true, + _ => constraint_sup_scc != target_scc, + } + } else { + match categorized_path[*i].0 { + ConstraintCategory::OpaqueType | ConstraintCategory::Boring | + ConstraintCategory::BoringNoLocation | ConstraintCategory::Internal => false, + _ => true + } + } + }; + + let best_choice = if blame_source { + range.rev().find(find_region) + } else { + range.find(find_region) + }; + + debug!("best_blame_constraint: best_choice={:?} blame_source={}", + best_choice, blame_source); + + if let Some(i) = best_choice { + if let Some(next) = categorized_path.get(i + 1) { + if categorized_path[i].0 == ConstraintCategory::Return + && next.0 == ConstraintCategory::OpaqueType + { + // The return expression is being influenced by the return type being + // impl Trait, point at the return type and not the return expr. + return *next; + } + } + return categorized_path[i]; + } + + // If that search fails, that is.. unusual. Maybe everything + // is in the same SCC or something. In that case, find what + // appears to be the most interesting point to report to the + // user via an even more ad-hoc guess. + categorized_path.sort_by(|p0, p1| p0.0.cmp(&p1.0)); + debug!("`: sorted_path={:#?}", categorized_path); + + *categorized_path.first().unwrap() + } + + /// Walks the graph of constraints (where `'a: 'b` is considered + /// an edge `'a -> 'b`) to find all paths from `from_region` to + /// `to_region`. The paths are accumulated into the vector + /// `results`. The paths are stored as a series of + /// `ConstraintIndex` values -- in other words, a list of *edges*. + /// + /// Returns: a series of constraints as well as the region `R` + /// that passed the target test. + fn find_constraint_paths_between_regions( + &self, + from_region: RegionVid, + target_test: impl Fn(RegionVid) -> bool, + ) -> Option<(Vec, RegionVid)> { + let mut context = IndexVec::from_elem(Trace::NotVisited, &self.definitions); + context[from_region] = Trace::StartRegion; + + // Use a deque so that we do a breadth-first search. We will + // stop at the first match, which ought to be the shortest + // path (fewest constraints). + let mut deque = VecDeque::new(); + deque.push_back(from_region); + + while let Some(r) = deque.pop_front() { + debug!( + "find_constraint_paths_between_regions: from_region={:?} r={:?} value={}", + from_region, + r, + self.region_value_str(r), + ); + + // Check if we reached the region we were looking for. If so, + // we can reconstruct the path that led to it and return it. + if target_test(r) { + let mut result = vec![]; + let mut p = r; + loop { + match context[p] { + Trace::NotVisited => { + bug!("found unvisited region {:?} on path to {:?}", p, r) + } + + Trace::FromOutlivesConstraint(c) => { + result.push(c); + p = c.sup; + } + + Trace::StartRegion => { + result.reverse(); + return Some((result, r)); + } + } + } + } + + // Otherwise, walk over the outgoing constraints and + // enqueue any regions we find, keeping track of how we + // reached them. + + // A constraint like `'r: 'x` can come from our constraint + // graph. + let fr_static = self.universal_regions.fr_static; + let outgoing_edges_from_graph = self.constraint_graph + .outgoing_edges(r, &self.constraints, fr_static); + + // Always inline this closure because it can be hot. + let mut handle_constraint = #[inline(always)] |constraint: OutlivesConstraint| { + debug_assert_eq!(constraint.sup, r); + let sub_region = constraint.sub; + if let Trace::NotVisited = context[sub_region] { + context[sub_region] = Trace::FromOutlivesConstraint(constraint); + deque.push_back(sub_region); + } + }; + + // This loop can be hot. + for constraint in outgoing_edges_from_graph { + handle_constraint(constraint); + } + + // Member constraints can also give rise to `'r: 'x` edges that + // were not part of the graph initially, so watch out for those. + // (But they are extremely rare; this loop is very cold.) + for constraint in self.applied_member_constraints(r) { + let p_c = &self.member_constraints[constraint.member_constraint_index]; + let constraint = OutlivesConstraint { + sup: r, + sub: constraint.min_choice, + locations: Locations::All(p_c.definition_span), + category: ConstraintCategory::OpaqueType, + }; + handle_constraint(constraint); + } + } + + None + } + + /// Report an error because the universal region `fr` was required to outlive + /// `outlived_fr` but it is not known to do so. For example: + /// + /// ``` + /// fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x } + /// ``` + /// + /// Here we would be invoked with `fr = 'a` and `outlived_fr = `'b`. + pub(in crate::borrow_check) fn report_error<'a>( + &'a self, + body: &Body<'tcx>, + local_names: &IndexVec>, + upvars: &[Upvar], + infcx: &'a InferCtxt<'a, 'tcx>, + mir_def_id: DefId, + fr: RegionVid, + fr_origin: NLLRegionVariableOrigin, + outlived_fr: RegionVid, + outlives_suggestion: &mut OutlivesSuggestionBuilder<'_>, + renctx: &mut RegionErrorNamingCtx, + ) -> DiagnosticBuilder<'a> { + debug!("report_error(fr={:?}, outlived_fr={:?})", fr, outlived_fr); + + let (category, _, span) = self.best_blame_constraint(body, fr, fr_origin, |r| { + self.provides_universal_region(r, fr, outlived_fr) + }); + + debug!("report_error: category={:?} {:?}", category, span); + // Check if we can use one of the "nice region errors". + if let (Some(f), Some(o)) = (self.to_error_region(fr), self.to_error_region(outlived_fr)) { + let tables = infcx.tcx.typeck_tables_of(mir_def_id); + let nice = NiceRegionError::new_from_span(infcx, span, o, f, Some(tables)); + if let Some(diag) = nice.try_report_from_nll() { + return diag; + } + } + + let (fr_is_local, outlived_fr_is_local): (bool, bool) = ( + self.universal_regions.is_local_free_region(fr), + self.universal_regions.is_local_free_region(outlived_fr), + ); + + debug!( + "report_error: fr_is_local={:?} outlived_fr_is_local={:?} category={:?}", + fr_is_local, outlived_fr_is_local, category + ); + + let errctx = ErrorReportingCtx { + region_infcx: self, + infcx, + mir_def_id, + body, + local_names, + upvars, + }; + + let errci = ErrorConstraintInfo { + fr, outlived_fr, fr_is_local, outlived_fr_is_local, category, span + }; + + match (category, fr_is_local, outlived_fr_is_local) { + (ConstraintCategory::Return, true, false) if self.is_closure_fn_mut(infcx, fr) => { + self.report_fnmut_error(&errctx, &errci, renctx) + } + (ConstraintCategory::Assignment, true, false) + | (ConstraintCategory::CallArgument, true, false) => { + let mut db = self.report_escaping_data_error(&errctx, &errci, renctx); + + outlives_suggestion.intermediate_suggestion(&errctx, &errci, renctx, &mut db); + outlives_suggestion.collect_constraint(fr, outlived_fr); + + db + } + _ => { + let mut db = self.report_general_error(&errctx, &errci, renctx); + + outlives_suggestion.intermediate_suggestion(&errctx, &errci, renctx, &mut db); + outlives_suggestion.collect_constraint(fr, outlived_fr); + + db + } + } + } + + /// We have a constraint `fr1: fr2` that is not satisfied, where + /// `fr2` represents some universal region. Here, `r` is some + /// region where we know that `fr1: r` and this function has the + /// job of determining whether `r` is "to blame" for the fact that + /// `fr1: fr2` is required. + /// + /// This is true under two conditions: + /// + /// - `r == fr2` + /// - `fr2` is `'static` and `r` is some placeholder in a universe + /// that cannot be named by `fr1`; in that case, we will require + /// that `fr1: 'static` because it is the only way to `fr1: r` to + /// be satisfied. (See `add_incompatible_universe`.) + fn provides_universal_region(&self, r: RegionVid, fr1: RegionVid, fr2: RegionVid) -> bool { + debug!( + "provides_universal_region(r={:?}, fr1={:?}, fr2={:?})", + r, fr1, fr2 + ); + let result = { + r == fr2 || { + fr2 == self.universal_regions.fr_static && self.cannot_name_placeholder(fr1, r) + } + }; + debug!("provides_universal_region: result = {:?}", result); + result + } + + /// Report a specialized error when `FnMut` closures return a reference to a captured variable. + /// This function expects `fr` to be local and `outlived_fr` to not be local. + /// + /// ```text + /// error: captured variable cannot escape `FnMut` closure body + /// --> $DIR/issue-53040.rs:15:8 + /// | + /// LL | || &mut v; + /// | -- ^^^^^^ creates a reference to a captured variable which escapes the closure body + /// | | + /// | inferred to be a `FnMut` closure + /// | + /// = note: `FnMut` closures only have access to their captured variables while they are + /// executing... + /// = note: ...therefore, returned references to captured variables will escape the closure + /// ``` + fn report_fnmut_error( + &self, + errctx: &ErrorReportingCtx<'_, '_, 'tcx>, + errci: &ErrorConstraintInfo, + renctx: &mut RegionErrorNamingCtx, + ) -> DiagnosticBuilder<'_> { + let ErrorConstraintInfo { + outlived_fr, span, .. + } = errci; + + let mut diag = errctx + .infcx + .tcx + .sess + .struct_span_err(*span, "captured variable cannot escape `FnMut` closure body"); + + // We should check if the return type of this closure is in fact a closure - in that + // case, we can special case the error further. + let return_type_is_closure = self.universal_regions.unnormalized_output_ty.is_closure(); + let message = if return_type_is_closure { + "returns a closure that contains a reference to a captured variable, which then \ + escapes the closure body" + } else { + "returns a reference to a captured variable which escapes the closure body" + }; + + diag.span_label(*span, message); + + match self.give_region_a_name(errctx, renctx, *outlived_fr).unwrap().source { + RegionNameSource::NamedEarlyBoundRegion(fr_span) + | RegionNameSource::NamedFreeRegion(fr_span) + | RegionNameSource::SynthesizedFreeEnvRegion(fr_span, _) + | RegionNameSource::CannotMatchHirTy(fr_span, _) + | RegionNameSource::MatchedHirTy(fr_span) + | RegionNameSource::MatchedAdtAndSegment(fr_span) + | RegionNameSource::AnonRegionFromUpvar(fr_span, _) + | RegionNameSource::AnonRegionFromOutput(fr_span, _, _) => { + diag.span_label(fr_span, "inferred to be a `FnMut` closure"); + } + _ => {} + } + + diag.note( + "`FnMut` closures only have access to their captured variables while they are \ + executing...", + ); + diag.note("...therefore, they cannot allow references to captured variables to escape"); + + diag + } + + /// Reports a error specifically for when data is escaping a closure. + /// + /// ```text + /// error: borrowed data escapes outside of function + /// --> $DIR/lifetime-bound-will-change-warning.rs:44:5 + /// | + /// LL | fn test2<'a>(x: &'a Box) { + /// | - `x` is a reference that is only valid in the function body + /// LL | // but ref_obj will not, so warn. + /// LL | ref_obj(x) + /// | ^^^^^^^^^^ `x` escapes the function body here + /// ``` + fn report_escaping_data_error( + &self, + errctx: &ErrorReportingCtx<'_, '_, 'tcx>, + errci: &ErrorConstraintInfo, + renctx: &mut RegionErrorNamingCtx, + ) -> DiagnosticBuilder<'_> { + let ErrorReportingCtx { + infcx, body, upvars, local_names, .. + } = errctx; + + let ErrorConstraintInfo { + span, category, .. + } = errci; + + let fr_name_and_span = + self.get_var_name_and_span_for_region(infcx.tcx, body, local_names, upvars, errci.fr); + let outlived_fr_name_and_span = self.get_var_name_and_span_for_region( + infcx.tcx, + body, + local_names, + upvars, + errci.outlived_fr, + ); + + let escapes_from = match self.universal_regions.defining_ty { + DefiningTy::Closure(..) => "closure", + DefiningTy::Generator(..) => "generator", + DefiningTy::FnDef(..) => "function", + DefiningTy::Const(..) => "const", + }; + + // Revert to the normal error in these cases. + // Assignments aren't "escapes" in function items. + if (fr_name_and_span.is_none() && outlived_fr_name_and_span.is_none()) + || (*category == ConstraintCategory::Assignment && escapes_from == "function") + || escapes_from == "const" + { + return self.report_general_error( + errctx, + &ErrorConstraintInfo { + fr_is_local: true, + outlived_fr_is_local: false, + .. *errci + }, + renctx, + ); + } + + let mut diag = borrowck_errors::borrowed_data_escapes_closure( + infcx.tcx, + *span, + escapes_from, + ); + + if let Some((Some(outlived_fr_name), outlived_fr_span)) = outlived_fr_name_and_span { + diag.span_label( + outlived_fr_span, + format!( + "`{}` is declared here, outside of the {} body", + outlived_fr_name, escapes_from + ), + ); + } + + if let Some((Some(fr_name), fr_span)) = fr_name_and_span { + diag.span_label( + fr_span, + format!( + "`{}` is a reference that is only valid in the {} body", + fr_name, escapes_from + ), + ); + + diag.span_label( + *span, + format!("`{}` escapes the {} body here", fr_name, escapes_from), + ); + } + + diag + } + + /// Reports a region inference error for the general case with named/synthesized lifetimes to + /// explain what is happening. + /// + /// ```text + /// error: unsatisfied lifetime constraints + /// --> $DIR/regions-creating-enums3.rs:17:5 + /// | + /// LL | fn mk_add_bad1<'a,'b>(x: &'a ast<'a>, y: &'b ast<'b>) -> ast<'a> { + /// | -- -- lifetime `'b` defined here + /// | | + /// | lifetime `'a` defined here + /// LL | ast::add(x, y) + /// | ^^^^^^^^^^^^^^ function was supposed to return data with lifetime `'a` but it + /// | is returning data with lifetime `'b` + /// ``` + fn report_general_error( + &self, + errctx: &ErrorReportingCtx<'_, '_, 'tcx>, + errci: &ErrorConstraintInfo, + renctx: &mut RegionErrorNamingCtx, + ) -> DiagnosticBuilder<'_> { + let ErrorReportingCtx { + infcx, mir_def_id, .. + } = errctx; + let ErrorConstraintInfo { + fr, fr_is_local, outlived_fr, outlived_fr_is_local, span, category, .. + } = errci; + + let mut diag = infcx.tcx.sess.struct_span_err( + *span, + "lifetime may not live long enough" + ); + + let mir_def_name = if infcx.tcx.is_closure(*mir_def_id) { + "closure" + } else { + "function" + }; + + let fr_name = self.give_region_a_name(errctx, renctx, *fr).unwrap(); + fr_name.highlight_region_name(&mut diag); + let outlived_fr_name = self.give_region_a_name(errctx, renctx, *outlived_fr).unwrap(); + outlived_fr_name.highlight_region_name(&mut diag); + + match (category, outlived_fr_is_local, fr_is_local) { + (ConstraintCategory::Return, true, _) => { + diag.span_label( + *span, + format!( + "{} was supposed to return data with lifetime `{}` but it is returning \ + data with lifetime `{}`", + mir_def_name, outlived_fr_name, fr_name + ), + ); + } + _ => { + diag.span_label( + *span, + format!( + "{}requires that `{}` must outlive `{}`", + category.description(), + fr_name, + outlived_fr_name, + ), + ); + } + } + + self.add_static_impl_trait_suggestion(infcx, &mut diag, *fr, fr_name, *outlived_fr); + + diag + } + + /// Adds a suggestion to errors where a `impl Trait` is returned. + /// + /// ```text + /// help: to allow this `impl Trait` to capture borrowed data with lifetime `'1`, add `'_` as + /// a constraint + /// | + /// LL | fn iter_values_anon(&self) -> impl Iterator + 'a { + /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + /// ``` + fn add_static_impl_trait_suggestion( + &self, + infcx: &InferCtxt<'_, 'tcx>, + diag: &mut DiagnosticBuilder<'_>, + fr: RegionVid, + // We need to pass `fr_name` - computing it again will label it twice. + fr_name: RegionName, + outlived_fr: RegionVid, + ) { + if let (Some(f), Some(ty::RegionKind::ReStatic)) = + (self.to_error_region(fr), self.to_error_region(outlived_fr)) + { + if let Some((ty::TyS { + kind: ty::Opaque(did, substs), + .. + }, _)) = infcx + .tcx + .is_suitable_region(f) + .map(|r| r.def_id) + .map(|id| infcx.tcx.return_type_impl_trait(id)) + .unwrap_or(None) + { + // Check whether or not the impl trait return type is intended to capture + // data with the static lifetime. + // + // eg. check for `impl Trait + 'static` instead of `impl Trait`. + let has_static_predicate = { + let predicates_of = infcx.tcx.predicates_of(*did); + let bounds = predicates_of.instantiate(infcx.tcx, substs); + + let mut found = false; + for predicate in bounds.predicates { + if let ty::Predicate::TypeOutlives(binder) = predicate { + if let ty::OutlivesPredicate(_, ty::RegionKind::ReStatic) = + binder.skip_binder() + { + found = true; + break; + } + } + } + + found + }; + + debug!( + "add_static_impl_trait_suggestion: has_static_predicate={:?}", + has_static_predicate + ); + let static_str = kw::StaticLifetime; + // If there is a static predicate, then the only sensible suggestion is to replace + // fr with `'static`. + if has_static_predicate { + diag.help(&format!( + "consider replacing `{}` with `{}`", + fr_name, static_str, + )); + } else { + // Otherwise, we should suggest adding a constraint on the return type. + let span = infcx.tcx.def_span(*did); + if let Ok(snippet) = infcx.tcx.sess.source_map().span_to_snippet(span) { + let suggestable_fr_name = if fr_name.was_named() { + fr_name.to_string() + } else { + "'_".to_string() + }; + + diag.span_suggestion( + span, + &format!( + "to allow this `impl Trait` to capture borrowed data with lifetime \ + `{}`, add `{}` as a constraint", + fr_name, suggestable_fr_name, + ), + format!("{} + {}", snippet, suggestable_fr_name), + Applicability::MachineApplicable, + ); + } + } + } + } + } + + crate fn free_region_constraint_info( + &self, + body: &Body<'tcx>, + local_names: &IndexVec>, + upvars: &[Upvar], + mir_def_id: DefId, + infcx: &InferCtxt<'_, 'tcx>, + borrow_region: RegionVid, + outlived_region: RegionVid, + ) -> (ConstraintCategory, bool, Span, Option) { + let (category, from_closure, span) = self.best_blame_constraint( + body, + borrow_region, + NLLRegionVariableOrigin::FreeRegion, + |r| self.provides_universal_region(r, borrow_region, outlived_region) + ); + + let mut renctx = RegionErrorNamingCtx::new(); + let errctx = ErrorReportingCtx { + infcx, body, local_names, upvars, mir_def_id, + region_infcx: self, + }; + let outlived_fr_name = self.give_region_a_name(&errctx, &mut renctx, outlived_region); + + (category, from_closure, span, outlived_fr_name) + } + + // Finds some region R such that `fr1: R` and `R` is live at + // `elem`. + crate fn find_sub_region_live_at( + &self, + fr1: RegionVid, + elem: Location, + ) -> RegionVid { + debug!("find_sub_region_live_at(fr1={:?}, elem={:?})", fr1, elem); + self.find_constraint_paths_between_regions(fr1, |r| { + // First look for some `r` such that `fr1: r` and `r` is live at `elem` + debug!( + "find_sub_region_live_at: liveness_constraints for {:?} are {:?}", + r, + self.liveness_constraints.region_value_str(r), + ); + self.liveness_constraints.contains(r, elem) + }).or_else(|| { + // If we fail to find that, we may find some `r` such that + // `fr1: r` and `r` is a placeholder from some universe + // `fr1` cannot name. This would force `fr1` to be + // `'static`. + self.find_constraint_paths_between_regions(fr1, |r| { + self.cannot_name_placeholder(fr1, r) + }) + }) + .or_else(|| { + // If we fail to find THAT, it may be that `fr1` is a + // placeholder that cannot "fit" into its SCC. In that + // case, there should be some `r` where `fr1: r`, both + // `fr1` and `r` are in the same SCC, and `fr1` is a + // placeholder that `r` cannot name. We can blame that + // edge. + self.find_constraint_paths_between_regions(fr1, |r| { + self.constraint_sccs.scc(fr1) == self.constraint_sccs.scc(r) + && self.cannot_name_placeholder(r, fr1) + }) + }) + .map(|(_path, r)| r) + .unwrap() + } + + // Finds a good span to blame for the fact that `fr1` outlives `fr2`. + crate fn find_outlives_blame_span( + &self, + body: &Body<'tcx>, + fr1: RegionVid, + fr1_origin: NLLRegionVariableOrigin, + fr2: RegionVid, + ) -> (ConstraintCategory, Span) { + let (category, _, span) = self.best_blame_constraint( + body, + fr1, + fr1_origin, + |r| self.provides_universal_region(r, fr1, fr2), + ); + (category, span) + } + + fn retrieve_closure_constraint_info( + &self, + body: &Body<'tcx>, + constraint: &OutlivesConstraint, + ) -> (ConstraintCategory, bool, Span) { + let loc = match constraint.locations { + Locations::All(span) => return (constraint.category, false, span), + Locations::Single(loc) => loc, + }; + + let opt_span_category = + self.closure_bounds_mapping[&loc].get(&(constraint.sup, constraint.sub)); + opt_span_category + .map(|&(category, span)| (category, true, span)) + .unwrap_or((constraint.category, false, body.source_info(loc).span)) + } + + /// Returns `true` if a closure is inferred to be an `FnMut` closure. + crate fn is_closure_fn_mut(&self, infcx: &InferCtxt<'_, 'tcx>, fr: RegionVid) -> bool { + if let Some(ty::ReFree(free_region)) = self.to_error_region(fr) { + if let ty::BoundRegion::BrEnv = free_region.bound_region { + if let DefiningTy::Closure(def_id, substs) = self.universal_regions.defining_ty { + let closure_kind_ty = substs.as_closure().kind_ty(def_id, infcx.tcx); + return Some(ty::ClosureKind::FnMut) == closure_kind_ty.to_opt_closure_kind(); + } + } + } + + false + } + + /// If `r2` represents a placeholder region, then this returns + /// `true` if `r1` cannot name that placeholder in its + /// value; otherwise, returns `false`. + fn cannot_name_placeholder(&self, r1: RegionVid, r2: RegionVid) -> bool { + debug!("cannot_name_value_of(r1={:?}, r2={:?})", r1, r2); + + match self.definitions[r2].origin { + NLLRegionVariableOrigin::Placeholder(placeholder) => { + let universe1 = self.definitions[r1].universe; + debug!( + "cannot_name_value_of: universe1={:?} placeholder={:?}", + universe1, placeholder + ); + universe1.cannot_name(placeholder.universe) + } + + NLLRegionVariableOrigin::FreeRegion | NLLRegionVariableOrigin::Existential { .. } => { + false + } + } + } +} diff --git a/src/librustc_mir/borrow_check/diagnostics/region_name.rs b/src/librustc_mir/borrow_check/diagnostics/region_name.rs new file mode 100644 index 00000000000..e2e75962aec --- /dev/null +++ b/src/librustc_mir/borrow_check/diagnostics/region_name.rs @@ -0,0 +1,836 @@ +use std::fmt::{self, Display}; + +use rustc::hir; +use rustc::hir::def::{Res, DefKind}; +use rustc::hir::def_id::DefId; +use rustc::infer::InferCtxt; +use rustc::mir::{Local, Body}; +use rustc::ty::subst::{SubstsRef, GenericArgKind}; +use rustc::ty::{self, RegionVid, Ty, TyCtxt}; +use rustc::ty::print::RegionHighlightMode; +use rustc_index::vec::IndexVec; +use rustc_errors::DiagnosticBuilder; +use syntax::symbol::kw; +use rustc_data_structures::fx::FxHashMap; +use syntax_pos::{Span, symbol::Symbol, DUMMY_SP}; + +use crate::borrow_check::{ + nll::region_infer::RegionInferenceContext, + nll::universal_regions::DefiningTy, + nll::ToRegionVid, + Upvar, +}; + +use super::region_errors::ErrorReportingCtx; + +/// A name for a particular region used in emitting diagnostics. This name could be a generated +/// name like `'1`, a name used by the user like `'a`, or a name like `'static`. +#[derive(Debug, Clone)] +crate struct RegionName { + /// The name of the region (interned). + crate name: Symbol, + /// Where the region comes from. + crate source: RegionNameSource, +} + +/// Denotes the source of a region that is named by a `RegionName`. For example, a free region that +/// was named by the user would get `NamedFreeRegion` and `'static` lifetime would get `Static`. +/// This helps to print the right kinds of diagnostics. +#[derive(Debug, Clone)] +crate enum RegionNameSource { + /// A bound (not free) region that was substituted at the def site (not an HRTB). + NamedEarlyBoundRegion(Span), + /// A free region that the user has a name (`'a`) for. + NamedFreeRegion(Span), + /// The `'static` region. + Static, + /// The free region corresponding to the environment of a closure. + SynthesizedFreeEnvRegion(Span, String), + /// The region name corresponds to a region where the type annotation is completely missing + /// from the code, e.g. in a closure arguments `|x| { ... }`, where `x` is a reference. + CannotMatchHirTy(Span, String), + /// The region name corresponds a reference that was found by traversing the type in the HIR. + MatchedHirTy(Span), + /// A region name from the generics list of a struct/enum/union. + MatchedAdtAndSegment(Span), + /// The region corresponding to a closure upvar. + AnonRegionFromUpvar(Span, String), + /// The region corresponding to the return type of a closure. + AnonRegionFromOutput(Span, String, String), + /// The region from a type yielded by a generator. + AnonRegionFromYieldTy(Span, String), + /// An anonymous region from an async fn. + AnonRegionFromAsyncFn(Span), +} + +/// Records region names that have been assigned before so that we can use the same ones in later +/// diagnostics. +#[derive(Debug, Clone)] +crate struct RegionErrorNamingCtx { + /// Record the region names generated for each region in the given + /// MIR def so that we can reuse them later in help/error messages. + renctx: FxHashMap, + + /// The counter for generating new region names. + counter: usize, +} + +impl RegionErrorNamingCtx { + crate fn new() -> Self { + Self { + counter: 1, + renctx: FxHashMap::default(), + } + } + + /// Get the name of `region` if it has previously been named. + crate fn get(&self, region: &RegionVid) -> Option<&RegionName> { + self.renctx.get(region) + } + + /// Give `region` the name `name`. + crate fn insert(&mut self, region: RegionVid, name: RegionName) { + self.renctx.insert(region, name); + } + + /// Creates a synthetic region named `'N`, where `N` is the next value of the counter. Then, + /// increment the counter. + /// + /// The name is not memoized. A separate call to `insert` should be made later. (Currently, + /// this happens at the end of `give_region_a_name`). + crate fn synthesize_region_name(&mut self) -> Symbol { + let c = self.counter; + self.counter += 1; + + Symbol::intern(&format!("'{:?}", c)) + } +} + +impl RegionName { + crate fn was_named(&self) -> bool { + match self.source { + RegionNameSource::NamedEarlyBoundRegion(..) | + RegionNameSource::NamedFreeRegion(..) | + RegionNameSource::Static => true, + RegionNameSource::SynthesizedFreeEnvRegion(..) | + RegionNameSource::CannotMatchHirTy(..) | + RegionNameSource::MatchedHirTy(..) | + RegionNameSource::MatchedAdtAndSegment(..) | + RegionNameSource::AnonRegionFromUpvar(..) | + RegionNameSource::AnonRegionFromOutput(..) | + RegionNameSource::AnonRegionFromYieldTy(..) | + RegionNameSource::AnonRegionFromAsyncFn(..) => false, + } + } + + crate fn highlight_region_name(&self, diag: &mut DiagnosticBuilder<'_>) { + match &self.source { + RegionNameSource::NamedFreeRegion(span) + | RegionNameSource::NamedEarlyBoundRegion(span) => { + diag.span_label(*span, format!("lifetime `{}` defined here", self)); + } + RegionNameSource::SynthesizedFreeEnvRegion(span, note) => { + diag.span_label( + *span, + format!("lifetime `{}` represents this closure's body", self), + ); + diag.note(¬e); + } + RegionNameSource::CannotMatchHirTy(span, type_name) => { + diag.span_label(*span, format!("has type `{}`", type_name)); + } + RegionNameSource::MatchedHirTy(span) | + RegionNameSource::AnonRegionFromAsyncFn(span) => { + diag.span_label( + *span, + format!("let's call the lifetime of this reference `{}`", self), + ); + } + RegionNameSource::MatchedAdtAndSegment(span) => { + diag.span_label(*span, format!("let's call this `{}`", self)); + } + RegionNameSource::AnonRegionFromUpvar(span, upvar_name) => { + diag.span_label( + *span, + format!( + "lifetime `{}` appears in the type of `{}`", + self, upvar_name + ), + ); + } + RegionNameSource::AnonRegionFromOutput(span, mir_description, type_name) => { + diag.span_label( + *span, + format!("return type{} is {}", mir_description, type_name), + ); + }, + RegionNameSource::AnonRegionFromYieldTy(span, type_name) => { + diag.span_label( + *span, + format!("yield type is {}", type_name), + ); + } + RegionNameSource::Static => {}, + } + } +} + +impl Display for RegionName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name) + } +} + +impl<'tcx> RegionInferenceContext<'tcx> { + /// Maps from an internal MIR region vid to something that we can + /// report to the user. In some cases, the region vids will map + /// directly to lifetimes that the user has a name for (e.g., + /// `'static`). But frequently they will not, in which case we + /// have to find some way to identify the lifetime to the user. To + /// that end, this function takes a "diagnostic" so that it can + /// create auxiliary notes as needed. + /// + /// Example (function arguments): + /// + /// Suppose we are trying to give a name to the lifetime of the + /// reference `x`: + /// + /// ``` + /// fn foo(x: &u32) { .. } + /// ``` + /// + /// This function would create a label like this: + /// + /// ``` + /// | fn foo(x: &u32) { .. } + /// ------- fully elaborated type of `x` is `&'1 u32` + /// ``` + /// + /// and then return the name `'1` for us to use. + crate fn give_region_a_name( + &self, + errctx: &ErrorReportingCtx<'_, '_, 'tcx>, + renctx: &mut RegionErrorNamingCtx, + fr: RegionVid, + ) -> Option { + let ErrorReportingCtx { + infcx, body, mir_def_id, local_names, upvars, .. + } = errctx; + + debug!("give_region_a_name(fr={:?}, counter={:?})", fr, renctx.counter); + + assert!(self.universal_regions.is_universal_region(fr)); + + if let Some(value) = renctx.get(&fr) { + return Some(value.clone()); + } + + let value = self + .give_name_from_error_region(infcx.tcx, *mir_def_id, fr, renctx) + .or_else(|| { + self.give_name_if_anonymous_region_appears_in_arguments( + infcx, body, local_names, *mir_def_id, fr, renctx, + ) + }) + .or_else(|| { + self.give_name_if_anonymous_region_appears_in_upvars( + infcx.tcx, upvars, fr, renctx + ) + }) + .or_else(|| { + self.give_name_if_anonymous_region_appears_in_output( + infcx, body, *mir_def_id, fr, renctx, + ) + }) + .or_else(|| { + self.give_name_if_anonymous_region_appears_in_yield_ty( + infcx, body, *mir_def_id, fr, renctx, + ) + }); + + if let Some(ref value) = value { + renctx.insert(fr, value.clone()); + } + + debug!("give_region_a_name: gave name {:?}", value); + value + } + + /// Checks for the case where `fr` maps to something that the + /// *user* has a name for. In that case, we'll be able to map + /// `fr` to a `Region<'tcx>`, and that region will be one of + /// named variants. + fn give_name_from_error_region( + &self, + tcx: TyCtxt<'tcx>, + mir_def_id: DefId, + fr: RegionVid, + renctx: &mut RegionErrorNamingCtx, + ) -> Option { + let error_region = self.to_error_region(fr)?; + + debug!("give_region_a_name: error_region = {:?}", error_region); + match error_region { + ty::ReEarlyBound(ebr) => { + if ebr.has_name() { + let span = tcx.hir().span_if_local(ebr.def_id).unwrap_or(DUMMY_SP); + Some(RegionName { + name: ebr.name, + source: RegionNameSource::NamedEarlyBoundRegion(span), + }) + } else { + None + } + } + + ty::ReStatic => Some(RegionName { + name: kw::StaticLifetime, + source: RegionNameSource::Static + }), + + ty::ReFree(free_region) => match free_region.bound_region { + ty::BoundRegion::BrNamed(region_def_id, name) => { + // Get the span to point to, even if we don't use the name. + let span = tcx.hir().span_if_local(region_def_id).unwrap_or(DUMMY_SP); + debug!("bound region named: {:?}, is_named: {:?}", + name, free_region.bound_region.is_named()); + + if free_region.bound_region.is_named() { + // A named region that is actually named. + Some(RegionName { + name, + source: RegionNameSource::NamedFreeRegion(span), + }) + } else { + // If we spuriously thought that the region is named, we should let the + // system generate a true name for error messages. Currently this can + // happen if we have an elided name in an async fn for example: the + // compiler will generate a region named `'_`, but reporting such a name is + // not actually useful, so we synthesize a name for it instead. + let name = renctx.synthesize_region_name(); + Some(RegionName { + name, + source: RegionNameSource::AnonRegionFromAsyncFn(span), + }) + } + } + + ty::BoundRegion::BrEnv => { + let mir_hir_id = tcx.hir().as_local_hir_id(mir_def_id).expect("non-local mir"); + let def_ty = self.universal_regions.defining_ty; + + if let DefiningTy::Closure(def_id, substs) = def_ty { + let args_span = if let hir::ExprKind::Closure(_, _, _, span, _) = + tcx.hir().expect_expr(mir_hir_id).kind + { + span + } else { + bug!("Closure is not defined by a closure expr"); + }; + let region_name = renctx.synthesize_region_name(); + + let closure_kind_ty = substs.as_closure().kind_ty(def_id, tcx); + let note = match closure_kind_ty.to_opt_closure_kind() { + Some(ty::ClosureKind::Fn) => { + "closure implements `Fn`, so references to captured variables \ + can't escape the closure" + } + Some(ty::ClosureKind::FnMut) => { + "closure implements `FnMut`, so references to captured variables \ + can't escape the closure" + } + Some(ty::ClosureKind::FnOnce) => { + bug!("BrEnv in a `FnOnce` closure"); + } + None => bug!("Closure kind not inferred in borrow check"), + }; + + Some(RegionName { + name: region_name, + source: RegionNameSource::SynthesizedFreeEnvRegion( + args_span, + note.to_string(), + ), + }) + } else { + // Can't have BrEnv in functions, constants or generators. + bug!("BrEnv outside of closure."); + } + } + + ty::BoundRegion::BrAnon(_) => None, + }, + + ty::ReLateBound(..) + | ty::ReScope(..) + | ty::ReVar(..) + | ty::RePlaceholder(..) + | ty::ReEmpty + | ty::ReErased + | ty::ReClosureBound(..) => None, + } + } + + /// Finds an argument that contains `fr` and label it with a fully + /// elaborated type, returning something like `'1`. Result looks + /// like: + /// + /// ``` + /// | fn foo(x: &u32) { .. } + /// ------- fully elaborated type of `x` is `&'1 u32` + /// ``` + fn give_name_if_anonymous_region_appears_in_arguments( + &self, + infcx: &InferCtxt<'_, 'tcx>, + body: &Body<'tcx>, + local_names: &IndexVec>, + mir_def_id: DefId, + fr: RegionVid, + renctx: &mut RegionErrorNamingCtx, + ) -> Option { + let implicit_inputs = self.universal_regions.defining_ty.implicit_inputs(); + let argument_index = self.get_argument_index_for_region(infcx.tcx, fr)?; + + let arg_ty = + self.universal_regions.unnormalized_input_tys[implicit_inputs + argument_index]; + if let Some(region_name) = self.give_name_if_we_can_match_hir_ty_from_argument( + infcx, + mir_def_id, + fr, + arg_ty, + argument_index, + renctx, + ) { + return Some(region_name); + } + + self.give_name_if_we_cannot_match_hir_ty(infcx, body, local_names, fr, arg_ty, renctx) + } + + fn give_name_if_we_can_match_hir_ty_from_argument( + &self, + infcx: &InferCtxt<'_, 'tcx>, + mir_def_id: DefId, + needle_fr: RegionVid, + argument_ty: Ty<'tcx>, + argument_index: usize, + renctx: &mut RegionErrorNamingCtx, + ) -> Option { + let mir_hir_id = infcx.tcx.hir().as_local_hir_id(mir_def_id)?; + let fn_decl = infcx.tcx.hir().fn_decl_by_hir_id(mir_hir_id)?; + let argument_hir_ty: &hir::Ty = fn_decl.inputs.get(argument_index)?; + match argument_hir_ty.kind { + // This indicates a variable with no type annotation, like + // `|x|`... in that case, we can't highlight the type but + // must highlight the variable. + // NOTE(eddyb) this is handled in/by the sole caller + // (`give_name_if_anonymous_region_appears_in_arguments`). + hir::TyKind::Infer => None, + + _ => self.give_name_if_we_can_match_hir_ty( + infcx.tcx, + needle_fr, + argument_ty, + argument_hir_ty, + renctx, + ), + } + } + + /// Attempts to highlight the specific part of a type in an argument + /// that has no type annotation. + /// For example, we might produce an annotation like this: + /// + /// ``` + /// | foo(|a, b| b) + /// | - - + /// | | | + /// | | has type `&'1 u32` + /// | has type `&'2 u32` + /// ``` + fn give_name_if_we_cannot_match_hir_ty( + &self, + infcx: &InferCtxt<'_, 'tcx>, + body: &Body<'tcx>, + local_names: &IndexVec>, + needle_fr: RegionVid, + argument_ty: Ty<'tcx>, + renctx: &mut RegionErrorNamingCtx, + ) -> Option { + let counter = renctx.counter; + let mut highlight = RegionHighlightMode::default(); + highlight.highlighting_region_vid(needle_fr, counter); + let type_name = infcx.extract_type_name(&argument_ty, Some(highlight)).0; + + debug!( + "give_name_if_we_cannot_match_hir_ty: type_name={:?} needle_fr={:?}", + type_name, needle_fr + ); + let assigned_region_name = if type_name.find(&format!("'{}", counter)).is_some() { + // Only add a label if we can confirm that a region was labelled. + let argument_index = self.get_argument_index_for_region(infcx.tcx, needle_fr)?; + let (_, span) = + self.get_argument_name_and_span_for_region(body, local_names, argument_index); + + Some(RegionName { + // This counter value will already have been used, so this function will increment + // it so the next value will be used next and return the region name that would + // have been used. + name: renctx.synthesize_region_name(), + source: RegionNameSource::CannotMatchHirTy(span, type_name), + }) + } else { + None + }; + + assigned_region_name + } + + /// Attempts to highlight the specific part of a type annotation + /// that contains the anonymous reference we want to give a name + /// to. For example, we might produce an annotation like this: + /// + /// ``` + /// | fn a(items: &[T]) -> Box> { + /// | - let's call the lifetime of this reference `'1` + /// ``` + /// + /// the way this works is that we match up `argument_ty`, which is + /// a `Ty<'tcx>` (the internal form of the type) with + /// `argument_hir_ty`, a `hir::Ty` (the syntax of the type + /// annotation). We are descending through the types stepwise, + /// looking in to find the region `needle_fr` in the internal + /// type. Once we find that, we can use the span of the `hir::Ty` + /// to add the highlight. + /// + /// This is a somewhat imperfect process, so along the way we also + /// keep track of the **closest** type we've found. If we fail to + /// find the exact `&` or `'_` to highlight, then we may fall back + /// to highlighting that closest type instead. + fn give_name_if_we_can_match_hir_ty( + &self, + tcx: TyCtxt<'tcx>, + needle_fr: RegionVid, + argument_ty: Ty<'tcx>, + argument_hir_ty: &hir::Ty, + renctx: &mut RegionErrorNamingCtx, + ) -> Option { + let search_stack: &mut Vec<(Ty<'tcx>, &hir::Ty)> = + &mut vec![(argument_ty, argument_hir_ty)]; + + while let Some((ty, hir_ty)) = search_stack.pop() { + match (&ty.kind, &hir_ty.kind) { + // Check if the `argument_ty` is `&'X ..` where `'X` + // is the region we are looking for -- if so, and we have a `&T` + // on the RHS, then we want to highlight the `&` like so: + // + // & + // - let's call the lifetime of this reference `'1` + ( + ty::Ref(region, referent_ty, _), + hir::TyKind::Rptr(_lifetime, referent_hir_ty), + ) => { + if region.to_region_vid() == needle_fr { + let region_name = renctx.synthesize_region_name(); + + // Just grab the first character, the `&`. + let source_map = tcx.sess.source_map(); + let ampersand_span = source_map.start_point(hir_ty.span); + + return Some(RegionName { + name: region_name, + source: RegionNameSource::MatchedHirTy(ampersand_span), + }); + } + + // Otherwise, let's descend into the referent types. + search_stack.push((referent_ty, &referent_hir_ty.ty)); + } + + // Match up something like `Foo<'1>` + ( + ty::Adt(_adt_def, substs), + hir::TyKind::Path(hir::QPath::Resolved(None, path)), + ) => { + match path.res { + // Type parameters of the type alias have no reason to + // be the same as those of the ADT. + // FIXME: We should be able to do something similar to + // match_adt_and_segment in this case. + Res::Def(DefKind::TyAlias, _) => (), + _ => if let Some(last_segment) = path.segments.last() { + if let Some(name) = self.match_adt_and_segment( + substs, + needle_fr, + last_segment, + renctx, + search_stack, + ) { + return Some(name); + } + } + } + } + + // The following cases don't have lifetimes, so we + // just worry about trying to match up the rustc type + // with the HIR types: + (ty::Tuple(elem_tys), hir::TyKind::Tup(elem_hir_tys)) => { + search_stack.extend(elem_tys.iter().map(|k| k.expect_ty()).zip(elem_hir_tys)); + } + + (ty::Slice(elem_ty), hir::TyKind::Slice(elem_hir_ty)) + | (ty::Array(elem_ty, _), hir::TyKind::Array(elem_hir_ty, _)) => { + search_stack.push((elem_ty, elem_hir_ty)); + } + + (ty::RawPtr(mut_ty), hir::TyKind::Ptr(mut_hir_ty)) => { + search_stack.push((mut_ty.ty, &mut_hir_ty.ty)); + } + + _ => { + // FIXME there are other cases that we could trace + } + } + } + + return None; + } + + /// We've found an enum/struct/union type with the substitutions + /// `substs` and -- in the HIR -- a path type with the final + /// segment `last_segment`. Try to find a `'_` to highlight in + /// the generic args (or, if not, to produce new zipped pairs of + /// types+hir to search through). + fn match_adt_and_segment<'hir>( + &self, + substs: SubstsRef<'tcx>, + needle_fr: RegionVid, + last_segment: &'hir hir::PathSegment, + renctx: &mut RegionErrorNamingCtx, + search_stack: &mut Vec<(Ty<'tcx>, &'hir hir::Ty)>, + ) -> Option { + // Did the user give explicit arguments? (e.g., `Foo<..>`) + let args = last_segment.args.as_ref()?; + let lifetime = + self.try_match_adt_and_generic_args(substs, needle_fr, args, search_stack)?; + match lifetime.name { + hir::LifetimeName::Param(_) + | hir::LifetimeName::Error + | hir::LifetimeName::Static + | hir::LifetimeName::Underscore => { + let region_name = renctx.synthesize_region_name(); + let ampersand_span = lifetime.span; + Some(RegionName { + name: region_name, + source: RegionNameSource::MatchedAdtAndSegment(ampersand_span), + }) + } + + hir::LifetimeName::ImplicitObjectLifetimeDefault + | hir::LifetimeName::Implicit => { + // In this case, the user left off the lifetime; so + // they wrote something like: + // + // ``` + // x: Foo + // ``` + // + // where the fully elaborated form is `Foo<'_, '1, + // T>`. We don't consider this a match; instead we let + // the "fully elaborated" type fallback above handle + // it. + None + } + } + } + + /// We've found an enum/struct/union type with the substitutions + /// `substs` and -- in the HIR -- a path with the generic + /// arguments `args`. If `needle_fr` appears in the args, return + /// the `hir::Lifetime` that corresponds to it. If not, push onto + /// `search_stack` the types+hir to search through. + fn try_match_adt_and_generic_args<'hir>( + &self, + substs: SubstsRef<'tcx>, + needle_fr: RegionVid, + args: &'hir hir::GenericArgs, + search_stack: &mut Vec<(Ty<'tcx>, &'hir hir::Ty)>, + ) -> Option<&'hir hir::Lifetime> { + for (kind, hir_arg) in substs.iter().zip(&args.args) { + match (kind.unpack(), hir_arg) { + (GenericArgKind::Lifetime(r), hir::GenericArg::Lifetime(lt)) => { + if r.to_region_vid() == needle_fr { + return Some(lt); + } + } + + (GenericArgKind::Type(ty), hir::GenericArg::Type(hir_ty)) => { + search_stack.push((ty, hir_ty)); + } + + (GenericArgKind::Const(_ct), hir::GenericArg::Const(_hir_ct)) => { + // Lifetimes cannot be found in consts, so we don't need + // to search anything here. + } + + (GenericArgKind::Lifetime(_), _) + | (GenericArgKind::Type(_), _) + | (GenericArgKind::Const(_), _) => { + // I *think* that HIR lowering should ensure this + // doesn't happen, even in erroneous + // programs. Else we should use delay-span-bug. + span_bug!( + hir_arg.span(), + "unmatched subst and hir arg: found {:?} vs {:?}", + kind, + hir_arg, + ); + } + } + } + + None + } + + /// Finds a closure upvar that contains `fr` and label it with a + /// fully elaborated type, returning something like `'1`. Result + /// looks like: + /// + /// ``` + /// | let x = Some(&22); + /// - fully elaborated type of `x` is `Option<&'1 u32>` + /// ``` + fn give_name_if_anonymous_region_appears_in_upvars( + &self, + tcx: TyCtxt<'tcx>, + upvars: &[Upvar], + fr: RegionVid, + renctx: &mut RegionErrorNamingCtx, + ) -> Option { + let upvar_index = self.get_upvar_index_for_region(tcx, fr)?; + let (upvar_name, upvar_span) = + self.get_upvar_name_and_span_for_region(tcx, upvars, upvar_index); + let region_name = renctx.synthesize_region_name(); + + Some(RegionName { + name: region_name, + source: RegionNameSource::AnonRegionFromUpvar(upvar_span, upvar_name.to_string()), + }) + } + + /// Checks for arguments appearing in the (closure) return type. It + /// must be a closure since, in a free fn, such an argument would + /// have to either also appear in an argument (if using elision) + /// or be early bound (named, not in argument). + fn give_name_if_anonymous_region_appears_in_output( + &self, + infcx: &InferCtxt<'_, 'tcx>, + body: &Body<'tcx>, + mir_def_id: DefId, + fr: RegionVid, + renctx: &mut RegionErrorNamingCtx, + ) -> Option { + let tcx = infcx.tcx; + + let return_ty = self.universal_regions.unnormalized_output_ty; + debug!( + "give_name_if_anonymous_region_appears_in_output: return_ty = {:?}", + return_ty + ); + if !tcx.any_free_region_meets(&return_ty, |r| r.to_region_vid() == fr) { + return None; + } + + let mut highlight = RegionHighlightMode::default(); + highlight.highlighting_region_vid(fr, renctx.counter); + let type_name = infcx.extract_type_name(&return_ty, Some(highlight)).0; + + let mir_hir_id = tcx.hir().as_local_hir_id(mir_def_id).expect("non-local mir"); + + let (return_span, mir_description) = match tcx.hir().get(mir_hir_id) { + hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Closure(_, return_ty, _, span, gen_move), + .. + }) => ( + match return_ty.output { + hir::FunctionRetTy::DefaultReturn(_) => tcx.sess.source_map().end_point(*span), + hir::FunctionRetTy::Return(_) => return_ty.output.span(), + }, + if gen_move.is_some() { + " of generator" + } else { + " of closure" + }, + ), + hir::Node::ImplItem(hir::ImplItem { + kind: hir::ImplItemKind::Method(method_sig, _), + .. + }) => (method_sig.decl.output.span(), ""), + _ => (body.span, ""), + }; + + Some(RegionName { + // This counter value will already have been used, so this function will increment it + // so the next value will be used next and return the region name that would have been + // used. + name: renctx.synthesize_region_name(), + source: RegionNameSource::AnonRegionFromOutput( + return_span, + mir_description.to_string(), + type_name, + ), + }) + } + + fn give_name_if_anonymous_region_appears_in_yield_ty( + &self, + infcx: &InferCtxt<'_, 'tcx>, + body: &Body<'tcx>, + mir_def_id: DefId, + fr: RegionVid, + renctx: &mut RegionErrorNamingCtx, + ) -> Option { + // Note: generators from `async fn` yield `()`, so we don't have to + // worry about them here. + let yield_ty = self.universal_regions.yield_ty?; + debug!( + "give_name_if_anonymous_region_appears_in_yield_ty: yield_ty = {:?}", + yield_ty, + ); + + let tcx = infcx.tcx; + + if !tcx.any_free_region_meets(&yield_ty, |r| r.to_region_vid() == fr) { + return None; + } + + let mut highlight = RegionHighlightMode::default(); + highlight.highlighting_region_vid(fr, renctx.counter); + let type_name = infcx.extract_type_name(&yield_ty, Some(highlight)).0; + + let mir_hir_id = tcx.hir().as_local_hir_id(mir_def_id).expect("non-local mir"); + + let yield_span = match tcx.hir().get(mir_hir_id) { + hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Closure(_, _, _, span, _), + .. + }) => ( + tcx.sess.source_map().end_point(*span) + ), + _ => body.span, + }; + + debug!( + "give_name_if_anonymous_region_appears_in_yield_ty: \ + type_name = {:?}, yield_span = {:?}", + yield_span, + type_name, + ); + + Some(RegionName { + name: renctx.synthesize_region_name(), + source: RegionNameSource::AnonRegionFromYieldTy(yield_span, type_name), + }) + } +} diff --git a/src/librustc_mir/borrow_check/diagnostics/var_name.rs b/src/librustc_mir/borrow_check/diagnostics/var_name.rs new file mode 100644 index 00000000000..1ac44c4fdb1 --- /dev/null +++ b/src/librustc_mir/borrow_check/diagnostics/var_name.rs @@ -0,0 +1,136 @@ +use crate::borrow_check::nll::region_infer::RegionInferenceContext; +use crate::borrow_check::nll::ToRegionVid; +use crate::borrow_check::Upvar; +use rustc::mir::{Local, Body}; +use rustc::ty::{RegionVid, TyCtxt}; +use rustc_index::vec::{Idx, IndexVec}; +use syntax::source_map::Span; +use syntax_pos::symbol::Symbol; + +impl<'tcx> RegionInferenceContext<'tcx> { + crate fn get_var_name_and_span_for_region( + &self, + tcx: TyCtxt<'tcx>, + body: &Body<'tcx>, + local_names: &IndexVec>, + upvars: &[Upvar], + fr: RegionVid, + ) -> Option<(Option, Span)> { + debug!("get_var_name_and_span_for_region(fr={:?})", fr); + assert!(self.universal_regions.is_universal_region(fr)); + + debug!("get_var_name_and_span_for_region: attempting upvar"); + self.get_upvar_index_for_region(tcx, fr) + .map(|index| { + let (name, span) = + self.get_upvar_name_and_span_for_region(tcx, upvars, index); + (Some(name), span) + }) + .or_else(|| { + debug!("get_var_name_and_span_for_region: attempting argument"); + self.get_argument_index_for_region(tcx, fr).map(|index| { + self.get_argument_name_and_span_for_region(body, local_names, index) + }) + }) + } + + /// Search the upvars (if any) to find one that references fr. Return its index. + crate fn get_upvar_index_for_region(&self, tcx: TyCtxt<'tcx>, fr: RegionVid) -> Option { + let upvar_index = self + .universal_regions + .defining_ty + .upvar_tys(tcx) + .position(|upvar_ty| { + debug!("get_upvar_index_for_region: upvar_ty={:?}", upvar_ty); + tcx.any_free_region_meets(&upvar_ty, |r| { + let r = r.to_region_vid(); + debug!("get_upvar_index_for_region: r={:?} fr={:?}", r, fr); + r == fr + }) + })?; + + let upvar_ty = self + .universal_regions + .defining_ty + .upvar_tys(tcx) + .nth(upvar_index); + + debug!( + "get_upvar_index_for_region: found {:?} in upvar {} which has type {:?}", + fr, upvar_index, upvar_ty, + ); + + Some(upvar_index) + } + + /// Given the index of an upvar, finds its name and the span from where it was + /// declared. + crate fn get_upvar_name_and_span_for_region( + &self, + tcx: TyCtxt<'tcx>, + upvars: &[Upvar], + upvar_index: usize, + ) -> (Symbol, Span) { + let upvar_hir_id = upvars[upvar_index].var_hir_id; + debug!("get_upvar_name_and_span_for_region: upvar_hir_id={:?}", upvar_hir_id); + + let upvar_name = tcx.hir().name(upvar_hir_id); + let upvar_span = tcx.hir().span(upvar_hir_id); + debug!("get_upvar_name_and_span_for_region: upvar_name={:?} upvar_span={:?}", + upvar_name, upvar_span); + + (upvar_name, upvar_span) + } + + /// Search the argument types for one that references fr (which should be a free region). + /// Returns Some(_) with the index of the input if one is found. + /// + /// N.B., in the case of a closure, the index is indexing into the signature as seen by the + /// user - in particular, index 0 is not the implicit self parameter. + crate fn get_argument_index_for_region( + &self, + tcx: TyCtxt<'tcx>, + fr: RegionVid, + ) -> Option { + let implicit_inputs = self.universal_regions.defining_ty.implicit_inputs(); + let argument_index = self + .universal_regions + .unnormalized_input_tys + .iter() + .skip(implicit_inputs) + .position(|arg_ty| { + debug!( + "get_argument_index_for_region: arg_ty = {:?}", + arg_ty + ); + tcx.any_free_region_meets(arg_ty, |r| r.to_region_vid() == fr) + })?; + + debug!( + "get_argument_index_for_region: found {:?} in argument {} which has type {:?}", + fr, argument_index, self.universal_regions.unnormalized_input_tys[argument_index], + ); + + Some(argument_index) + } + + /// Given the index of an argument, finds its name (if any) and the span from where it was + /// declared. + crate fn get_argument_name_and_span_for_region( + &self, + body: &Body<'tcx>, + local_names: &IndexVec>, + argument_index: usize, + ) -> (Option, Span) { + let implicit_inputs = self.universal_regions.defining_ty.implicit_inputs(); + let argument_local = Local::new(implicit_inputs + argument_index + 1); + debug!("get_argument_name_and_span_for_region: argument_local={:?}", argument_local); + + let argument_name = local_names[argument_local]; + let argument_span = body.local_decls[argument_local].source_info.span; + debug!("get_argument_name_and_span_for_region: argument_name={:?} argument_span={:?}", + argument_name, argument_span); + + (argument_name, argument_span) + } +} 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/mod.rs b/src/librustc_mir/borrow_check/mod.rs index bacff0b3e54..a1932b551c1 100644 --- a/src/librustc_mir/borrow_check/mod.rs +++ b/src/librustc_mir/borrow_check/mod.rs @@ -46,17 +46,14 @@ use self::location::LocationTable; use self::prefixes::PrefixSet; use self::MutateMode::{JustWrite, WriteAndRead}; -use self::mutability_errors::AccessKind; +use self::diagnostics::AccessKind; use self::path_utils::*; crate mod borrow_set; -mod error_reporting; +mod diagnostics; mod flows; mod location; -mod conflict_errors; -mod move_errors; -mod mutability_errors; mod path_utils; crate mod place_ext; crate mod places_conflict; 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 - } -} diff --git a/src/librustc_mir/borrow_check/nll/explain_borrow/find_use.rs b/src/librustc_mir/borrow_check/nll/explain_borrow/find_use.rs deleted file mode 100644 index 7ab069260f9..00000000000 --- a/src/librustc_mir/borrow_check/nll/explain_borrow/find_use.rs +++ /dev/null @@ -1,135 +0,0 @@ -use std::collections::VecDeque; -use std::rc::Rc; - -use crate::borrow_check::nll::region_infer::{Cause, RegionInferenceContext}; -use crate::borrow_check::nll::ToRegionVid; -use crate::util::liveness::{self, DefUse}; -use rustc::mir::visit::{MirVisitable, PlaceContext, Visitor}; -use rustc::mir::{Local, Location, Body}; -use rustc::ty::{RegionVid, TyCtxt}; -use rustc_data_structures::fx::FxHashSet; - -crate fn find<'tcx>( - body: &Body<'tcx>, - regioncx: &Rc>, - tcx: TyCtxt<'tcx>, - region_vid: RegionVid, - start_point: Location, -) -> Option { - let mut uf = UseFinder { - body, - regioncx, - tcx, - region_vid, - start_point, - }; - - uf.find() -} - -struct UseFinder<'cx, 'tcx> { - body: &'cx Body<'tcx>, - regioncx: &'cx Rc>, - tcx: TyCtxt<'tcx>, - region_vid: RegionVid, - start_point: Location, -} - -impl<'cx, 'tcx> UseFinder<'cx, 'tcx> { - fn find(&mut self) -> Option { - let mut queue = VecDeque::new(); - let mut visited = FxHashSet::default(); - - queue.push_back(self.start_point); - while let Some(p) = queue.pop_front() { - if !self.regioncx.region_contains(self.region_vid, p) { - continue; - } - - if !visited.insert(p) { - continue; - } - - let block_data = &self.body[p.block]; - - match self.def_use(p, block_data.visitable(p.statement_index)) { - Some(DefUseResult::Def) => {} - - Some(DefUseResult::UseLive { local }) => { - return Some(Cause::LiveVar(local, p)); - } - - Some(DefUseResult::UseDrop { local }) => { - return Some(Cause::DropVar(local, p)); - } - - None => { - if p.statement_index < block_data.statements.len() { - queue.push_back(p.successor_within_block()); - } else { - queue.extend( - block_data - .terminator() - .successors() - .filter(|&bb| Some(&Some(*bb)) != block_data.terminator().unwind()) - .map(|&bb| Location { - statement_index: 0, - block: bb, - }), - ); - } - } - } - } - - None - } - - fn def_use(&self, location: Location, thing: &dyn MirVisitable<'tcx>) -> Option { - let mut visitor = DefUseVisitor { - body: self.body, - tcx: self.tcx, - region_vid: self.region_vid, - def_use_result: None, - }; - - thing.apply(location, &mut visitor); - - visitor.def_use_result - } -} - -struct DefUseVisitor<'cx, 'tcx> { - body: &'cx Body<'tcx>, - tcx: TyCtxt<'tcx>, - region_vid: RegionVid, - def_use_result: Option, -} - -enum DefUseResult { - Def, - UseLive { local: Local }, - UseDrop { local: Local }, -} - -impl<'cx, 'tcx> Visitor<'tcx> for DefUseVisitor<'cx, 'tcx> { - fn visit_local(&mut self, &local: &Local, context: PlaceContext, _: Location) { - let local_ty = self.body.local_decls[local].ty; - - let mut found_it = false; - self.tcx.for_each_free_region(&local_ty, |r| { - if r.to_region_vid() == self.region_vid { - found_it = true; - } - }); - - if found_it { - self.def_use_result = match liveness::categorize(context) { - Some(DefUse::Def) => Some(DefUseResult::Def), - Some(DefUse::Use) => Some(DefUseResult::UseLive { local }), - Some(DefUse::Drop) => Some(DefUseResult::UseDrop { local }), - None => None, - }; - } - } -} diff --git a/src/librustc_mir/borrow_check/nll/explain_borrow/mod.rs b/src/librustc_mir/borrow_check/nll/explain_borrow/mod.rs deleted file mode 100644 index 43aabac00bc..00000000000 --- a/src/librustc_mir/borrow_check/nll/explain_borrow/mod.rs +++ /dev/null @@ -1,669 +0,0 @@ -use std::collections::VecDeque; - -use crate::borrow_check::borrow_set::BorrowData; -use crate::borrow_check::error_reporting::UseSpans; -use crate::borrow_check::nll::region_infer::{Cause, RegionName}; -use crate::borrow_check::nll::ConstraintDescription; -use crate::borrow_check::{MirBorrowckCtxt, WriteKind}; -use rustc::mir::{ - CastKind, ConstraintCategory, FakeReadCause, Local, Location, Body, Operand, Place, Rvalue, - Statement, StatementKind, TerminatorKind, -}; -use rustc::ty::{self, TyCtxt}; -use rustc::ty::adjustment::{PointerCast}; -use rustc_index::vec::IndexVec; -use rustc_data_structures::fx::FxHashSet; -use rustc_errors::DiagnosticBuilder; -use syntax_pos::Span; -use syntax_pos::symbol::Symbol; - -mod find_use; - -#[derive(Debug)] -pub(in crate::borrow_check) enum BorrowExplanation { - UsedLater(LaterUseKind, Span), - UsedLaterInLoop(LaterUseKind, Span), - UsedLaterWhenDropped { - drop_loc: Location, - dropped_local: Local, - should_note_order: bool, - }, - MustBeValidFor { - category: ConstraintCategory, - from_closure: bool, - span: Span, - region_name: RegionName, - opt_place_desc: Option, - }, - Unexplained, -} - -#[derive(Clone, Copy, Debug)] -pub(in crate::borrow_check) enum LaterUseKind { - TraitCapture, - ClosureCapture, - Call, - FakeLetRead, - Other, -} - -impl BorrowExplanation { - pub(in crate::borrow_check) fn is_explained(&self) -> bool { - match self { - BorrowExplanation::Unexplained => false, - _ => true, - } - } - pub(in crate::borrow_check) fn add_explanation_to_diagnostic<'tcx>( - &self, - tcx: TyCtxt<'tcx>, - body: &Body<'tcx>, - local_names: &IndexVec>, - err: &mut DiagnosticBuilder<'_>, - borrow_desc: &str, - borrow_span: Option, - ) { - match *self { - BorrowExplanation::UsedLater(later_use_kind, var_or_use_span) => { - let message = match later_use_kind { - LaterUseKind::TraitCapture => "captured here by trait object", - LaterUseKind::ClosureCapture => "captured here by closure", - LaterUseKind::Call => "used by call", - LaterUseKind::FakeLetRead => "stored here", - LaterUseKind::Other => "used here", - }; - if !borrow_span.map(|sp| sp.overlaps(var_or_use_span)).unwrap_or(false) { - err.span_label( - var_or_use_span, - format!("{}borrow later {}", borrow_desc, message), - ); - } - } - BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span) => { - let message = match later_use_kind { - LaterUseKind::TraitCapture => { - "borrow captured here by trait object, in later iteration of loop" - } - LaterUseKind::ClosureCapture => { - "borrow captured here by closure, in later iteration of loop" - } - LaterUseKind::Call => "borrow used by call, in later iteration of loop", - LaterUseKind::FakeLetRead => "borrow later stored here", - LaterUseKind::Other => "borrow used here, in later iteration of loop", - }; - err.span_label(var_or_use_span, format!("{}{}", borrow_desc, message)); - } - BorrowExplanation::UsedLaterWhenDropped { - drop_loc, - dropped_local, - should_note_order, - } => { - let local_decl = &body.local_decls[dropped_local]; - let (dtor_desc, type_desc) = match local_decl.ty.kind { - // If type is an ADT that implements Drop, then - // simplify output by reporting just the ADT name. - ty::Adt(adt, _substs) if adt.has_dtor(tcx) && !adt.is_box() => ( - "`Drop` code", - format!("type `{}`", tcx.def_path_str(adt.did)), - ), - - // Otherwise, just report the whole type (and use - // the intentionally fuzzy phrase "destructor") - ty::Closure(..) => ("destructor", "closure".to_owned()), - ty::Generator(..) => ("destructor", "generator".to_owned()), - - _ => ("destructor", format!("type `{}`", local_decl.ty)), - }; - - match local_names[dropped_local] { - Some(local_name) if !local_decl.from_compiler_desugaring() => { - let message = format!( - "{B}borrow might be used here, when `{LOC}` is dropped \ - and runs the {DTOR} for {TYPE}", - B = borrow_desc, - LOC = local_name, - TYPE = type_desc, - DTOR = dtor_desc - ); - err.span_label(body.source_info(drop_loc).span, message); - - if should_note_order { - err.note( - "values in a scope are dropped \ - in the opposite order they are defined", - ); - } - } - _ => { - err.span_label( - local_decl.source_info.span, - format!( - "a temporary with access to the {B}borrow \ - is created here ...", - B = borrow_desc - ), - ); - let message = format!( - "... and the {B}borrow might be used here, \ - when that temporary is dropped \ - and runs the {DTOR} for {TYPE}", - B = borrow_desc, - TYPE = type_desc, - DTOR = dtor_desc - ); - err.span_label(body.source_info(drop_loc).span, message); - - if let Some(info) = &local_decl.is_block_tail { - // FIXME: use span_suggestion instead, highlighting the - // whole block tail expression. - let msg = if info.tail_result_is_ignored { - "The temporary is part of an expression at the end of a block. \ - Consider adding semicolon after the expression so its temporaries \ - are dropped sooner, before the local variables declared by the \ - block are dropped." - } else { - "The temporary is part of an expression at the end of a block. \ - Consider forcing this temporary to be dropped sooner, before \ - the block's local variables are dropped. \ - For example, you could save the expression's value in a new \ - local variable `x` and then make `x` be the expression \ - at the end of the block." - }; - - err.note(msg); - } - } - } - } - BorrowExplanation::MustBeValidFor { - category, - span, - ref region_name, - ref opt_place_desc, - from_closure: _, - } => { - region_name.highlight_region_name(err); - - if let Some(desc) = opt_place_desc { - err.span_label( - span, - format!( - "{}requires that `{}` is borrowed for `{}`", - category.description(), - desc, - region_name, - ), - ); - } else { - err.span_label( - span, - format!( - "{}requires that {}borrow lasts for `{}`", - category.description(), - borrow_desc, - region_name, - ), - ); - }; - } - _ => {} - } - } -} - -impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { - /// Returns structured explanation for *why* the borrow contains the - /// point from `location`. This is key for the "3-point errors" - /// [described in the NLL RFC][d]. - /// - /// # Parameters - /// - /// - `borrow`: the borrow in question - /// - `location`: where the borrow occurs - /// - `kind_place`: if Some, this describes the statement that triggered the error. - /// - first half is the kind of write, if any, being performed - /// - second half is the place being accessed - /// - /// [d]: https://rust-lang.github.io/rfcs/2094-nll.html#leveraging-intuition-framing-errors-in-terms-of-points - pub(in crate::borrow_check) fn explain_why_borrow_contains_point( - &self, - location: Location, - borrow: &BorrowData<'tcx>, - kind_place: Option<(WriteKind, &Place<'tcx>)>, - ) -> BorrowExplanation { - debug!( - "explain_why_borrow_contains_point(location={:?}, borrow={:?}, kind_place={:?})", - location, borrow, kind_place - ); - - let regioncx = &self.nonlexical_regioncx; - let body: &Body<'_> = &self.body; - let tcx = self.infcx.tcx; - - let borrow_region_vid = borrow.region; - debug!( - "explain_why_borrow_contains_point: borrow_region_vid={:?}", - borrow_region_vid - ); - - let region_sub = regioncx.find_sub_region_live_at(borrow_region_vid, location); - debug!( - "explain_why_borrow_contains_point: region_sub={:?}", - region_sub - ); - - match find_use::find(body, regioncx, tcx, region_sub, location) { - Some(Cause::LiveVar(local, location)) => { - let span = body.source_info(location).span; - let spans = self - .move_spans(Place::from(local).as_ref(), location) - .or_else(|| self.borrow_spans(span, location)); - - let borrow_location = location; - if self.is_use_in_later_iteration_of_loop(borrow_location, location) { - let later_use = self.later_use_kind(borrow, spans, location); - BorrowExplanation::UsedLaterInLoop(later_use.0, later_use.1) - } else { - // Check if the location represents a `FakeRead`, and adapt the error - // message to the `FakeReadCause` it is from: in particular, - // the ones inserted in optimized `let var = ` patterns. - let later_use = self.later_use_kind(borrow, spans, location); - BorrowExplanation::UsedLater(later_use.0, later_use.1) - } - } - - Some(Cause::DropVar(local, location)) => { - let mut should_note_order = false; - if self.local_names[local].is_some() { - if let Some((WriteKind::StorageDeadOrDrop, place)) = kind_place { - if let Some(borrowed_local) = place.as_local() { - if self.local_names[borrowed_local].is_some() - && local != borrowed_local - { - should_note_order = true; - } - } - } - } - - BorrowExplanation::UsedLaterWhenDropped { - drop_loc: location, - dropped_local: local, - should_note_order, - } - } - - None => { - if let Some(region) = regioncx.to_error_region_vid(borrow_region_vid) { - let (category, from_closure, span, region_name) = - self.nonlexical_regioncx.free_region_constraint_info( - &self.body, - &self.local_names, - &self.upvars, - self.mir_def_id, - self.infcx, - borrow_region_vid, - region, - ); - if let Some(region_name) = region_name { - let opt_place_desc = - self.describe_place(borrow.borrowed_place.as_ref()); - BorrowExplanation::MustBeValidFor { - category, - from_closure, - span, - region_name, - opt_place_desc, - } - } else { - debug!("explain_why_borrow_contains_point: \ - Could not generate a region name"); - BorrowExplanation::Unexplained - } - } else { - debug!("explain_why_borrow_contains_point: \ - Could not generate an error region vid"); - BorrowExplanation::Unexplained - } - } - } - } - - /// true if `borrow_location` can reach `use_location` by going through a loop and - /// `use_location` is also inside of that loop - fn is_use_in_later_iteration_of_loop( - &self, - borrow_location: Location, - use_location: Location, - ) -> bool { - let back_edge = self.reach_through_backedge(borrow_location, use_location); - back_edge.map_or(false, |back_edge| { - self.can_reach_head_of_loop(use_location, back_edge) - }) - } - - /// Returns the outmost back edge if `from` location can reach `to` location passing through - /// that back edge - fn reach_through_backedge(&self, from: Location, to: Location) -> Option { - let mut visited_locations = FxHashSet::default(); - let mut pending_locations = VecDeque::new(); - visited_locations.insert(from); - pending_locations.push_back(from); - debug!("reach_through_backedge: from={:?} to={:?}", from, to,); - - let mut outmost_back_edge = None; - while let Some(location) = pending_locations.pop_front() { - debug!( - "reach_through_backedge: location={:?} outmost_back_edge={:?} - pending_locations={:?} visited_locations={:?}", - location, outmost_back_edge, pending_locations, visited_locations - ); - - if location == to && outmost_back_edge.is_some() { - // We've managed to reach the use location - debug!("reach_through_backedge: found!"); - return outmost_back_edge; - } - - let block = &self.body.basic_blocks()[location.block]; - - if location.statement_index < block.statements.len() { - let successor = location.successor_within_block(); - if visited_locations.insert(successor) { - pending_locations.push_back(successor); - } - } else { - pending_locations.extend( - block - .terminator() - .successors() - .map(|bb| Location { - statement_index: 0, - block: *bb, - }) - .filter(|s| visited_locations.insert(*s)) - .map(|s| { - if self.is_back_edge(location, s) { - match outmost_back_edge { - None => { - outmost_back_edge = Some(location); - } - - Some(back_edge) - if location.dominates(back_edge, &self.dominators) => - { - outmost_back_edge = Some(location); - } - - Some(_) => {} - } - } - - s - }), - ); - } - } - - None - } - - /// true if `from` location can reach `loop_head` location and `loop_head` dominates all the - /// intermediate nodes - fn can_reach_head_of_loop(&self, from: Location, loop_head: Location) -> bool { - self.find_loop_head_dfs(from, loop_head, &mut FxHashSet::default()) - } - - fn find_loop_head_dfs( - &self, - from: Location, - loop_head: Location, - visited_locations: &mut FxHashSet, - ) -> bool { - visited_locations.insert(from); - - if from == loop_head { - return true; - } - - if loop_head.dominates(from, &self.dominators) { - let block = &self.body.basic_blocks()[from.block]; - - if from.statement_index < block.statements.len() { - let successor = from.successor_within_block(); - - if !visited_locations.contains(&successor) - && self.find_loop_head_dfs(successor, loop_head, visited_locations) - { - return true; - } - } else { - for bb in block.terminator().successors() { - let successor = Location { - statement_index: 0, - block: *bb, - }; - - if !visited_locations.contains(&successor) - && self.find_loop_head_dfs(successor, loop_head, visited_locations) - { - return true; - } - } - } - } - - false - } - - /// True if an edge `source -> target` is a backedge -- in other words, if the target - /// dominates the source. - fn is_back_edge(&self, source: Location, target: Location) -> bool { - target.dominates(source, &self.dominators) - } - - /// Determine how the borrow was later used. - fn later_use_kind( - &self, - borrow: &BorrowData<'tcx>, - use_spans: UseSpans, - location: Location, - ) -> (LaterUseKind, Span) { - match use_spans { - UseSpans::ClosureUse { var_span, .. } => { - // Used in a closure. - (LaterUseKind::ClosureCapture, var_span) - } - UseSpans::OtherUse(span) => { - let block = &self.body.basic_blocks()[location.block]; - - let kind = if let Some(&Statement { - kind: StatementKind::FakeRead(FakeReadCause::ForLet, _), - .. - }) = block.statements.get(location.statement_index) - { - LaterUseKind::FakeLetRead - } else if self.was_captured_by_trait_object(borrow) { - LaterUseKind::TraitCapture - } else if location.statement_index == block.statements.len() { - if let TerminatorKind::Call { - ref func, - from_hir_call: true, - .. - } = block.terminator().kind - { - // Just point to the function, to reduce the chance of overlapping spans. - let function_span = match func { - Operand::Constant(c) => c.span, - Operand::Copy(place) | - Operand::Move(place) => { - if let Some(l) = place.as_local() { - let local_decl = &self.body.local_decls[l]; - if self.local_names[l].is_none() { - local_decl.source_info.span - } else { - span - } - } else { - span - } - } - }; - return (LaterUseKind::Call, function_span); - } else { - LaterUseKind::Other - } - } else { - LaterUseKind::Other - }; - - (kind, span) - } - } - } - - /// Checks if a borrowed value was captured by a trait object. We do this by - /// looking forward in the MIR from the reserve location and checking if we see - /// a unsized cast to a trait object on our data. - fn was_captured_by_trait_object(&self, borrow: &BorrowData<'tcx>) -> bool { - // Start at the reserve location, find the place that we want to see cast to a trait object. - let location = borrow.reserve_location; - let block = &self.body[location.block]; - let stmt = block.statements.get(location.statement_index); - debug!( - "was_captured_by_trait_object: location={:?} stmt={:?}", - location, stmt - ); - - // We make a `queue` vector that has the locations we want to visit. As of writing, this - // will only ever have one item at any given time, but by using a vector, we can pop from - // it which simplifies the termination logic. - let mut queue = vec![location]; - let mut target = if let Some(&Statement { - kind: StatementKind::Assign(box(ref place, _)), - .. - }) = stmt { - if let Some(local) = place.as_local() { - local - } else { - return false; - } - } else { - return false; - }; - - debug!( - "was_captured_by_trait: target={:?} queue={:?}", - target, queue - ); - while let Some(current_location) = queue.pop() { - debug!("was_captured_by_trait: target={:?}", target); - let block = &self.body[current_location.block]; - // We need to check the current location to find out if it is a terminator. - let is_terminator = current_location.statement_index == block.statements.len(); - if !is_terminator { - let stmt = &block.statements[current_location.statement_index]; - debug!("was_captured_by_trait_object: stmt={:?}", stmt); - - // The only kind of statement that we care about is assignments... - if let StatementKind::Assign(box(place, rvalue)) = &stmt.kind { - let into = match place.local_or_deref_local() { - Some(into) => into, - None => { - // Continue at the next location. - queue.push(current_location.successor_within_block()); - continue; - } - }; - - match rvalue { - // If we see a use, we should check whether it is our data, and if so - // update the place that we're looking for to that new place. - Rvalue::Use(operand) => match operand { - Operand::Copy(place) - | Operand::Move(place) => { - if let Some(from) = place.as_local() { - if from == target { - target = into; - } - } - } - _ => {} - }, - // If we see a unsized cast, then if it is our data we should check - // whether it is being cast to a trait object. - Rvalue::Cast( - CastKind::Pointer(PointerCast::Unsize), operand, ty - ) => match operand { - Operand::Copy(place) - | Operand::Move(place) => { - if let Some(from) = place.as_local() { - if from == target { - debug!("was_captured_by_trait_object: ty={:?}", ty); - // Check the type for a trait object. - return match ty.kind { - // `&dyn Trait` - ty::Ref(_, ty, _) if ty.is_trait() => true, - // `Box` - _ if ty.is_box() && ty.boxed_ty().is_trait() => true, - // `dyn Trait` - _ if ty.is_trait() => true, - // Anything else. - _ => false, - }; - } - } - return false; - } - _ => return false, - }, - _ => {} - } - } - - // Continue at the next location. - queue.push(current_location.successor_within_block()); - } else { - // The only thing we need to do for terminators is progress to the next block. - let terminator = block.terminator(); - debug!("was_captured_by_trait_object: terminator={:?}", terminator); - - if let TerminatorKind::Call { - destination: Some((place, block)), - args, - .. - } = &terminator.kind { - if let Some(dest) = place.as_local() { - debug!( - "was_captured_by_trait_object: target={:?} dest={:?} args={:?}", - target, dest, args - ); - // Check if one of the arguments to this function is the target place. - let found_target = args.iter().any(|arg| { - if let Operand::Move(place) = arg { - if let Some(potential) = place.as_local() { - potential == target - } else { - false - } - } else { - false - } - }); - - // If it is, follow this to the next block and update the target. - if found_target { - target = dest; - queue.push(block.start_location()); - } - } - } - } - - debug!("was_captured_by_trait: queue={:?}", queue); - } - - // We didn't find anything and ran out of locations to check. - false - } -} diff --git a/src/librustc_mir/borrow_check/nll/mod.rs b/src/librustc_mir/borrow_check/nll/mod.rs index 16182fe24fa..b9363200cdf 100644 --- a/src/librustc_mir/borrow_check/nll/mod.rs +++ b/src/librustc_mir/borrow_check/nll/mod.rs @@ -32,17 +32,17 @@ use crate::util::pretty; mod constraint_generation; -pub mod explain_borrow; mod facts; mod invalidation; -crate mod region_infer; mod renumber; -crate mod type_check; -mod universal_regions; -mod constraints; mod member_constraints; +crate mod constraints; +crate mod universal_regions; +crate mod type_check; +crate mod region_infer; + use self::facts::AllFacts; use self::region_infer::RegionInferenceContext; use self::universal_regions::UniversalRegions; diff --git a/src/librustc_mir/borrow_check/nll/region_infer/error_reporting/mod.rs b/src/librustc_mir/borrow_check/nll/region_infer/error_reporting/mod.rs deleted file mode 100644 index 5e79a2fea9b..00000000000 --- a/src/librustc_mir/borrow_check/nll/region_infer/error_reporting/mod.rs +++ /dev/null @@ -1,937 +0,0 @@ -use crate::borrow_check::nll::constraints::OutlivesConstraint; -use crate::borrow_check::nll::region_infer::RegionInferenceContext; -use crate::borrow_check::nll::type_check::Locations; -use crate::borrow_check::nll::universal_regions::DefiningTy; -use crate::borrow_check::nll::ConstraintDescription; -use crate::borrow_check::Upvar; -use crate::util::borrowck_errors; -use rustc::hir::def_id::DefId; -use rustc::infer::error_reporting::nice_region_error::NiceRegionError; -use rustc::infer::InferCtxt; -use rustc::infer::NLLRegionVariableOrigin; -use rustc::mir::{ConstraintCategory, Local, Location, Body}; -use rustc::ty::{self, RegionVid}; -use rustc_index::vec::IndexVec; -use rustc_errors::DiagnosticBuilder; -use std::collections::VecDeque; -use syntax::errors::Applicability; -use syntax::symbol::kw; -use syntax_pos::Span; -use syntax_pos::symbol::Symbol; - -use self::outlives_suggestion::OutlivesSuggestionBuilder; - -pub mod outlives_suggestion; - -mod region_name; -mod var_name; - -crate use self::region_name::{RegionName, RegionNameSource, RegionErrorNamingCtx}; - -impl ConstraintDescription for ConstraintCategory { - fn description(&self) -> &'static str { - // Must end with a space. Allows for empty names to be provided. - match self { - ConstraintCategory::Assignment => "assignment ", - ConstraintCategory::Return => "returning this value ", - ConstraintCategory::Yield => "yielding this value ", - ConstraintCategory::UseAsConst => "using this value as a constant ", - ConstraintCategory::UseAsStatic => "using this value as a static ", - ConstraintCategory::Cast => "cast ", - ConstraintCategory::CallArgument => "argument ", - ConstraintCategory::TypeAnnotation => "type annotation ", - ConstraintCategory::ClosureBounds => "closure body ", - ConstraintCategory::SizedBound => "proving this value is `Sized` ", - ConstraintCategory::CopyBound => "copying this value ", - ConstraintCategory::OpaqueType => "opaque type ", - ConstraintCategory::Boring - | ConstraintCategory::BoringNoLocation - | ConstraintCategory::Internal => "", - } - } -} - -#[derive(Copy, Clone, PartialEq, Eq)] -enum Trace { - StartRegion, - FromOutlivesConstraint(OutlivesConstraint), - NotVisited, -} - -/// Various pieces of state used when reporting borrow checker errors. -pub struct ErrorReportingCtx<'a, 'b, 'tcx> { - /// The region inference context used for borrow chekcing this MIR body. - region_infcx: &'b RegionInferenceContext<'tcx>, - - /// The inference context used for type checking. - infcx: &'b InferCtxt<'a, 'tcx>, - - /// The MIR def we are reporting errors on. - mir_def_id: DefId, - - /// The MIR body we are reporting errors on (for convenience). - body: &'b Body<'tcx>, - - /// User variable names for MIR locals (where applicable). - local_names: &'b IndexVec>, - - /// Any upvars for the MIR body we have kept track of during borrow checking. - upvars: &'b [Upvar], -} - -/// Information about the various region constraints involved in a borrow checker error. -#[derive(Clone, Debug)] -pub struct ErrorConstraintInfo { - // fr: outlived_fr - fr: RegionVid, - fr_is_local: bool, - outlived_fr: RegionVid, - outlived_fr_is_local: bool, - - // Category and span for best blame constraint - category: ConstraintCategory, - span: Span, -} - -impl<'tcx> RegionInferenceContext<'tcx> { - /// Tries to find the best constraint to blame for the fact that - /// `R: from_region`, where `R` is some region that meets - /// `target_test`. This works by following the constraint graph, - /// creating a constraint path that forces `R` to outlive - /// `from_region`, and then finding the best choices within that - /// path to blame. - fn best_blame_constraint( - &self, - body: &Body<'tcx>, - from_region: RegionVid, - from_region_origin: NLLRegionVariableOrigin, - target_test: impl Fn(RegionVid) -> bool, - ) -> (ConstraintCategory, bool, Span) { - debug!("best_blame_constraint(from_region={:?}, from_region_origin={:?})", - from_region, from_region_origin); - - // Find all paths - let (path, target_region) = - self.find_constraint_paths_between_regions(from_region, target_test) - .unwrap(); - debug!( - "best_blame_constraint: path={:#?}", - path.iter() - .map(|&c| format!( - "{:?} ({:?}: {:?})", - c, - self.constraint_sccs.scc(c.sup), - self.constraint_sccs.scc(c.sub), - )) - .collect::>() - ); - - // Classify each of the constraints along the path. - let mut categorized_path: Vec<(ConstraintCategory, bool, Span)> = path.iter() - .map(|constraint| { - if constraint.category == ConstraintCategory::ClosureBounds { - self.retrieve_closure_constraint_info(body, &constraint) - } else { - (constraint.category, false, constraint.locations.span(body)) - } - }) - .collect(); - debug!( - "best_blame_constraint: categorized_path={:#?}", - categorized_path - ); - - // To find the best span to cite, we first try to look for the - // final constraint that is interesting and where the `sup` is - // not unified with the ultimate target region. The reason - // for this is that we have a chain of constraints that lead - // from the source to the target region, something like: - // - // '0: '1 ('0 is the source) - // '1: '2 - // '2: '3 - // '3: '4 - // '4: '5 - // '5: '6 ('6 is the target) - // - // Some of those regions are unified with `'6` (in the same - // SCC). We want to screen those out. After that point, the - // "closest" constraint we have to the end is going to be the - // most likely to be the point where the value escapes -- but - // we still want to screen for an "interesting" point to - // highlight (e.g., a call site or something). - let target_scc = self.constraint_sccs.scc(target_region); - let mut range = 0..path.len(); - - // As noted above, when reporting an error, there is typically a chain of constraints - // leading from some "source" region which must outlive some "target" region. - // In most cases, we prefer to "blame" the constraints closer to the target -- - // but there is one exception. When constraints arise from higher-ranked subtyping, - // we generally prefer to blame the source value, - // as the "target" in this case tends to be some type annotation that the user gave. - // Therefore, if we find that the region origin is some instantiation - // of a higher-ranked region, we start our search from the "source" point - // rather than the "target", and we also tweak a few other things. - // - // An example might be this bit of Rust code: - // - // ```rust - // let x: fn(&'static ()) = |_| {}; - // let y: for<'a> fn(&'a ()) = x; - // ``` - // - // In MIR, this will be converted into a combination of assignments and type ascriptions. - // In particular, the 'static is imposed through a type ascription: - // - // ```rust - // x = ...; - // AscribeUserType(x, fn(&'static ()) - // y = x; - // ``` - // - // We wind up ultimately with constraints like - // - // ```rust - // !a: 'temp1 // from the `y = x` statement - // 'temp1: 'temp2 - // 'temp2: 'static // from the AscribeUserType - // ``` - // - // and here we prefer to blame the source (the y = x statement). - let blame_source = match from_region_origin { - NLLRegionVariableOrigin::FreeRegion - | NLLRegionVariableOrigin::Existential { from_forall: false } => { - true - } - NLLRegionVariableOrigin::Placeholder(_) - | NLLRegionVariableOrigin::Existential { from_forall: true } => { - false - } - }; - - let find_region = |i: &usize| { - let constraint = path[*i]; - - let constraint_sup_scc = self.constraint_sccs.scc(constraint.sup); - - if blame_source { - match categorized_path[*i].0 { - ConstraintCategory::OpaqueType | ConstraintCategory::Boring | - ConstraintCategory::BoringNoLocation | ConstraintCategory::Internal => false, - ConstraintCategory::TypeAnnotation | ConstraintCategory::Return | - ConstraintCategory::Yield => true, - _ => constraint_sup_scc != target_scc, - } - } else { - match categorized_path[*i].0 { - ConstraintCategory::OpaqueType | ConstraintCategory::Boring | - ConstraintCategory::BoringNoLocation | ConstraintCategory::Internal => false, - _ => true - } - } - }; - - let best_choice = if blame_source { - range.rev().find(find_region) - } else { - range.find(find_region) - }; - - debug!("best_blame_constraint: best_choice={:?} blame_source={}", - best_choice, blame_source); - - if let Some(i) = best_choice { - if let Some(next) = categorized_path.get(i + 1) { - if categorized_path[i].0 == ConstraintCategory::Return - && next.0 == ConstraintCategory::OpaqueType - { - // The return expression is being influenced by the return type being - // impl Trait, point at the return type and not the return expr. - return *next; - } - } - return categorized_path[i]; - } - - // If that search fails, that is.. unusual. Maybe everything - // is in the same SCC or something. In that case, find what - // appears to be the most interesting point to report to the - // user via an even more ad-hoc guess. - categorized_path.sort_by(|p0, p1| p0.0.cmp(&p1.0)); - debug!("`: sorted_path={:#?}", categorized_path); - - *categorized_path.first().unwrap() - } - - /// Walks the graph of constraints (where `'a: 'b` is considered - /// an edge `'a -> 'b`) to find all paths from `from_region` to - /// `to_region`. The paths are accumulated into the vector - /// `results`. The paths are stored as a series of - /// `ConstraintIndex` values -- in other words, a list of *edges*. - /// - /// Returns: a series of constraints as well as the region `R` - /// that passed the target test. - fn find_constraint_paths_between_regions( - &self, - from_region: RegionVid, - target_test: impl Fn(RegionVid) -> bool, - ) -> Option<(Vec, RegionVid)> { - let mut context = IndexVec::from_elem(Trace::NotVisited, &self.definitions); - context[from_region] = Trace::StartRegion; - - // Use a deque so that we do a breadth-first search. We will - // stop at the first match, which ought to be the shortest - // path (fewest constraints). - let mut deque = VecDeque::new(); - deque.push_back(from_region); - - while let Some(r) = deque.pop_front() { - debug!( - "find_constraint_paths_between_regions: from_region={:?} r={:?} value={}", - from_region, - r, - self.region_value_str(r), - ); - - // Check if we reached the region we were looking for. If so, - // we can reconstruct the path that led to it and return it. - if target_test(r) { - let mut result = vec![]; - let mut p = r; - loop { - match context[p] { - Trace::NotVisited => { - bug!("found unvisited region {:?} on path to {:?}", p, r) - } - - Trace::FromOutlivesConstraint(c) => { - result.push(c); - p = c.sup; - } - - Trace::StartRegion => { - result.reverse(); - return Some((result, r)); - } - } - } - } - - // Otherwise, walk over the outgoing constraints and - // enqueue any regions we find, keeping track of how we - // reached them. - - // A constraint like `'r: 'x` can come from our constraint - // graph. - let fr_static = self.universal_regions.fr_static; - let outgoing_edges_from_graph = self.constraint_graph - .outgoing_edges(r, &self.constraints, fr_static); - - // Always inline this closure because it can be hot. - let mut handle_constraint = #[inline(always)] |constraint: OutlivesConstraint| { - debug_assert_eq!(constraint.sup, r); - let sub_region = constraint.sub; - if let Trace::NotVisited = context[sub_region] { - context[sub_region] = Trace::FromOutlivesConstraint(constraint); - deque.push_back(sub_region); - } - }; - - // This loop can be hot. - for constraint in outgoing_edges_from_graph { - handle_constraint(constraint); - } - - // Member constraints can also give rise to `'r: 'x` edges that - // were not part of the graph initially, so watch out for those. - // (But they are extremely rare; this loop is very cold.) - for constraint in self.applied_member_constraints(r) { - let p_c = &self.member_constraints[constraint.member_constraint_index]; - let constraint = OutlivesConstraint { - sup: r, - sub: constraint.min_choice, - locations: Locations::All(p_c.definition_span), - category: ConstraintCategory::OpaqueType, - }; - handle_constraint(constraint); - } - } - - None - } - - /// Report an error because the universal region `fr` was required to outlive - /// `outlived_fr` but it is not known to do so. For example: - /// - /// ``` - /// fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x } - /// ``` - /// - /// Here we would be invoked with `fr = 'a` and `outlived_fr = `'b`. - pub(super) fn report_error<'a>( - &'a self, - body: &Body<'tcx>, - local_names: &IndexVec>, - upvars: &[Upvar], - infcx: &'a InferCtxt<'a, 'tcx>, - mir_def_id: DefId, - fr: RegionVid, - fr_origin: NLLRegionVariableOrigin, - outlived_fr: RegionVid, - outlives_suggestion: &mut OutlivesSuggestionBuilder<'_>, - renctx: &mut RegionErrorNamingCtx, - ) -> DiagnosticBuilder<'a> { - debug!("report_error(fr={:?}, outlived_fr={:?})", fr, outlived_fr); - - let (category, _, span) = self.best_blame_constraint(body, fr, fr_origin, |r| { - self.provides_universal_region(r, fr, outlived_fr) - }); - - debug!("report_error: category={:?} {:?}", category, span); - // Check if we can use one of the "nice region errors". - if let (Some(f), Some(o)) = (self.to_error_region(fr), self.to_error_region(outlived_fr)) { - let tables = infcx.tcx.typeck_tables_of(mir_def_id); - let nice = NiceRegionError::new_from_span(infcx, span, o, f, Some(tables)); - if let Some(diag) = nice.try_report_from_nll() { - return diag; - } - } - - let (fr_is_local, outlived_fr_is_local): (bool, bool) = ( - self.universal_regions.is_local_free_region(fr), - self.universal_regions.is_local_free_region(outlived_fr), - ); - - debug!( - "report_error: fr_is_local={:?} outlived_fr_is_local={:?} category={:?}", - fr_is_local, outlived_fr_is_local, category - ); - - let errctx = ErrorReportingCtx { - region_infcx: self, - infcx, - mir_def_id, - body, - local_names, - upvars, - }; - - let errci = ErrorConstraintInfo { - fr, outlived_fr, fr_is_local, outlived_fr_is_local, category, span - }; - - match (category, fr_is_local, outlived_fr_is_local) { - (ConstraintCategory::Return, true, false) if self.is_closure_fn_mut(infcx, fr) => { - self.report_fnmut_error(&errctx, &errci, renctx) - } - (ConstraintCategory::Assignment, true, false) - | (ConstraintCategory::CallArgument, true, false) => { - let mut db = self.report_escaping_data_error(&errctx, &errci, renctx); - - outlives_suggestion.intermediate_suggestion(&errctx, &errci, renctx, &mut db); - outlives_suggestion.collect_constraint(fr, outlived_fr); - - db - } - _ => { - let mut db = self.report_general_error(&errctx, &errci, renctx); - - outlives_suggestion.intermediate_suggestion(&errctx, &errci, renctx, &mut db); - outlives_suggestion.collect_constraint(fr, outlived_fr); - - db - } - } - } - - /// We have a constraint `fr1: fr2` that is not satisfied, where - /// `fr2` represents some universal region. Here, `r` is some - /// region where we know that `fr1: r` and this function has the - /// job of determining whether `r` is "to blame" for the fact that - /// `fr1: fr2` is required. - /// - /// This is true under two conditions: - /// - /// - `r == fr2` - /// - `fr2` is `'static` and `r` is some placeholder in a universe - /// that cannot be named by `fr1`; in that case, we will require - /// that `fr1: 'static` because it is the only way to `fr1: r` to - /// be satisfied. (See `add_incompatible_universe`.) - fn provides_universal_region(&self, r: RegionVid, fr1: RegionVid, fr2: RegionVid) -> bool { - debug!( - "provides_universal_region(r={:?}, fr1={:?}, fr2={:?})", - r, fr1, fr2 - ); - let result = { - r == fr2 || { - fr2 == self.universal_regions.fr_static && self.cannot_name_placeholder(fr1, r) - } - }; - debug!("provides_universal_region: result = {:?}", result); - result - } - - /// Report a specialized error when `FnMut` closures return a reference to a captured variable. - /// This function expects `fr` to be local and `outlived_fr` to not be local. - /// - /// ```text - /// error: captured variable cannot escape `FnMut` closure body - /// --> $DIR/issue-53040.rs:15:8 - /// | - /// LL | || &mut v; - /// | -- ^^^^^^ creates a reference to a captured variable which escapes the closure body - /// | | - /// | inferred to be a `FnMut` closure - /// | - /// = note: `FnMut` closures only have access to their captured variables while they are - /// executing... - /// = note: ...therefore, returned references to captured variables will escape the closure - /// ``` - fn report_fnmut_error( - &self, - errctx: &ErrorReportingCtx<'_, '_, 'tcx>, - errci: &ErrorConstraintInfo, - renctx: &mut RegionErrorNamingCtx, - ) -> DiagnosticBuilder<'_> { - let ErrorConstraintInfo { - outlived_fr, span, .. - } = errci; - - let mut diag = errctx - .infcx - .tcx - .sess - .struct_span_err(*span, "captured variable cannot escape `FnMut` closure body"); - - // We should check if the return type of this closure is in fact a closure - in that - // case, we can special case the error further. - let return_type_is_closure = self.universal_regions.unnormalized_output_ty.is_closure(); - let message = if return_type_is_closure { - "returns a closure that contains a reference to a captured variable, which then \ - escapes the closure body" - } else { - "returns a reference to a captured variable which escapes the closure body" - }; - - diag.span_label(*span, message); - - match self.give_region_a_name(errctx, renctx, *outlived_fr).unwrap().source { - RegionNameSource::NamedEarlyBoundRegion(fr_span) - | RegionNameSource::NamedFreeRegion(fr_span) - | RegionNameSource::SynthesizedFreeEnvRegion(fr_span, _) - | RegionNameSource::CannotMatchHirTy(fr_span, _) - | RegionNameSource::MatchedHirTy(fr_span) - | RegionNameSource::MatchedAdtAndSegment(fr_span) - | RegionNameSource::AnonRegionFromUpvar(fr_span, _) - | RegionNameSource::AnonRegionFromOutput(fr_span, _, _) => { - diag.span_label(fr_span, "inferred to be a `FnMut` closure"); - } - _ => {} - } - - diag.note( - "`FnMut` closures only have access to their captured variables while they are \ - executing...", - ); - diag.note("...therefore, they cannot allow references to captured variables to escape"); - - diag - } - - /// Reports a error specifically for when data is escaping a closure. - /// - /// ```text - /// error: borrowed data escapes outside of function - /// --> $DIR/lifetime-bound-will-change-warning.rs:44:5 - /// | - /// LL | fn test2<'a>(x: &'a Box) { - /// | - `x` is a reference that is only valid in the function body - /// LL | // but ref_obj will not, so warn. - /// LL | ref_obj(x) - /// | ^^^^^^^^^^ `x` escapes the function body here - /// ``` - fn report_escaping_data_error( - &self, - errctx: &ErrorReportingCtx<'_, '_, 'tcx>, - errci: &ErrorConstraintInfo, - renctx: &mut RegionErrorNamingCtx, - ) -> DiagnosticBuilder<'_> { - let ErrorReportingCtx { - infcx, body, upvars, local_names, .. - } = errctx; - - let ErrorConstraintInfo { - span, category, .. - } = errci; - - let fr_name_and_span = - self.get_var_name_and_span_for_region(infcx.tcx, body, local_names, upvars, errci.fr); - let outlived_fr_name_and_span = self.get_var_name_and_span_for_region( - infcx.tcx, - body, - local_names, - upvars, - errci.outlived_fr, - ); - - let escapes_from = match self.universal_regions.defining_ty { - DefiningTy::Closure(..) => "closure", - DefiningTy::Generator(..) => "generator", - DefiningTy::FnDef(..) => "function", - DefiningTy::Const(..) => "const", - }; - - // Revert to the normal error in these cases. - // Assignments aren't "escapes" in function items. - if (fr_name_and_span.is_none() && outlived_fr_name_and_span.is_none()) - || (*category == ConstraintCategory::Assignment && escapes_from == "function") - || escapes_from == "const" - { - return self.report_general_error( - errctx, - &ErrorConstraintInfo { - fr_is_local: true, - outlived_fr_is_local: false, - .. *errci - }, - renctx, - ); - } - - let mut diag = borrowck_errors::borrowed_data_escapes_closure( - infcx.tcx, - *span, - escapes_from, - ); - - if let Some((Some(outlived_fr_name), outlived_fr_span)) = outlived_fr_name_and_span { - diag.span_label( - outlived_fr_span, - format!( - "`{}` is declared here, outside of the {} body", - outlived_fr_name, escapes_from - ), - ); - } - - if let Some((Some(fr_name), fr_span)) = fr_name_and_span { - diag.span_label( - fr_span, - format!( - "`{}` is a reference that is only valid in the {} body", - fr_name, escapes_from - ), - ); - - diag.span_label( - *span, - format!("`{}` escapes the {} body here", fr_name, escapes_from), - ); - } - - diag - } - - /// Reports a region inference error for the general case with named/synthesized lifetimes to - /// explain what is happening. - /// - /// ```text - /// error: unsatisfied lifetime constraints - /// --> $DIR/regions-creating-enums3.rs:17:5 - /// | - /// LL | fn mk_add_bad1<'a,'b>(x: &'a ast<'a>, y: &'b ast<'b>) -> ast<'a> { - /// | -- -- lifetime `'b` defined here - /// | | - /// | lifetime `'a` defined here - /// LL | ast::add(x, y) - /// | ^^^^^^^^^^^^^^ function was supposed to return data with lifetime `'a` but it - /// | is returning data with lifetime `'b` - /// ``` - fn report_general_error( - &self, - errctx: &ErrorReportingCtx<'_, '_, 'tcx>, - errci: &ErrorConstraintInfo, - renctx: &mut RegionErrorNamingCtx, - ) -> DiagnosticBuilder<'_> { - let ErrorReportingCtx { - infcx, mir_def_id, .. - } = errctx; - let ErrorConstraintInfo { - fr, fr_is_local, outlived_fr, outlived_fr_is_local, span, category, .. - } = errci; - - let mut diag = infcx.tcx.sess.struct_span_err( - *span, - "lifetime may not live long enough" - ); - - let mir_def_name = if infcx.tcx.is_closure(*mir_def_id) { - "closure" - } else { - "function" - }; - - let fr_name = self.give_region_a_name(errctx, renctx, *fr).unwrap(); - fr_name.highlight_region_name(&mut diag); - let outlived_fr_name = self.give_region_a_name(errctx, renctx, *outlived_fr).unwrap(); - outlived_fr_name.highlight_region_name(&mut diag); - - match (category, outlived_fr_is_local, fr_is_local) { - (ConstraintCategory::Return, true, _) => { - diag.span_label( - *span, - format!( - "{} was supposed to return data with lifetime `{}` but it is returning \ - data with lifetime `{}`", - mir_def_name, outlived_fr_name, fr_name - ), - ); - } - _ => { - diag.span_label( - *span, - format!( - "{}requires that `{}` must outlive `{}`", - category.description(), - fr_name, - outlived_fr_name, - ), - ); - } - } - - self.add_static_impl_trait_suggestion(infcx, &mut diag, *fr, fr_name, *outlived_fr); - - diag - } - - /// Adds a suggestion to errors where a `impl Trait` is returned. - /// - /// ```text - /// help: to allow this `impl Trait` to capture borrowed data with lifetime `'1`, add `'_` as - /// a constraint - /// | - /// LL | fn iter_values_anon(&self) -> impl Iterator + 'a { - /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - /// ``` - fn add_static_impl_trait_suggestion( - &self, - infcx: &InferCtxt<'_, 'tcx>, - diag: &mut DiagnosticBuilder<'_>, - fr: RegionVid, - // We need to pass `fr_name` - computing it again will label it twice. - fr_name: RegionName, - outlived_fr: RegionVid, - ) { - if let (Some(f), Some(ty::RegionKind::ReStatic)) = - (self.to_error_region(fr), self.to_error_region(outlived_fr)) - { - if let Some((ty::TyS { - kind: ty::Opaque(did, substs), - .. - }, _)) = infcx - .tcx - .is_suitable_region(f) - .map(|r| r.def_id) - .map(|id| infcx.tcx.return_type_impl_trait(id)) - .unwrap_or(None) - { - // Check whether or not the impl trait return type is intended to capture - // data with the static lifetime. - // - // eg. check for `impl Trait + 'static` instead of `impl Trait`. - let has_static_predicate = { - let predicates_of = infcx.tcx.predicates_of(*did); - let bounds = predicates_of.instantiate(infcx.tcx, substs); - - let mut found = false; - for predicate in bounds.predicates { - if let ty::Predicate::TypeOutlives(binder) = predicate { - if let ty::OutlivesPredicate(_, ty::RegionKind::ReStatic) = - binder.skip_binder() - { - found = true; - break; - } - } - } - - found - }; - - debug!( - "add_static_impl_trait_suggestion: has_static_predicate={:?}", - has_static_predicate - ); - let static_str = kw::StaticLifetime; - // If there is a static predicate, then the only sensible suggestion is to replace - // fr with `'static`. - if has_static_predicate { - diag.help(&format!( - "consider replacing `{}` with `{}`", - fr_name, static_str, - )); - } else { - // Otherwise, we should suggest adding a constraint on the return type. - let span = infcx.tcx.def_span(*did); - if let Ok(snippet) = infcx.tcx.sess.source_map().span_to_snippet(span) { - let suggestable_fr_name = if fr_name.was_named() { - fr_name.to_string() - } else { - "'_".to_string() - }; - - diag.span_suggestion( - span, - &format!( - "to allow this `impl Trait` to capture borrowed data with lifetime \ - `{}`, add `{}` as a constraint", - fr_name, suggestable_fr_name, - ), - format!("{} + {}", snippet, suggestable_fr_name), - Applicability::MachineApplicable, - ); - } - } - } - } - } - - crate fn free_region_constraint_info( - &self, - body: &Body<'tcx>, - local_names: &IndexVec>, - upvars: &[Upvar], - mir_def_id: DefId, - infcx: &InferCtxt<'_, 'tcx>, - borrow_region: RegionVid, - outlived_region: RegionVid, - ) -> (ConstraintCategory, bool, Span, Option) { - let (category, from_closure, span) = self.best_blame_constraint( - body, - borrow_region, - NLLRegionVariableOrigin::FreeRegion, - |r| self.provides_universal_region(r, borrow_region, outlived_region) - ); - - let mut renctx = RegionErrorNamingCtx::new(); - let errctx = ErrorReportingCtx { - infcx, body, local_names, upvars, mir_def_id, - region_infcx: self, - }; - let outlived_fr_name = self.give_region_a_name(&errctx, &mut renctx, outlived_region); - - (category, from_closure, span, outlived_fr_name) - } - - // Finds some region R such that `fr1: R` and `R` is live at - // `elem`. - crate fn find_sub_region_live_at( - &self, - fr1: RegionVid, - elem: Location, - ) -> RegionVid { - debug!("find_sub_region_live_at(fr1={:?}, elem={:?})", fr1, elem); - self.find_constraint_paths_between_regions(fr1, |r| { - // First look for some `r` such that `fr1: r` and `r` is live at `elem` - debug!( - "find_sub_region_live_at: liveness_constraints for {:?} are {:?}", - r, - self.liveness_constraints.region_value_str(r), - ); - self.liveness_constraints.contains(r, elem) - }).or_else(|| { - // If we fail to find that, we may find some `r` such that - // `fr1: r` and `r` is a placeholder from some universe - // `fr1` cannot name. This would force `fr1` to be - // `'static`. - self.find_constraint_paths_between_regions(fr1, |r| { - self.cannot_name_placeholder(fr1, r) - }) - }) - .or_else(|| { - // If we fail to find THAT, it may be that `fr1` is a - // placeholder that cannot "fit" into its SCC. In that - // case, there should be some `r` where `fr1: r`, both - // `fr1` and `r` are in the same SCC, and `fr1` is a - // placeholder that `r` cannot name. We can blame that - // edge. - self.find_constraint_paths_between_regions(fr1, |r| { - self.constraint_sccs.scc(fr1) == self.constraint_sccs.scc(r) - && self.cannot_name_placeholder(r, fr1) - }) - }) - .map(|(_path, r)| r) - .unwrap() - } - - // Finds a good span to blame for the fact that `fr1` outlives `fr2`. - crate fn find_outlives_blame_span( - &self, - body: &Body<'tcx>, - fr1: RegionVid, - fr1_origin: NLLRegionVariableOrigin, - fr2: RegionVid, - ) -> (ConstraintCategory, Span) { - let (category, _, span) = self.best_blame_constraint( - body, - fr1, - fr1_origin, - |r| self.provides_universal_region(r, fr1, fr2), - ); - (category, span) - } - - fn retrieve_closure_constraint_info( - &self, - body: &Body<'tcx>, - constraint: &OutlivesConstraint, - ) -> (ConstraintCategory, bool, Span) { - let loc = match constraint.locations { - Locations::All(span) => return (constraint.category, false, span), - Locations::Single(loc) => loc, - }; - - let opt_span_category = - self.closure_bounds_mapping[&loc].get(&(constraint.sup, constraint.sub)); - opt_span_category - .map(|&(category, span)| (category, true, span)) - .unwrap_or((constraint.category, false, body.source_info(loc).span)) - } - - /// Returns `true` if a closure is inferred to be an `FnMut` closure. - crate fn is_closure_fn_mut(&self, infcx: &InferCtxt<'_, 'tcx>, fr: RegionVid) -> bool { - if let Some(ty::ReFree(free_region)) = self.to_error_region(fr) { - if let ty::BoundRegion::BrEnv = free_region.bound_region { - if let DefiningTy::Closure(def_id, substs) = self.universal_regions.defining_ty { - let closure_kind_ty = substs.as_closure().kind_ty(def_id, infcx.tcx); - return Some(ty::ClosureKind::FnMut) == closure_kind_ty.to_opt_closure_kind(); - } - } - } - - false - } - - /// If `r2` represents a placeholder region, then this returns - /// `true` if `r1` cannot name that placeholder in its - /// value; otherwise, returns `false`. - fn cannot_name_placeholder(&self, r1: RegionVid, r2: RegionVid) -> bool { - debug!("cannot_name_value_of(r1={:?}, r2={:?})", r1, r2); - - match self.definitions[r2].origin { - NLLRegionVariableOrigin::Placeholder(placeholder) => { - let universe1 = self.definitions[r1].universe; - debug!( - "cannot_name_value_of: universe1={:?} placeholder={:?}", - universe1, placeholder - ); - universe1.cannot_name(placeholder.universe) - } - - NLLRegionVariableOrigin::FreeRegion | NLLRegionVariableOrigin::Existential { .. } => { - false - } - } - } -} diff --git a/src/librustc_mir/borrow_check/nll/region_infer/error_reporting/outlives_suggestion.rs b/src/librustc_mir/borrow_check/nll/region_infer/error_reporting/outlives_suggestion.rs deleted file mode 100644 index 938059c2a92..00000000000 --- a/src/librustc_mir/borrow_check/nll/region_infer/error_reporting/outlives_suggestion.rs +++ /dev/null @@ -1,321 +0,0 @@ -//! Contains utilities for generating suggestions for borrowck errors related to unsatisified -//! outlives constraints. - -use std::collections::BTreeMap; - -use log::debug; -use rustc::{hir::def_id::DefId, infer::InferCtxt, ty::RegionVid}; -use rustc::mir::{Body, Local}; -use rustc_data_structures::fx::FxHashSet; -use rustc_errors::{Diagnostic, DiagnosticBuilder}; -use rustc_index::vec::IndexVec; -use syntax_pos::symbol::Symbol; - -use smallvec::SmallVec; - -use crate::borrow_check::nll::region_infer::{ - error_reporting::{ - region_name::{RegionName, RegionNameSource}, - ErrorConstraintInfo, ErrorReportingCtx, RegionErrorNamingCtx, - }, - RegionInferenceContext, -}; - -/// The different things we could suggest. -enum SuggestedConstraint { - /// Outlives(a, [b, c, d, ...]) => 'a: 'b + 'c + 'd + ... - Outlives(RegionName, SmallVec<[RegionName; 2]>), - - /// 'a = 'b - Equal(RegionName, RegionName), - - /// 'a: 'static i.e. 'a = 'static and the user should just use 'static - Static(RegionName), -} - -/// Collects information about outlives constraints that needed to be added for a given MIR node -/// corresponding to a function definition. -/// -/// Adds a help note suggesting adding a where clause with the needed constraints. -pub struct OutlivesSuggestionBuilder<'a> { - /// The MIR DefId of the fn with the lifetime error. - mir_def_id: DefId, - - local_names: &'a IndexVec>, - - /// The list of outlives constraints that need to be added. Specifically, we map each free - /// region to all other regions that it must outlive. I will use the shorthand `fr: - /// outlived_frs`. Not all of these regions will already have names necessarily. Some could be - /// implicit free regions that we inferred. These will need to be given names in the final - /// suggestion message. - constraints_to_add: BTreeMap>, -} - -impl OutlivesSuggestionBuilder<'a> { - /// Create a new builder for the given MIR node representing a fn definition. - crate fn new( - mir_def_id: DefId, - local_names: &'a IndexVec>, - ) -> Self { - OutlivesSuggestionBuilder { - mir_def_id, - local_names, - constraints_to_add: BTreeMap::default(), - } - } - - /// Returns `true` iff the `RegionNameSource` is a valid source for an outlives - /// suggestion. - // - // FIXME: Currently, we only report suggestions if the `RegionNameSource` is an early-bound - // region or a named region, avoiding using regions with synthetic names altogether. This - // allows us to avoid giving impossible suggestions (e.g. adding bounds to closure args). - // We can probably be less conservative, since some inferred free regions are namable (e.g. - // the user can explicitly name them. To do this, we would allow some regions whose names - // come from `MatchedAdtAndSegment`, being careful to filter out bad suggestions, such as - // naming the `'self` lifetime in methods, etc. - fn region_name_is_suggestable(name: &RegionName) -> bool { - match name.source { - RegionNameSource::NamedEarlyBoundRegion(..) - | RegionNameSource::NamedFreeRegion(..) - | RegionNameSource::Static => true, - - // Don't give suggestions for upvars, closure return types, or other unnamable - // regions. - RegionNameSource::SynthesizedFreeEnvRegion(..) - | RegionNameSource::CannotMatchHirTy(..) - | RegionNameSource::MatchedHirTy(..) - | RegionNameSource::MatchedAdtAndSegment(..) - | RegionNameSource::AnonRegionFromUpvar(..) - | RegionNameSource::AnonRegionFromOutput(..) - | RegionNameSource::AnonRegionFromYieldTy(..) - | RegionNameSource::AnonRegionFromAsyncFn(..) => { - debug!("Region {:?} is NOT suggestable", name); - false - } - } - } - - /// Returns a name for the region if it is suggestable. See `region_name_is_suggestable`. - fn region_vid_to_name( - &self, - errctx: &ErrorReportingCtx<'_, '_, '_>, - renctx: &mut RegionErrorNamingCtx, - region: RegionVid, - ) -> Option { - errctx - .region_infcx - .give_region_a_name(errctx, renctx, region) - .filter(Self::region_name_is_suggestable) - } - - /// Compiles a list of all suggestions to be printed in the final big suggestion. - fn compile_all_suggestions<'tcx>( - &self, - body: &Body<'tcx>, - region_infcx: &RegionInferenceContext<'tcx>, - infcx: &InferCtxt<'_, 'tcx>, - renctx: &mut RegionErrorNamingCtx, - ) -> SmallVec<[SuggestedConstraint; 2]> { - let mut suggested = SmallVec::new(); - - // Keep track of variables that we have already suggested unifying so that we don't print - // out silly duplicate messages. - let mut unified_already = FxHashSet::default(); - - let errctx = ErrorReportingCtx { - region_infcx, - infcx, - body, - mir_def_id: self.mir_def_id, - local_names: self.local_names, - - // We should not be suggesting naming upvars, so we pass in a dummy set of upvars that - // should never be used. - upvars: &[], - }; - - for (fr, outlived) in &self.constraints_to_add { - let fr_name = if let Some(fr_name) = self.region_vid_to_name(&errctx, renctx, *fr) { - fr_name - } else { - continue; - }; - - let outlived = outlived - .iter() - // if there is a `None`, we will just omit that constraint - .filter_map(|fr| { - self.region_vid_to_name(&errctx, renctx, *fr).map(|rname| (fr, rname)) - }) - .collect::>(); - - // No suggestable outlived lifetimes. - if outlived.is_empty() { - continue; - } - - // There are three types of suggestions we can make: - // 1) Suggest a bound: 'a: 'b - // 2) Suggest replacing 'a with 'static. If any of `outlived` is `'static`, then we - // should just replace 'a with 'static. - // 3) Suggest unifying 'a with 'b if we have both 'a: 'b and 'b: 'a - - if outlived.iter().any(|(_, outlived_name)| { - if let RegionNameSource::Static = outlived_name.source { - true - } else { - false - } - }) { - suggested.push(SuggestedConstraint::Static(fr_name)); - } else { - // We want to isolate out all lifetimes that should be unified and print out - // separate messages for them. - - let (unified, other): (Vec<_>, Vec<_>) = outlived.into_iter().partition( - // Do we have both 'fr: 'r and 'r: 'fr? - |(r, _)| { - self.constraints_to_add - .get(r) - .map(|r_outlived| r_outlived.as_slice().contains(fr)) - .unwrap_or(false) - }, - ); - - for (r, bound) in unified.into_iter() { - if !unified_already.contains(fr) { - suggested.push(SuggestedConstraint::Equal(fr_name.clone(), bound)); - unified_already.insert(r); - } - } - - if !other.is_empty() { - let other = - other.iter().map(|(_, rname)| rname.clone()).collect::>(); - suggested.push(SuggestedConstraint::Outlives(fr_name, other)) - } - } - } - - suggested - } - - /// Add the outlives constraint `fr: outlived_fr` to the set of constraints we need to suggest. - crate fn collect_constraint(&mut self, fr: RegionVid, outlived_fr: RegionVid) { - debug!("Collected {:?}: {:?}", fr, outlived_fr); - - // Add to set of constraints for final help note. - self.constraints_to_add.entry(fr).or_insert(Vec::new()).push(outlived_fr); - } - - /// Emit an intermediate note on the given `Diagnostic` if the involved regions are - /// suggestable. - crate fn intermediate_suggestion( - &mut self, - errctx: &ErrorReportingCtx<'_, '_, '_>, - errci: &ErrorConstraintInfo, - renctx: &mut RegionErrorNamingCtx, - diag: &mut DiagnosticBuilder<'_>, - ) { - // Emit an intermediate note. - let fr_name = self.region_vid_to_name(errctx, renctx, errci.fr); - let outlived_fr_name = self.region_vid_to_name(errctx, renctx, errci.outlived_fr); - - if let (Some(fr_name), Some(outlived_fr_name)) = (fr_name, outlived_fr_name) { - if let RegionNameSource::Static = outlived_fr_name.source { - diag.help(&format!("consider replacing `{}` with `'static`", fr_name)); - } else { - diag.help(&format!( - "consider adding the following bound: `{}: {}`", - fr_name, outlived_fr_name - )); - } - } - } - - /// If there is a suggestion to emit, add a diagnostic to the buffer. This is the final - /// suggestion including all collected constraints. - crate fn add_suggestion<'tcx>( - &self, - body: &Body<'tcx>, - region_infcx: &RegionInferenceContext<'tcx>, - infcx: &InferCtxt<'_, 'tcx>, - errors_buffer: &mut Vec, - renctx: &mut RegionErrorNamingCtx, - ) { - // No constraints to add? Done. - if self.constraints_to_add.is_empty() { - debug!("No constraints to suggest."); - return; - } - - // If there is only one constraint to suggest, then we already suggested it in the - // intermediate suggestion above. - if self.constraints_to_add.len() == 1 { - debug!("Only 1 suggestion. Skipping."); - return; - } - - // Get all suggestable constraints. - let suggested = self.compile_all_suggestions(body, region_infcx, infcx, renctx); - - // If there are no suggestable constraints... - if suggested.is_empty() { - debug!("Only 1 suggestable constraint. Skipping."); - return; - } - - // If there is exactly one suggestable constraints, then just suggest it. Otherwise, emit a - // list of diagnostics. - let mut diag = if suggested.len() == 1 { - infcx.tcx.sess.diagnostic().struct_help(&match suggested.last().unwrap() { - SuggestedConstraint::Outlives(a, bs) => { - let bs: SmallVec<[String; 2]> = bs.iter().map(|r| format!("{}", r)).collect(); - format!("add bound `{}: {}`", a, bs.join(" + ")) - } - - SuggestedConstraint::Equal(a, b) => { - format!("`{}` and `{}` must be the same: replace one with the other", a, b) - } - SuggestedConstraint::Static(a) => format!("replace `{}` with `'static`", a), - }) - } else { - // Create a new diagnostic. - let mut diag = infcx - .tcx - .sess - .diagnostic() - .struct_help("the following changes may resolve your lifetime errors"); - - // Add suggestions. - for constraint in suggested { - match constraint { - SuggestedConstraint::Outlives(a, bs) => { - let bs: SmallVec<[String; 2]> = - bs.iter().map(|r| format!("{}", r)).collect(); - diag.help(&format!("add bound `{}: {}`", a, bs.join(" + "))); - } - SuggestedConstraint::Equal(a, b) => { - diag.help(&format!( - "`{}` and `{}` must be the same: replace one with the other", - a, b - )); - } - SuggestedConstraint::Static(a) => { - diag.help(&format!("replace `{}` with `'static`", a)); - } - } - } - - diag - }; - - // We want this message to appear after other messages on the mir def. - let mir_span = infcx.tcx.def_span(self.mir_def_id); - diag.sort_span = mir_span.shrink_to_hi(); - - // Buffer the diagnostic - diag.buffer(errors_buffer); - } -} diff --git a/src/librustc_mir/borrow_check/nll/region_infer/error_reporting/region_name.rs b/src/librustc_mir/borrow_check/nll/region_infer/error_reporting/region_name.rs deleted file mode 100644 index 0f5d1c5edc4..00000000000 --- a/src/librustc_mir/borrow_check/nll/region_infer/error_reporting/region_name.rs +++ /dev/null @@ -1,834 +0,0 @@ -use std::fmt::{self, Display}; - -use crate::borrow_check::nll::region_infer::{ - RegionInferenceContext, - error_reporting::ErrorReportingCtx, -}; -use crate::borrow_check::nll::universal_regions::DefiningTy; -use crate::borrow_check::nll::ToRegionVid; -use crate::borrow_check::Upvar; -use rustc::hir; -use rustc::hir::def::{Res, DefKind}; -use rustc::hir::def_id::DefId; -use rustc::infer::InferCtxt; -use rustc::mir::{Local, Body}; -use rustc::ty::subst::{SubstsRef, GenericArgKind}; -use rustc::ty::{self, RegionVid, Ty, TyCtxt}; -use rustc::ty::print::RegionHighlightMode; -use rustc_index::vec::IndexVec; -use rustc_errors::DiagnosticBuilder; -use syntax::symbol::kw; -use rustc_data_structures::fx::FxHashMap; -use syntax_pos::{Span, symbol::Symbol, DUMMY_SP}; - -/// A name for a particular region used in emitting diagnostics. This name could be a generated -/// name like `'1`, a name used by the user like `'a`, or a name like `'static`. -#[derive(Debug, Clone)] -crate struct RegionName { - /// The name of the region (interned). - crate name: Symbol, - /// Where the region comes from. - crate source: RegionNameSource, -} - -/// Denotes the source of a region that is named by a `RegionName`. For example, a free region that -/// was named by the user would get `NamedFreeRegion` and `'static` lifetime would get `Static`. -/// This helps to print the right kinds of diagnostics. -#[derive(Debug, Clone)] -crate enum RegionNameSource { - /// A bound (not free) region that was substituted at the def site (not an HRTB). - NamedEarlyBoundRegion(Span), - /// A free region that the user has a name (`'a`) for. - NamedFreeRegion(Span), - /// The `'static` region. - Static, - /// The free region corresponding to the environment of a closure. - SynthesizedFreeEnvRegion(Span, String), - /// The region name corresponds to a region where the type annotation is completely missing - /// from the code, e.g. in a closure arguments `|x| { ... }`, where `x` is a reference. - CannotMatchHirTy(Span, String), - /// The region name corresponds a reference that was found by traversing the type in the HIR. - MatchedHirTy(Span), - /// A region name from the generics list of a struct/enum/union. - MatchedAdtAndSegment(Span), - /// The region corresponding to a closure upvar. - AnonRegionFromUpvar(Span, String), - /// The region corresponding to the return type of a closure. - AnonRegionFromOutput(Span, String, String), - /// The region from a type yielded by a generator. - AnonRegionFromYieldTy(Span, String), - /// An anonymous region from an async fn. - AnonRegionFromAsyncFn(Span), -} - -/// Records region names that have been assigned before so that we can use the same ones in later -/// diagnostics. -#[derive(Debug, Clone)] -crate struct RegionErrorNamingCtx { - /// Record the region names generated for each region in the given - /// MIR def so that we can reuse them later in help/error messages. - renctx: FxHashMap, - - /// The counter for generating new region names. - counter: usize, -} - -impl RegionErrorNamingCtx { - crate fn new() -> Self { - Self { - counter: 1, - renctx: FxHashMap::default(), - } - } - - /// Get the name of `region` if it has previously been named. - crate fn get(&self, region: &RegionVid) -> Option<&RegionName> { - self.renctx.get(region) - } - - /// Give `region` the name `name`. - crate fn insert(&mut self, region: RegionVid, name: RegionName) { - self.renctx.insert(region, name); - } - - /// Creates a synthetic region named `'N`, where `N` is the next value of the counter. Then, - /// increment the counter. - /// - /// The name is not memoized. A separate call to `insert` should be made later. (Currently, - /// this happens at the end of `give_region_a_name`). - crate fn synthesize_region_name(&mut self) -> Symbol { - let c = self.counter; - self.counter += 1; - - Symbol::intern(&format!("'{:?}", c)) - } -} - -impl RegionName { - crate fn was_named(&self) -> bool { - match self.source { - RegionNameSource::NamedEarlyBoundRegion(..) | - RegionNameSource::NamedFreeRegion(..) | - RegionNameSource::Static => true, - RegionNameSource::SynthesizedFreeEnvRegion(..) | - RegionNameSource::CannotMatchHirTy(..) | - RegionNameSource::MatchedHirTy(..) | - RegionNameSource::MatchedAdtAndSegment(..) | - RegionNameSource::AnonRegionFromUpvar(..) | - RegionNameSource::AnonRegionFromOutput(..) | - RegionNameSource::AnonRegionFromYieldTy(..) | - RegionNameSource::AnonRegionFromAsyncFn(..) => false, - } - } - - crate fn highlight_region_name(&self, diag: &mut DiagnosticBuilder<'_>) { - match &self.source { - RegionNameSource::NamedFreeRegion(span) - | RegionNameSource::NamedEarlyBoundRegion(span) => { - diag.span_label(*span, format!("lifetime `{}` defined here", self)); - } - RegionNameSource::SynthesizedFreeEnvRegion(span, note) => { - diag.span_label( - *span, - format!("lifetime `{}` represents this closure's body", self), - ); - diag.note(¬e); - } - RegionNameSource::CannotMatchHirTy(span, type_name) => { - diag.span_label(*span, format!("has type `{}`", type_name)); - } - RegionNameSource::MatchedHirTy(span) | - RegionNameSource::AnonRegionFromAsyncFn(span) => { - diag.span_label( - *span, - format!("let's call the lifetime of this reference `{}`", self), - ); - } - RegionNameSource::MatchedAdtAndSegment(span) => { - diag.span_label(*span, format!("let's call this `{}`", self)); - } - RegionNameSource::AnonRegionFromUpvar(span, upvar_name) => { - diag.span_label( - *span, - format!( - "lifetime `{}` appears in the type of `{}`", - self, upvar_name - ), - ); - } - RegionNameSource::AnonRegionFromOutput(span, mir_description, type_name) => { - diag.span_label( - *span, - format!("return type{} is {}", mir_description, type_name), - ); - }, - RegionNameSource::AnonRegionFromYieldTy(span, type_name) => { - diag.span_label( - *span, - format!("yield type is {}", type_name), - ); - } - RegionNameSource::Static => {}, - } - } -} - -impl Display for RegionName { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.name) - } -} - -impl<'tcx> RegionInferenceContext<'tcx> { - /// Maps from an internal MIR region vid to something that we can - /// report to the user. In some cases, the region vids will map - /// directly to lifetimes that the user has a name for (e.g., - /// `'static`). But frequently they will not, in which case we - /// have to find some way to identify the lifetime to the user. To - /// that end, this function takes a "diagnostic" so that it can - /// create auxiliary notes as needed. - /// - /// Example (function arguments): - /// - /// Suppose we are trying to give a name to the lifetime of the - /// reference `x`: - /// - /// ``` - /// fn foo(x: &u32) { .. } - /// ``` - /// - /// This function would create a label like this: - /// - /// ``` - /// | fn foo(x: &u32) { .. } - /// ------- fully elaborated type of `x` is `&'1 u32` - /// ``` - /// - /// and then return the name `'1` for us to use. - crate fn give_region_a_name( - &self, - errctx: &ErrorReportingCtx<'_, '_, 'tcx>, - renctx: &mut RegionErrorNamingCtx, - fr: RegionVid, - ) -> Option { - let ErrorReportingCtx { - infcx, body, mir_def_id, local_names, upvars, .. - } = errctx; - - debug!("give_region_a_name(fr={:?}, counter={:?})", fr, renctx.counter); - - assert!(self.universal_regions.is_universal_region(fr)); - - if let Some(value) = renctx.get(&fr) { - return Some(value.clone()); - } - - let value = self - .give_name_from_error_region(infcx.tcx, *mir_def_id, fr, renctx) - .or_else(|| { - self.give_name_if_anonymous_region_appears_in_arguments( - infcx, body, local_names, *mir_def_id, fr, renctx, - ) - }) - .or_else(|| { - self.give_name_if_anonymous_region_appears_in_upvars( - infcx.tcx, upvars, fr, renctx - ) - }) - .or_else(|| { - self.give_name_if_anonymous_region_appears_in_output( - infcx, body, *mir_def_id, fr, renctx, - ) - }) - .or_else(|| { - self.give_name_if_anonymous_region_appears_in_yield_ty( - infcx, body, *mir_def_id, fr, renctx, - ) - }); - - if let Some(ref value) = value { - renctx.insert(fr, value.clone()); - } - - debug!("give_region_a_name: gave name {:?}", value); - value - } - - /// Checks for the case where `fr` maps to something that the - /// *user* has a name for. In that case, we'll be able to map - /// `fr` to a `Region<'tcx>`, and that region will be one of - /// named variants. - fn give_name_from_error_region( - &self, - tcx: TyCtxt<'tcx>, - mir_def_id: DefId, - fr: RegionVid, - renctx: &mut RegionErrorNamingCtx, - ) -> Option { - let error_region = self.to_error_region(fr)?; - - debug!("give_region_a_name: error_region = {:?}", error_region); - match error_region { - ty::ReEarlyBound(ebr) => { - if ebr.has_name() { - let span = tcx.hir().span_if_local(ebr.def_id).unwrap_or(DUMMY_SP); - Some(RegionName { - name: ebr.name, - source: RegionNameSource::NamedEarlyBoundRegion(span), - }) - } else { - None - } - } - - ty::ReStatic => Some(RegionName { - name: kw::StaticLifetime, - source: RegionNameSource::Static - }), - - ty::ReFree(free_region) => match free_region.bound_region { - ty::BoundRegion::BrNamed(region_def_id, name) => { - // Get the span to point to, even if we don't use the name. - let span = tcx.hir().span_if_local(region_def_id).unwrap_or(DUMMY_SP); - debug!("bound region named: {:?}, is_named: {:?}", - name, free_region.bound_region.is_named()); - - if free_region.bound_region.is_named() { - // A named region that is actually named. - Some(RegionName { - name, - source: RegionNameSource::NamedFreeRegion(span), - }) - } else { - // If we spuriously thought that the region is named, we should let the - // system generate a true name for error messages. Currently this can - // happen if we have an elided name in an async fn for example: the - // compiler will generate a region named `'_`, but reporting such a name is - // not actually useful, so we synthesize a name for it instead. - let name = renctx.synthesize_region_name(); - Some(RegionName { - name, - source: RegionNameSource::AnonRegionFromAsyncFn(span), - }) - } - } - - ty::BoundRegion::BrEnv => { - let mir_hir_id = tcx.hir().as_local_hir_id(mir_def_id).expect("non-local mir"); - let def_ty = self.universal_regions.defining_ty; - - if let DefiningTy::Closure(def_id, substs) = def_ty { - let args_span = if let hir::ExprKind::Closure(_, _, _, span, _) = - tcx.hir().expect_expr(mir_hir_id).kind - { - span - } else { - bug!("Closure is not defined by a closure expr"); - }; - let region_name = renctx.synthesize_region_name(); - - let closure_kind_ty = substs.as_closure().kind_ty(def_id, tcx); - let note = match closure_kind_ty.to_opt_closure_kind() { - Some(ty::ClosureKind::Fn) => { - "closure implements `Fn`, so references to captured variables \ - can't escape the closure" - } - Some(ty::ClosureKind::FnMut) => { - "closure implements `FnMut`, so references to captured variables \ - can't escape the closure" - } - Some(ty::ClosureKind::FnOnce) => { - bug!("BrEnv in a `FnOnce` closure"); - } - None => bug!("Closure kind not inferred in borrow check"), - }; - - Some(RegionName { - name: region_name, - source: RegionNameSource::SynthesizedFreeEnvRegion( - args_span, - note.to_string(), - ), - }) - } else { - // Can't have BrEnv in functions, constants or generators. - bug!("BrEnv outside of closure."); - } - } - - ty::BoundRegion::BrAnon(_) => None, - }, - - ty::ReLateBound(..) - | ty::ReScope(..) - | ty::ReVar(..) - | ty::RePlaceholder(..) - | ty::ReEmpty - | ty::ReErased - | ty::ReClosureBound(..) => None, - } - } - - /// Finds an argument that contains `fr` and label it with a fully - /// elaborated type, returning something like `'1`. Result looks - /// like: - /// - /// ``` - /// | fn foo(x: &u32) { .. } - /// ------- fully elaborated type of `x` is `&'1 u32` - /// ``` - fn give_name_if_anonymous_region_appears_in_arguments( - &self, - infcx: &InferCtxt<'_, 'tcx>, - body: &Body<'tcx>, - local_names: &IndexVec>, - mir_def_id: DefId, - fr: RegionVid, - renctx: &mut RegionErrorNamingCtx, - ) -> Option { - let implicit_inputs = self.universal_regions.defining_ty.implicit_inputs(); - let argument_index = self.get_argument_index_for_region(infcx.tcx, fr)?; - - let arg_ty = - self.universal_regions.unnormalized_input_tys[implicit_inputs + argument_index]; - if let Some(region_name) = self.give_name_if_we_can_match_hir_ty_from_argument( - infcx, - mir_def_id, - fr, - arg_ty, - argument_index, - renctx, - ) { - return Some(region_name); - } - - self.give_name_if_we_cannot_match_hir_ty(infcx, body, local_names, fr, arg_ty, renctx) - } - - fn give_name_if_we_can_match_hir_ty_from_argument( - &self, - infcx: &InferCtxt<'_, 'tcx>, - mir_def_id: DefId, - needle_fr: RegionVid, - argument_ty: Ty<'tcx>, - argument_index: usize, - renctx: &mut RegionErrorNamingCtx, - ) -> Option { - let mir_hir_id = infcx.tcx.hir().as_local_hir_id(mir_def_id)?; - let fn_decl = infcx.tcx.hir().fn_decl_by_hir_id(mir_hir_id)?; - let argument_hir_ty: &hir::Ty = fn_decl.inputs.get(argument_index)?; - match argument_hir_ty.kind { - // This indicates a variable with no type annotation, like - // `|x|`... in that case, we can't highlight the type but - // must highlight the variable. - // NOTE(eddyb) this is handled in/by the sole caller - // (`give_name_if_anonymous_region_appears_in_arguments`). - hir::TyKind::Infer => None, - - _ => self.give_name_if_we_can_match_hir_ty( - infcx.tcx, - needle_fr, - argument_ty, - argument_hir_ty, - renctx, - ), - } - } - - /// Attempts to highlight the specific part of a type in an argument - /// that has no type annotation. - /// For example, we might produce an annotation like this: - /// - /// ``` - /// | foo(|a, b| b) - /// | - - - /// | | | - /// | | has type `&'1 u32` - /// | has type `&'2 u32` - /// ``` - fn give_name_if_we_cannot_match_hir_ty( - &self, - infcx: &InferCtxt<'_, 'tcx>, - body: &Body<'tcx>, - local_names: &IndexVec>, - needle_fr: RegionVid, - argument_ty: Ty<'tcx>, - renctx: &mut RegionErrorNamingCtx, - ) -> Option { - let counter = renctx.counter; - let mut highlight = RegionHighlightMode::default(); - highlight.highlighting_region_vid(needle_fr, counter); - let type_name = infcx.extract_type_name(&argument_ty, Some(highlight)).0; - - debug!( - "give_name_if_we_cannot_match_hir_ty: type_name={:?} needle_fr={:?}", - type_name, needle_fr - ); - let assigned_region_name = if type_name.find(&format!("'{}", counter)).is_some() { - // Only add a label if we can confirm that a region was labelled. - let argument_index = self.get_argument_index_for_region(infcx.tcx, needle_fr)?; - let (_, span) = - self.get_argument_name_and_span_for_region(body, local_names, argument_index); - - Some(RegionName { - // This counter value will already have been used, so this function will increment - // it so the next value will be used next and return the region name that would - // have been used. - name: renctx.synthesize_region_name(), - source: RegionNameSource::CannotMatchHirTy(span, type_name), - }) - } else { - None - }; - - assigned_region_name - } - - /// Attempts to highlight the specific part of a type annotation - /// that contains the anonymous reference we want to give a name - /// to. For example, we might produce an annotation like this: - /// - /// ``` - /// | fn a(items: &[T]) -> Box> { - /// | - let's call the lifetime of this reference `'1` - /// ``` - /// - /// the way this works is that we match up `argument_ty`, which is - /// a `Ty<'tcx>` (the internal form of the type) with - /// `argument_hir_ty`, a `hir::Ty` (the syntax of the type - /// annotation). We are descending through the types stepwise, - /// looking in to find the region `needle_fr` in the internal - /// type. Once we find that, we can use the span of the `hir::Ty` - /// to add the highlight. - /// - /// This is a somewhat imperfect process, so along the way we also - /// keep track of the **closest** type we've found. If we fail to - /// find the exact `&` or `'_` to highlight, then we may fall back - /// to highlighting that closest type instead. - fn give_name_if_we_can_match_hir_ty( - &self, - tcx: TyCtxt<'tcx>, - needle_fr: RegionVid, - argument_ty: Ty<'tcx>, - argument_hir_ty: &hir::Ty, - renctx: &mut RegionErrorNamingCtx, - ) -> Option { - let search_stack: &mut Vec<(Ty<'tcx>, &hir::Ty)> = - &mut vec![(argument_ty, argument_hir_ty)]; - - while let Some((ty, hir_ty)) = search_stack.pop() { - match (&ty.kind, &hir_ty.kind) { - // Check if the `argument_ty` is `&'X ..` where `'X` - // is the region we are looking for -- if so, and we have a `&T` - // on the RHS, then we want to highlight the `&` like so: - // - // & - // - let's call the lifetime of this reference `'1` - ( - ty::Ref(region, referent_ty, _), - hir::TyKind::Rptr(_lifetime, referent_hir_ty), - ) => { - if region.to_region_vid() == needle_fr { - let region_name = renctx.synthesize_region_name(); - - // Just grab the first character, the `&`. - let source_map = tcx.sess.source_map(); - let ampersand_span = source_map.start_point(hir_ty.span); - - return Some(RegionName { - name: region_name, - source: RegionNameSource::MatchedHirTy(ampersand_span), - }); - } - - // Otherwise, let's descend into the referent types. - search_stack.push((referent_ty, &referent_hir_ty.ty)); - } - - // Match up something like `Foo<'1>` - ( - ty::Adt(_adt_def, substs), - hir::TyKind::Path(hir::QPath::Resolved(None, path)), - ) => { - match path.res { - // Type parameters of the type alias have no reason to - // be the same as those of the ADT. - // FIXME: We should be able to do something similar to - // match_adt_and_segment in this case. - Res::Def(DefKind::TyAlias, _) => (), - _ => if let Some(last_segment) = path.segments.last() { - if let Some(name) = self.match_adt_and_segment( - substs, - needle_fr, - last_segment, - renctx, - search_stack, - ) { - return Some(name); - } - } - } - } - - // The following cases don't have lifetimes, so we - // just worry about trying to match up the rustc type - // with the HIR types: - (ty::Tuple(elem_tys), hir::TyKind::Tup(elem_hir_tys)) => { - search_stack.extend(elem_tys.iter().map(|k| k.expect_ty()).zip(elem_hir_tys)); - } - - (ty::Slice(elem_ty), hir::TyKind::Slice(elem_hir_ty)) - | (ty::Array(elem_ty, _), hir::TyKind::Array(elem_hir_ty, _)) => { - search_stack.push((elem_ty, elem_hir_ty)); - } - - (ty::RawPtr(mut_ty), hir::TyKind::Ptr(mut_hir_ty)) => { - search_stack.push((mut_ty.ty, &mut_hir_ty.ty)); - } - - _ => { - // FIXME there are other cases that we could trace - } - } - } - - return None; - } - - /// We've found an enum/struct/union type with the substitutions - /// `substs` and -- in the HIR -- a path type with the final - /// segment `last_segment`. Try to find a `'_` to highlight in - /// the generic args (or, if not, to produce new zipped pairs of - /// types+hir to search through). - fn match_adt_and_segment<'hir>( - &self, - substs: SubstsRef<'tcx>, - needle_fr: RegionVid, - last_segment: &'hir hir::PathSegment, - renctx: &mut RegionErrorNamingCtx, - search_stack: &mut Vec<(Ty<'tcx>, &'hir hir::Ty)>, - ) -> Option { - // Did the user give explicit arguments? (e.g., `Foo<..>`) - let args = last_segment.args.as_ref()?; - let lifetime = - self.try_match_adt_and_generic_args(substs, needle_fr, args, search_stack)?; - match lifetime.name { - hir::LifetimeName::Param(_) - | hir::LifetimeName::Error - | hir::LifetimeName::Static - | hir::LifetimeName::Underscore => { - let region_name = renctx.synthesize_region_name(); - let ampersand_span = lifetime.span; - Some(RegionName { - name: region_name, - source: RegionNameSource::MatchedAdtAndSegment(ampersand_span), - }) - } - - hir::LifetimeName::ImplicitObjectLifetimeDefault - | hir::LifetimeName::Implicit => { - // In this case, the user left off the lifetime; so - // they wrote something like: - // - // ``` - // x: Foo - // ``` - // - // where the fully elaborated form is `Foo<'_, '1, - // T>`. We don't consider this a match; instead we let - // the "fully elaborated" type fallback above handle - // it. - None - } - } - } - - /// We've found an enum/struct/union type with the substitutions - /// `substs` and -- in the HIR -- a path with the generic - /// arguments `args`. If `needle_fr` appears in the args, return - /// the `hir::Lifetime` that corresponds to it. If not, push onto - /// `search_stack` the types+hir to search through. - fn try_match_adt_and_generic_args<'hir>( - &self, - substs: SubstsRef<'tcx>, - needle_fr: RegionVid, - args: &'hir hir::GenericArgs, - search_stack: &mut Vec<(Ty<'tcx>, &'hir hir::Ty)>, - ) -> Option<&'hir hir::Lifetime> { - for (kind, hir_arg) in substs.iter().zip(&args.args) { - match (kind.unpack(), hir_arg) { - (GenericArgKind::Lifetime(r), hir::GenericArg::Lifetime(lt)) => { - if r.to_region_vid() == needle_fr { - return Some(lt); - } - } - - (GenericArgKind::Type(ty), hir::GenericArg::Type(hir_ty)) => { - search_stack.push((ty, hir_ty)); - } - - (GenericArgKind::Const(_ct), hir::GenericArg::Const(_hir_ct)) => { - // Lifetimes cannot be found in consts, so we don't need - // to search anything here. - } - - (GenericArgKind::Lifetime(_), _) - | (GenericArgKind::Type(_), _) - | (GenericArgKind::Const(_), _) => { - // I *think* that HIR lowering should ensure this - // doesn't happen, even in erroneous - // programs. Else we should use delay-span-bug. - span_bug!( - hir_arg.span(), - "unmatched subst and hir arg: found {:?} vs {:?}", - kind, - hir_arg, - ); - } - } - } - - None - } - - /// Finds a closure upvar that contains `fr` and label it with a - /// fully elaborated type, returning something like `'1`. Result - /// looks like: - /// - /// ``` - /// | let x = Some(&22); - /// - fully elaborated type of `x` is `Option<&'1 u32>` - /// ``` - fn give_name_if_anonymous_region_appears_in_upvars( - &self, - tcx: TyCtxt<'tcx>, - upvars: &[Upvar], - fr: RegionVid, - renctx: &mut RegionErrorNamingCtx, - ) -> Option { - let upvar_index = self.get_upvar_index_for_region(tcx, fr)?; - let (upvar_name, upvar_span) = - self.get_upvar_name_and_span_for_region(tcx, upvars, upvar_index); - let region_name = renctx.synthesize_region_name(); - - Some(RegionName { - name: region_name, - source: RegionNameSource::AnonRegionFromUpvar(upvar_span, upvar_name.to_string()), - }) - } - - /// Checks for arguments appearing in the (closure) return type. It - /// must be a closure since, in a free fn, such an argument would - /// have to either also appear in an argument (if using elision) - /// or be early bound (named, not in argument). - fn give_name_if_anonymous_region_appears_in_output( - &self, - infcx: &InferCtxt<'_, 'tcx>, - body: &Body<'tcx>, - mir_def_id: DefId, - fr: RegionVid, - renctx: &mut RegionErrorNamingCtx, - ) -> Option { - let tcx = infcx.tcx; - - let return_ty = self.universal_regions.unnormalized_output_ty; - debug!( - "give_name_if_anonymous_region_appears_in_output: return_ty = {:?}", - return_ty - ); - if !tcx.any_free_region_meets(&return_ty, |r| r.to_region_vid() == fr) { - return None; - } - - let mut highlight = RegionHighlightMode::default(); - highlight.highlighting_region_vid(fr, renctx.counter); - let type_name = infcx.extract_type_name(&return_ty, Some(highlight)).0; - - let mir_hir_id = tcx.hir().as_local_hir_id(mir_def_id).expect("non-local mir"); - - let (return_span, mir_description) = match tcx.hir().get(mir_hir_id) { - hir::Node::Expr(hir::Expr { - kind: hir::ExprKind::Closure(_, return_ty, _, span, gen_move), - .. - }) => ( - match return_ty.output { - hir::FunctionRetTy::DefaultReturn(_) => tcx.sess.source_map().end_point(*span), - hir::FunctionRetTy::Return(_) => return_ty.output.span(), - }, - if gen_move.is_some() { - " of generator" - } else { - " of closure" - }, - ), - hir::Node::ImplItem(hir::ImplItem { - kind: hir::ImplItemKind::Method(method_sig, _), - .. - }) => (method_sig.decl.output.span(), ""), - _ => (body.span, ""), - }; - - Some(RegionName { - // This counter value will already have been used, so this function will increment it - // so the next value will be used next and return the region name that would have been - // used. - name: renctx.synthesize_region_name(), - source: RegionNameSource::AnonRegionFromOutput( - return_span, - mir_description.to_string(), - type_name, - ), - }) - } - - fn give_name_if_anonymous_region_appears_in_yield_ty( - &self, - infcx: &InferCtxt<'_, 'tcx>, - body: &Body<'tcx>, - mir_def_id: DefId, - fr: RegionVid, - renctx: &mut RegionErrorNamingCtx, - ) -> Option { - // Note: generators from `async fn` yield `()`, so we don't have to - // worry about them here. - let yield_ty = self.universal_regions.yield_ty?; - debug!( - "give_name_if_anonymous_region_appears_in_yield_ty: yield_ty = {:?}", - yield_ty, - ); - - let tcx = infcx.tcx; - - if !tcx.any_free_region_meets(&yield_ty, |r| r.to_region_vid() == fr) { - return None; - } - - let mut highlight = RegionHighlightMode::default(); - highlight.highlighting_region_vid(fr, renctx.counter); - let type_name = infcx.extract_type_name(&yield_ty, Some(highlight)).0; - - let mir_hir_id = tcx.hir().as_local_hir_id(mir_def_id).expect("non-local mir"); - - let yield_span = match tcx.hir().get(mir_hir_id) { - hir::Node::Expr(hir::Expr { - kind: hir::ExprKind::Closure(_, _, _, span, _), - .. - }) => ( - tcx.sess.source_map().end_point(*span) - ), - _ => body.span, - }; - - debug!( - "give_name_if_anonymous_region_appears_in_yield_ty: \ - type_name = {:?}, yield_span = {:?}", - yield_span, - type_name, - ); - - Some(RegionName { - name: renctx.synthesize_region_name(), - source: RegionNameSource::AnonRegionFromYieldTy(yield_span, type_name), - }) - } -} diff --git a/src/librustc_mir/borrow_check/nll/region_infer/error_reporting/var_name.rs b/src/librustc_mir/borrow_check/nll/region_infer/error_reporting/var_name.rs deleted file mode 100644 index 1ac44c4fdb1..00000000000 --- a/src/librustc_mir/borrow_check/nll/region_infer/error_reporting/var_name.rs +++ /dev/null @@ -1,136 +0,0 @@ -use crate::borrow_check::nll::region_infer::RegionInferenceContext; -use crate::borrow_check::nll::ToRegionVid; -use crate::borrow_check::Upvar; -use rustc::mir::{Local, Body}; -use rustc::ty::{RegionVid, TyCtxt}; -use rustc_index::vec::{Idx, IndexVec}; -use syntax::source_map::Span; -use syntax_pos::symbol::Symbol; - -impl<'tcx> RegionInferenceContext<'tcx> { - crate fn get_var_name_and_span_for_region( - &self, - tcx: TyCtxt<'tcx>, - body: &Body<'tcx>, - local_names: &IndexVec>, - upvars: &[Upvar], - fr: RegionVid, - ) -> Option<(Option, Span)> { - debug!("get_var_name_and_span_for_region(fr={:?})", fr); - assert!(self.universal_regions.is_universal_region(fr)); - - debug!("get_var_name_and_span_for_region: attempting upvar"); - self.get_upvar_index_for_region(tcx, fr) - .map(|index| { - let (name, span) = - self.get_upvar_name_and_span_for_region(tcx, upvars, index); - (Some(name), span) - }) - .or_else(|| { - debug!("get_var_name_and_span_for_region: attempting argument"); - self.get_argument_index_for_region(tcx, fr).map(|index| { - self.get_argument_name_and_span_for_region(body, local_names, index) - }) - }) - } - - /// Search the upvars (if any) to find one that references fr. Return its index. - crate fn get_upvar_index_for_region(&self, tcx: TyCtxt<'tcx>, fr: RegionVid) -> Option { - let upvar_index = self - .universal_regions - .defining_ty - .upvar_tys(tcx) - .position(|upvar_ty| { - debug!("get_upvar_index_for_region: upvar_ty={:?}", upvar_ty); - tcx.any_free_region_meets(&upvar_ty, |r| { - let r = r.to_region_vid(); - debug!("get_upvar_index_for_region: r={:?} fr={:?}", r, fr); - r == fr - }) - })?; - - let upvar_ty = self - .universal_regions - .defining_ty - .upvar_tys(tcx) - .nth(upvar_index); - - debug!( - "get_upvar_index_for_region: found {:?} in upvar {} which has type {:?}", - fr, upvar_index, upvar_ty, - ); - - Some(upvar_index) - } - - /// Given the index of an upvar, finds its name and the span from where it was - /// declared. - crate fn get_upvar_name_and_span_for_region( - &self, - tcx: TyCtxt<'tcx>, - upvars: &[Upvar], - upvar_index: usize, - ) -> (Symbol, Span) { - let upvar_hir_id = upvars[upvar_index].var_hir_id; - debug!("get_upvar_name_and_span_for_region: upvar_hir_id={:?}", upvar_hir_id); - - let upvar_name = tcx.hir().name(upvar_hir_id); - let upvar_span = tcx.hir().span(upvar_hir_id); - debug!("get_upvar_name_and_span_for_region: upvar_name={:?} upvar_span={:?}", - upvar_name, upvar_span); - - (upvar_name, upvar_span) - } - - /// Search the argument types for one that references fr (which should be a free region). - /// Returns Some(_) with the index of the input if one is found. - /// - /// N.B., in the case of a closure, the index is indexing into the signature as seen by the - /// user - in particular, index 0 is not the implicit self parameter. - crate fn get_argument_index_for_region( - &self, - tcx: TyCtxt<'tcx>, - fr: RegionVid, - ) -> Option { - let implicit_inputs = self.universal_regions.defining_ty.implicit_inputs(); - let argument_index = self - .universal_regions - .unnormalized_input_tys - .iter() - .skip(implicit_inputs) - .position(|arg_ty| { - debug!( - "get_argument_index_for_region: arg_ty = {:?}", - arg_ty - ); - tcx.any_free_region_meets(arg_ty, |r| r.to_region_vid() == fr) - })?; - - debug!( - "get_argument_index_for_region: found {:?} in argument {} which has type {:?}", - fr, argument_index, self.universal_regions.unnormalized_input_tys[argument_index], - ); - - Some(argument_index) - } - - /// Given the index of an argument, finds its name (if any) and the span from where it was - /// declared. - crate fn get_argument_name_and_span_for_region( - &self, - body: &Body<'tcx>, - local_names: &IndexVec>, - argument_index: usize, - ) -> (Option, Span) { - let implicit_inputs = self.universal_regions.defining_ty.implicit_inputs(); - let argument_local = Local::new(implicit_inputs + argument_index + 1); - debug!("get_argument_name_and_span_for_region: argument_local={:?}", argument_local); - - let argument_name = local_names[argument_local]; - let argument_span = body.local_decls[argument_local].source_info.span; - debug!("get_argument_name_and_span_for_region: argument_name={:?} argument_span={:?}", - argument_name, argument_span); - - (argument_name, argument_span) - } -} diff --git a/src/librustc_mir/borrow_check/nll/region_infer/mod.rs b/src/librustc_mir/borrow_check/nll/region_infer/mod.rs index bd9e97e5b63..85031d6210a 100644 --- a/src/librustc_mir/borrow_check/nll/region_infer/mod.rs +++ b/src/librustc_mir/borrow_check/nll/region_infer/mod.rs @@ -1,21 +1,5 @@ use std::rc::Rc; -use crate::borrow_check::nll::{ - constraints::{ - graph::NormalConstraintGraph, - ConstraintSccIndex, - OutlivesConstraint, - OutlivesConstraintSet, - }, - member_constraints::{MemberConstraintSet, NllMemberConstraintIndex}, - region_infer::values::{ - PlaceholderIndices, RegionElement, ToElementIndex - }, - region_infer::error_reporting::outlives_suggestion::OutlivesSuggestionBuilder, - type_check::{free_region_relations::UniversalRegionRelations, Locations}, -}; -use crate::borrow_check::Upvar; - use rustc::hir::def_id::DefId; use rustc::infer::canonical::QueryOutlivesConstraint; use rustc::infer::opaque_types; @@ -38,13 +22,31 @@ use syntax_pos::Span; use syntax_pos::symbol::Symbol; -crate use self::error_reporting::{RegionName, RegionNameSource, RegionErrorNamingCtx}; +use crate::borrow_check::{ + nll::{ + constraints::{ + graph::NormalConstraintGraph, + ConstraintSccIndex, + OutlivesConstraint, + OutlivesConstraintSet, + }, + member_constraints::{MemberConstraintSet, NllMemberConstraintIndex}, + region_infer::values::{ + PlaceholderIndices, RegionElement, ToElementIndex + }, + type_check::{free_region_relations::UniversalRegionRelations, Locations}, + }, + diagnostics::{ + OutlivesSuggestionBuilder, RegionErrorNamingCtx, + }, + Upvar, +}; + use self::values::{LivenessValues, RegionValueElements, RegionValues}; use super::universal_regions::UniversalRegions; use super::ToRegionVid; mod dump_mir; -mod error_reporting; mod graphviz; pub mod values; @@ -54,48 +56,51 @@ pub struct RegionInferenceContext<'tcx> { /// variables are identified by their index (`RegionVid`). The /// definition contains information about where the region came /// from as well as its final inferred value. - definitions: IndexVec>, + pub(in crate::borrow_check) definitions: IndexVec>, /// The liveness constraints added to each region. For most /// regions, these start out empty and steadily grow, though for /// each universally quantified region R they start out containing /// the entire CFG and `end(R)`. - liveness_constraints: LivenessValues, + pub(in crate::borrow_check) liveness_constraints: LivenessValues, /// The outlives constraints computed by the type-check. - constraints: Rc, + pub(in crate::borrow_check) constraints: Rc, /// The constraint-set, but in graph form, making it easy to traverse /// the constraints adjacent to a particular region. Used to construct /// the SCC (see `constraint_sccs`) and for error reporting. - constraint_graph: Rc, + pub(in crate::borrow_check) constraint_graph: Rc, /// The SCC computed from `constraints` and the constraint /// graph. We have an edge from SCC A to SCC B if `A: B`. Used to /// compute the values of each region. - constraint_sccs: Rc>, + pub(in crate::borrow_check) constraint_sccs: Rc>, /// Reverse of the SCC constraint graph -- i.e., an edge `A -> B` /// exists if `B: A`. Computed lazilly. - rev_constraint_graph: Option>>, + pub(in crate::borrow_check) rev_constraint_graph: + Option>>, /// The "R0 member of [R1..Rn]" constraints, indexed by SCC. - member_constraints: Rc>, + pub(in crate::borrow_check) member_constraints: + Rc>, /// Records the member constraints that we applied to each scc. /// This is useful for error reporting. Once constraint /// propagation is done, this vector is sorted according to /// `member_region_scc`. - member_constraints_applied: Vec, + pub(in crate::borrow_check) member_constraints_applied: Vec, /// Map closure bounds to a `Span` that should be used for error reporting. - closure_bounds_mapping: + pub(in crate::borrow_check) closure_bounds_mapping: FxHashMap>, /// Contains the minimum universe of any variable within the same /// SCC. We will ensure that no SCC contains values that are not /// visible from this index. - scc_universes: IndexVec, + pub(in crate::borrow_check) scc_universes: + IndexVec, /// Contains a "representative" from each SCC. This will be the /// minimal RegionVid belonging to that universe. It is used as a @@ -104,23 +109,25 @@ pub struct RegionInferenceContext<'tcx> { /// of its SCC and be sure that -- if they have the same repr -- /// they *must* be equal (though not having the same repr does not /// mean they are unequal). - scc_representatives: IndexVec, + pub(in crate::borrow_check) scc_representatives: + IndexVec, /// The final inferred values of the region variables; we compute /// one value per SCC. To get the value for any given *region*, /// you first find which scc it is a part of. - scc_values: RegionValues, + pub(in crate::borrow_check) scc_values: RegionValues, /// Type constraints that we check after solving. - type_tests: Vec>, + pub(in crate::borrow_check) type_tests: Vec>, /// Information about the universally quantified regions in scope /// on this function. - universal_regions: Rc>, + pub (in crate::borrow_check) universal_regions: Rc>, /// Information about how the universally quantified regions in /// scope on this function relate to one another. - universal_region_relations: Rc>, + pub(in crate::borrow_check) universal_region_relations: + Rc>, } /// Each time that `apply_member_constraint` is successful, it appends @@ -132,38 +139,38 @@ pub struct RegionInferenceContext<'tcx> { /// with `'R: 'O` where `'R` is the pick-region and `'O` is the /// minimal viable option. #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] -struct AppliedMemberConstraint { +pub(crate) struct AppliedMemberConstraint { /// The SCC that was affected. (The "member region".) /// /// The vector if `AppliedMemberConstraint` elements is kept sorted /// by this field. - member_region_scc: ConstraintSccIndex, + pub(in crate::borrow_check) member_region_scc: ConstraintSccIndex, /// The "best option" that `apply_member_constraint` found -- this was /// added as an "ad-hoc" lower-bound to `member_region_scc`. - min_choice: ty::RegionVid, + pub(in crate::borrow_check) min_choice: ty::RegionVid, /// The "member constraint index" -- we can find out details about /// the constraint from /// `set.member_constraints[member_constraint_index]`. - member_constraint_index: NllMemberConstraintIndex, + pub(in crate::borrow_check) member_constraint_index: NllMemberConstraintIndex, } -struct RegionDefinition<'tcx> { +pub(crate) struct RegionDefinition<'tcx> { /// What kind of variable is this -- a free region? existential /// variable? etc. (See the `NLLRegionVariableOrigin` for more /// info.) - origin: NLLRegionVariableOrigin, + pub(in crate::borrow_check) origin: NLLRegionVariableOrigin, /// Which universe is this region variable defined in? This is /// most often `ty::UniverseIndex::ROOT`, but when we encounter /// forall-quantifiers like `for<'a> { 'a = 'b }`, we would create /// the variable for `'a` in a fresh universe that extends ROOT. - universe: ty::UniverseIndex, + pub(in crate::borrow_check) universe: ty::UniverseIndex, /// If this is 'static or an early-bound region, then this is /// `Some(X)` where `X` is the name of the region. - external_name: Option>, + pub(in crate::borrow_check) external_name: Option>, } /// N.B., the variants in `Cause` are intentionally ordered. Lower @@ -455,7 +462,9 @@ pub fn to_region_vid(&self, r: ty::Region<'tcx>) -> RegionVid { /// Once region solving has completed, this function will return /// the member constraints that were applied to the value of a given /// region `r`. See `AppliedMemberConstraint`. - fn applied_member_constraints(&self, r: impl ToRegionVid) -> &[AppliedMemberConstraint] { + pub(in crate::borrow_check) fn applied_member_constraints( + &self, r: impl ToRegionVid + ) -> &[AppliedMemberConstraint] { let scc = self.constraint_sccs.scc(r.to_region_vid()); binary_search_util::binary_search_slice( &self.member_constraints_applied, diff --git a/src/librustc_parse/Cargo.toml b/src/librustc_parse/Cargo.toml index 95b3256f53a..fb5cb742ab6 100644 --- a/src/librustc_parse/Cargo.toml +++ b/src/librustc_parse/Cargo.toml @@ -12,12 +12,11 @@ doctest = false [dependencies] bitflags = "1.0" log = "0.4" -syntax_pos = { path = "../libsyntax_pos" } -syntax = { path = "../libsyntax" } -errors = { path = "../librustc_errors", package = "rustc_errors" } rustc_data_structures = { path = "../librustc_data_structures" } rustc_feature = { path = "../librustc_feature" } rustc_lexer = { path = "../librustc_lexer" } -rustc_target = { path = "../librustc_target" } -smallvec = { version = "1.0", features = ["union", "may_dangle"] } +rustc_errors = { path = "../librustc_errors" } rustc_error_codes = { path = "../librustc_error_codes" } +smallvec = { version = "1.0", features = ["union", "may_dangle"] } +syntax_pos = { path = "../libsyntax_pos" } +syntax = { path = "../libsyntax" } diff --git a/src/librustc_parse/config.rs b/src/librustc_parse/config.rs index 1bf6e9ecbc0..30e056e52d2 100644 --- a/src/librustc_parse/config.rs +++ b/src/librustc_parse/config.rs @@ -10,6 +10,7 @@ use crate::validate_attr; use rustc_feature::Features; +use rustc_errors::Applicability; use syntax::attr::HasAttrs; use syntax::feature_gate::{feature_err, get_features}; use syntax::attr; @@ -21,7 +22,6 @@ use syntax::util::map_in_place::MapInPlace; use syntax_pos::symbol::sym; -use errors::Applicability; use smallvec::SmallVec; /// A folder that strips out items that do not belong in the current configuration. diff --git a/src/librustc_parse/lexer/mod.rs b/src/librustc_parse/lexer/mod.rs index 5de63cb39d1..ddcfea18980 100644 --- a/src/librustc_parse/lexer/mod.rs +++ b/src/librustc_parse/lexer/mod.rs @@ -1,16 +1,15 @@ +use rustc_data_structures::sync::Lrc; +use rustc_errors::{FatalError, DiagnosticBuilder}; +use rustc_lexer::Base; +use rustc_lexer::unescape; use syntax::token::{self, Token, TokenKind}; use syntax::sess::ParseSess; -use syntax::symbol::{sym, Symbol}; use syntax::util::comments; - -use errors::{FatalError, DiagnosticBuilder}; +use syntax_pos::symbol::{sym, Symbol}; use syntax_pos::{BytePos, Pos, Span}; -use rustc_lexer::Base; -use rustc_lexer::unescape; use std::char; use std::convert::TryInto; -use rustc_data_structures::sync::Lrc; use log::debug; mod tokentrees; diff --git a/src/librustc_parse/lexer/tokentrees.rs b/src/librustc_parse/lexer/tokentrees.rs index 1353591308b..5791c6396c5 100644 --- a/src/librustc_parse/lexer/tokentrees.rs +++ b/src/librustc_parse/lexer/tokentrees.rs @@ -1,13 +1,11 @@ -use rustc_data_structures::fx::FxHashMap; -use syntax_pos::Span; - use super::{StringReader, UnmatchedBrace}; +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::PResult; use syntax::print::pprust::token_to_string; use syntax::token::{self, Token}; use syntax::tokenstream::{DelimSpan, IsJoint::{self, *}, TokenStream, TokenTree, TreeAndJoint}; - -use errors::PResult; +use syntax_pos::Span; impl<'a> StringReader<'a> { crate fn into_token_trees(self) -> (PResult<'a, TokenStream>, Vec) { diff --git a/src/librustc_parse/lexer/unicode_chars.rs b/src/librustc_parse/lexer/unicode_chars.rs index 6eb995b61d3..edfebc7de94 100644 --- a/src/librustc_parse/lexer/unicode_chars.rs +++ b/src/librustc_parse/lexer/unicode_chars.rs @@ -2,9 +2,9 @@ // http://www.unicode.org/Public/security/10.0.0/confusables.txt use super::StringReader; -use errors::{Applicability, DiagnosticBuilder}; -use syntax_pos::{BytePos, Pos, Span, symbol::kw}; use crate::token; +use rustc_errors::{Applicability, DiagnosticBuilder}; +use syntax_pos::{BytePos, Pos, Span, symbol::kw}; #[rustfmt::skip] // for line breaks const UNICODE_ARRAY: &[(char, &str, char)] = &[ diff --git a/src/librustc_parse/lib.rs b/src/librustc_parse/lib.rs index a22b383e5f3..a222f3f00c4 100644 --- a/src/librustc_parse/lib.rs +++ b/src/librustc_parse/lib.rs @@ -8,7 +8,7 @@ use syntax::token::{self, Nonterminal}; use syntax::tokenstream::{self, TokenStream, TokenTree}; -use errors::{PResult, FatalError, Level, Diagnostic}; +use rustc_errors::{PResult, FatalError, Level, Diagnostic}; use rustc_data_structures::sync::Lrc; use syntax_pos::{Span, SourceFile, FileName}; @@ -53,7 +53,7 @@ pub enum DirectoryOwnership { macro_rules! panictry_buffer { ($handler:expr, $e:expr) => ({ use std::result::Result::{Ok, Err}; - use errors::FatalError; + use rustc_errors::FatalError; match $e { Ok(e) => e, Err(errs) => { diff --git a/src/librustc_parse/parser/attr.rs b/src/librustc_parse/parser/attr.rs index c7261404f54..b2ae934ce64 100644 --- a/src/librustc_parse/parser/attr.rs +++ b/src/librustc_parse/parser/attr.rs @@ -1,10 +1,10 @@ use super::{SeqSep, Parser, TokenType, PathStyle}; +use rustc_errors::PResult; use syntax::attr; use syntax::ast; use syntax::util::comments; use syntax::token::{self, Nonterminal}; use syntax_pos::{Span, Symbol}; -use errors::PResult; use log::debug; diff --git a/src/librustc_parse/parser/diagnostics.rs b/src/librustc_parse/parser/diagnostics.rs index da8bf89ebf3..ba125cacab4 100644 --- a/src/librustc_parse/parser/diagnostics.rs +++ b/src/librustc_parse/parser/diagnostics.rs @@ -1,25 +1,22 @@ use super::{BlockMode, PathStyle, SemiColonMode, TokenType, TokenExpectType, SeqSep, Parser}; -use syntax::ast::{ - self, Param, BinOpKind, BindingMode, BlockCheckMode, Expr, ExprKind, Ident, Item, ItemKind, - Mutability, Pat, PatKind, PathSegment, QSelf, Ty, TyKind, -}; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::{self, PResult, Applicability, DiagnosticBuilder, Handler, pluralize}; +use rustc_error_codes::*; +use syntax::ast::{self, Param, BinOpKind, BindingMode, BlockCheckMode, Expr, ExprKind, Ident, Item}; +use syntax::ast::{ItemKind, Mutability, Pat, PatKind, PathSegment, QSelf, Ty, TyKind}; use syntax::token::{self, TokenKind, token_can_begin_expr}; use syntax::print::pprust; use syntax::ptr::P; -use syntax::symbol::{kw, sym}; use syntax::ThinVec; use syntax::util::parser::AssocOp; use syntax::struct_span_err; - -use errors::{PResult, Applicability, DiagnosticBuilder, pluralize}; -use rustc_data_structures::fx::FxHashSet; +use syntax_pos::symbol::{kw, sym}; use syntax_pos::{Span, DUMMY_SP, MultiSpan, SpanSnippetError}; + use log::{debug, trace}; use std::mem; -use rustc_error_codes::*; - const TURBOFISH: &'static str = "use `::<...>` instead of `<...>` to specify type arguments"; /// Creates a placeholder argument. @@ -61,10 +58,10 @@ pub enum Error { } impl Error { - fn span_err>( + fn span_err( self, - sp: S, - handler: &errors::Handler, + sp: impl Into, + handler: &Handler, ) -> DiagnosticBuilder<'_> { match self { Error::FileNotFoundForModule { @@ -212,7 +209,7 @@ pub fn span_bug>(&self, sp: S, m: &str) -> ! { self.sess.span_diagnostic.span_bug(sp, m) } - pub(super) fn diagnostic(&self) -> &'a errors::Handler { + pub(super) fn diagnostic(&self) -> &'a Handler { &self.sess.span_diagnostic } diff --git a/src/librustc_parse/parser/expr.rs b/src/librustc_parse/parser/expr.rs index 1112274dc46..3cd4988ce0b 100644 --- a/src/librustc_parse/parser/expr.rs +++ b/src/librustc_parse/parser/expr.rs @@ -4,23 +4,20 @@ use super::diagnostics::Error; use crate::maybe_recover_from_interpolated_ty_qpath; -use syntax::ast::{ - self, DUMMY_NODE_ID, Attribute, AttrStyle, Ident, CaptureBy, BlockCheckMode, - Expr, ExprKind, RangeLimits, Label, Movability, IsAsync, Arm, Ty, TyKind, - FunctionRetTy, Param, FnDecl, BinOpKind, BinOp, UnOp, Mac, AnonConst, Field, Lit, -}; +use rustc_data_structures::thin_vec::ThinVec; +use rustc_errors::{PResult, Applicability}; +use syntax::ast::{self, DUMMY_NODE_ID, Attribute, AttrStyle, Ident, CaptureBy, BlockCheckMode}; +use syntax::ast::{Expr, ExprKind, RangeLimits, Label, Movability, IsAsync, Arm, Ty, TyKind}; +use syntax::ast::{FunctionRetTy, Param, FnDecl, BinOpKind, BinOp, UnOp, Mac, AnonConst, Field, Lit}; use syntax::token::{self, Token, TokenKind}; use syntax::print::pprust; use syntax::ptr::P; -use syntax::source_map::{self, Span}; use syntax::util::classify; use syntax::util::literal::LitError; use syntax::util::parser::{AssocOp, Fixity, prec_let_scrutinee_needs_par}; -use syntax_pos::symbol::{kw, sym}; -use syntax_pos::Symbol; -use errors::{PResult, Applicability}; +use syntax_pos::source_map::{self, Span}; +use syntax_pos::symbol::{kw, sym, Symbol}; use std::mem; -use rustc_data_structures::thin_vec::ThinVec; /// Possibly accepts an `token::Interpolated` expression (a pre-parsed expression /// dropped into the token stream, which happens while parsing the result of diff --git a/src/librustc_parse/parser/generics.rs b/src/librustc_parse/parser/generics.rs index ba5eafc0ed7..32819cca42b 100644 --- a/src/librustc_parse/parser/generics.rs +++ b/src/librustc_parse/parser/generics.rs @@ -1,11 +1,11 @@ use super::Parser; +use rustc_errors::PResult; use syntax::ast::{self, WhereClause, GenericParam, GenericParamKind, GenericBounds, Attribute}; use syntax::token; use syntax::source_map::DUMMY_SP; use syntax_pos::symbol::{kw, sym}; -use errors::PResult; impl<'a> Parser<'a> { /// Parses bounds of a lifetime parameter `BOUND + BOUND + BOUND`, possibly with trailing `+`. diff --git a/src/librustc_parse/parser/item.rs b/src/librustc_parse/parser/item.rs index 66a13527421..ccf78e6402b 100644 --- a/src/librustc_parse/parser/item.rs +++ b/src/librustc_parse/parser/item.rs @@ -3,6 +3,8 @@ use crate::maybe_whole; +use rustc_errors::{PResult, Applicability, DiagnosticBuilder, StashKey}; +use rustc_error_codes::*; use syntax::ast::{self, DUMMY_NODE_ID, Ident, Attribute, AttrKind, AttrStyle, AnonConst, Item}; use syntax::ast::{ItemKind, ImplItem, ImplItemKind, TraitItem, TraitItemKind, UseTree, UseTreeKind}; use syntax::ast::{PathSegment, IsAuto, Constness, IsAsync, Unsafety, Defaultness, Extern, StrLit}; @@ -14,16 +16,13 @@ use syntax::ThinVec; use syntax::token; use syntax::tokenstream::{DelimSpan, TokenTree, TokenStream}; -use syntax::source_map::{self, respan, Span}; use syntax::struct_span_err; use syntax_pos::BytePos; +use syntax_pos::source_map::{self, respan, Span}; use syntax_pos::symbol::{kw, sym, Symbol}; -use rustc_error_codes::*; - use log::debug; use std::mem; -use errors::{PResult, Applicability, DiagnosticBuilder, StashKey}; pub(super) type ItemInfo = (Ident, ItemKind, Option>); diff --git a/src/librustc_parse/parser/mod.rs b/src/librustc_parse/parser/mod.rs index 28689720044..07e99cfe012 100644 --- a/src/librustc_parse/parser/mod.rs +++ b/src/librustc_parse/parser/mod.rs @@ -14,23 +14,20 @@ use crate::{Directory, DirectoryOwnership}; use crate::lexer::UnmatchedBrace; -use syntax::ast::{ - self, DUMMY_NODE_ID, AttrStyle, Attribute, CrateSugar, Extern, Ident, StrLit, - IsAsync, MacArgs, MacDelimiter, Mutability, Visibility, VisibilityKind, Unsafety, -}; - +use rustc_errors::{PResult, Applicability, DiagnosticBuilder, FatalError}; +use rustc_data_structures::thin_vec::ThinVec; +use syntax::ast::{self, DUMMY_NODE_ID, AttrStyle, Attribute, CrateSugar, Extern, Ident, StrLit}; +use syntax::ast::{IsAsync, MacArgs, MacDelimiter, Mutability, Visibility, VisibilityKind, Unsafety}; use syntax::print::pprust; use syntax::ptr::P; use syntax::token::{self, Token, TokenKind, DelimToken}; use syntax::tokenstream::{self, DelimSpan, TokenTree, TokenStream, TreeAndJoint}; use syntax::sess::ParseSess; -use syntax::source_map::respan; use syntax::struct_span_err; use syntax::util::comments::{doc_comment_style, strip_doc_comment_decoration}; +use syntax_pos::source_map::respan; use syntax_pos::symbol::{kw, sym, Symbol}; use syntax_pos::{Span, BytePos, DUMMY_SP, FileName}; -use rustc_data_structures::thin_vec::ThinVec; -use errors::{PResult, Applicability, DiagnosticBuilder, FatalError}; use log::debug; use std::borrow::Cow; diff --git a/src/librustc_parse/parser/module.rs b/src/librustc_parse/parser/module.rs index 59d7c2b4239..3777e17b5a1 100644 --- a/src/librustc_parse/parser/module.rs +++ b/src/librustc_parse/parser/module.rs @@ -4,13 +4,12 @@ use crate::{new_sub_parser_from_file, DirectoryOwnership}; +use rustc_errors::PResult; use syntax::attr; use syntax::ast::{self, Ident, Attribute, ItemKind, Mod, Crate}; use syntax::token::{self, TokenKind}; -use syntax::source_map::{SourceMap, Span, DUMMY_SP, FileName}; - +use syntax_pos::source_map::{SourceMap, Span, DUMMY_SP, FileName}; use syntax_pos::symbol::sym; -use errors::PResult; use std::path::{self, Path, PathBuf}; @@ -212,13 +211,13 @@ pub(super) fn default_submod_path( // `./.rs` and `.//mod.rs`. let relative_prefix_string; let relative_prefix = if let Some(ident) = relative { - relative_prefix_string = format!("{}{}", ident, path::MAIN_SEPARATOR); + relative_prefix_string = format!("{}{}", ident.name, path::MAIN_SEPARATOR); &relative_prefix_string } else { "" }; - let mod_name = id.to_string(); + let mod_name = id.name.to_string(); let default_path_str = format!("{}{}.rs", relative_prefix, mod_name); let secondary_path_str = format!("{}{}{}mod.rs", relative_prefix, mod_name, path::MAIN_SEPARATOR); diff --git a/src/librustc_parse/parser/pat.rs b/src/librustc_parse/parser/pat.rs index 1127c4b2d5f..42ece96adb9 100644 --- a/src/librustc_parse/parser/pat.rs +++ b/src/librustc_parse/parser/pat.rs @@ -1,5 +1,6 @@ use super::{Parser, PathStyle}; use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole}; +use rustc_errors::{PResult, Applicability, DiagnosticBuilder}; use syntax::ast::{self, Attribute, Pat, PatKind, FieldPat, RangeEnd, RangeSyntax, Mac}; use syntax::ast::{BindingMode, Ident, Mutability, Path, QSelf, Expr, ExprKind}; use syntax::mut_visit::{noop_visit_pat, noop_visit_mac, MutVisitor}; @@ -7,9 +8,8 @@ use syntax::print::pprust; use syntax::ThinVec; use syntax::token; -use syntax::source_map::{respan, Span, Spanned}; +use syntax_pos::source_map::{respan, Span, Spanned}; use syntax_pos::symbol::{kw, sym}; -use errors::{PResult, Applicability, DiagnosticBuilder}; type Expected = Option<&'static str>; diff --git a/src/librustc_parse/parser/path.rs b/src/librustc_parse/parser/path.rs index 75bb67d47bc..70c3458e7c0 100644 --- a/src/librustc_parse/parser/path.rs +++ b/src/librustc_parse/parser/path.rs @@ -1,16 +1,16 @@ use super::{Parser, TokenType}; use crate::maybe_whole; +use rustc_errors::{PResult, Applicability, pluralize}; use syntax::ast::{self, QSelf, Path, PathSegment, Ident, ParenthesizedArgs, AngleBracketedArgs}; use syntax::ast::{AnonConst, GenericArg, AssocTyConstraint, AssocTyConstraintKind, BlockCheckMode}; use syntax::ast::MacArgs; use syntax::ThinVec; use syntax::token::{self, Token}; -use syntax::source_map::{Span, BytePos}; +use syntax_pos::source_map::{Span, BytePos}; use syntax_pos::symbol::{kw, sym}; use std::mem; use log::debug; -use errors::{PResult, Applicability, pluralize}; /// Specifies how to parse a path. #[derive(Copy, Clone, PartialEq)] diff --git a/src/librustc_parse/parser/stmt.rs b/src/librustc_parse/parser/stmt.rs index b952e8814a3..943b6ecc825 100644 --- a/src/librustc_parse/parser/stmt.rs +++ b/src/librustc_parse/parser/stmt.rs @@ -6,6 +6,7 @@ use crate::maybe_whole; use crate::DirectoryOwnership; +use rustc_errors::{PResult, Applicability}; use syntax::ThinVec; use syntax::ptr::P; use syntax::ast; @@ -13,11 +14,10 @@ use syntax::ast::{Attribute, AttrStyle, VisibilityKind, MacStmtStyle, Mac}; use syntax::util::classify; use syntax::token; -use syntax::source_map::{respan, Span}; -use syntax::symbol::{kw, sym}; +use syntax_pos::source_map::{respan, Span}; +use syntax_pos::symbol::{kw, sym}; use std::mem; -use errors::{PResult, Applicability}; impl<'a> Parser<'a> { /// Parses a statement. This stops just before trailing semicolons on everything but items. diff --git a/src/librustc_parse/parser/ty.rs b/src/librustc_parse/parser/ty.rs index 32142796905..84ffef68e9a 100644 --- a/src/librustc_parse/parser/ty.rs +++ b/src/librustc_parse/parser/ty.rs @@ -3,19 +3,17 @@ use crate::{maybe_whole, maybe_recover_from_interpolated_ty_qpath}; +use rustc_errors::{PResult, Applicability, pluralize}; +use rustc_error_codes::*; use syntax::ptr::P; use syntax::ast::{self, Ty, TyKind, MutTy, BareFnTy, FunctionRetTy, GenericParam, Lifetime, Ident}; use syntax::ast::{TraitBoundModifier, TraitObjectSyntax, GenericBound, GenericBounds, PolyTraitRef}; use syntax::ast::{Mutability, AnonConst, Mac}; use syntax::token::{self, Token}; -use syntax::source_map::Span; use syntax::struct_span_fatal; +use syntax_pos::source_map::Span; use syntax_pos::symbol::kw; -use errors::{PResult, Applicability, pluralize}; - -use rustc_error_codes::*; - /// Returns `true` if `IDENT t` can start a type -- `IDENT::a::b`, `IDENT`, /// `IDENT<::AssocTy>`. /// diff --git a/src/librustc_parse/validate_attr.rs b/src/librustc_parse/validate_attr.rs index 8601add3f6f..97e9cb8dcdf 100644 --- a/src/librustc_parse/validate_attr.rs +++ b/src/librustc_parse/validate_attr.rs @@ -1,6 +1,6 @@ //! Meta-syntax validation logic of attributes for post-expansion. -use errors::{PResult, Applicability}; +use rustc_errors::{PResult, Applicability}; use rustc_feature::{AttributeTemplate, BUILTIN_ATTRIBUTE_MAP}; use syntax::ast::{self, Attribute, AttrKind, Ident, MacArgs, MetaItem, MetaItemKind}; use syntax::attr::mk_name_value_item_str; diff --git a/src/librustc_typeck/check/demand.rs b/src/librustc_typeck/check/demand.rs index 4331d441aa0..32df6c4636c 100644 --- a/src/librustc_typeck/check/demand.rs +++ b/src/librustc_typeck/check/demand.rs @@ -65,13 +65,13 @@ pub fn demand_eqtype_with_origin(&self, } } - pub fn demand_eqtype_pat( + pub fn demand_eqtype_pat_diag( &self, cause_span: Span, expected: Ty<'tcx>, actual: Ty<'tcx>, match_expr_span: Option, - ) { + ) -> Option> { let cause = if let Some(span) = match_expr_span { self.cause( cause_span, @@ -80,9 +80,19 @@ pub fn demand_eqtype_pat( } else { self.misc(cause_span) }; - self.demand_eqtype_with_origin(&cause, expected, actual).map(|mut err| err.emit()); + self.demand_eqtype_with_origin(&cause, expected, actual) } + pub fn demand_eqtype_pat( + &self, + cause_span: Span, + expected: Ty<'tcx>, + actual: Ty<'tcx>, + match_expr_span: Option, + ) { + self.demand_eqtype_pat_diag(cause_span, expected, actual, match_expr_span) + .map(|mut err| err.emit()); + } pub fn demand_coerce(&self, expr: &hir::Expr, diff --git a/src/librustc_typeck/check/expr.rs b/src/librustc_typeck/check/expr.rs index 4766360c048..5bfc60c7540 100644 --- a/src/librustc_typeck/check/expr.rs +++ b/src/librustc_typeck/check/expr.rs @@ -582,11 +582,21 @@ fn check_expr_break( // If this is a break with a value, we need to type-check // the expression. Get an expected type from the loop context. let opt_coerce_to = { + // We should release `enclosing_breakables` before the `check_expr_with_hint` + // below, so can't move this block of code to the enclosing scope and share + // `ctxt` with the second `encloding_breakables` borrow below. let mut enclosing_breakables = self.enclosing_breakables.borrow_mut(); - enclosing_breakables.find_breakable(target_id) - .coerce - .as_ref() - .map(|coerce| coerce.expected_ty()) + match enclosing_breakables.opt_find_breakable(target_id) { + Some(ctxt) => + ctxt.coerce.as_ref().map(|coerce| coerce.expected_ty()), + None => { // Avoid ICE when `break` is inside a closure (#65383). + self.tcx.sess.delay_span_bug( + expr.span, + "break was outside loop, but no error was emitted", + ); + return tcx.types.err; + } + } }; // If the loop context is not a `loop { }`, then break with diff --git a/src/librustc_typeck/check/pat.rs b/src/librustc_typeck/check/pat.rs index 9dd3bc624a5..71d1cd869a6 100644 --- a/src/librustc_typeck/check/pat.rs +++ b/src/librustc_typeck/check/pat.rs @@ -703,7 +703,10 @@ fn check_pat_tuple_struct( let pat_ty = pat_ty.fn_sig(tcx).output(); let pat_ty = pat_ty.no_bound_vars().expect("expected fn type"); - self.demand_eqtype_pat(pat.span, expected, pat_ty, match_arm_pat_span); + // Type-check the tuple struct pattern against the expected type. + let diag = self.demand_eqtype_pat_diag(pat.span, expected, pat_ty, match_arm_pat_span); + let had_err = diag.is_some(); + diag.map(|mut err| err.emit()); // Type-check subpatterns. if subpats.len() == variant.fields.len() @@ -721,7 +724,7 @@ fn check_pat_tuple_struct( } } else { // Pattern has wrong number of fields. - self.e0023(pat.span, res, qpath, subpats, &variant.fields, expected); + self.e0023(pat.span, res, qpath, subpats, &variant.fields, expected, had_err); on_error(); return tcx.types.err; } @@ -734,8 +737,9 @@ fn e0023( res: Res, qpath: &hir::QPath, subpats: &'tcx [P], - fields: &[ty::FieldDef], - expected: Ty<'tcx> + fields: &'tcx [ty::FieldDef], + expected: Ty<'tcx>, + had_err: bool, ) { let subpats_ending = pluralize!(subpats.len()); let fields_ending = pluralize!(fields.len()); @@ -763,9 +767,12 @@ fn e0023( // More generally, the expected type wants a tuple variant with one field of an // N-arity-tuple, e.g., `V_i((p_0, .., p_N))`. Meanwhile, the user supplied a pattern // with the subpatterns directly in the tuple variant pattern, e.g., `V_i(p_0, .., p_N)`. - let missing_parenthesis = match expected.kind { - ty::Adt(_, substs) if fields.len() == 1 => { - let field_ty = fields[0].ty(self.tcx, substs); + let missing_parenthesis = match (&expected.kind, fields, had_err) { + // #67037: only do this if we could sucessfully type-check the expected type against + // the tuple struct pattern. Otherwise the substs could get out of range on e.g., + // `let P() = U;` where `P != U` with `struct P(T);`. + (ty::Adt(_, substs), [field], false) => { + let field_ty = self.field_ty(pat_span, field, substs); match field_ty.kind { ty::Tuple(_) => field_ty.tuple_fields().count() == subpats.len(), _ => false, diff --git a/src/libsyntax_pos/symbol.rs b/src/libsyntax_pos/symbol.rs index 2af66711d3c..c68e03be88c 100644 --- a/src/libsyntax_pos/symbol.rs +++ b/src/libsyntax_pos/symbol.rs @@ -869,12 +869,18 @@ fn hash(&self, state: &mut H) { impl fmt::Debug for Ident { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_raw_guess() { + write!(f, "r#")?; + } write!(f, "{}{:?}", self.name, self.span.ctxt()) } } impl fmt::Display for Ident { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_raw_guess() { + write!(f, "r#")?; + } fmt::Display::fmt(&self.name, f) } } diff --git a/src/rustllvm/PassWrapper.cpp b/src/rustllvm/PassWrapper.cpp index 1cb123e674c..6698e5d58be 100644 --- a/src/rustllvm/PassWrapper.cpp +++ b/src/rustllvm/PassWrapper.cpp @@ -658,46 +658,11 @@ public: } }; -class RustPrintModulePass : public ModulePass { - raw_ostream* OS; - DemangleFn Demangle; -public: - static char ID; - RustPrintModulePass() : ModulePass(ID), OS(nullptr), Demangle(nullptr) {} - RustPrintModulePass(raw_ostream &OS, DemangleFn Demangle) - : ModulePass(ID), OS(&OS), Demangle(Demangle) {} - - bool runOnModule(Module &M) override { - RustAssemblyAnnotationWriter AW(Demangle); - - M.print(*OS, &AW, false); - - return false; - } - - void getAnalysisUsage(AnalysisUsage &AU) const override { - AU.setPreservesAll(); - } - - static StringRef name() { return "RustPrintModulePass"; } -}; - } // namespace -namespace llvm { - void initializeRustPrintModulePassPass(PassRegistry&); -} - -char RustPrintModulePass::ID = 0; -INITIALIZE_PASS(RustPrintModulePass, "print-rust-module", - "Print rust module to stderr", false, false) - extern "C" LLVMRustResult -LLVMRustPrintModule(LLVMPassManagerRef PMR, LLVMModuleRef M, - const char *Path, DemangleFn Demangle) { - llvm::legacy::PassManager *PM = unwrap(PMR); +LLVMRustPrintModule(LLVMModuleRef M, const char *Path, DemangleFn Demangle) { std::string ErrorInfo; - std::error_code EC; raw_fd_ostream OS(Path, EC, sys::fs::F_None); if (EC) @@ -707,11 +672,9 @@ LLVMRustPrintModule(LLVMPassManagerRef PMR, LLVMModuleRef M, return LLVMRustResult::Failure; } + RustAssemblyAnnotationWriter AAW(Demangle); formatted_raw_ostream FOS(OS); - - PM->add(new RustPrintModulePass(FOS, Demangle)); - - PM->run(*unwrap(M)); + unwrap(M)->print(FOS, &AAW); return LLVMRustResult::Success; } diff --git a/src/test/ui/associated-types/cache/project-fn-ret-invariant.transmute.stderr b/src/test/ui/associated-types/cache/project-fn-ret-invariant.transmute.stderr index 627609c4a9c..3e39c8a7924 100644 --- a/src/test/ui/associated-types/cache/project-fn-ret-invariant.transmute.stderr +++ b/src/test/ui/associated-types/cache/project-fn-ret-invariant.transmute.stderr @@ -9,13 +9,21 @@ note: first, the lifetime cannot outlive the lifetime `'a` as defined on the fun | LL | fn baz<'a,'b>(x: Type<'a>) -> Type<'static> { | ^^ - = note: ...so that the expression is assignable: - expected Type<'_> - found Type<'a> +note: ...so that the expression is assignable + --> $DIR/project-fn-ret-invariant.rs:48:13 + | +LL | bar(foo, x) + | ^ + = note: expected `Type<'_>` + found `Type<'a>` = note: but, the lifetime must be valid for the static lifetime... - = note: ...so that the expression is assignable: - expected Type<'static> - found Type<'_> +note: ...so that the expression is assignable + --> $DIR/project-fn-ret-invariant.rs:48:4 + | +LL | bar(foo, x) + | ^^^^^^^^^^^ + = note: expected `Type<'static>` + found `Type<'_>` error: aborting due to previous error diff --git a/src/test/ui/c-variadic/variadic-ffi-4.stderr b/src/test/ui/c-variadic/variadic-ffi-4.stderr index c80ed5ebf5c..cd4cd8b198d 100644 --- a/src/test/ui/c-variadic/variadic-ffi-4.stderr +++ b/src/test/ui/c-variadic/variadic-ffi-4.stderr @@ -49,9 +49,13 @@ note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on th | LL | let _ = ap.with_copy(|ap| { ap }); | ^^^^^^^^^^^ - = note: ...so that the expression is assignable: - expected core::ffi::VaList<'_, '_> - found core::ffi::VaList<'_, '_> +note: ...so that the expression is assignable + --> $DIR/variadic-ffi-4.rs:16:33 + | +LL | let _ = ap.with_copy(|ap| { ap }); + | ^^ + = note: expected `core::ffi::VaList<'_, '_>` + found `core::ffi::VaList<'_, '_>` note: but, the lifetime must be valid for the method call at 16:13... --> $DIR/variadic-ffi-4.rs:16:13 | diff --git a/src/test/ui/impl-header-lifetime-elision/dyn-trait.stderr b/src/test/ui/impl-header-lifetime-elision/dyn-trait.stderr index 5e80c673258..3300293bb36 100644 --- a/src/test/ui/impl-header-lifetime-elision/dyn-trait.stderr +++ b/src/test/ui/impl-header-lifetime-elision/dyn-trait.stderr @@ -9,13 +9,21 @@ note: first, the lifetime cannot outlive the lifetime `'a` as defined on the fun | LL | fn with_dyn_debug_static<'a>(x: Box) { | ^^ - = note: ...so that the expression is assignable: - expected std::boxed::Box - found std::boxed::Box<(dyn std::fmt::Debug + 'a)> +note: ...so that the expression is assignable + --> $DIR/dyn-trait.rs:20:16 + | +LL | static_val(x); + | ^ + = note: expected `std::boxed::Box` + found `std::boxed::Box<(dyn std::fmt::Debug + 'a)>` = note: but, the lifetime must be valid for the static lifetime... - = note: ...so that the types are compatible: - expected StaticTrait - found StaticTrait +note: ...so that the types are compatible + --> $DIR/dyn-trait.rs:20:5 + | +LL | static_val(x); + | ^^^^^^^^^^ + = note: expected `StaticTrait` + found `StaticTrait` error: aborting due to previous error diff --git a/src/test/ui/issues/issue-16683.stderr b/src/test/ui/issues/issue-16683.stderr index b663e213ed0..99700f2084e 100644 --- a/src/test/ui/issues/issue-16683.stderr +++ b/src/test/ui/issues/issue-16683.stderr @@ -21,9 +21,13 @@ note: but, the lifetime must be valid for the lifetime `'a` as defined on the tr | LL | trait T<'a> { | ^^ - = note: ...so that the types are compatible: - expected &'a Self - found &Self +note: ...so that the types are compatible + --> $DIR/issue-16683.rs:4:14 + | +LL | self.a(); + | ^ + = note: expected `&'a Self` + found `&Self` error: aborting due to previous error diff --git a/src/test/ui/issues/issue-17758.stderr b/src/test/ui/issues/issue-17758.stderr index adcbb62e3d5..adfc3f50858 100644 --- a/src/test/ui/issues/issue-17758.stderr +++ b/src/test/ui/issues/issue-17758.stderr @@ -22,9 +22,13 @@ note: but, the lifetime must be valid for the lifetime `'a` as defined on the tr | LL | trait Foo<'a> { | ^^ - = note: ...so that the types are compatible: - expected &'a Self - found &Self +note: ...so that the types are compatible + --> $DIR/issue-17758.rs:7:14 + | +LL | self.foo(); + | ^^^ + = note: expected `&'a Self` + found `&Self` error: aborting due to previous error diff --git a/src/test/ui/issues/issue-20831-debruijn.stderr b/src/test/ui/issues/issue-20831-debruijn.stderr index 13c9c09461e..c7fd134a129 100644 --- a/src/test/ui/issues/issue-20831-debruijn.stderr +++ b/src/test/ui/issues/issue-20831-debruijn.stderr @@ -88,9 +88,19 @@ note: ...but the lifetime must also be valid for the lifetime `'a` as defined on | LL | impl<'a> Publisher<'a> for MyStruct<'a> { | ^^ - = note: ...so that the types are compatible: - expected Publisher<'_> - found Publisher<'_> +note: ...so that the types are compatible + --> $DIR/issue-20831-debruijn.rs:28:5 + | +LL | / fn subscribe(&mut self, t : Box::Output> + 'a>) { +LL | | // Not obvious, but there is an implicit lifetime here -------^ +LL | | +LL | | +... | +LL | | self.sub = t; +LL | | } + | |_____^ + = note: expected `Publisher<'_>` + found `Publisher<'_>` error: aborting due to 3 previous errors diff --git a/src/test/ui/issues/issue-52213.stderr b/src/test/ui/issues/issue-52213.stderr index b79a5ddf3e1..a8960f77563 100644 --- a/src/test/ui/issues/issue-52213.stderr +++ b/src/test/ui/issues/issue-52213.stderr @@ -9,9 +9,13 @@ note: first, the lifetime cannot outlive the lifetime `'a` as defined on the fun | LL | fn transmute_lifetime<'a, 'b, T>(t: &'a (T,)) -> &'b T { | ^^ - = note: ...so that the types are compatible: - expected (&&(T,),) - found (&&'a (T,),) +note: ...so that the types are compatible + --> $DIR/issue-52213.rs:2:11 + | +LL | match (&t,) { + | ^^^^^ + = note: expected `(&&(T,),)` + found `(&&'a (T,),)` note: but, the lifetime must be valid for the lifetime `'b` as defined on the function body at 1:27... --> $DIR/issue-52213.rs:1:27 | diff --git a/src/test/ui/issues/issue-55796.stderr b/src/test/ui/issues/issue-55796.stderr index 7b910f5e3e5..b8cafdc5c14 100644 --- a/src/test/ui/issues/issue-55796.stderr +++ b/src/test/ui/issues/issue-55796.stderr @@ -15,9 +15,13 @@ note: ...so that the type `std::iter::Map<>::EdgesIter, [closu LL | Box::new(self.out_edges(u).map(|e| e.target())) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: but, the lifetime must be valid for the static lifetime... - = note: ...so that the expression is assignable: - expected std::boxed::Box<(dyn std::iter::Iterator>::Node> + 'static)> - found std::boxed::Box>::Node>> +note: ...so that the expression is assignable + --> $DIR/issue-55796.rs:16:9 + | +LL | Box::new(self.out_edges(u).map(|e| e.target())) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: expected `std::boxed::Box<(dyn std::iter::Iterator>::Node> + 'static)>` + found `std::boxed::Box>::Node>>` error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements --> $DIR/issue-55796.rs:21:9 @@ -36,9 +40,13 @@ note: ...so that the type `std::iter::Map<>::EdgesIter, [closu LL | Box::new(self.in_edges(u).map(|e| e.target())) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: but, the lifetime must be valid for the static lifetime... - = note: ...so that the expression is assignable: - expected std::boxed::Box<(dyn std::iter::Iterator>::Node> + 'static)> - found std::boxed::Box>::Node>> +note: ...so that the expression is assignable + --> $DIR/issue-55796.rs:21:9 + | +LL | Box::new(self.in_edges(u).map(|e| e.target())) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: expected `std::boxed::Box<(dyn std::iter::Iterator>::Node> + 'static)>` + found `std::boxed::Box>::Node>>` error: aborting due to 2 previous errors diff --git a/src/test/ui/issues/issue-65634-raw-ident-suggestion.rs b/src/test/ui/issues/issue-65634-raw-ident-suggestion.rs new file mode 100644 index 00000000000..b928510258b --- /dev/null +++ b/src/test/ui/issues/issue-65634-raw-ident-suggestion.rs @@ -0,0 +1,22 @@ +#![allow(non_camel_case_types)] + +trait r#async { + fn r#struct(&self) { + println!("async"); + } +} + +trait r#await { + fn r#struct(&self) { + println!("await"); + } +} + +struct r#fn {} + +impl r#async for r#fn {} +impl r#await for r#fn {} + +fn main() { + r#fn {}.r#struct(); //~ ERROR multiple applicable items in scope +} diff --git a/src/test/ui/issues/issue-65634-raw-ident-suggestion.stderr b/src/test/ui/issues/issue-65634-raw-ident-suggestion.stderr new file mode 100644 index 00000000000..c7bb653dc1f --- /dev/null +++ b/src/test/ui/issues/issue-65634-raw-ident-suggestion.stderr @@ -0,0 +1,22 @@ +error[E0034]: multiple applicable items in scope + --> $DIR/issue-65634-raw-ident-suggestion.rs:21:13 + | +LL | r#fn {}.r#struct(); + | ^^^^^^^^ multiple `r#struct` found + | +note: candidate #1 is defined in an impl of the trait `async` for the type `r#fn` + --> $DIR/issue-65634-raw-ident-suggestion.rs:4:5 + | +LL | fn r#struct(&self) { + | ^^^^^^^^^^^^^^^^^^ + = help: to disambiguate the method call, write `async::r#struct(r#fn {})` instead +note: candidate #2 is defined in an impl of the trait `await` for the type `r#fn` + --> $DIR/issue-65634-raw-ident-suggestion.rs:10:5 + | +LL | fn r#struct(&self) { + | ^^^^^^^^^^^^^^^^^^ + = help: to disambiguate the method call, write `await::r#struct(r#fn {})` instead + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0034`. diff --git a/src/test/ui/issues/issue-66702-break-outside-loop-val.rs b/src/test/ui/issues/issue-66702-break-outside-loop-val.rs new file mode 100644 index 00000000000..bd3c00d2621 --- /dev/null +++ b/src/test/ui/issues/issue-66702-break-outside-loop-val.rs @@ -0,0 +1,7 @@ +// Breaks with values inside closures used to ICE (#66863) + +fn main() { + 'some_label: loop { + || break 'some_label (); //~ ERROR: `break` inside of a closure + } +} diff --git a/src/test/ui/issues/issue-66702-break-outside-loop-val.stderr b/src/test/ui/issues/issue-66702-break-outside-loop-val.stderr new file mode 100644 index 00000000000..83bde9775b2 --- /dev/null +++ b/src/test/ui/issues/issue-66702-break-outside-loop-val.stderr @@ -0,0 +1,11 @@ +error[E0267]: `break` inside of a closure + --> $DIR/issue-66702-break-outside-loop-val.rs:5:12 + | +LL | || break 'some_label (); + | -- ^^^^^^^^^^^^^^^^^^^^ cannot `break` inside of a closure + | | + | enclosing closure + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0267`. diff --git a/src/test/ui/issues/issue-67037-pat-tup-scrut-ty-diff-less-fields.rs b/src/test/ui/issues/issue-67037-pat-tup-scrut-ty-diff-less-fields.rs new file mode 100644 index 00000000000..44bd645598a --- /dev/null +++ b/src/test/ui/issues/issue-67037-pat-tup-scrut-ty-diff-less-fields.rs @@ -0,0 +1,21 @@ +// Regression test for #67037. +// +// In type checking patterns, E0023 occurs when the tuple pattern and the expected +// tuple pattern have different number of fields. For example, as below, `P()`, +// the tuple struct pattern, has 0 fields, but requires 1 field. +// +// In emitting E0023, we try to see if this is a case of e.g., `Some(a, b, c)` but where +// the scrutinee was of type `Some((a, b, c))`, and suggest that parenthesis be added. +// +// However, we did not account for the expected type being different than the tuple pattern type. +// This caused an issue when the tuple pattern type (`P`) was generic. +// Specifically, we tried deriving the 0th field's type using the `substs` of the expected type. +// When attempting to substitute `T`, there was no such substitution, so "out of range" occured. + +struct U {} // 0 type parameters offered +struct P(T); // 1 type parameter wanted + +fn main() { + let P() = U {}; //~ ERROR mismatched types + //~^ ERROR this pattern has 0 fields, but the corresponding tuple struct has 1 field +} diff --git a/src/test/ui/issues/issue-67037-pat-tup-scrut-ty-diff-less-fields.stderr b/src/test/ui/issues/issue-67037-pat-tup-scrut-ty-diff-less-fields.stderr new file mode 100644 index 00000000000..521dd0256f7 --- /dev/null +++ b/src/test/ui/issues/issue-67037-pat-tup-scrut-ty-diff-less-fields.stderr @@ -0,0 +1,22 @@ +error[E0308]: mismatched types + --> $DIR/issue-67037-pat-tup-scrut-ty-diff-less-fields.rs:19:9 + | +LL | let P() = U {}; + | ^^^ expected struct `U`, found struct `P` + | + = note: expected struct `U` + found struct `P<_>` + +error[E0023]: this pattern has 0 fields, but the corresponding tuple struct has 1 field + --> $DIR/issue-67037-pat-tup-scrut-ty-diff-less-fields.rs:19:9 + | +LL | struct P(T); // 1 type parameter wanted + | --------------- tuple struct defined here +... +LL | let P() = U {}; + | ^^^ expected 1 field, found 0 + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0023, E0308. +For more information about an error, try `rustc --explain E0023`. diff --git a/src/test/ui/nll/issue-55394.stderr b/src/test/ui/nll/issue-55394.stderr index 714a63b670c..69a6ab004fd 100644 --- a/src/test/ui/nll/issue-55394.stderr +++ b/src/test/ui/nll/issue-55394.stderr @@ -21,9 +21,13 @@ note: but, the lifetime must be valid for the lifetime `'_` as defined on the im | LL | impl Foo<'_> { | ^^ - = note: ...so that the expression is assignable: - expected Foo<'_> - found Foo<'_> +note: ...so that the expression is assignable + --> $DIR/issue-55394.rs:9:9 + | +LL | Foo { bar } + | ^^^^^^^^^^^ + = note: expected `Foo<'_>` + found `Foo<'_>` error: aborting due to previous error diff --git a/src/test/ui/nll/normalization-bounds-error.stderr b/src/test/ui/nll/normalization-bounds-error.stderr index 3a152fbc6fc..58f206742f4 100644 --- a/src/test/ui/nll/normalization-bounds-error.stderr +++ b/src/test/ui/nll/normalization-bounds-error.stderr @@ -14,9 +14,13 @@ note: ...but the lifetime must also be valid for the lifetime `'a` as defined on | LL | fn visit_seq<'d, 'a: 'd>() -> <&'a () as Visitor<'d>>::Value {} | ^^ - = note: ...so that the types are compatible: - expected Visitor<'d> - found Visitor<'_> +note: ...so that the types are compatible + --> $DIR/normalization-bounds-error.rs:12:1 + | +LL | fn visit_seq<'d, 'a: 'd>() -> <&'a () as Visitor<'d>>::Value {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: expected `Visitor<'d>` + found `Visitor<'_>` error: aborting due to previous error diff --git a/src/test/ui/nll/type-alias-free-regions.stderr b/src/test/ui/nll/type-alias-free-regions.stderr index 6986389af88..5191deca281 100644 --- a/src/test/ui/nll/type-alias-free-regions.stderr +++ b/src/test/ui/nll/type-alias-free-regions.stderr @@ -11,17 +11,25 @@ LL | / fn from_box(b: Box) -> Self { LL | | C { f: b } LL | | } | |_____^ - = note: ...so that the expression is assignable: - expected std::boxed::Box> - found std::boxed::Box> +note: ...so that the expression is assignable + --> $DIR/type-alias-free-regions.rs:17:16 + | +LL | C { f: b } + | ^ + = note: expected `std::boxed::Box>` + found `std::boxed::Box>` note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 15:6... --> $DIR/type-alias-free-regions.rs:15:6 | LL | impl<'a> FromBox<'a> for C<'a> { | ^^ - = note: ...so that the expression is assignable: - expected C<'a> - found C<'_> +note: ...so that the expression is assignable + --> $DIR/type-alias-free-regions.rs:17:9 + | +LL | C { f: b } + | ^^^^^^^^^^ + = note: expected `C<'a>` + found `C<'_>` error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements --> $DIR/type-alias-free-regions.rs:27:16 @@ -36,17 +44,25 @@ LL | / fn from_tuple(b: (B,)) -> Self { LL | | C { f: Box::new(b.0) } LL | | } | |_____^ - = note: ...so that the expression is assignable: - expected std::boxed::Box<&isize> - found std::boxed::Box<&isize> +note: ...so that the expression is assignable + --> $DIR/type-alias-free-regions.rs:27:25 + | +LL | C { f: Box::new(b.0) } + | ^^^ + = note: expected `std::boxed::Box<&isize>` + found `std::boxed::Box<&isize>` note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 25:6... --> $DIR/type-alias-free-regions.rs:25:6 | LL | impl<'a> FromTuple<'a> for C<'a> { | ^^ - = note: ...so that the expression is assignable: - expected C<'a> - found C<'_> +note: ...so that the expression is assignable + --> $DIR/type-alias-free-regions.rs:27:9 + | +LL | C { f: Box::new(b.0) } + | ^^^^^^^^^^^^^^^^^^^^^^ + = note: expected `C<'a>` + found `C<'_>` error: aborting due to 2 previous errors diff --git a/src/test/ui/nll/user-annotations/constant-in-expr-inherent-1.stderr b/src/test/ui/nll/user-annotations/constant-in-expr-inherent-1.stderr index 4ebd9910788..37be450fd0a 100644 --- a/src/test/ui/nll/user-annotations/constant-in-expr-inherent-1.stderr +++ b/src/test/ui/nll/user-annotations/constant-in-expr-inherent-1.stderr @@ -9,9 +9,13 @@ note: first, the lifetime cannot outlive the lifetime `'a` as defined on the fun | LL | fn foo<'a>(_: &'a u32) -> &'static u32 { | ^^ - = note: ...so that the types are compatible: - expected Foo<'_> - found Foo<'a> +note: ...so that the types are compatible + --> $DIR/constant-in-expr-inherent-1.rs:8:5 + | +LL | >::C + | ^^^^^^^^^^^^ + = note: expected `Foo<'_>` + found `Foo<'a>` = note: but, the lifetime must be valid for the static lifetime... note: ...so that reference does not outlive borrowed content --> $DIR/constant-in-expr-inherent-1.rs:8:5 diff --git a/src/test/ui/nll/user-annotations/constant-in-expr-trait-item-3.stderr b/src/test/ui/nll/user-annotations/constant-in-expr-trait-item-3.stderr index d61659e7e9a..4ee32847c5e 100644 --- a/src/test/ui/nll/user-annotations/constant-in-expr-trait-item-3.stderr +++ b/src/test/ui/nll/user-annotations/constant-in-expr-trait-item-3.stderr @@ -9,9 +9,13 @@ note: first, the lifetime cannot outlive the lifetime `'a` as defined on the fun | LL | fn foo<'a, T: Foo<'a>>() -> &'static u32 { | ^^ - = note: ...so that the types are compatible: - expected Foo<'_> - found Foo<'a> +note: ...so that the types are compatible + --> $DIR/constant-in-expr-trait-item-3.rs:10:5 + | +LL | T::C + | ^^^^ + = note: expected `Foo<'_>` + found `Foo<'a>` = note: but, the lifetime must be valid for the static lifetime... note: ...so that reference does not outlive borrowed content --> $DIR/constant-in-expr-trait-item-3.rs:10:5 diff --git a/src/test/ui/object-lifetime/object-lifetime-default-elision.stderr b/src/test/ui/object-lifetime/object-lifetime-default-elision.stderr index d66322c48ec..1952ee8269d 100644 --- a/src/test/ui/object-lifetime/object-lifetime-default-elision.stderr +++ b/src/test/ui/object-lifetime/object-lifetime-default-elision.stderr @@ -19,9 +19,13 @@ note: but, the lifetime must be valid for the lifetime `'b` as defined on the fu | LL | fn load3<'a,'b>(ss: &'a dyn SomeTrait) -> &'b dyn SomeTrait { | ^^ - = note: ...so that the expression is assignable: - expected &'b (dyn SomeTrait + 'b) - found &dyn SomeTrait +note: ...so that the expression is assignable + --> $DIR/object-lifetime-default-elision.rs:71:5 + | +LL | ss + | ^^ + = note: expected `&'b (dyn SomeTrait + 'b)` + found `&dyn SomeTrait` error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements --> $DIR/object-lifetime-default-elision.rs:71:5 @@ -44,9 +48,13 @@ note: but, the lifetime must be valid for the lifetime `'b` as defined on the fu | LL | fn load3<'a,'b>(ss: &'a dyn SomeTrait) -> &'b dyn SomeTrait { | ^^ - = note: ...so that the expression is assignable: - expected &'b (dyn SomeTrait + 'b) - found &dyn SomeTrait +note: ...so that the expression is assignable + --> $DIR/object-lifetime-default-elision.rs:71:5 + | +LL | ss + | ^^ + = note: expected `&'b (dyn SomeTrait + 'b)` + found `&dyn SomeTrait` error: aborting due to 2 previous errors diff --git a/src/test/ui/parser/raw/raw-literal-keywords.rs b/src/test/ui/parser/raw/raw-literal-keywords.rs index bf9cbcdab2e..a986980fab9 100644 --- a/src/test/ui/parser/raw/raw-literal-keywords.rs +++ b/src/test/ui/parser/raw/raw-literal-keywords.rs @@ -11,11 +11,11 @@ fn test_union() { } fn test_if_2() { - let _ = r#if; //~ ERROR cannot find value `if` in this scope + let _ = r#if; //~ ERROR cannot find value `r#if` in this scope } fn test_struct_2() { - let _ = r#struct; //~ ERROR cannot find value `struct` in this scope + let _ = r#struct; //~ ERROR cannot find value `r#struct` in this scope } fn test_union_2() { diff --git a/src/test/ui/parser/raw/raw-literal-keywords.stderr b/src/test/ui/parser/raw/raw-literal-keywords.stderr index fd8eda3770d..f7b6c894a90 100644 --- a/src/test/ui/parser/raw/raw-literal-keywords.stderr +++ b/src/test/ui/parser/raw/raw-literal-keywords.stderr @@ -16,13 +16,13 @@ error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found LL | r#union Test; | ^^^^ expected one of 8 possible tokens -error[E0425]: cannot find value `if` in this scope +error[E0425]: cannot find value `r#if` in this scope --> $DIR/raw-literal-keywords.rs:14:13 | LL | let _ = r#if; | ^^^^ not found in this scope -error[E0425]: cannot find value `struct` in this scope +error[E0425]: cannot find value `r#struct` in this scope --> $DIR/raw-literal-keywords.rs:18:13 | LL | let _ = r#struct; diff --git a/src/test/ui/regions/region-object-lifetime-in-coercion.stderr b/src/test/ui/regions/region-object-lifetime-in-coercion.stderr index 14934d6fa48..e8896516470 100644 --- a/src/test/ui/regions/region-object-lifetime-in-coercion.stderr +++ b/src/test/ui/regions/region-object-lifetime-in-coercion.stderr @@ -34,17 +34,25 @@ note: first, the lifetime cannot outlive the lifetime `'a` as defined on the fun | LL | fn d<'a,'b>(v: &'a [u8]) -> Box { | ^^ - = note: ...so that the expression is assignable: - expected &[u8] - found &'a [u8] +note: ...so that the expression is assignable + --> $DIR/region-object-lifetime-in-coercion.rs:26:14 + | +LL | Box::new(v) + | ^ + = note: expected `&[u8]` + found `&'a [u8]` note: but, the lifetime must be valid for the lifetime `'b` as defined on the function body at 25:9... --> $DIR/region-object-lifetime-in-coercion.rs:25:9 | LL | fn d<'a,'b>(v: &'a [u8]) -> Box { | ^^ - = note: ...so that the expression is assignable: - expected std::boxed::Box<(dyn Foo + 'b)> - found std::boxed::Box +note: ...so that the expression is assignable + --> $DIR/region-object-lifetime-in-coercion.rs:26:5 + | +LL | Box::new(v) + | ^^^^^^^^^^^ + = note: expected `std::boxed::Box<(dyn Foo + 'b)>` + found `std::boxed::Box` error: aborting due to 4 previous errors diff --git a/src/test/ui/regions/regions-assoc-type-region-bound-in-trait-not-met.stderr b/src/test/ui/regions/regions-assoc-type-region-bound-in-trait-not-met.stderr index a636c9ef22c..865e967fba3 100644 --- a/src/test/ui/regions/regions-assoc-type-region-bound-in-trait-not-met.stderr +++ b/src/test/ui/regions/regions-assoc-type-region-bound-in-trait-not-met.stderr @@ -9,9 +9,13 @@ note: first, the lifetime cannot outlive the lifetime `'a` as defined on the imp | LL | impl<'a> Foo<'static> for &'a i32 { | ^^ - = note: ...so that the types are compatible: - expected Foo<'static> - found Foo<'static> +note: ...so that the types are compatible + --> $DIR/regions-assoc-type-region-bound-in-trait-not-met.rs:14:10 + | +LL | impl<'a> Foo<'static> for &'a i32 { + | ^^^^^^^^^^^^ + = note: expected `Foo<'static>` + found `Foo<'static>` = note: but, the lifetime must be valid for the static lifetime... note: ...so that the type `&i32` will meet its required lifetime bounds --> $DIR/regions-assoc-type-region-bound-in-trait-not-met.rs:14:10 @@ -30,9 +34,13 @@ note: first, the lifetime cannot outlive the lifetime `'a` as defined on the imp | LL | impl<'a,'b> Foo<'b> for &'a i64 { | ^^ - = note: ...so that the types are compatible: - expected Foo<'b> - found Foo<'_> +note: ...so that the types are compatible + --> $DIR/regions-assoc-type-region-bound-in-trait-not-met.rs:19:13 + | +LL | impl<'a,'b> Foo<'b> for &'a i64 { + | ^^^^^^^ + = note: expected `Foo<'b>` + found `Foo<'_>` note: but, the lifetime must be valid for the lifetime `'b` as defined on the impl at 19:9... --> $DIR/regions-assoc-type-region-bound-in-trait-not-met.rs:19:9 | diff --git a/src/test/ui/regions/regions-assoc-type-static-bound-in-trait-not-met.stderr b/src/test/ui/regions/regions-assoc-type-static-bound-in-trait-not-met.stderr index 81256e3b46c..6a34871c07e 100644 --- a/src/test/ui/regions/regions-assoc-type-static-bound-in-trait-not-met.stderr +++ b/src/test/ui/regions/regions-assoc-type-static-bound-in-trait-not-met.stderr @@ -9,9 +9,13 @@ note: first, the lifetime cannot outlive the lifetime `'a` as defined on the imp | LL | impl<'a> Foo for &'a i32 { | ^^ - = note: ...so that the types are compatible: - expected Foo - found Foo +note: ...so that the types are compatible + --> $DIR/regions-assoc-type-static-bound-in-trait-not-met.rs:9:10 + | +LL | impl<'a> Foo for &'a i32 { + | ^^^ + = note: expected `Foo` + found `Foo` = note: but, the lifetime must be valid for the static lifetime... note: ...so that the type `&i32` will meet its required lifetime bounds --> $DIR/regions-assoc-type-static-bound-in-trait-not-met.rs:9:10 diff --git a/src/test/ui/regions/regions-close-object-into-object-2.stderr b/src/test/ui/regions/regions-close-object-into-object-2.stderr index 8e473dad693..28873ab807f 100644 --- a/src/test/ui/regions/regions-close-object-into-object-2.stderr +++ b/src/test/ui/regions/regions-close-object-into-object-2.stderr @@ -15,9 +15,13 @@ note: ...so that the type `(dyn A + 'a)` is not borrowed for too long LL | box B(&*v) as Box | ^^^ = note: but, the lifetime must be valid for the static lifetime... - = note: ...so that the expression is assignable: - expected std::boxed::Box<(dyn X + 'static)> - found std::boxed::Box +note: ...so that the expression is assignable + --> $DIR/regions-close-object-into-object-2.rs:10:5 + | +LL | box B(&*v) as Box + | ^^^^^^^^^^^^^^^^^^^^^^^^ + = note: expected `std::boxed::Box<(dyn X + 'static)>` + found `std::boxed::Box` error: aborting due to previous error diff --git a/src/test/ui/regions/regions-close-object-into-object-4.stderr b/src/test/ui/regions/regions-close-object-into-object-4.stderr index c80d13e15b1..449a5b5fdd4 100644 --- a/src/test/ui/regions/regions-close-object-into-object-4.stderr +++ b/src/test/ui/regions/regions-close-object-into-object-4.stderr @@ -15,9 +15,13 @@ note: ...so that the type `(dyn A + 'a)` is not borrowed for too long LL | box B(&*v) as Box | ^^^ = note: but, the lifetime must be valid for the static lifetime... - = note: ...so that the expression is assignable: - expected std::boxed::Box<(dyn X + 'static)> - found std::boxed::Box +note: ...so that the expression is assignable + --> $DIR/regions-close-object-into-object-4.rs:10:5 + | +LL | box B(&*v) as Box + | ^^^^^^^^^^^^^^^^^^^^^^^^ + = note: expected `std::boxed::Box<(dyn X + 'static)>` + found `std::boxed::Box` error: aborting due to previous error diff --git a/src/test/ui/regions/regions-close-over-type-parameter-multiple.stderr b/src/test/ui/regions/regions-close-over-type-parameter-multiple.stderr index ef21316ea83..b2a7afaf1b4 100644 --- a/src/test/ui/regions/regions-close-over-type-parameter-multiple.stderr +++ b/src/test/ui/regions/regions-close-over-type-parameter-multiple.stderr @@ -19,9 +19,13 @@ note: but, the lifetime must be valid for the lifetime `'c` as defined on the fu | LL | fn make_object_bad<'a,'b,'c,A:SomeTrait+'a+'b>(v: A) -> Box { | ^^ - = note: ...so that the expression is assignable: - expected std::boxed::Box<(dyn SomeTrait + 'c)> - found std::boxed::Box +note: ...so that the expression is assignable + --> $DIR/regions-close-over-type-parameter-multiple.rs:20:5 + | +LL | box v as Box + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: expected `std::boxed::Box<(dyn SomeTrait + 'c)>` + found `std::boxed::Box` error: aborting due to previous error diff --git a/src/test/ui/regions/regions-creating-enums4.stderr b/src/test/ui/regions/regions-creating-enums4.stderr index 12b89787d5f..58f74e4ee14 100644 --- a/src/test/ui/regions/regions-creating-enums4.stderr +++ b/src/test/ui/regions/regions-creating-enums4.stderr @@ -9,17 +9,25 @@ note: first, the lifetime cannot outlive the lifetime `'a` as defined on the fun | LL | fn mk_add_bad2<'a,'b>(x: &'a Ast<'a>, y: &'a Ast<'a>, z: &Ast) -> Ast<'b> { | ^^ - = note: ...so that the expression is assignable: - expected &Ast<'_> - found &Ast<'a> +note: ...so that the expression is assignable + --> $DIR/regions-creating-enums4.rs:7:14 + | +LL | Ast::Add(x, y) + | ^ + = note: expected `&Ast<'_>` + found `&Ast<'a>` note: but, the lifetime must be valid for the lifetime `'b` as defined on the function body at 6:19... --> $DIR/regions-creating-enums4.rs:6:19 | LL | fn mk_add_bad2<'a,'b>(x: &'a Ast<'a>, y: &'a Ast<'a>, z: &Ast) -> Ast<'b> { | ^^ - = note: ...so that the expression is assignable: - expected Ast<'b> - found Ast<'_> +note: ...so that the expression is assignable + --> $DIR/regions-creating-enums4.rs:7:5 + | +LL | Ast::Add(x, y) + | ^^^^^^^^^^^^^^ + = note: expected `Ast<'b>` + found `Ast<'_>` error: aborting due to previous error diff --git a/src/test/ui/regions/regions-escape-method.stderr b/src/test/ui/regions/regions-escape-method.stderr index b93dd0d4c57..ffc2a259485 100644 --- a/src/test/ui/regions/regions-escape-method.stderr +++ b/src/test/ui/regions/regions-escape-method.stderr @@ -9,9 +9,13 @@ note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on th | LL | s.f(|p| p) | ^^^^^ - = note: ...so that the expression is assignable: - expected &i32 - found &i32 +note: ...so that the expression is assignable + --> $DIR/regions-escape-method.rs:15:13 + | +LL | s.f(|p| p) + | ^ + = note: expected `&i32` + found `&i32` note: but, the lifetime must be valid for the method call at 15:5... --> $DIR/regions-escape-method.rs:15:5 | diff --git a/src/test/ui/regions/regions-escape-via-trait-or-not.stderr b/src/test/ui/regions/regions-escape-via-trait-or-not.stderr index a6b165e2d44..90823464c56 100644 --- a/src/test/ui/regions/regions-escape-via-trait-or-not.stderr +++ b/src/test/ui/regions/regions-escape-via-trait-or-not.stderr @@ -9,9 +9,13 @@ note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on th | LL | with(|o| o) | ^^^^^ - = note: ...so that the expression is assignable: - expected &isize - found &isize +note: ...so that the expression is assignable + --> $DIR/regions-escape-via-trait-or-not.rs:18:14 + | +LL | with(|o| o) + | ^ + = note: expected `&isize` + found `&isize` note: but, the lifetime must be valid for the expression at 18:5... --> $DIR/regions-escape-via-trait-or-not.rs:18:5 | diff --git a/src/test/ui/regions/regions-nested-fns.stderr b/src/test/ui/regions/regions-nested-fns.stderr index f4eb5c8644f..8fce1609d78 100644 --- a/src/test/ui/regions/regions-nested-fns.stderr +++ b/src/test/ui/regions/regions-nested-fns.stderr @@ -29,9 +29,18 @@ LL | | if false { return ay; } LL | | return z; LL | | })); | |_____^ - = note: ...so that the types are compatible: - expected &isize - found &isize +note: ...so that the types are compatible + --> $DIR/regions-nested-fns.rs:13:76 + | +LL | ignore::< Box FnMut(&'z isize) -> &'z isize>>(Box::new(|z| { + | ____________________________________________________________________________^ +LL | | if false { return x; } +LL | | if false { return ay; } +LL | | return z; +LL | | })); + | |_____^ + = note: expected `&isize` + found `&isize` error[E0312]: lifetime of reference outlives lifetime of borrowed content... --> $DIR/regions-nested-fns.rs:14:27 diff --git a/src/test/ui/regions/regions-normalize-in-where-clause-list.stderr b/src/test/ui/regions/regions-normalize-in-where-clause-list.stderr index d29fd80943f..8a600d2a1e6 100644 --- a/src/test/ui/regions/regions-normalize-in-where-clause-list.stderr +++ b/src/test/ui/regions/regions-normalize-in-where-clause-list.stderr @@ -17,9 +17,16 @@ note: ...but the lifetime must also be valid for the lifetime `'b` as defined on | LL | fn bar<'a, 'b>() | ^^ - = note: ...so that the types are compatible: - expected Project<'a, 'b> - found Project<'_, '_> +note: ...so that the types are compatible + --> $DIR/regions-normalize-in-where-clause-list.rs:22:1 + | +LL | / fn bar<'a, 'b>() +LL | | where <() as Project<'a, 'b>>::Item : Eq +LL | | { +LL | | } + | |_^ + = note: expected `Project<'a, 'b>` + found `Project<'_, '_>` error: aborting due to previous error diff --git a/src/test/ui/regions/regions-ret-borrowed-1.stderr b/src/test/ui/regions/regions-ret-borrowed-1.stderr index 49076673ad3..2895a0ccdee 100644 --- a/src/test/ui/regions/regions-ret-borrowed-1.stderr +++ b/src/test/ui/regions/regions-ret-borrowed-1.stderr @@ -9,9 +9,13 @@ note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on th | LL | with(|o| o) | ^^^^^ - = note: ...so that the expression is assignable: - expected &isize - found &isize +note: ...so that the expression is assignable + --> $DIR/regions-ret-borrowed-1.rs:10:14 + | +LL | with(|o| o) + | ^ + = note: expected `&isize` + found `&isize` note: but, the lifetime must be valid for the lifetime `'a` as defined on the function body at 9:14... --> $DIR/regions-ret-borrowed-1.rs:9:14 | diff --git a/src/test/ui/regions/regions-ret-borrowed.stderr b/src/test/ui/regions/regions-ret-borrowed.stderr index eb1ade27ace..b74f10f5075 100644 --- a/src/test/ui/regions/regions-ret-borrowed.stderr +++ b/src/test/ui/regions/regions-ret-borrowed.stderr @@ -9,9 +9,13 @@ note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on th | LL | with(|o| o) | ^^^^^ - = note: ...so that the expression is assignable: - expected &isize - found &isize +note: ...so that the expression is assignable + --> $DIR/regions-ret-borrowed.rs:13:14 + | +LL | with(|o| o) + | ^ + = note: expected `&isize` + found `&isize` note: but, the lifetime must be valid for the lifetime `'a` as defined on the function body at 12:14... --> $DIR/regions-ret-borrowed.rs:12:14 | diff --git a/src/test/ui/regions/regions-trait-object-subtyping.stderr b/src/test/ui/regions/regions-trait-object-subtyping.stderr index ef69f2535dc..58b79d21270 100644 --- a/src/test/ui/regions/regions-trait-object-subtyping.stderr +++ b/src/test/ui/regions/regions-trait-object-subtyping.stderr @@ -36,9 +36,13 @@ note: but, the lifetime must be valid for the lifetime `'b` as defined on the fu | LL | fn foo3<'a,'b>(x: &'a mut dyn Dummy) -> &'b mut dyn Dummy { | ^^ - = note: ...so that the expression is assignable: - expected &'b mut (dyn Dummy + 'b) - found &mut (dyn Dummy + 'b) +note: ...so that the expression is assignable + --> $DIR/regions-trait-object-subtyping.rs:15:5 + | +LL | x + | ^ + = note: expected `&'b mut (dyn Dummy + 'b)` + found `&mut (dyn Dummy + 'b)` error[E0308]: mismatched types --> $DIR/regions-trait-object-subtyping.rs:22:5 diff --git a/src/test/ui/reject-specialized-drops-8142.stderr b/src/test/ui/reject-specialized-drops-8142.stderr index e55f0232ff9..527babb0120 100644 --- a/src/test/ui/reject-specialized-drops-8142.stderr +++ b/src/test/ui/reject-specialized-drops-8142.stderr @@ -105,9 +105,13 @@ note: ...but the lifetime must also be valid for the lifetime `'l2` as defined o | LL | struct W<'l1, 'l2> { x: &'l1 i8, y: &'l2 u8 } | ^^^ - = note: ...so that the types are compatible: - expected W<'l1, 'l2> - found W<'_, '_> +note: ...so that the types are compatible + --> $DIR/reject-specialized-drops-8142.rs:54:1 + | +LL | impl<'lw> Drop for W<'lw,'lw> { fn drop(&mut self) { } } // REJECT + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: expected `W<'l1, 'l2>` + found `W<'_, '_>` error: aborting due to 8 previous errors diff --git a/src/test/ui/suggestions/raw-name-use-suggestion.rs b/src/test/ui/suggestions/raw-name-use-suggestion.rs index 6c01383d961..0a8073c0be2 100644 --- a/src/test/ui/suggestions/raw-name-use-suggestion.rs +++ b/src/test/ui/suggestions/raw-name-use-suggestion.rs @@ -5,5 +5,5 @@ pub fn break() {} //~ ERROR expected identifier, found keyword `break` fn main() { foo::let(); //~ ERROR expected identifier, found keyword `let` - r#break(); //~ ERROR cannot find function `break` in this scope + r#break(); //~ ERROR cannot find function `r#break` in this scope } diff --git a/src/test/ui/suggestions/raw-name-use-suggestion.stderr b/src/test/ui/suggestions/raw-name-use-suggestion.stderr index 58eb87c00a4..62b76318e09 100644 --- a/src/test/ui/suggestions/raw-name-use-suggestion.stderr +++ b/src/test/ui/suggestions/raw-name-use-suggestion.stderr @@ -20,7 +20,7 @@ help: you can escape reserved keywords to use them as identifiers LL | foo::r#let(); | ^^^^^ -error[E0425]: cannot find function `break` in this scope +error[E0425]: cannot find function `r#break` in this scope --> $DIR/raw-name-use-suggestion.rs:8:5 | LL | r#break(); diff --git a/src/test/ui/traits/trait-impl-of-supertrait-has-wrong-lifetime-parameters.stderr b/src/test/ui/traits/trait-impl-of-supertrait-has-wrong-lifetime-parameters.stderr index 88c9c473eb0..9fdcd4de495 100644 --- a/src/test/ui/traits/trait-impl-of-supertrait-has-wrong-lifetime-parameters.stderr +++ b/src/test/ui/traits/trait-impl-of-supertrait-has-wrong-lifetime-parameters.stderr @@ -14,9 +14,13 @@ note: ...but the lifetime must also be valid for the lifetime `'b` as defined on | LL | impl<'a,'b> T2<'a, 'b> for S<'a, 'b> { | ^^ - = note: ...so that the types are compatible: - expected T1<'a> - found T1<'_> +note: ...so that the types are compatible + --> $DIR/trait-impl-of-supertrait-has-wrong-lifetime-parameters.rs:24:13 + | +LL | impl<'a,'b> T2<'a, 'b> for S<'a, 'b> { + | ^^^^^^^^^^ + = note: expected `T1<'a>` + found `T1<'_>` error: aborting due to previous error diff --git a/src/test/ui/underscore-lifetime/dyn-trait-underscore.stderr b/src/test/ui/underscore-lifetime/dyn-trait-underscore.stderr index d0475bf08c3..e6029e0d462 100644 --- a/src/test/ui/underscore-lifetime/dyn-trait-underscore.stderr +++ b/src/test/ui/underscore-lifetime/dyn-trait-underscore.stderr @@ -18,9 +18,13 @@ note: ...so that reference does not outlive borrowed content LL | Box::new(items.iter()) | ^^^^^ = note: but, the lifetime must be valid for the static lifetime... - = note: ...so that the expression is assignable: - expected std::boxed::Box<(dyn std::iter::Iterator + 'static)> - found std::boxed::Box> +note: ...so that the expression is assignable + --> $DIR/dyn-trait-underscore.rs:8:5 + | +LL | Box::new(items.iter()) + | ^^^^^^^^^^^^^^^^^^^^^^ + = note: expected `std::boxed::Box<(dyn std::iter::Iterator + 'static)>` + found `std::boxed::Box>` error: aborting due to previous error