summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_borrowck
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /compiler/rustc_borrowck
parentInitial commit. (diff)
downloadrustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz
rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compiler/rustc_borrowck')
-rw-r--r--compiler/rustc_borrowck/Cargo.toml31
-rw-r--r--compiler/rustc_borrowck/src/borrow_set.rs345
-rw-r--r--compiler/rustc_borrowck/src/borrowck_errors.rs486
-rw-r--r--compiler/rustc_borrowck/src/constraint_generation.rs250
-rw-r--r--compiler/rustc_borrowck/src/constraints/graph.rs235
-rw-r--r--compiler/rustc_borrowck/src/constraints/mod.rs124
-rw-r--r--compiler/rustc_borrowck/src/consumers.rs39
-rw-r--r--compiler/rustc_borrowck/src/dataflow.rs438
-rw-r--r--compiler/rustc_borrowck/src/def_use.rs80
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs494
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs2773
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs744
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/find_all_local_uses.rs26
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/find_use.rs128
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/mod.rs1127
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/move_errors.rs529
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs1115
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs261
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/region_errors.rs904
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/region_name.rs896
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/var_name.rs133
-rw-r--r--compiler/rustc_borrowck/src/facts.rs212
-rw-r--r--compiler/rustc_borrowck/src/invalidation.rs442
-rw-r--r--compiler/rustc_borrowck/src/lib.rs2380
-rw-r--r--compiler/rustc_borrowck/src/location.rs107
-rw-r--r--compiler/rustc_borrowck/src/member_constraints.rs230
-rw-r--r--compiler/rustc_borrowck/src/nll.rs462
-rw-r--r--compiler/rustc_borrowck/src/path_utils.rs171
-rw-r--r--compiler/rustc_borrowck/src/place_ext.rs81
-rw-r--r--compiler/rustc_borrowck/src/places_conflict.rs537
-rw-r--r--compiler/rustc_borrowck/src/prefixes.rs145
-rw-r--r--compiler/rustc_borrowck/src/region_infer/dump_mir.rs93
-rw-r--r--compiler/rustc_borrowck/src/region_infer/graphviz.rs140
-rw-r--r--compiler/rustc_borrowck/src/region_infer/mod.rs2365
-rw-r--r--compiler/rustc_borrowck/src/region_infer/opaque_types.rs662
-rw-r--r--compiler/rustc_borrowck/src/region_infer/reverse_sccs.rs68
-rw-r--r--compiler/rustc_borrowck/src/region_infer/values.rs488
-rw-r--r--compiler/rustc_borrowck/src/renumber.rs83
-rw-r--r--compiler/rustc_borrowck/src/session_diagnostics.rs44
-rw-r--r--compiler/rustc_borrowck/src/type_check/canonical.rs171
-rw-r--r--compiler/rustc_borrowck/src/type_check/constraint_conversion.rs204
-rw-r--r--compiler/rustc_borrowck/src/type_check/free_region_relations.rs374
-rw-r--r--compiler/rustc_borrowck/src/type_check/input_output.rs245
-rw-r--r--compiler/rustc_borrowck/src/type_check/liveness/local_use_map.rs170
-rw-r--r--compiler/rustc_borrowck/src/type_check/liveness/mod.rs139
-rw-r--r--compiler/rustc_borrowck/src/type_check/liveness/polonius.rs140
-rw-r--r--compiler/rustc_borrowck/src/type_check/liveness/trace.rs578
-rw-r--r--compiler/rustc_borrowck/src/type_check/mod.rs2721
-rw-r--r--compiler/rustc_borrowck/src/type_check/relate_tys.rs187
-rw-r--r--compiler/rustc_borrowck/src/universal_regions.rs841
-rw-r--r--compiler/rustc_borrowck/src/used_muts.rs110
51 files changed, 25748 insertions, 0 deletions
diff --git a/compiler/rustc_borrowck/Cargo.toml b/compiler/rustc_borrowck/Cargo.toml
new file mode 100644
index 000000000..fbf628e86
--- /dev/null
+++ b/compiler/rustc_borrowck/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "rustc_borrowck"
+version = "0.0.0"
+edition = "2021"
+
+[lib]
+doctest = false
+
+[dependencies]
+either = "1.5.0"
+itertools = "0.10.1"
+tracing = "0.1"
+polonius-engine = "0.13.0"
+smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }
+rustc_data_structures = { path = "../rustc_data_structures" }
+rustc_errors = { path = "../rustc_errors" }
+rustc_graphviz = { path = "../rustc_graphviz" }
+rustc_hir = { path = "../rustc_hir" }
+rustc_index = { path = "../rustc_index" }
+rustc_infer = { path = "../rustc_infer" }
+rustc_lexer = { path = "../rustc_lexer" }
+rustc_macros = { path = "../rustc_macros" }
+rustc_middle = { path = "../rustc_middle" }
+rustc_const_eval = { path = "../rustc_const_eval" }
+rustc_mir_dataflow = { path = "../rustc_mir_dataflow" }
+rustc_serialize = { path = "../rustc_serialize" }
+rustc_session = { path = "../rustc_session" }
+rustc_target = { path = "../rustc_target" }
+rustc_trait_selection = { path = "../rustc_trait_selection" }
+rustc_traits = { path = "../rustc_traits" }
+rustc_span = { path = "../rustc_span" }
diff --git a/compiler/rustc_borrowck/src/borrow_set.rs b/compiler/rustc_borrowck/src/borrow_set.rs
new file mode 100644
index 000000000..41279588e
--- /dev/null
+++ b/compiler/rustc_borrowck/src/borrow_set.rs
@@ -0,0 +1,345 @@
+use crate::nll::ToRegionVid;
+use crate::path_utils::allow_two_phase_borrow;
+use crate::place_ext::PlaceExt;
+use crate::BorrowIndex;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
+use rustc_index::bit_set::BitSet;
+use rustc_middle::mir::traversal;
+use rustc_middle::mir::visit::{MutatingUseContext, NonUseContext, PlaceContext, Visitor};
+use rustc_middle::mir::{self, Body, Local, Location};
+use rustc_middle::ty::{RegionVid, TyCtxt};
+use rustc_mir_dataflow::move_paths::MoveData;
+use std::fmt;
+use std::ops::Index;
+
+pub struct BorrowSet<'tcx> {
+ /// The fundamental map relating bitvector indexes to the borrows
+ /// in the MIR. Each borrow is also uniquely identified in the MIR
+ /// by the `Location` of the assignment statement in which it
+ /// appears on the right hand side. Thus the location is the map
+ /// key, and its position in the map corresponds to `BorrowIndex`.
+ pub location_map: FxIndexMap<Location, BorrowData<'tcx>>,
+
+ /// Locations which activate borrows.
+ /// NOTE: a given location may activate more than one borrow in the future
+ /// when more general two-phase borrow support is introduced, but for now we
+ /// only need to store one borrow index.
+ pub activation_map: FxHashMap<Location, Vec<BorrowIndex>>,
+
+ /// Map from local to all the borrows on that local.
+ pub local_map: FxHashMap<mir::Local, FxHashSet<BorrowIndex>>,
+
+ pub(crate) locals_state_at_exit: LocalsStateAtExit,
+}
+
+impl<'tcx> Index<BorrowIndex> for BorrowSet<'tcx> {
+ type Output = BorrowData<'tcx>;
+
+ fn index(&self, index: BorrowIndex) -> &BorrowData<'tcx> {
+ &self.location_map[index.as_usize()]
+ }
+}
+
+/// Location where a two-phase borrow is activated, if a borrow
+/// is in fact a two-phase borrow.
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum TwoPhaseActivation {
+ NotTwoPhase,
+ NotActivated,
+ ActivatedAt(Location),
+}
+
+#[derive(Debug, Clone)]
+pub struct BorrowData<'tcx> {
+ /// Location where the borrow reservation starts.
+ /// In many cases, this will be equal to the activation location but not always.
+ pub reserve_location: Location,
+ /// Location where the borrow is activated.
+ pub activation_location: TwoPhaseActivation,
+ /// What kind of borrow this is
+ pub kind: mir::BorrowKind,
+ /// The region for which this borrow is live
+ pub region: RegionVid,
+ /// Place from which we are borrowing
+ pub borrowed_place: mir::Place<'tcx>,
+ /// Place to which the borrow was stored
+ pub assigned_place: mir::Place<'tcx>,
+}
+
+impl<'tcx> fmt::Display for BorrowData<'tcx> {
+ fn fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let kind = match self.kind {
+ mir::BorrowKind::Shared => "",
+ mir::BorrowKind::Shallow => "shallow ",
+ mir::BorrowKind::Unique => "uniq ",
+ mir::BorrowKind::Mut { .. } => "mut ",
+ };
+ write!(w, "&{:?} {}{:?}", self.region, kind, self.borrowed_place)
+ }
+}
+
+pub enum LocalsStateAtExit {
+ AllAreInvalidated,
+ SomeAreInvalidated { has_storage_dead_or_moved: BitSet<Local> },
+}
+
+impl LocalsStateAtExit {
+ fn build<'tcx>(
+ locals_are_invalidated_at_exit: bool,
+ body: &Body<'tcx>,
+ move_data: &MoveData<'tcx>,
+ ) -> Self {
+ struct HasStorageDead(BitSet<Local>);
+
+ impl<'tcx> Visitor<'tcx> for HasStorageDead {
+ fn visit_local(&mut self, local: Local, ctx: PlaceContext, _: Location) {
+ if ctx == PlaceContext::NonUse(NonUseContext::StorageDead) {
+ self.0.insert(local);
+ }
+ }
+ }
+
+ if locals_are_invalidated_at_exit {
+ LocalsStateAtExit::AllAreInvalidated
+ } else {
+ let mut has_storage_dead = HasStorageDead(BitSet::new_empty(body.local_decls.len()));
+ has_storage_dead.visit_body(&body);
+ let mut has_storage_dead_or_moved = has_storage_dead.0;
+ for move_out in &move_data.moves {
+ if let Some(index) = move_data.base_local(move_out.path) {
+ has_storage_dead_or_moved.insert(index);
+ }
+ }
+ LocalsStateAtExit::SomeAreInvalidated { has_storage_dead_or_moved }
+ }
+ }
+}
+
+impl<'tcx> BorrowSet<'tcx> {
+ pub fn build(
+ tcx: TyCtxt<'tcx>,
+ body: &Body<'tcx>,
+ locals_are_invalidated_at_exit: bool,
+ move_data: &MoveData<'tcx>,
+ ) -> Self {
+ let mut visitor = GatherBorrows {
+ tcx,
+ body: &body,
+ location_map: Default::default(),
+ activation_map: Default::default(),
+ local_map: Default::default(),
+ pending_activations: Default::default(),
+ locals_state_at_exit: LocalsStateAtExit::build(
+ locals_are_invalidated_at_exit,
+ body,
+ move_data,
+ ),
+ };
+
+ for (block, block_data) in traversal::preorder(&body) {
+ visitor.visit_basic_block_data(block, block_data);
+ }
+
+ BorrowSet {
+ location_map: visitor.location_map,
+ activation_map: visitor.activation_map,
+ local_map: visitor.local_map,
+ locals_state_at_exit: visitor.locals_state_at_exit,
+ }
+ }
+
+ pub(crate) fn activations_at_location(&self, location: Location) -> &[BorrowIndex] {
+ self.activation_map.get(&location).map_or(&[], |activations| &activations[..])
+ }
+
+ pub(crate) fn len(&self) -> usize {
+ self.location_map.len()
+ }
+
+ pub(crate) fn indices(&self) -> impl Iterator<Item = BorrowIndex> {
+ BorrowIndex::from_usize(0)..BorrowIndex::from_usize(self.len())
+ }
+
+ pub(crate) fn iter_enumerated(&self) -> impl Iterator<Item = (BorrowIndex, &BorrowData<'tcx>)> {
+ self.indices().zip(self.location_map.values())
+ }
+
+ pub(crate) fn get_index_of(&self, location: &Location) -> Option<BorrowIndex> {
+ self.location_map.get_index_of(location).map(BorrowIndex::from)
+ }
+}
+
+struct GatherBorrows<'a, 'tcx> {
+ tcx: TyCtxt<'tcx>,
+ body: &'a Body<'tcx>,
+ location_map: FxIndexMap<Location, BorrowData<'tcx>>,
+ activation_map: FxHashMap<Location, Vec<BorrowIndex>>,
+ local_map: FxHashMap<mir::Local, FxHashSet<BorrowIndex>>,
+
+ /// When we encounter a 2-phase borrow statement, it will always
+ /// be assigning into a temporary TEMP:
+ ///
+ /// TEMP = &foo
+ ///
+ /// We add TEMP into this map with `b`, where `b` is the index of
+ /// the borrow. When we find a later use of this activation, we
+ /// remove from the map (and add to the "tombstone" set below).
+ pending_activations: FxHashMap<mir::Local, BorrowIndex>,
+
+ locals_state_at_exit: LocalsStateAtExit,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for GatherBorrows<'a, 'tcx> {
+ fn visit_assign(
+ &mut self,
+ assigned_place: &mir::Place<'tcx>,
+ rvalue: &mir::Rvalue<'tcx>,
+ location: mir::Location,
+ ) {
+ if let mir::Rvalue::Ref(region, kind, ref borrowed_place) = *rvalue {
+ if borrowed_place.ignore_borrow(self.tcx, self.body, &self.locals_state_at_exit) {
+ debug!("ignoring_borrow of {:?}", borrowed_place);
+ return;
+ }
+
+ let region = region.to_region_vid();
+
+ let borrow = BorrowData {
+ kind,
+ region,
+ reserve_location: location,
+ activation_location: TwoPhaseActivation::NotTwoPhase,
+ borrowed_place: *borrowed_place,
+ assigned_place: *assigned_place,
+ };
+ let (idx, _) = self.location_map.insert_full(location, borrow);
+ let idx = BorrowIndex::from(idx);
+
+ self.insert_as_pending_if_two_phase(location, assigned_place, kind, idx);
+
+ self.local_map.entry(borrowed_place.local).or_default().insert(idx);
+ }
+
+ self.super_assign(assigned_place, rvalue, location)
+ }
+
+ fn visit_local(&mut self, temp: Local, context: PlaceContext, location: Location) {
+ if !context.is_use() {
+ return;
+ }
+
+ // We found a use of some temporary TMP
+ // check whether we (earlier) saw a 2-phase borrow like
+ //
+ // TMP = &mut place
+ if let Some(&borrow_index) = self.pending_activations.get(&temp) {
+ let borrow_data = &mut self.location_map[borrow_index.as_usize()];
+
+ // Watch out: the use of TMP in the borrow itself
+ // doesn't count as an activation. =)
+ if borrow_data.reserve_location == location
+ && context == PlaceContext::MutatingUse(MutatingUseContext::Store)
+ {
+ return;
+ }
+
+ if let TwoPhaseActivation::ActivatedAt(other_location) = borrow_data.activation_location
+ {
+ span_bug!(
+ self.body.source_info(location).span,
+ "found two uses for 2-phase borrow temporary {:?}: \
+ {:?} and {:?}",
+ temp,
+ location,
+ other_location,
+ );
+ }
+
+ // Otherwise, this is the unique later use that we expect.
+ // Double check: This borrow is indeed a two-phase borrow (that is,
+ // we are 'transitioning' from `NotActivated` to `ActivatedAt`) and
+ // we've not found any other activations (checked above).
+ assert_eq!(
+ borrow_data.activation_location,
+ TwoPhaseActivation::NotActivated,
+ "never found an activation for this borrow!",
+ );
+ self.activation_map.entry(location).or_default().push(borrow_index);
+
+ borrow_data.activation_location = TwoPhaseActivation::ActivatedAt(location);
+ }
+ }
+
+ fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: mir::Location) {
+ if let mir::Rvalue::Ref(region, kind, ref place) = *rvalue {
+ // double-check that we already registered a BorrowData for this
+
+ let borrow_data = &self.location_map[&location];
+ assert_eq!(borrow_data.reserve_location, location);
+ assert_eq!(borrow_data.kind, kind);
+ assert_eq!(borrow_data.region, region.to_region_vid());
+ assert_eq!(borrow_data.borrowed_place, *place);
+ }
+
+ self.super_rvalue(rvalue, location)
+ }
+}
+
+impl<'a, 'tcx> GatherBorrows<'a, 'tcx> {
+ /// If this is a two-phase borrow, then we will record it
+ /// as "pending" until we find the activating use.
+ fn insert_as_pending_if_two_phase(
+ &mut self,
+ start_location: Location,
+ assigned_place: &mir::Place<'tcx>,
+ kind: mir::BorrowKind,
+ borrow_index: BorrowIndex,
+ ) {
+ debug!(
+ "Borrows::insert_as_pending_if_two_phase({:?}, {:?}, {:?})",
+ start_location, assigned_place, borrow_index,
+ );
+
+ if !allow_two_phase_borrow(kind) {
+ debug!(" -> {:?}", start_location);
+ return;
+ }
+
+ // When we encounter a 2-phase borrow statement, it will always
+ // be assigning into a temporary TEMP:
+ //
+ // TEMP = &foo
+ //
+ // so extract `temp`.
+ let Some(temp) = assigned_place.as_local() else {
+ span_bug!(
+ self.body.source_info(start_location).span,
+ "expected 2-phase borrow to assign to a local, not `{:?}`",
+ assigned_place,
+ );
+ };
+
+ // Consider the borrow not activated to start. When we find an activation, we'll update
+ // this field.
+ {
+ let borrow_data = &mut self.location_map[borrow_index.as_usize()];
+ borrow_data.activation_location = TwoPhaseActivation::NotActivated;
+ }
+
+ // Insert `temp` into the list of pending activations. From
+ // now on, we'll be on the lookout for a use of it. Note that
+ // we are guaranteed that this use will come after the
+ // assignment.
+ let old_value = self.pending_activations.insert(temp, borrow_index);
+ if let Some(old_index) = old_value {
+ span_bug!(
+ self.body.source_info(start_location).span,
+ "found already pending activation for temp: {:?} \
+ at borrow_index: {:?} with associated data {:?}",
+ temp,
+ old_index,
+ self.location_map[old_index.as_usize()]
+ );
+ }
+ }
+}
diff --git a/compiler/rustc_borrowck/src/borrowck_errors.rs b/compiler/rustc_borrowck/src/borrowck_errors.rs
new file mode 100644
index 000000000..08ea00d71
--- /dev/null
+++ b/compiler/rustc_borrowck/src/borrowck_errors.rs
@@ -0,0 +1,486 @@
+use rustc_errors::{
+ struct_span_err, DiagnosticBuilder, DiagnosticId, DiagnosticMessage, ErrorGuaranteed, MultiSpan,
+};
+use rustc_middle::ty::{self, Ty, TyCtxt};
+use rustc_span::Span;
+
+impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> {
+ pub(crate) fn cannot_move_when_borrowed(
+ &self,
+ span: Span,
+ desc: &str,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ struct_span_err!(self, span, E0505, "cannot move out of {} because it is borrowed", desc,)
+ }
+
+ pub(crate) fn cannot_use_when_mutably_borrowed(
+ &self,
+ span: Span,
+ desc: &str,
+ borrow_span: Span,
+ borrow_desc: &str,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ let mut err = struct_span_err!(
+ self,
+ span,
+ E0503,
+ "cannot use {} because it was mutably borrowed",
+ desc,
+ );
+
+ err.span_label(borrow_span, format!("borrow of {} occurs here", borrow_desc));
+ err.span_label(span, format!("use of borrowed {}", borrow_desc));
+ err
+ }
+
+ pub(crate) fn cannot_mutably_borrow_multiply(
+ &self,
+ new_loan_span: Span,
+ desc: &str,
+ opt_via: &str,
+ old_loan_span: Span,
+ old_opt_via: &str,
+ old_load_end_span: Option<Span>,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ let via =
+ |msg: &str| if msg.is_empty() { "".to_string() } else { format!(" (via {})", msg) };
+ let mut err = struct_span_err!(
+ self,
+ new_loan_span,
+ E0499,
+ "cannot borrow {}{} as mutable more than once at a time",
+ desc,
+ via(opt_via),
+ );
+ if old_loan_span == new_loan_span {
+ // Both borrows are happening in the same place
+ // Meaning the borrow is occurring in a loop
+ err.span_label(
+ new_loan_span,
+ format!(
+ "{}{} was mutably borrowed here in the previous iteration of the loop{}",
+ desc,
+ via(opt_via),
+ opt_via,
+ ),
+ );
+ if let Some(old_load_end_span) = old_load_end_span {
+ err.span_label(old_load_end_span, "mutable borrow ends here");
+ }
+ } else {
+ err.span_label(
+ old_loan_span,
+ format!("first mutable borrow occurs here{}", via(old_opt_via)),
+ );
+ err.span_label(
+ new_loan_span,
+ format!("second mutable borrow occurs here{}", via(opt_via)),
+ );
+ if let Some(old_load_end_span) = old_load_end_span {
+ err.span_label(old_load_end_span, "first borrow ends here");
+ }
+ }
+ err
+ }
+
+ pub(crate) fn cannot_uniquely_borrow_by_two_closures(
+ &self,
+ new_loan_span: Span,
+ desc: &str,
+ old_loan_span: Span,
+ old_load_end_span: Option<Span>,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ let mut err = struct_span_err!(
+ self,
+ new_loan_span,
+ E0524,
+ "two closures require unique access to {} at the same time",
+ desc,
+ );
+ if old_loan_span == new_loan_span {
+ err.span_label(
+ old_loan_span,
+ "closures are constructed here in different iterations of loop",
+ );
+ } else {
+ err.span_label(old_loan_span, "first closure is constructed here");
+ err.span_label(new_loan_span, "second closure is constructed here");
+ }
+ if let Some(old_load_end_span) = old_load_end_span {
+ err.span_label(old_load_end_span, "borrow from first closure ends here");
+ }
+ err
+ }
+
+ pub(crate) fn cannot_uniquely_borrow_by_one_closure(
+ &self,
+ new_loan_span: Span,
+ container_name: &str,
+ desc_new: &str,
+ opt_via: &str,
+ old_loan_span: Span,
+ noun_old: &str,
+ old_opt_via: &str,
+ previous_end_span: Option<Span>,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ let mut err = struct_span_err!(
+ self,
+ new_loan_span,
+ E0500,
+ "closure requires unique access to {} but {} is already borrowed{}",
+ desc_new,
+ noun_old,
+ old_opt_via,
+ );
+ err.span_label(
+ new_loan_span,
+ format!("{} construction occurs here{}", container_name, opt_via),
+ );
+ err.span_label(old_loan_span, format!("borrow occurs here{}", old_opt_via));
+ if let Some(previous_end_span) = previous_end_span {
+ err.span_label(previous_end_span, "borrow ends here");
+ }
+ err
+ }
+
+ pub(crate) fn cannot_reborrow_already_uniquely_borrowed(
+ &self,
+ new_loan_span: Span,
+ container_name: &str,
+ desc_new: &str,
+ opt_via: &str,
+ kind_new: &str,
+ old_loan_span: Span,
+ old_opt_via: &str,
+ previous_end_span: Option<Span>,
+ second_borrow_desc: &str,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ let mut err = struct_span_err!(
+ self,
+ new_loan_span,
+ E0501,
+ "cannot borrow {}{} as {} because previous closure requires unique access",
+ desc_new,
+ opt_via,
+ kind_new,
+ );
+ err.span_label(
+ new_loan_span,
+ format!("{}borrow occurs here{}", second_borrow_desc, opt_via),
+ );
+ err.span_label(
+ old_loan_span,
+ format!("{} construction occurs here{}", container_name, old_opt_via),
+ );
+ if let Some(previous_end_span) = previous_end_span {
+ err.span_label(previous_end_span, "borrow from closure ends here");
+ }
+ err
+ }
+
+ pub(crate) fn cannot_reborrow_already_borrowed(
+ &self,
+ span: Span,
+ desc_new: &str,
+ msg_new: &str,
+ kind_new: &str,
+ old_span: Span,
+ noun_old: &str,
+ kind_old: &str,
+ msg_old: &str,
+ old_load_end_span: Option<Span>,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ let via =
+ |msg: &str| if msg.is_empty() { "".to_string() } else { format!(" (via {})", msg) };
+ let mut err = struct_span_err!(
+ self,
+ span,
+ E0502,
+ "cannot borrow {}{} as {} because {} is also borrowed as {}{}",
+ desc_new,
+ via(msg_new),
+ kind_new,
+ noun_old,
+ kind_old,
+ via(msg_old),
+ );
+
+ if msg_new == "" {
+ // If `msg_new` is empty, then this isn't a borrow of a union field.
+ err.span_label(span, format!("{} borrow occurs here", kind_new));
+ err.span_label(old_span, format!("{} borrow occurs here", kind_old));
+ } else {
+ // If `msg_new` isn't empty, then this a borrow of a union field.
+ err.span_label(
+ span,
+ format!(
+ "{} borrow of {} -- which overlaps with {} -- occurs here",
+ kind_new, msg_new, msg_old,
+ ),
+ );
+ err.span_label(old_span, format!("{} borrow occurs here{}", kind_old, via(msg_old)));
+ }
+
+ if let Some(old_load_end_span) = old_load_end_span {
+ err.span_label(old_load_end_span, format!("{} borrow ends here", kind_old));
+ }
+ err
+ }
+
+ pub(crate) fn cannot_assign_to_borrowed(
+ &self,
+ span: Span,
+ borrow_span: Span,
+ desc: &str,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ let mut err = struct_span_err!(
+ self,
+ span,
+ E0506,
+ "cannot assign to {} because it is borrowed",
+ desc,
+ );
+
+ err.span_label(borrow_span, format!("borrow of {} occurs here", desc));
+ err.span_label(span, format!("assignment to borrowed {} occurs here", desc));
+ err
+ }
+
+ pub(crate) fn cannot_reassign_immutable(
+ &self,
+ span: Span,
+ desc: &str,
+ is_arg: bool,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ let msg = if is_arg { "to immutable argument" } else { "twice to immutable variable" };
+ struct_span_err!(self, span, E0384, "cannot assign {} {}", msg, desc)
+ }
+
+ pub(crate) fn cannot_assign(
+ &self,
+ span: Span,
+ desc: &str,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ struct_span_err!(self, span, E0594, "cannot assign to {}", desc)
+ }
+
+ pub(crate) fn cannot_move_out_of(
+ &self,
+ move_from_span: Span,
+ move_from_desc: &str,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ struct_span_err!(self, move_from_span, E0507, "cannot move out of {}", move_from_desc,)
+ }
+
+ /// Signal an error due to an attempt to move out of the interior
+ /// of an array or slice. `is_index` is None when error origin
+ /// didn't capture whether there was an indexing operation or not.
+ pub(crate) fn cannot_move_out_of_interior_noncopy(
+ &self,
+ move_from_span: Span,
+ ty: Ty<'_>,
+ is_index: Option<bool>,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ let type_name = match (&ty.kind(), is_index) {
+ (&ty::Array(_, _), Some(true)) | (&ty::Array(_, _), None) => "array",
+ (&ty::Slice(_), _) => "slice",
+ _ => span_bug!(move_from_span, "this path should not cause illegal move"),
+ };
+ let mut err = struct_span_err!(
+ self,
+ move_from_span,
+ E0508,
+ "cannot move out of type `{}`, a non-copy {}",
+ ty,
+ type_name,
+ );
+ err.span_label(move_from_span, "cannot move out of here");
+ err
+ }
+
+ pub(crate) fn cannot_move_out_of_interior_of_drop(
+ &self,
+ move_from_span: Span,
+ container_ty: Ty<'_>,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ let mut err = struct_span_err!(
+ self,
+ move_from_span,
+ E0509,
+ "cannot move out of type `{}`, which implements the `Drop` trait",
+ container_ty,
+ );
+ err.span_label(move_from_span, "cannot move out of here");
+ err
+ }
+
+ pub(crate) fn cannot_act_on_moved_value(
+ &self,
+ use_span: Span,
+ verb: &str,
+ optional_adverb_for_moved: &str,
+ moved_path: Option<String>,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ let moved_path = moved_path.map(|mp| format!(": `{}`", mp)).unwrap_or_default();
+
+ struct_span_err!(
+ self,
+ use_span,
+ E0382,
+ "{} of {}moved value{}",
+ verb,
+ optional_adverb_for_moved,
+ moved_path,
+ )
+ }
+
+ pub(crate) fn cannot_borrow_path_as_mutable_because(
+ &self,
+ span: Span,
+ path: &str,
+ reason: &str,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ struct_span_err!(self, span, E0596, "cannot borrow {} as mutable{}", path, reason,)
+ }
+
+ pub(crate) fn cannot_mutate_in_immutable_section(
+ &self,
+ mutate_span: Span,
+ immutable_span: Span,
+ immutable_place: &str,
+ immutable_section: &str,
+ action: &str,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ let mut err = struct_span_err!(
+ self,
+ mutate_span,
+ E0510,
+ "cannot {} {} in {}",
+ action,
+ immutable_place,
+ immutable_section,
+ );
+ err.span_label(mutate_span, format!("cannot {}", action));
+ err.span_label(immutable_span, format!("value is immutable in {}", immutable_section));
+ err
+ }
+
+ pub(crate) fn cannot_borrow_across_generator_yield(
+ &self,
+ span: Span,
+ yield_span: Span,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ let mut err = struct_span_err!(
+ self,
+ span,
+ E0626,
+ "borrow may still be in use when generator yields",
+ );
+ err.span_label(yield_span, "possible yield occurs here");
+ err
+ }
+
+ pub(crate) fn cannot_borrow_across_destructor(
+ &self,
+ borrow_span: Span,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ struct_span_err!(
+ self,
+ borrow_span,
+ E0713,
+ "borrow may still be in use when destructor runs",
+ )
+ }
+
+ pub(crate) fn path_does_not_live_long_enough(
+ &self,
+ span: Span,
+ path: &str,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ struct_span_err!(self, span, E0597, "{} does not live long enough", path,)
+ }
+
+ pub(crate) fn cannot_return_reference_to_local(
+ &self,
+ span: Span,
+ return_kind: &str,
+ reference_desc: &str,
+ path_desc: &str,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ let mut err = struct_span_err!(
+ self,
+ span,
+ E0515,
+ "cannot {RETURN} {REFERENCE} {LOCAL}",
+ RETURN = return_kind,
+ REFERENCE = reference_desc,
+ LOCAL = path_desc,
+ );
+
+ err.span_label(
+ span,
+ format!("{}s a {} data owned by the current function", return_kind, reference_desc),
+ );
+
+ err
+ }
+
+ pub(crate) fn cannot_capture_in_long_lived_closure(
+ &self,
+ closure_span: Span,
+ closure_kind: &str,
+ borrowed_path: &str,
+ capture_span: Span,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ let mut err = struct_span_err!(
+ self,
+ closure_span,
+ E0373,
+ "{} may outlive the current function, but it borrows {}, which is owned by the current \
+ function",
+ closure_kind,
+ borrowed_path,
+ );
+ err.span_label(capture_span, format!("{} is borrowed here", borrowed_path))
+ .span_label(closure_span, format!("may outlive borrowed value {}", borrowed_path));
+ err
+ }
+
+ pub(crate) fn thread_local_value_does_not_live_long_enough(
+ &self,
+ span: Span,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ struct_span_err!(self, span, E0712, "thread-local variable borrowed past end of function",)
+ }
+
+ pub(crate) fn temporary_value_borrowed_for_too_long(
+ &self,
+ span: Span,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ struct_span_err!(self, span, E0716, "temporary value dropped while borrowed",)
+ }
+
+ #[rustc_lint_diagnostics]
+ pub(crate) fn struct_span_err_with_code<S: Into<MultiSpan>>(
+ &self,
+ sp: S,
+ msg: impl Into<DiagnosticMessage>,
+ code: DiagnosticId,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ self.infcx.tcx.sess.struct_span_err_with_code(sp, msg, code)
+ }
+}
+
+pub(crate) fn borrowed_data_escapes_closure<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ escape_span: Span,
+ escapes_from: &str,
+) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ struct_span_err!(
+ tcx.sess,
+ escape_span,
+ E0521,
+ "borrowed data escapes outside of {}",
+ escapes_from,
+ )
+}
diff --git a/compiler/rustc_borrowck/src/constraint_generation.rs b/compiler/rustc_borrowck/src/constraint_generation.rs
new file mode 100644
index 000000000..5e9cec5c3
--- /dev/null
+++ b/compiler/rustc_borrowck/src/constraint_generation.rs
@@ -0,0 +1,250 @@
+use rustc_infer::infer::InferCtxt;
+use rustc_middle::mir::visit::TyContext;
+use rustc_middle::mir::visit::Visitor;
+use rustc_middle::mir::{
+ BasicBlock, BasicBlockData, Body, Local, Location, Place, PlaceRef, ProjectionElem, Rvalue,
+ SourceInfo, Statement, StatementKind, Terminator, TerminatorKind, UserTypeProjection,
+};
+use rustc_middle::ty::subst::SubstsRef;
+use rustc_middle::ty::visit::TypeVisitable;
+use rustc_middle::ty::{self, RegionVid, Ty};
+
+use crate::{
+ borrow_set::BorrowSet, facts::AllFacts, location::LocationTable, nll::ToRegionVid,
+ places_conflict, region_infer::values::LivenessValues,
+};
+
+pub(super) fn generate_constraints<'cx, 'tcx>(
+ infcx: &InferCtxt<'cx, 'tcx>,
+ liveness_constraints: &mut LivenessValues<RegionVid>,
+ all_facts: &mut Option<AllFacts>,
+ location_table: &LocationTable,
+ body: &Body<'tcx>,
+ borrow_set: &BorrowSet<'tcx>,
+) {
+ let mut cg = ConstraintGeneration {
+ borrow_set,
+ infcx,
+ liveness_constraints,
+ location_table,
+ all_facts,
+ body,
+ };
+
+ for (bb, data) in body.basic_blocks().iter_enumerated() {
+ cg.visit_basic_block_data(bb, data);
+ }
+}
+
+/// 'cg = the duration of the constraint generation process itself.
+struct ConstraintGeneration<'cg, 'cx, 'tcx> {
+ infcx: &'cg InferCtxt<'cx, 'tcx>,
+ all_facts: &'cg mut Option<AllFacts>,
+ location_table: &'cg LocationTable,
+ liveness_constraints: &'cg mut LivenessValues<RegionVid>,
+ borrow_set: &'cg BorrowSet<'tcx>,
+ body: &'cg Body<'tcx>,
+}
+
+impl<'cg, 'cx, 'tcx> Visitor<'tcx> for ConstraintGeneration<'cg, 'cx, 'tcx> {
+ fn visit_basic_block_data(&mut self, bb: BasicBlock, data: &BasicBlockData<'tcx>) {
+ self.super_basic_block_data(bb, data);
+ }
+
+ /// We sometimes have `substs` within an rvalue, or within a
+ /// call. Make them live at the location where they appear.
+ fn visit_substs(&mut self, substs: &SubstsRef<'tcx>, location: Location) {
+ self.add_regular_live_constraint(*substs, location);
+ self.super_substs(substs);
+ }
+
+ /// We sometimes have `region` within an rvalue, or within a
+ /// call. Make them live at the location where they appear.
+ fn visit_region(&mut self, region: ty::Region<'tcx>, location: Location) {
+ self.add_regular_live_constraint(region, location);
+ self.super_region(region);
+ }
+
+ /// We sometimes have `ty` within an rvalue, or within a
+ /// call. Make them live at the location where they appear.
+ fn visit_ty(&mut self, ty: Ty<'tcx>, ty_context: TyContext) {
+ match ty_context {
+ TyContext::ReturnTy(SourceInfo { span, .. })
+ | TyContext::YieldTy(SourceInfo { span, .. })
+ | TyContext::UserTy(span)
+ | TyContext::LocalDecl { source_info: SourceInfo { span, .. }, .. } => {
+ span_bug!(span, "should not be visiting outside of the CFG: {:?}", ty_context);
+ }
+ TyContext::Location(location) => {
+ self.add_regular_live_constraint(ty, location);
+ }
+ }
+
+ self.super_ty(ty);
+ }
+
+ fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
+ if let Some(all_facts) = self.all_facts {
+ let _prof_timer = self.infcx.tcx.prof.generic_activity("polonius_fact_generation");
+ all_facts.cfg_edge.push((
+ self.location_table.start_index(location),
+ self.location_table.mid_index(location),
+ ));
+
+ all_facts.cfg_edge.push((
+ self.location_table.mid_index(location),
+ self.location_table.start_index(location.successor_within_block()),
+ ));
+
+ // If there are borrows on this now dead local, we need to record them as `killed`.
+ if let StatementKind::StorageDead(local) = statement.kind {
+ record_killed_borrows_for_local(
+ all_facts,
+ self.borrow_set,
+ self.location_table,
+ local,
+ location,
+ );
+ }
+ }
+
+ self.super_statement(statement, location);
+ }
+
+ fn visit_assign(&mut self, place: &Place<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) {
+ // When we see `X = ...`, then kill borrows of
+ // `(*X).foo` and so forth.
+ self.record_killed_borrows_for_place(*place, location);
+
+ self.super_assign(place, rvalue, location);
+ }
+
+ fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
+ if let Some(all_facts) = self.all_facts {
+ let _prof_timer = self.infcx.tcx.prof.generic_activity("polonius_fact_generation");
+ all_facts.cfg_edge.push((
+ self.location_table.start_index(location),
+ self.location_table.mid_index(location),
+ ));
+
+ let successor_blocks = terminator.successors();
+ all_facts.cfg_edge.reserve(successor_blocks.size_hint().0);
+ for successor_block in successor_blocks {
+ all_facts.cfg_edge.push((
+ self.location_table.mid_index(location),
+ self.location_table.start_index(successor_block.start_location()),
+ ));
+ }
+ }
+
+ // A `Call` terminator's return value can be a local which has borrows,
+ // so we need to record those as `killed` as well.
+ if let TerminatorKind::Call { destination, .. } = terminator.kind {
+ self.record_killed_borrows_for_place(destination, location);
+ }
+
+ self.super_terminator(terminator, location);
+ }
+
+ fn visit_ascribe_user_ty(
+ &mut self,
+ _place: &Place<'tcx>,
+ _variance: ty::Variance,
+ _user_ty: &UserTypeProjection,
+ _location: Location,
+ ) {
+ }
+}
+
+impl<'cx, 'cg, 'tcx> ConstraintGeneration<'cx, 'cg, 'tcx> {
+ /// Some variable with type `live_ty` is "regular live" at
+ /// `location` -- i.e., it may be used later. This means that all
+ /// regions appearing in the type `live_ty` must be live at
+ /// `location`.
+ fn add_regular_live_constraint<T>(&mut self, live_ty: T, location: Location)
+ where
+ T: TypeVisitable<'tcx>,
+ {
+ debug!("add_regular_live_constraint(live_ty={:?}, location={:?})", live_ty, location);
+
+ self.infcx.tcx.for_each_free_region(&live_ty, |live_region| {
+ let vid = live_region.to_region_vid();
+ self.liveness_constraints.add_element(vid, location);
+ });
+ }
+
+ /// When recording facts for Polonius, records the borrows on the specified place
+ /// as `killed`. For example, when assigning to a local, or on a call's return destination.
+ fn record_killed_borrows_for_place(&mut self, place: Place<'tcx>, location: Location) {
+ if let Some(all_facts) = self.all_facts {
+ let _prof_timer = self.infcx.tcx.prof.generic_activity("polonius_fact_generation");
+
+ // Depending on the `Place` we're killing:
+ // - if it's a local, or a single deref of a local,
+ // we kill all the borrows on the local.
+ // - if it's a deeper projection, we have to filter which
+ // of the borrows are killed: the ones whose `borrowed_place`
+ // conflicts with the `place`.
+ match place.as_ref() {
+ PlaceRef { local, projection: &[] }
+ | PlaceRef { local, projection: &[ProjectionElem::Deref] } => {
+ debug!(
+ "Recording `killed` facts for borrows of local={:?} at location={:?}",
+ local, location
+ );
+
+ record_killed_borrows_for_local(
+ all_facts,
+ self.borrow_set,
+ self.location_table,
+ local,
+ location,
+ );
+ }
+
+ PlaceRef { local, projection: &[.., _] } => {
+ // Kill conflicting borrows of the innermost local.
+ debug!(
+ "Recording `killed` facts for borrows of \
+ innermost projected local={:?} at location={:?}",
+ local, location
+ );
+
+ if let Some(borrow_indices) = self.borrow_set.local_map.get(&local) {
+ for &borrow_index in borrow_indices {
+ let places_conflict = places_conflict::places_conflict(
+ self.infcx.tcx,
+ self.body,
+ self.borrow_set[borrow_index].borrowed_place,
+ place,
+ places_conflict::PlaceConflictBias::NoOverlap,
+ );
+
+ if places_conflict {
+ let location_index = self.location_table.mid_index(location);
+ all_facts.loan_killed_at.push((borrow_index, location_index));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+/// When recording facts for Polonius, records the borrows on the specified local as `killed`.
+fn record_killed_borrows_for_local(
+ all_facts: &mut AllFacts,
+ borrow_set: &BorrowSet<'_>,
+ location_table: &LocationTable,
+ local: Local,
+ location: Location,
+) {
+ if let Some(borrow_indices) = borrow_set.local_map.get(&local) {
+ all_facts.loan_killed_at.reserve(borrow_indices.len());
+ for &borrow_index in borrow_indices {
+ let location_index = location_table.mid_index(location);
+ all_facts.loan_killed_at.push((borrow_index, location_index));
+ }
+ }
+}
diff --git a/compiler/rustc_borrowck/src/constraints/graph.rs b/compiler/rustc_borrowck/src/constraints/graph.rs
new file mode 100644
index 000000000..609fbc2bc
--- /dev/null
+++ b/compiler/rustc_borrowck/src/constraints/graph.rs
@@ -0,0 +1,235 @@
+use rustc_data_structures::graph;
+use rustc_index::vec::IndexVec;
+use rustc_middle::mir::ConstraintCategory;
+use rustc_middle::ty::{RegionVid, VarianceDiagInfo};
+use rustc_span::DUMMY_SP;
+
+use crate::{
+ constraints::OutlivesConstraintIndex,
+ constraints::{OutlivesConstraint, OutlivesConstraintSet},
+ type_check::Locations,
+};
+
+/// The construct graph organizes the constraints by their end-points.
+/// It can be used to view a `R1: R2` constraint as either an edge `R1
+/// -> R2` or `R2 -> R1` depending on the direction type `D`.
+pub(crate) struct ConstraintGraph<D: ConstraintGraphDirecton> {
+ _direction: D,
+ first_constraints: IndexVec<RegionVid, Option<OutlivesConstraintIndex>>,
+ next_constraints: IndexVec<OutlivesConstraintIndex, Option<OutlivesConstraintIndex>>,
+}
+
+pub(crate) type NormalConstraintGraph = ConstraintGraph<Normal>;
+
+pub(crate) type ReverseConstraintGraph = ConstraintGraph<Reverse>;
+
+/// Marker trait that controls whether a `R1: R2` constraint
+/// represents an edge `R1 -> R2` or `R2 -> R1`.
+pub(crate) trait ConstraintGraphDirecton: Copy + 'static {
+ fn start_region(c: &OutlivesConstraint<'_>) -> RegionVid;
+ fn end_region(c: &OutlivesConstraint<'_>) -> RegionVid;
+ fn is_normal() -> bool;
+}
+
+/// In normal mode, a `R1: R2` constraint results in an edge `R1 ->
+/// R2`. This is what we use when constructing the SCCs for
+/// inference. This is because we compute the value of R1 by union'ing
+/// all the things that it relies on.
+#[derive(Copy, Clone, Debug)]
+pub(crate) struct Normal;
+
+impl ConstraintGraphDirecton for Normal {
+ fn start_region(c: &OutlivesConstraint<'_>) -> RegionVid {
+ c.sup
+ }
+
+ fn end_region(c: &OutlivesConstraint<'_>) -> RegionVid {
+ c.sub
+ }
+
+ fn is_normal() -> bool {
+ true
+ }
+}
+
+/// In reverse mode, a `R1: R2` constraint results in an edge `R2 ->
+/// R1`. We use this for optimizing liveness computation, because then
+/// we wish to iterate from a region (e.g., R2) to all the regions
+/// that will outlive it (e.g., R1).
+#[derive(Copy, Clone, Debug)]
+pub(crate) struct Reverse;
+
+impl ConstraintGraphDirecton for Reverse {
+ fn start_region(c: &OutlivesConstraint<'_>) -> RegionVid {
+ c.sub
+ }
+
+ fn end_region(c: &OutlivesConstraint<'_>) -> RegionVid {
+ c.sup
+ }
+
+ fn is_normal() -> bool {
+ false
+ }
+}
+
+impl<D: ConstraintGraphDirecton> ConstraintGraph<D> {
+ /// Creates a "dependency graph" where each region constraint `R1:
+ /// R2` is treated as an edge `R1 -> R2`. We use this graph to
+ /// construct SCCs for region inference but also for error
+ /// reporting.
+ pub(crate) fn new(
+ direction: D,
+ set: &OutlivesConstraintSet<'_>,
+ num_region_vars: usize,
+ ) -> Self {
+ let mut first_constraints = IndexVec::from_elem_n(None, num_region_vars);
+ let mut next_constraints = IndexVec::from_elem(None, &set.outlives);
+
+ for (idx, constraint) in set.outlives.iter_enumerated().rev() {
+ let head = &mut first_constraints[D::start_region(constraint)];
+ let next = &mut next_constraints[idx];
+ debug_assert!(next.is_none());
+ *next = *head;
+ *head = Some(idx);
+ }
+
+ Self { _direction: direction, first_constraints, next_constraints }
+ }
+
+ /// Given the constraint set from which this graph was built
+ /// creates a region graph so that you can iterate over *regions*
+ /// and not constraints.
+ pub(crate) fn region_graph<'rg, 'tcx>(
+ &'rg self,
+ set: &'rg OutlivesConstraintSet<'tcx>,
+ static_region: RegionVid,
+ ) -> RegionGraph<'rg, 'tcx, D> {
+ RegionGraph::new(set, self, static_region)
+ }
+
+ /// Given a region `R`, iterate over all constraints `R: R1`.
+ pub(crate) fn outgoing_edges<'a, 'tcx>(
+ &'a self,
+ region_sup: RegionVid,
+ constraints: &'a OutlivesConstraintSet<'tcx>,
+ static_region: RegionVid,
+ ) -> Edges<'a, 'tcx, D> {
+ //if this is the `'static` region and the graph's direction is normal,
+ //then setup the Edges iterator to return all regions #53178
+ if region_sup == static_region && D::is_normal() {
+ Edges {
+ graph: self,
+ constraints,
+ pointer: None,
+ next_static_idx: Some(0),
+ static_region,
+ }
+ } else {
+ //otherwise, just setup the iterator as normal
+ let first = self.first_constraints[region_sup];
+ Edges { graph: self, constraints, pointer: first, next_static_idx: None, static_region }
+ }
+ }
+}
+
+pub(crate) struct Edges<'s, 'tcx, D: ConstraintGraphDirecton> {
+ graph: &'s ConstraintGraph<D>,
+ constraints: &'s OutlivesConstraintSet<'tcx>,
+ pointer: Option<OutlivesConstraintIndex>,
+ next_static_idx: Option<usize>,
+ static_region: RegionVid,
+}
+
+impl<'s, 'tcx, D: ConstraintGraphDirecton> Iterator for Edges<'s, 'tcx, D> {
+ type Item = OutlivesConstraint<'tcx>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if let Some(p) = self.pointer {
+ self.pointer = self.graph.next_constraints[p];
+
+ Some(self.constraints[p].clone())
+ } else if let Some(next_static_idx) = self.next_static_idx {
+ self.next_static_idx = if next_static_idx == (self.graph.first_constraints.len() - 1) {
+ None
+ } else {
+ Some(next_static_idx + 1)
+ };
+
+ Some(OutlivesConstraint {
+ sup: self.static_region,
+ sub: next_static_idx.into(),
+ locations: Locations::All(DUMMY_SP),
+ span: DUMMY_SP,
+ category: ConstraintCategory::Internal,
+ variance_info: VarianceDiagInfo::default(),
+ })
+ } else {
+ None
+ }
+ }
+}
+
+/// This struct brings together a constraint set and a (normal, not
+/// reverse) constraint graph. It implements the graph traits and is
+/// usd for doing the SCC computation.
+pub(crate) struct RegionGraph<'s, 'tcx, D: ConstraintGraphDirecton> {
+ set: &'s OutlivesConstraintSet<'tcx>,
+ constraint_graph: &'s ConstraintGraph<D>,
+ static_region: RegionVid,
+}
+
+impl<'s, 'tcx, D: ConstraintGraphDirecton> RegionGraph<'s, 'tcx, D> {
+ /// Creates a "dependency graph" where each region constraint `R1:
+ /// R2` is treated as an edge `R1 -> R2`. We use this graph to
+ /// construct SCCs for region inference but also for error
+ /// reporting.
+ pub(crate) fn new(
+ set: &'s OutlivesConstraintSet<'tcx>,
+ constraint_graph: &'s ConstraintGraph<D>,
+ static_region: RegionVid,
+ ) -> Self {
+ Self { set, constraint_graph, static_region }
+ }
+
+ /// Given a region `R`, iterate over all regions `R1` such that
+ /// there exists a constraint `R: R1`.
+ pub(crate) fn outgoing_regions(&self, region_sup: RegionVid) -> Successors<'s, 'tcx, D> {
+ Successors {
+ edges: self.constraint_graph.outgoing_edges(region_sup, self.set, self.static_region),
+ }
+ }
+}
+
+pub(crate) struct Successors<'s, 'tcx, D: ConstraintGraphDirecton> {
+ edges: Edges<'s, 'tcx, D>,
+}
+
+impl<'s, 'tcx, D: ConstraintGraphDirecton> Iterator for Successors<'s, 'tcx, D> {
+ type Item = RegionVid;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.edges.next().map(|c| D::end_region(&c))
+ }
+}
+
+impl<'s, 'tcx, D: ConstraintGraphDirecton> graph::DirectedGraph for RegionGraph<'s, 'tcx, D> {
+ type Node = RegionVid;
+}
+
+impl<'s, 'tcx, D: ConstraintGraphDirecton> graph::WithNumNodes for RegionGraph<'s, 'tcx, D> {
+ fn num_nodes(&self) -> usize {
+ self.constraint_graph.first_constraints.len()
+ }
+}
+
+impl<'s, 'tcx, D: ConstraintGraphDirecton> graph::WithSuccessors for RegionGraph<'s, 'tcx, D> {
+ fn successors(&self, node: Self::Node) -> <Self as graph::GraphSuccessors<'_>>::Iter {
+ self.outgoing_regions(node)
+ }
+}
+
+impl<'s, 'tcx, D: ConstraintGraphDirecton> graph::GraphSuccessors<'_> for RegionGraph<'s, 'tcx, D> {
+ type Item = RegionVid;
+ type Iter = Successors<'s, 'tcx, D>;
+}
diff --git a/compiler/rustc_borrowck/src/constraints/mod.rs b/compiler/rustc_borrowck/src/constraints/mod.rs
new file mode 100644
index 000000000..a504d0c91
--- /dev/null
+++ b/compiler/rustc_borrowck/src/constraints/mod.rs
@@ -0,0 +1,124 @@
+use rustc_data_structures::graph::scc::Sccs;
+use rustc_index::vec::IndexVec;
+use rustc_middle::mir::ConstraintCategory;
+use rustc_middle::ty::{RegionVid, VarianceDiagInfo};
+use rustc_span::Span;
+use std::fmt;
+use std::ops::Index;
+
+use crate::type_check::Locations;
+
+pub(crate) mod graph;
+
+/// A set of NLL region constraints. These include "outlives"
+/// constraints of the form `R1: R2`. Each constraint is identified by
+/// a unique `OutlivesConstraintIndex` and you can index into the set
+/// (`constraint_set[i]`) to access the constraint details.
+#[derive(Clone, Default)]
+pub(crate) struct OutlivesConstraintSet<'tcx> {
+ outlives: IndexVec<OutlivesConstraintIndex, OutlivesConstraint<'tcx>>,
+}
+
+impl<'tcx> OutlivesConstraintSet<'tcx> {
+ pub(crate) fn push(&mut self, constraint: OutlivesConstraint<'tcx>) {
+ debug!(
+ "OutlivesConstraintSet::push({:?}: {:?} @ {:?}",
+ constraint.sup, constraint.sub, constraint.locations
+ );
+ if constraint.sup == constraint.sub {
+ // 'a: 'a is pretty uninteresting
+ return;
+ }
+ self.outlives.push(constraint);
+ }
+
+ /// Constructs a "normal" graph from the constraint set; the graph makes it
+ /// easy to find the constraints affecting a particular region.
+ ///
+ /// N.B., this graph contains a "frozen" view of the current
+ /// constraints. Any new constraints added to the `OutlivesConstraintSet`
+ /// after the graph is built will not be present in the graph.
+ pub(crate) fn graph(&self, num_region_vars: usize) -> graph::NormalConstraintGraph {
+ graph::ConstraintGraph::new(graph::Normal, self, num_region_vars)
+ }
+
+ /// Like `graph`, but constraints a reverse graph where `R1: R2`
+ /// represents an edge `R2 -> R1`.
+ pub(crate) fn reverse_graph(&self, num_region_vars: usize) -> graph::ReverseConstraintGraph {
+ graph::ConstraintGraph::new(graph::Reverse, self, num_region_vars)
+ }
+
+ /// Computes cycles (SCCs) in the graph of regions. In particular,
+ /// find all regions R1, R2 such that R1: R2 and R2: R1 and group
+ /// them into an SCC, and find the relationships between SCCs.
+ pub(crate) fn compute_sccs(
+ &self,
+ constraint_graph: &graph::NormalConstraintGraph,
+ static_region: RegionVid,
+ ) -> Sccs<RegionVid, ConstraintSccIndex> {
+ let region_graph = &constraint_graph.region_graph(self, static_region);
+ Sccs::new(region_graph)
+ }
+
+ pub(crate) fn outlives(&self) -> &IndexVec<OutlivesConstraintIndex, OutlivesConstraint<'tcx>> {
+ &self.outlives
+ }
+}
+
+impl<'tcx> Index<OutlivesConstraintIndex> for OutlivesConstraintSet<'tcx> {
+ type Output = OutlivesConstraint<'tcx>;
+
+ fn index(&self, i: OutlivesConstraintIndex) -> &Self::Output {
+ &self.outlives[i]
+ }
+}
+
+#[derive(Clone, PartialEq, Eq)]
+pub struct OutlivesConstraint<'tcx> {
+ // NB. The ordering here is not significant for correctness, but
+ // it is for convenience. Before we dump the constraints in the
+ // debugging logs, we sort them, and we'd like the "super region"
+ // to be first, etc. (In particular, span should remain last.)
+ /// The region SUP must outlive SUB...
+ pub sup: RegionVid,
+
+ /// Region that must be outlived.
+ pub sub: RegionVid,
+
+ /// Where did this constraint arise?
+ pub locations: Locations,
+
+ /// The `Span` associated with the creation of this constraint.
+ /// This should be used in preference to obtaining the span from
+ /// `locations`, since the `locations` may give a poor span
+ /// in some cases (e.g. converting a constraint from a promoted).
+ pub span: Span,
+
+ /// What caused this constraint?
+ pub category: ConstraintCategory<'tcx>,
+
+ /// Variance diagnostic information
+ pub variance_info: VarianceDiagInfo<'tcx>,
+}
+
+impl<'tcx> fmt::Debug for OutlivesConstraint<'tcx> {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ formatter,
+ "({:?}: {:?}) due to {:?} ({:?})",
+ self.sup, self.sub, self.locations, self.variance_info
+ )
+ }
+}
+
+rustc_index::newtype_index! {
+ pub struct OutlivesConstraintIndex {
+ DEBUG_FORMAT = "OutlivesConstraintIndex({})"
+ }
+}
+
+rustc_index::newtype_index! {
+ pub struct ConstraintSccIndex {
+ DEBUG_FORMAT = "ConstraintSccIndex({})"
+ }
+}
diff --git a/compiler/rustc_borrowck/src/consumers.rs b/compiler/rustc_borrowck/src/consumers.rs
new file mode 100644
index 000000000..efc17a173
--- /dev/null
+++ b/compiler/rustc_borrowck/src/consumers.rs
@@ -0,0 +1,39 @@
+//! This file provides API for compiler consumers.
+
+use rustc_hir::def_id::LocalDefId;
+use rustc_index::vec::IndexVec;
+use rustc_infer::infer::{DefiningAnchor, TyCtxtInferExt};
+use rustc_middle::mir::Body;
+use rustc_middle::ty::{self, TyCtxt};
+
+pub use super::{
+ facts::{AllFacts as PoloniusInput, RustcFacts},
+ location::{LocationTable, RichLocation},
+ nll::PoloniusOutput,
+ BodyWithBorrowckFacts,
+};
+
+/// This function computes Polonius facts for the given body. It makes a copy of
+/// the body because it needs to regenerate the region identifiers. This function
+/// should never be invoked during a typical compilation session due to performance
+/// issues with Polonius.
+///
+/// Note:
+/// * This function will panic if the required body was already stolen. This
+/// can, for example, happen when requesting a body of a `const` function
+/// because they are evaluated during typechecking. The panic can be avoided
+/// by overriding the `mir_borrowck` query. You can find a complete example
+/// that shows how to do this at `src/test/run-make/obtain-borrowck/`.
+///
+/// * Polonius is highly unstable, so expect regular changes in its signature or other details.
+pub fn get_body_with_borrowck_facts<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ def: ty::WithOptConstParam<LocalDefId>,
+) -> BodyWithBorrowckFacts<'tcx> {
+ let (input_body, promoted) = tcx.mir_promoted(def);
+ tcx.infer_ctxt().with_opaque_type_inference(DefiningAnchor::Bind(def.did)).enter(|infcx| {
+ let input_body: &Body<'_> = &input_body.borrow();
+ let promoted: &IndexVec<_, _> = &promoted.borrow();
+ *super::do_mir_borrowck(&infcx, input_body, promoted, true).1.unwrap()
+ })
+}
diff --git a/compiler/rustc_borrowck/src/dataflow.rs b/compiler/rustc_borrowck/src/dataflow.rs
new file mode 100644
index 000000000..97d5a8d15
--- /dev/null
+++ b/compiler/rustc_borrowck/src/dataflow.rs
@@ -0,0 +1,438 @@
+use rustc_data_structures::fx::FxHashMap;
+use rustc_index::bit_set::BitSet;
+use rustc_middle::mir::{self, BasicBlock, Body, Location, Place};
+use rustc_middle::ty::RegionVid;
+use rustc_middle::ty::TyCtxt;
+use rustc_mir_dataflow::impls::{EverInitializedPlaces, MaybeUninitializedPlaces};
+use rustc_mir_dataflow::ResultsVisitable;
+use rustc_mir_dataflow::{self, fmt::DebugWithContext, CallReturnPlaces, GenKill};
+use rustc_mir_dataflow::{Analysis, Direction, Results};
+use std::fmt;
+
+use crate::{
+ places_conflict, BorrowSet, PlaceConflictBias, PlaceExt, RegionInferenceContext, ToRegionVid,
+};
+
+/// A tuple with named fields that can hold either the results or the transient state of the
+/// dataflow analyses used by the borrow checker.
+#[derive(Debug)]
+pub struct BorrowckAnalyses<B, U, E> {
+ pub borrows: B,
+ pub uninits: U,
+ pub ever_inits: E,
+}
+
+/// The results of the dataflow analyses used by the borrow checker.
+pub type BorrowckResults<'mir, 'tcx> = BorrowckAnalyses<
+ Results<'tcx, Borrows<'mir, 'tcx>>,
+ Results<'tcx, MaybeUninitializedPlaces<'mir, 'tcx>>,
+ Results<'tcx, EverInitializedPlaces<'mir, 'tcx>>,
+>;
+
+/// The transient state of the dataflow analyses used by the borrow checker.
+pub type BorrowckFlowState<'mir, 'tcx> =
+ <BorrowckResults<'mir, 'tcx> as ResultsVisitable<'tcx>>::FlowState;
+
+macro_rules! impl_visitable {
+ ( $(
+ $T:ident { $( $field:ident : $A:ident ),* $(,)? }
+ )* ) => { $(
+ impl<'tcx, $($A),*, D: Direction> ResultsVisitable<'tcx> for $T<$( Results<'tcx, $A> ),*>
+ where
+ $( $A: Analysis<'tcx, Direction = D>, )*
+ {
+ type Direction = D;
+ type FlowState = $T<$( $A::Domain ),*>;
+
+ fn new_flow_state(&self, body: &mir::Body<'tcx>) -> Self::FlowState {
+ $T {
+ $( $field: self.$field.analysis.bottom_value(body) ),*
+ }
+ }
+
+ fn reset_to_block_entry(
+ &self,
+ state: &mut Self::FlowState,
+ block: BasicBlock,
+ ) {
+ $( state.$field.clone_from(&self.$field.entry_set_for_block(block)); )*
+ }
+
+ fn reconstruct_before_statement_effect(
+ &self,
+ state: &mut Self::FlowState,
+ stmt: &mir::Statement<'tcx>,
+ loc: Location,
+ ) {
+ $( self.$field.analysis
+ .apply_before_statement_effect(&mut state.$field, stmt, loc); )*
+ }
+
+ fn reconstruct_statement_effect(
+ &self,
+ state: &mut Self::FlowState,
+ stmt: &mir::Statement<'tcx>,
+ loc: Location,
+ ) {
+ $( self.$field.analysis
+ .apply_statement_effect(&mut state.$field, stmt, loc); )*
+ }
+
+ fn reconstruct_before_terminator_effect(
+ &self,
+ state: &mut Self::FlowState,
+ term: &mir::Terminator<'tcx>,
+ loc: Location,
+ ) {
+ $( self.$field.analysis
+ .apply_before_terminator_effect(&mut state.$field, term, loc); )*
+ }
+
+ fn reconstruct_terminator_effect(
+ &self,
+ state: &mut Self::FlowState,
+ term: &mir::Terminator<'tcx>,
+ loc: Location,
+ ) {
+ $( self.$field.analysis
+ .apply_terminator_effect(&mut state.$field, term, loc); )*
+ }
+ }
+ )* }
+}
+
+impl_visitable! {
+ BorrowckAnalyses { borrows: B, uninits: U, ever_inits: E }
+}
+
+rustc_index::newtype_index! {
+ pub struct BorrowIndex {
+ DEBUG_FORMAT = "bw{}"
+ }
+}
+
+/// `Borrows` stores the data used in the analyses that track the flow
+/// of borrows.
+///
+/// It uniquely identifies every borrow (`Rvalue::Ref`) by a
+/// `BorrowIndex`, and maps each such index to a `BorrowData`
+/// describing the borrow. These indexes are used for representing the
+/// borrows in compact bitvectors.
+pub struct Borrows<'a, 'tcx> {
+ tcx: TyCtxt<'tcx>,
+ body: &'a Body<'tcx>,
+
+ borrow_set: &'a BorrowSet<'tcx>,
+ borrows_out_of_scope_at_location: FxHashMap<Location, Vec<BorrowIndex>>,
+}
+
+struct StackEntry {
+ bb: mir::BasicBlock,
+ lo: usize,
+ hi: usize,
+}
+
+struct OutOfScopePrecomputer<'a, 'tcx> {
+ visited: BitSet<mir::BasicBlock>,
+ visit_stack: Vec<StackEntry>,
+ body: &'a Body<'tcx>,
+ regioncx: &'a RegionInferenceContext<'tcx>,
+ borrows_out_of_scope_at_location: FxHashMap<Location, Vec<BorrowIndex>>,
+}
+
+impl<'a, 'tcx> OutOfScopePrecomputer<'a, 'tcx> {
+ fn new(body: &'a Body<'tcx>, regioncx: &'a RegionInferenceContext<'tcx>) -> Self {
+ OutOfScopePrecomputer {
+ visited: BitSet::new_empty(body.basic_blocks().len()),
+ visit_stack: vec![],
+ body,
+ regioncx,
+ borrows_out_of_scope_at_location: FxHashMap::default(),
+ }
+ }
+}
+
+impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> {
+ fn precompute_borrows_out_of_scope(
+ &mut self,
+ borrow_index: BorrowIndex,
+ borrow_region: RegionVid,
+ location: Location,
+ ) {
+ // We visit one BB at a time. The complication is that we may start in the
+ // middle of the first BB visited (the one containing `location`), in which
+ // case we may have to later on process the first part of that BB if there
+ // is a path back to its start.
+
+ // For visited BBs, we record the index of the first statement processed.
+ // (In fully processed BBs this index is 0.) Note also that we add BBs to
+ // `visited` once they are added to `stack`, before they are actually
+ // processed, because this avoids the need to look them up again on
+ // completion.
+ self.visited.insert(location.block);
+
+ let mut first_lo = location.statement_index;
+ let first_hi = self.body[location.block].statements.len();
+
+ self.visit_stack.push(StackEntry { bb: location.block, lo: first_lo, hi: first_hi });
+
+ while let Some(StackEntry { bb, lo, hi }) = self.visit_stack.pop() {
+ // If we process the first part of the first basic block (i.e. we encounter that block
+ // for the second time), we no longer have to visit its successors again.
+ let mut finished_early = bb == location.block && hi != first_hi;
+ for i in lo..=hi {
+ let location = Location { block: bb, statement_index: i };
+ // If region does not contain a point at the location, then add to list and skip
+ // successor locations.
+ if !self.regioncx.region_contains(borrow_region, location) {
+ debug!("borrow {:?} gets killed at {:?}", borrow_index, location);
+ self.borrows_out_of_scope_at_location
+ .entry(location)
+ .or_default()
+ .push(borrow_index);
+ finished_early = true;
+ break;
+ }
+ }
+
+ if !finished_early {
+ // Add successor BBs to the work list, if necessary.
+ let bb_data = &self.body[bb];
+ debug_assert!(hi == bb_data.statements.len());
+ for succ_bb in bb_data.terminator().successors() {
+ if !self.visited.insert(succ_bb) {
+ if succ_bb == location.block && first_lo > 0 {
+ // `succ_bb` has been seen before. If it wasn't
+ // fully processed, add its first part to `stack`
+ // for processing.
+ self.visit_stack.push(StackEntry {
+ bb: succ_bb,
+ lo: 0,
+ hi: first_lo - 1,
+ });
+
+ // And update this entry with 0, to represent the
+ // whole BB being processed.
+ first_lo = 0;
+ }
+ } else {
+ // succ_bb hasn't been seen before. Add it to
+ // `stack` for processing.
+ self.visit_stack.push(StackEntry {
+ bb: succ_bb,
+ lo: 0,
+ hi: self.body[succ_bb].statements.len(),
+ });
+ }
+ }
+ }
+ }
+
+ self.visited.clear();
+ }
+}
+
+impl<'a, 'tcx> Borrows<'a, 'tcx> {
+ pub(crate) fn new(
+ tcx: TyCtxt<'tcx>,
+ body: &'a Body<'tcx>,
+ nonlexical_regioncx: &'a RegionInferenceContext<'tcx>,
+ borrow_set: &'a BorrowSet<'tcx>,
+ ) -> Self {
+ let mut prec = OutOfScopePrecomputer::new(body, nonlexical_regioncx);
+ for (borrow_index, borrow_data) in borrow_set.iter_enumerated() {
+ let borrow_region = borrow_data.region.to_region_vid();
+ let location = borrow_data.reserve_location;
+
+ prec.precompute_borrows_out_of_scope(borrow_index, borrow_region, location);
+ }
+
+ Borrows {
+ tcx,
+ body,
+ borrow_set,
+ borrows_out_of_scope_at_location: prec.borrows_out_of_scope_at_location,
+ }
+ }
+
+ pub fn location(&self, idx: BorrowIndex) -> &Location {
+ &self.borrow_set[idx].reserve_location
+ }
+
+ /// Add all borrows to the kill set, if those borrows are out of scope at `location`.
+ /// That means they went out of a nonlexical scope
+ fn kill_loans_out_of_scope_at_location(
+ &self,
+ trans: &mut impl GenKill<BorrowIndex>,
+ location: Location,
+ ) {
+ // NOTE: The state associated with a given `location`
+ // reflects the dataflow on entry to the statement.
+ // Iterate over each of the borrows that we've precomputed
+ // to have went out of scope at this location and kill them.
+ //
+ // We are careful always to call this function *before* we
+ // set up the gen-bits for the statement or
+ // terminator. That way, if the effect of the statement or
+ // terminator *does* introduce a new loan of the same
+ // region, then setting that gen-bit will override any
+ // potential kill introduced here.
+ if let Some(indices) = self.borrows_out_of_scope_at_location.get(&location) {
+ trans.kill_all(indices.iter().copied());
+ }
+ }
+
+ /// Kill any borrows that conflict with `place`.
+ fn kill_borrows_on_place(&self, trans: &mut impl GenKill<BorrowIndex>, place: Place<'tcx>) {
+ debug!("kill_borrows_on_place: place={:?}", place);
+
+ let other_borrows_of_local = self
+ .borrow_set
+ .local_map
+ .get(&place.local)
+ .into_iter()
+ .flat_map(|bs| bs.iter())
+ .copied();
+
+ // If the borrowed place is a local with no projections, all other borrows of this
+ // local must conflict. This is purely an optimization so we don't have to call
+ // `places_conflict` for every borrow.
+ if place.projection.is_empty() {
+ if !self.body.local_decls[place.local].is_ref_to_static() {
+ trans.kill_all(other_borrows_of_local);
+ }
+ return;
+ }
+
+ // By passing `PlaceConflictBias::NoOverlap`, we conservatively assume that any given
+ // pair of array indices are unequal, so that when `places_conflict` returns true, we
+ // will be assured that two places being compared definitely denotes the same sets of
+ // locations.
+ let definitely_conflicting_borrows = other_borrows_of_local.filter(|&i| {
+ places_conflict(
+ self.tcx,
+ self.body,
+ self.borrow_set[i].borrowed_place,
+ place,
+ PlaceConflictBias::NoOverlap,
+ )
+ });
+
+ trans.kill_all(definitely_conflicting_borrows);
+ }
+}
+
+impl<'tcx> rustc_mir_dataflow::AnalysisDomain<'tcx> for Borrows<'_, 'tcx> {
+ type Domain = BitSet<BorrowIndex>;
+
+ const NAME: &'static str = "borrows";
+
+ fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
+ // bottom = nothing is reserved or activated yet;
+ BitSet::new_empty(self.borrow_set.len() * 2)
+ }
+
+ fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) {
+ // no borrows of code region_scopes have been taken prior to
+ // function execution, so this method has no effect.
+ }
+}
+
+impl<'tcx> rustc_mir_dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> {
+ type Idx = BorrowIndex;
+
+ fn before_statement_effect(
+ &self,
+ trans: &mut impl GenKill<Self::Idx>,
+ _statement: &mir::Statement<'tcx>,
+ location: Location,
+ ) {
+ self.kill_loans_out_of_scope_at_location(trans, location);
+ }
+
+ fn statement_effect(
+ &self,
+ trans: &mut impl GenKill<Self::Idx>,
+ stmt: &mir::Statement<'tcx>,
+ location: Location,
+ ) {
+ match stmt.kind {
+ mir::StatementKind::Assign(box (lhs, ref rhs)) => {
+ if let mir::Rvalue::Ref(_, _, place) = *rhs {
+ if place.ignore_borrow(
+ self.tcx,
+ self.body,
+ &self.borrow_set.locals_state_at_exit,
+ ) {
+ return;
+ }
+ let index = self.borrow_set.get_index_of(&location).unwrap_or_else(|| {
+ panic!("could not find BorrowIndex for location {:?}", location);
+ });
+
+ trans.gen(index);
+ }
+
+ // Make sure there are no remaining borrows for variables
+ // that are assigned over.
+ self.kill_borrows_on_place(trans, lhs);
+ }
+
+ mir::StatementKind::StorageDead(local) => {
+ // Make sure there are no remaining borrows for locals that
+ // are gone out of scope.
+ self.kill_borrows_on_place(trans, Place::from(local));
+ }
+
+ mir::StatementKind::FakeRead(..)
+ | mir::StatementKind::SetDiscriminant { .. }
+ | mir::StatementKind::Deinit(..)
+ | mir::StatementKind::StorageLive(..)
+ | mir::StatementKind::Retag { .. }
+ | mir::StatementKind::AscribeUserType(..)
+ | mir::StatementKind::Coverage(..)
+ | mir::StatementKind::CopyNonOverlapping(..)
+ | mir::StatementKind::Nop => {}
+ }
+ }
+
+ fn before_terminator_effect(
+ &self,
+ trans: &mut impl GenKill<Self::Idx>,
+ _terminator: &mir::Terminator<'tcx>,
+ location: Location,
+ ) {
+ self.kill_loans_out_of_scope_at_location(trans, location);
+ }
+
+ fn terminator_effect(
+ &self,
+ trans: &mut impl GenKill<Self::Idx>,
+ terminator: &mir::Terminator<'tcx>,
+ _location: Location,
+ ) {
+ if let mir::TerminatorKind::InlineAsm { operands, .. } = &terminator.kind {
+ for op in operands {
+ if let mir::InlineAsmOperand::Out { place: Some(place), .. }
+ | mir::InlineAsmOperand::InOut { out_place: Some(place), .. } = *op
+ {
+ self.kill_borrows_on_place(trans, place);
+ }
+ }
+ }
+ }
+
+ fn call_return_effect(
+ &self,
+ _trans: &mut impl GenKill<Self::Idx>,
+ _block: mir::BasicBlock,
+ _return_places: CallReturnPlaces<'_, 'tcx>,
+ ) {
+ }
+}
+
+impl DebugWithContext<Borrows<'_, '_>> for BorrowIndex {
+ fn fmt_with(&self, ctxt: &Borrows<'_, '_>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{:?}", ctxt.location(*self))
+ }
+}
diff --git a/compiler/rustc_borrowck/src/def_use.rs b/compiler/rustc_borrowck/src/def_use.rs
new file mode 100644
index 000000000..a5c0d7742
--- /dev/null
+++ b/compiler/rustc_borrowck/src/def_use.rs
@@ -0,0 +1,80 @@
+use rustc_middle::mir::visit::{
+ MutatingUseContext, NonMutatingUseContext, NonUseContext, PlaceContext,
+};
+
+#[derive(Eq, PartialEq, Clone)]
+pub enum DefUse {
+ Def,
+ Use,
+ Drop,
+}
+
+pub fn categorize(context: PlaceContext) -> Option<DefUse> {
+ match context {
+ ///////////////////////////////////////////////////////////////////////////
+ // DEFS
+
+ PlaceContext::MutatingUse(MutatingUseContext::Store) |
+
+ // We let Call define the result in both the success and
+ // unwind cases. This is not really correct, however it
+ // does not seem to be observable due to the way that we
+ // generate MIR. To do things properly, we would apply
+ // the def in call only to the input from the success
+ // path and not the unwind path. -nmatsakis
+ PlaceContext::MutatingUse(MutatingUseContext::Call) |
+ PlaceContext::MutatingUse(MutatingUseContext::AsmOutput) |
+ PlaceContext::MutatingUse(MutatingUseContext::Yield) |
+
+ // Storage live and storage dead aren't proper defines, but we can ignore
+ // values that come before them.
+ PlaceContext::NonUse(NonUseContext::StorageLive) |
+ PlaceContext::NonUse(NonUseContext::StorageDead) => Some(DefUse::Def),
+
+ ///////////////////////////////////////////////////////////////////////////
+ // REGULAR USES
+ //
+ // These are uses that occur *outside* of a drop. For the
+ // purposes of NLL, these are special in that **all** the
+ // lifetimes appearing in the variable must be live for each regular use.
+
+ PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) |
+ PlaceContext::MutatingUse(MutatingUseContext::Projection) |
+
+ // Borrows only consider their local used at the point of the borrow.
+ // This won't affect the results since we use this analysis for generators
+ // and we only care about the result at suspension points. Borrows cannot
+ // cross suspension points so this behavior is unproblematic.
+ PlaceContext::MutatingUse(MutatingUseContext::Borrow) |
+ PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) |
+ PlaceContext::NonMutatingUse(NonMutatingUseContext::ShallowBorrow) |
+ PlaceContext::NonMutatingUse(NonMutatingUseContext::UniqueBorrow) |
+
+ PlaceContext::MutatingUse(MutatingUseContext::AddressOf) |
+ PlaceContext::NonMutatingUse(NonMutatingUseContext::AddressOf) |
+ PlaceContext::NonMutatingUse(NonMutatingUseContext::Inspect) |
+ PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy) |
+ PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) |
+ PlaceContext::NonUse(NonUseContext::AscribeUserTy) |
+ PlaceContext::MutatingUse(MutatingUseContext::Retag) =>
+ Some(DefUse::Use),
+
+ ///////////////////////////////////////////////////////////////////////////
+ // DROP USES
+ //
+ // These are uses that occur in a DROP (a MIR drop, not a
+ // call to `std::mem::drop()`). For the purposes of NLL,
+ // uses in drop are special because `#[may_dangle]`
+ // attributes can affect whether lifetimes must be live.
+
+ PlaceContext::MutatingUse(MutatingUseContext::Drop) =>
+ Some(DefUse::Drop),
+
+ // Debug info is neither def nor use.
+ PlaceContext::NonUse(NonUseContext::VarDebugInfo) => None,
+
+ PlaceContext::MutatingUse(MutatingUseContext::Deinit | MutatingUseContext::SetDiscriminant) => {
+ bug!("These statements are not allowed in this MIR phase")
+ }
+ }
+}
diff --git a/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs
new file mode 100644
index 000000000..1ef2b0ae9
--- /dev/null
+++ b/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs
@@ -0,0 +1,494 @@
+use rustc_errors::{DiagnosticBuilder, ErrorGuaranteed};
+use rustc_infer::infer::canonical::Canonical;
+use rustc_infer::infer::error_reporting::nice_region_error::NiceRegionError;
+use rustc_infer::infer::region_constraints::Constraint;
+use rustc_infer::infer::region_constraints::RegionConstraintData;
+use rustc_infer::infer::RegionVariableOrigin;
+use rustc_infer::infer::{InferCtxt, RegionResolutionError, SubregionOrigin, TyCtxtInferExt as _};
+use rustc_infer::traits::{Normalized, ObligationCause, TraitEngine, TraitEngineExt};
+use rustc_middle::ty::error::TypeError;
+use rustc_middle::ty::RegionVid;
+use rustc_middle::ty::UniverseIndex;
+use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable};
+use rustc_span::Span;
+use rustc_trait_selection::traits::query::type_op;
+use rustc_trait_selection::traits::{SelectionContext, TraitEngineExt as _};
+use rustc_traits::{type_op_ascribe_user_type_with_span, type_op_prove_predicate_with_cause};
+
+use std::fmt;
+use std::rc::Rc;
+
+use crate::region_infer::values::RegionElement;
+use crate::session_diagnostics::HigherRankedErrorCause;
+use crate::session_diagnostics::HigherRankedLifetimeError;
+use crate::session_diagnostics::HigherRankedSubtypeError;
+use crate::MirBorrowckCtxt;
+
+#[derive(Clone)]
+pub(crate) struct UniverseInfo<'tcx>(UniverseInfoInner<'tcx>);
+
+/// What operation a universe was created for.
+#[derive(Clone)]
+enum UniverseInfoInner<'tcx> {
+ /// Relating two types which have binders.
+ RelateTys { expected: Ty<'tcx>, found: Ty<'tcx> },
+ /// Created from performing a `TypeOp`.
+ TypeOp(Rc<dyn TypeOpInfo<'tcx> + 'tcx>),
+ /// Any other reason.
+ Other,
+}
+
+impl<'tcx> UniverseInfo<'tcx> {
+ pub(crate) fn other() -> UniverseInfo<'tcx> {
+ UniverseInfo(UniverseInfoInner::Other)
+ }
+
+ pub(crate) fn relate(expected: Ty<'tcx>, found: Ty<'tcx>) -> UniverseInfo<'tcx> {
+ UniverseInfo(UniverseInfoInner::RelateTys { expected, found })
+ }
+
+ pub(crate) fn report_error(
+ &self,
+ mbcx: &mut MirBorrowckCtxt<'_, 'tcx>,
+ placeholder: ty::PlaceholderRegion,
+ error_element: RegionElement,
+ cause: ObligationCause<'tcx>,
+ ) {
+ match self.0 {
+ UniverseInfoInner::RelateTys { expected, found } => {
+ let err = mbcx.infcx.report_mismatched_types(
+ &cause,
+ expected,
+ found,
+ TypeError::RegionsPlaceholderMismatch,
+ );
+ mbcx.buffer_error(err);
+ }
+ UniverseInfoInner::TypeOp(ref type_op_info) => {
+ type_op_info.report_error(mbcx, placeholder, error_element, cause);
+ }
+ UniverseInfoInner::Other => {
+ // FIXME: This error message isn't great, but it doesn't show
+ // up in the existing UI tests. Consider investigating this
+ // some more.
+ mbcx.buffer_error(
+ mbcx.infcx.tcx.sess.create_err(HigherRankedSubtypeError { span: cause.span }),
+ );
+ }
+ }
+ }
+}
+
+pub(crate) trait ToUniverseInfo<'tcx> {
+ fn to_universe_info(self, base_universe: ty::UniverseIndex) -> UniverseInfo<'tcx>;
+}
+
+impl<'tcx> ToUniverseInfo<'tcx> for crate::type_check::InstantiateOpaqueType<'tcx> {
+ fn to_universe_info(self, base_universe: ty::UniverseIndex) -> UniverseInfo<'tcx> {
+ UniverseInfo(UniverseInfoInner::TypeOp(Rc::new(crate::type_check::InstantiateOpaqueType {
+ base_universe: Some(base_universe),
+ ..self
+ })))
+ }
+}
+
+impl<'tcx> ToUniverseInfo<'tcx>
+ for Canonical<'tcx, ty::ParamEnvAnd<'tcx, type_op::prove_predicate::ProvePredicate<'tcx>>>
+{
+ fn to_universe_info(self, base_universe: ty::UniverseIndex) -> UniverseInfo<'tcx> {
+ UniverseInfo(UniverseInfoInner::TypeOp(Rc::new(PredicateQuery {
+ canonical_query: self,
+ base_universe,
+ })))
+ }
+}
+
+impl<'tcx, T: Copy + fmt::Display + TypeFoldable<'tcx> + 'tcx> ToUniverseInfo<'tcx>
+ for Canonical<'tcx, ty::ParamEnvAnd<'tcx, type_op::Normalize<T>>>
+{
+ fn to_universe_info(self, base_universe: ty::UniverseIndex) -> UniverseInfo<'tcx> {
+ UniverseInfo(UniverseInfoInner::TypeOp(Rc::new(NormalizeQuery {
+ canonical_query: self,
+ base_universe,
+ })))
+ }
+}
+
+impl<'tcx> ToUniverseInfo<'tcx>
+ for Canonical<'tcx, ty::ParamEnvAnd<'tcx, type_op::AscribeUserType<'tcx>>>
+{
+ fn to_universe_info(self, base_universe: ty::UniverseIndex) -> UniverseInfo<'tcx> {
+ UniverseInfo(UniverseInfoInner::TypeOp(Rc::new(AscribeUserTypeQuery {
+ canonical_query: self,
+ base_universe,
+ })))
+ }
+}
+
+impl<'tcx, F, G> ToUniverseInfo<'tcx> for Canonical<'tcx, type_op::custom::CustomTypeOp<F, G>> {
+ fn to_universe_info(self, _base_universe: ty::UniverseIndex) -> UniverseInfo<'tcx> {
+ // We can't rerun custom type ops.
+ UniverseInfo::other()
+ }
+}
+
+impl<'tcx> ToUniverseInfo<'tcx> for ! {
+ fn to_universe_info(self, _base_universe: ty::UniverseIndex) -> UniverseInfo<'tcx> {
+ self
+ }
+}
+
+#[allow(unused_lifetimes)]
+trait TypeOpInfo<'tcx> {
+ /// Returns an error to be reported if rerunning the type op fails to
+ /// recover the error's cause.
+ fn fallback_error(
+ &self,
+ tcx: TyCtxt<'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed>;
+
+ fn base_universe(&self) -> ty::UniverseIndex;
+
+ fn nice_error(
+ &self,
+ mbcx: &mut MirBorrowckCtxt<'_, 'tcx>,
+ cause: ObligationCause<'tcx>,
+ placeholder_region: ty::Region<'tcx>,
+ error_region: Option<ty::Region<'tcx>>,
+ ) -> Option<DiagnosticBuilder<'tcx, ErrorGuaranteed>>;
+
+ fn report_error(
+ &self,
+ mbcx: &mut MirBorrowckCtxt<'_, 'tcx>,
+ placeholder: ty::PlaceholderRegion,
+ error_element: RegionElement,
+ cause: ObligationCause<'tcx>,
+ ) {
+ let tcx = mbcx.infcx.tcx;
+ let base_universe = self.base_universe();
+
+ let Some(adjusted_universe) =
+ placeholder.universe.as_u32().checked_sub(base_universe.as_u32())
+ else {
+ mbcx.buffer_error(self.fallback_error(tcx, cause.span));
+ return;
+ };
+
+ let placeholder_region = tcx.mk_region(ty::RePlaceholder(ty::Placeholder {
+ name: placeholder.name,
+ universe: adjusted_universe.into(),
+ }));
+
+ let error_region =
+ if let RegionElement::PlaceholderRegion(error_placeholder) = error_element {
+ let adjusted_universe =
+ error_placeholder.universe.as_u32().checked_sub(base_universe.as_u32());
+ adjusted_universe.map(|adjusted| {
+ tcx.mk_region(ty::RePlaceholder(ty::Placeholder {
+ name: error_placeholder.name,
+ universe: adjusted.into(),
+ }))
+ })
+ } else {
+ None
+ };
+
+ debug!(?placeholder_region);
+
+ let span = cause.span;
+ let nice_error = self.nice_error(mbcx, cause, placeholder_region, error_region);
+
+ if let Some(nice_error) = nice_error {
+ mbcx.buffer_error(nice_error);
+ } else {
+ mbcx.buffer_error(self.fallback_error(tcx, span));
+ }
+ }
+}
+
+struct PredicateQuery<'tcx> {
+ canonical_query:
+ Canonical<'tcx, ty::ParamEnvAnd<'tcx, type_op::prove_predicate::ProvePredicate<'tcx>>>,
+ base_universe: ty::UniverseIndex,
+}
+
+impl<'tcx> TypeOpInfo<'tcx> for PredicateQuery<'tcx> {
+ fn fallback_error(
+ &self,
+ tcx: TyCtxt<'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ tcx.sess.create_err(HigherRankedLifetimeError {
+ cause: Some(HigherRankedErrorCause::CouldNotProve {
+ predicate: self.canonical_query.value.value.predicate.to_string(),
+ }),
+ span,
+ })
+ }
+
+ fn base_universe(&self) -> ty::UniverseIndex {
+ self.base_universe
+ }
+
+ fn nice_error(
+ &self,
+ mbcx: &mut MirBorrowckCtxt<'_, 'tcx>,
+ cause: ObligationCause<'tcx>,
+ placeholder_region: ty::Region<'tcx>,
+ error_region: Option<ty::Region<'tcx>>,
+ ) -> Option<DiagnosticBuilder<'tcx, ErrorGuaranteed>> {
+ mbcx.infcx.tcx.infer_ctxt().enter_with_canonical(
+ cause.span,
+ &self.canonical_query,
+ |ref infcx, key, _| {
+ let mut fulfill_cx = <dyn TraitEngine<'_>>::new(infcx.tcx);
+ type_op_prove_predicate_with_cause(infcx, &mut *fulfill_cx, key, cause);
+ try_extract_error_from_fulfill_cx(
+ fulfill_cx,
+ infcx,
+ placeholder_region,
+ error_region,
+ )
+ },
+ )
+ }
+}
+
+struct NormalizeQuery<'tcx, T> {
+ canonical_query: Canonical<'tcx, ty::ParamEnvAnd<'tcx, type_op::Normalize<T>>>,
+ base_universe: ty::UniverseIndex,
+}
+
+impl<'tcx, T> TypeOpInfo<'tcx> for NormalizeQuery<'tcx, T>
+where
+ T: Copy + fmt::Display + TypeFoldable<'tcx> + 'tcx,
+{
+ fn fallback_error(
+ &self,
+ tcx: TyCtxt<'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ tcx.sess.create_err(HigherRankedLifetimeError {
+ cause: Some(HigherRankedErrorCause::CouldNotNormalize {
+ value: self.canonical_query.value.value.value.to_string(),
+ }),
+ span,
+ })
+ }
+
+ fn base_universe(&self) -> ty::UniverseIndex {
+ self.base_universe
+ }
+
+ fn nice_error(
+ &self,
+ mbcx: &mut MirBorrowckCtxt<'_, 'tcx>,
+ cause: ObligationCause<'tcx>,
+ placeholder_region: ty::Region<'tcx>,
+ error_region: Option<ty::Region<'tcx>>,
+ ) -> Option<DiagnosticBuilder<'tcx, ErrorGuaranteed>> {
+ mbcx.infcx.tcx.infer_ctxt().enter_with_canonical(
+ cause.span,
+ &self.canonical_query,
+ |ref infcx, key, _| {
+ let mut fulfill_cx = <dyn TraitEngine<'_>>::new(infcx.tcx);
+
+ let mut selcx = SelectionContext::new(infcx);
+
+ // FIXME(lqd): Unify and de-duplicate the following with the actual
+ // `rustc_traits::type_op::type_op_normalize` query to allow the span we need in the
+ // `ObligationCause`. The normalization results are currently different between
+ // `AtExt::normalize` used in the query and `normalize` called below: the former fails
+ // to normalize the `nll/relate_tys/impl-fn-ignore-binder-via-bottom.rs` test. Check
+ // after #85499 lands to see if its fixes have erased this difference.
+ let (param_env, value) = key.into_parts();
+ let Normalized { value: _, obligations } = rustc_trait_selection::traits::normalize(
+ &mut selcx,
+ param_env,
+ cause,
+ value.value,
+ );
+ fulfill_cx.register_predicate_obligations(infcx, obligations);
+
+ try_extract_error_from_fulfill_cx(
+ fulfill_cx,
+ infcx,
+ placeholder_region,
+ error_region,
+ )
+ },
+ )
+ }
+}
+
+struct AscribeUserTypeQuery<'tcx> {
+ canonical_query: Canonical<'tcx, ty::ParamEnvAnd<'tcx, type_op::AscribeUserType<'tcx>>>,
+ base_universe: ty::UniverseIndex,
+}
+
+impl<'tcx> TypeOpInfo<'tcx> for AscribeUserTypeQuery<'tcx> {
+ fn fallback_error(
+ &self,
+ tcx: TyCtxt<'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ // FIXME: This error message isn't great, but it doesn't show up in the existing UI tests,
+ // and is only the fallback when the nice error fails. Consider improving this some more.
+ tcx.sess.create_err(HigherRankedLifetimeError { cause: None, span })
+ }
+
+ fn base_universe(&self) -> ty::UniverseIndex {
+ self.base_universe
+ }
+
+ fn nice_error(
+ &self,
+ mbcx: &mut MirBorrowckCtxt<'_, 'tcx>,
+ cause: ObligationCause<'tcx>,
+ placeholder_region: ty::Region<'tcx>,
+ error_region: Option<ty::Region<'tcx>>,
+ ) -> Option<DiagnosticBuilder<'tcx, ErrorGuaranteed>> {
+ mbcx.infcx.tcx.infer_ctxt().enter_with_canonical(
+ cause.span,
+ &self.canonical_query,
+ |ref infcx, key, _| {
+ let mut fulfill_cx = <dyn TraitEngine<'_>>::new(infcx.tcx);
+ type_op_ascribe_user_type_with_span(infcx, &mut *fulfill_cx, key, Some(cause.span))
+ .ok()?;
+ try_extract_error_from_fulfill_cx(
+ fulfill_cx,
+ infcx,
+ placeholder_region,
+ error_region,
+ )
+ },
+ )
+ }
+}
+
+impl<'tcx> TypeOpInfo<'tcx> for crate::type_check::InstantiateOpaqueType<'tcx> {
+ fn fallback_error(
+ &self,
+ tcx: TyCtxt<'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ // FIXME: This error message isn't great, but it doesn't show up in the existing UI tests,
+ // and is only the fallback when the nice error fails. Consider improving this some more.
+ tcx.sess.create_err(HigherRankedLifetimeError { cause: None, span })
+ }
+
+ fn base_universe(&self) -> ty::UniverseIndex {
+ self.base_universe.unwrap()
+ }
+
+ fn nice_error(
+ &self,
+ mbcx: &mut MirBorrowckCtxt<'_, 'tcx>,
+ _cause: ObligationCause<'tcx>,
+ placeholder_region: ty::Region<'tcx>,
+ error_region: Option<ty::Region<'tcx>>,
+ ) -> Option<DiagnosticBuilder<'tcx, ErrorGuaranteed>> {
+ try_extract_error_from_region_constraints(
+ mbcx.infcx,
+ placeholder_region,
+ error_region,
+ self.region_constraints.as_ref().unwrap(),
+ // We're using the original `InferCtxt` that we
+ // started MIR borrowchecking with, so the region
+ // constraints have already been taken. Use the data from
+ // our `mbcx` instead.
+ |vid| mbcx.regioncx.var_infos[vid].origin,
+ |vid| mbcx.regioncx.var_infos[vid].universe,
+ )
+ }
+}
+
+#[instrument(skip(fulfill_cx, infcx), level = "debug")]
+fn try_extract_error_from_fulfill_cx<'tcx>(
+ mut fulfill_cx: Box<dyn TraitEngine<'tcx> + 'tcx>,
+ infcx: &InferCtxt<'_, 'tcx>,
+ placeholder_region: ty::Region<'tcx>,
+ error_region: Option<ty::Region<'tcx>>,
+) -> Option<DiagnosticBuilder<'tcx, ErrorGuaranteed>> {
+ // We generally shouldn't have errors here because the query was
+ // already run, but there's no point using `delay_span_bug`
+ // when we're going to emit an error here anyway.
+ let _errors = fulfill_cx.select_all_or_error(infcx);
+ let region_constraints = infcx.with_region_constraints(|r| r.clone());
+ try_extract_error_from_region_constraints(
+ infcx,
+ placeholder_region,
+ error_region,
+ &region_constraints,
+ |vid| infcx.region_var_origin(vid),
+ |vid| infcx.universe_of_region(infcx.tcx.mk_region(ty::ReVar(vid))),
+ )
+}
+
+fn try_extract_error_from_region_constraints<'tcx>(
+ infcx: &InferCtxt<'_, 'tcx>,
+ placeholder_region: ty::Region<'tcx>,
+ error_region: Option<ty::Region<'tcx>>,
+ region_constraints: &RegionConstraintData<'tcx>,
+ mut region_var_origin: impl FnMut(RegionVid) -> RegionVariableOrigin,
+ mut universe_of_region: impl FnMut(RegionVid) -> UniverseIndex,
+) -> Option<DiagnosticBuilder<'tcx, ErrorGuaranteed>> {
+ let (sub_region, cause) =
+ region_constraints.constraints.iter().find_map(|(constraint, cause)| {
+ match *constraint {
+ Constraint::RegSubReg(sub, sup) if sup == placeholder_region && sup != sub => {
+ Some((sub, cause.clone()))
+ }
+ // FIXME: Should this check the universe of the var?
+ Constraint::VarSubReg(vid, sup) if sup == placeholder_region => {
+ Some((infcx.tcx.mk_region(ty::ReVar(vid)), cause.clone()))
+ }
+ _ => None,
+ }
+ })?;
+
+ debug!(?sub_region, "cause = {:#?}", cause);
+ let nice_error = match (error_region, *sub_region) {
+ (Some(error_region), ty::ReVar(vid)) => NiceRegionError::new(
+ infcx,
+ RegionResolutionError::SubSupConflict(
+ vid,
+ region_var_origin(vid),
+ cause.clone(),
+ error_region,
+ cause.clone(),
+ placeholder_region,
+ vec![],
+ ),
+ ),
+ (Some(error_region), _) => NiceRegionError::new(
+ infcx,
+ RegionResolutionError::ConcreteFailure(cause.clone(), error_region, placeholder_region),
+ ),
+ // Note universe here is wrong...
+ (None, ty::ReVar(vid)) => NiceRegionError::new(
+ infcx,
+ RegionResolutionError::UpperBoundUniverseConflict(
+ vid,
+ region_var_origin(vid),
+ universe_of_region(vid),
+ cause.clone(),
+ placeholder_region,
+ ),
+ ),
+ (None, _) => NiceRegionError::new(
+ infcx,
+ RegionResolutionError::ConcreteFailure(cause.clone(), sub_region, placeholder_region),
+ ),
+ };
+ nice_error.try_report_from_nll().or_else(|| {
+ if let SubregionOrigin::Subtype(trace) = cause {
+ Some(
+ infcx.report_and_explain_type_error(*trace, &TypeError::RegionsPlaceholderMismatch),
+ )
+ } else {
+ None
+ }
+ })
+}
diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
new file mode 100644
index 000000000..8bc8964bb
--- /dev/null
+++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
@@ -0,0 +1,2773 @@
+use either::Either;
+use rustc_const_eval::util::CallKind;
+use rustc_data_structures::captures::Captures;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::{
+ struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan,
+};
+use rustc_hir as hir;
+use rustc_hir::intravisit::{walk_block, walk_expr, Visitor};
+use rustc_hir::{AsyncGeneratorKind, GeneratorKind};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_infer::traits::ObligationCause;
+use rustc_middle::mir::tcx::PlaceTy;
+use rustc_middle::mir::{
+ self, AggregateKind, BindingForm, BorrowKind, ClearCrossCrate, ConstraintCategory,
+ FakeReadCause, LocalDecl, LocalInfo, LocalKind, Location, Operand, Place, PlaceRef,
+ ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, VarBindingForm,
+};
+use rustc_middle::ty::{self, subst::Subst, suggest_constraining_type_params, PredicateKind, Ty};
+use rustc_mir_dataflow::move_paths::{InitKind, MoveOutIndex, MovePathIndex};
+use rustc_span::def_id::LocalDefId;
+use rustc_span::hygiene::DesugaringKind;
+use rustc_span::symbol::sym;
+use rustc_span::{BytePos, Span, Symbol};
+use rustc_trait_selection::infer::InferCtxtExt;
+use rustc_trait_selection::traits::TraitEngineExt as _;
+
+use crate::borrow_set::TwoPhaseActivation;
+use crate::borrowck_errors;
+
+use crate::diagnostics::conflict_errors::StorageDeadOrDrop::LocalStorageDead;
+use crate::diagnostics::find_all_local_uses;
+use crate::{
+ borrow_set::BorrowData, diagnostics::Instance, prefixes::IsPrefixOf,
+ InitializationRequiringAction, MirBorrowckCtxt, PrefixSet, WriteKind,
+};
+
+use super::{
+ explain_borrow::{BorrowExplanation, LaterUseKind},
+ DescribePlaceOpt, RegionName, RegionNameSource, UseSpans,
+};
+
+#[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(crate) fn report_use_of_moved_or_uninitialized(
+ &mut self,
+ location: Location,
+ desired_action: InitializationRequiringAction,
+ (moved_place, used_place, span): (PlaceRef<'tcx>, PlaceRef<'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, maybe_reinitialized_locations) = self.get_moved_indexes(location, mpi);
+ debug!(
+ "report_use_of_moved_or_uninitialized: move_site_vec={:?} use_spans={:?}",
+ move_site_vec, use_spans
+ );
+ 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 = PlaceRef { projection: &[], ..used_place };
+
+ if !self.uninitialized_error_reported.insert(root_place) {
+ debug!(
+ "report_use_of_moved_or_uninitialized place: error about {:?} suppressed",
+ root_place
+ );
+ return;
+ }
+
+ let err = self.report_use_of_uninitialized(
+ mpi,
+ used_place,
+ moved_place,
+ desired_action,
+ span,
+ use_spans,
+ );
+ self.buffer_error(err);
+ } else {
+ if let Some((reported_place, _)) = self.has_move_error(&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 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;
+ // `*(_1)` where `_1` is a `Box` is actually a move out.
+ let is_box_move = moved_place.as_ref().projection == [ProjectionElem::Deref]
+ && self.body.local_decls[moved_place.local].ty.is_box();
+
+ !is_box_move
+ && used_place != moved_place.as_ref()
+ && used_place.is_prefix_of(moved_place.as_ref())
+ });
+
+ let partial_str = if is_partial_move { "partial " } else { "" };
+ let partially_str = if is_partial_move { "partially " } else { "" };
+
+ let mut err = self.cannot_act_on_moved_value(
+ span,
+ desired_action.as_noun(),
+ partially_str,
+ self.describe_place_with_options(
+ moved_place,
+ DescribePlaceOpt { including_downcast: true, including_tuple_field: true },
+ ),
+ );
+
+ let reinit_spans = maybe_reinitialized_locations
+ .iter()
+ .take(3)
+ .map(|loc| {
+ self.move_spans(self.move_data.move_paths[mpi].place.as_ref(), *loc)
+ .args_or_use()
+ })
+ .collect::<Vec<Span>>();
+
+ let reinits = maybe_reinitialized_locations.len();
+ if reinits == 1 {
+ err.span_label(reinit_spans[0], "this reinitialization might get skipped");
+ } else if reinits > 1 {
+ err.span_note(
+ MultiSpan::from_spans(reinit_spans),
+ &if reinits <= 3 {
+ format!("these {} reinitializations might get skipped", reinits)
+ } else {
+ format!(
+ "these 3 reinitializations and {} other{} might get skipped",
+ reinits - 3,
+ if reinits == 4 { "" } else { "s" }
+ )
+ },
+ );
+ }
+
+ self.add_moved_or_invoked_closure_note(location, used_place, &mut err);
+
+ let mut is_loop_move = false;
+ let mut in_pattern = false;
+
+ 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 { "" };
+
+ let loop_message = if location == move_out.source || move_site.traversed_back_edge {
+ ", in previous iteration of loop"
+ } else {
+ ""
+ };
+
+ if location == move_out.source {
+ is_loop_move = true;
+ }
+
+ self.explain_captures(
+ &mut err,
+ span,
+ move_span,
+ move_spans,
+ *moved_place,
+ Some(used_place),
+ partially_str,
+ loop_message,
+ move_msg,
+ is_loop_move,
+ maybe_reinitialized_locations.is_empty(),
+ );
+
+ if let (UseSpans::PatUse(span), []) =
+ (move_spans, &maybe_reinitialized_locations[..])
+ {
+ if maybe_reinitialized_locations.is_empty() {
+ err.span_suggestion_verbose(
+ span.shrink_to_lo(),
+ &format!(
+ "borrow this field in the pattern to avoid moving {}",
+ self.describe_place(moved_place.as_ref())
+ .map(|n| format!("`{}`", n))
+ .unwrap_or_else(|| "the value".to_string())
+ ),
+ "ref ",
+ Applicability::MachineApplicable,
+ );
+ in_pattern = true;
+ }
+ }
+ }
+
+ use_spans.var_span_label_path_only(
+ &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 after {}move",
+ desired_action.as_verb_in_past_tense(),
+ partial_str
+ ),
+ );
+ }
+
+ let ty = used_place.ty(self.body, self.infcx.tcx).ty;
+ let needs_note = match ty.kind() {
+ ty::Closure(id, _) => {
+ let tables = self.infcx.tcx.typeck(id.expect_local());
+ let hir_id = self.infcx.tcx.hir().local_def_id_to_hir_id(id.expect_local());
+
+ tables.closure_kind_origins().get(hir_id).is_none()
+ }
+ _ => true,
+ };
+
+ 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;
+
+ // If we're in pattern, we do nothing in favor of the previous suggestion (#80913).
+ if is_loop_move & !in_pattern {
+ if let ty::Ref(_, _, hir::Mutability::Mut) = ty.kind() {
+ // We have a `&mut` ref, we need to reborrow on each iteration (#62112).
+ err.span_suggestion_verbose(
+ span.shrink_to_lo(),
+ &format!(
+ "consider creating a fresh reborrow of {} here",
+ self.describe_place(moved_place)
+ .map(|n| format!("`{}`", n))
+ .unwrap_or_else(|| "the mutable reference".to_string()),
+ ),
+ "&mut *",
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+
+ let opt_name = self.describe_place_with_options(
+ place.as_ref(),
+ DescribePlaceOpt { including_downcast: true, including_tuple_field: true },
+ );
+ let note_msg = match opt_name {
+ Some(ref name) => format!("`{}`", name),
+ None => "value".to_owned(),
+ };
+ if self.suggest_borrow_fn_like(&mut err, ty, &move_site_vec, &note_msg) {
+ // Suppress the next suggestion since we don't want to put more bounds onto
+ // something that already has `Fn`-like bounds (or is a closure), so we can't
+ // restrict anyways.
+ } else {
+ self.suggest_adding_copy_bounds(&mut err, ty, span);
+ }
+
+ if needs_note {
+ let span = if let Some(local) = place.as_local() {
+ Some(self.body.local_decls[local].source_info.span)
+ } else {
+ None
+ };
+ self.note_type_does_not_implement_copy(&mut err, &note_msg, ty, span, partial_str);
+ }
+
+ if let UseSpans::FnSelfUse {
+ kind: CallKind::DerefCoercion { deref_target, deref_target_ty, .. },
+ ..
+ } = use_spans
+ {
+ err.note(&format!(
+ "{} occurs due to deref coercion to `{}`",
+ desired_action.as_noun(),
+ deref_target_ty
+ ));
+
+ // Check first whether the source is accessible (issue #87060)
+ if self.infcx.tcx.sess.source_map().is_span_accessible(deref_target) {
+ err.span_note(deref_target, "deref defined here");
+ }
+ }
+
+ self.buffer_move_error(move_out_indices, (used_place, err));
+ }
+ }
+
+ fn report_use_of_uninitialized(
+ &self,
+ mpi: MovePathIndex,
+ used_place: PlaceRef<'tcx>,
+ moved_place: PlaceRef<'tcx>,
+ desired_action: InitializationRequiringAction,
+ span: Span,
+ use_spans: UseSpans<'tcx>,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ // We need all statements in the body where the binding was assigned to to later find all
+ // the branching code paths where the binding *wasn't* assigned to.
+ let inits = &self.move_data.init_path_map[mpi];
+ let move_path = &self.move_data.move_paths[mpi];
+ let decl_span = self.body.local_decls[move_path.place.local].source_info.span;
+ let mut spans = vec![];
+ for init_idx in inits {
+ let init = &self.move_data.inits[*init_idx];
+ let span = init.span(&self.body);
+ if !span.is_dummy() {
+ spans.push(span);
+ }
+ }
+
+ let (name, desc) = match self.describe_place_with_options(
+ moved_place,
+ DescribePlaceOpt { including_downcast: true, including_tuple_field: true },
+ ) {
+ Some(name) => (format!("`{name}`"), format!("`{name}` ")),
+ None => ("the variable".to_string(), String::new()),
+ };
+ let path = match self.describe_place_with_options(
+ used_place,
+ DescribePlaceOpt { including_downcast: true, including_tuple_field: true },
+ ) {
+ Some(name) => format!("`{name}`"),
+ None => "value".to_string(),
+ };
+
+ // We use the statements were the binding was initialized, and inspect the HIR to look
+ // for the branching codepaths that aren't covered, to point at them.
+ let map = self.infcx.tcx.hir();
+ let body_id = map.body_owned_by(self.mir_def_id());
+ let body = map.body(body_id);
+
+ let mut visitor = ConditionVisitor { spans: &spans, name: &name, errors: vec![] };
+ visitor.visit_body(&body);
+
+ let isnt_initialized = if let InitializationRequiringAction::PartialAssignment
+ | InitializationRequiringAction::Assignment = desired_action
+ {
+ // The same error is emitted for bindings that are *sometimes* initialized and the ones
+ // that are *partially* initialized by assigning to a field of an uninitialized
+ // binding. We differentiate between them for more accurate wording here.
+ "isn't fully initialized"
+ } else if spans
+ .iter()
+ .filter(|i| {
+ // We filter these to avoid misleading wording in cases like the following,
+ // where `x` has an `init`, but it is in the same place we're looking at:
+ // ```
+ // let x;
+ // x += 1;
+ // ```
+ !i.contains(span)
+ // We filter these to avoid incorrect main message on `match-cfg-fake-edges.rs`
+ && !visitor
+ .errors
+ .iter()
+ .map(|(sp, _)| *sp)
+ .any(|sp| span < sp && !sp.contains(span))
+ })
+ .count()
+ == 0
+ {
+ "isn't initialized"
+ } else {
+ "is possibly-uninitialized"
+ };
+
+ let used = desired_action.as_general_verb_in_past_tense();
+ let mut err =
+ struct_span_err!(self, span, E0381, "{used} binding {desc}{isnt_initialized}");
+ use_spans.var_span_label_path_only(
+ &mut err,
+ format!("{} occurs due to use{}", desired_action.as_noun(), use_spans.describe()),
+ );
+
+ if let InitializationRequiringAction::PartialAssignment
+ | InitializationRequiringAction::Assignment = desired_action
+ {
+ err.help(
+ "partial initialization isn't supported, fully initialize the binding with a \
+ default value and mutate it, or use `std::mem::MaybeUninit`",
+ );
+ }
+ err.span_label(span, format!("{path} {used} here but it {isnt_initialized}"));
+
+ let mut shown = false;
+ for (sp, label) in visitor.errors {
+ if sp < span && !sp.overlaps(span) {
+ // When we have a case like `match-cfg-fake-edges.rs`, we don't want to mention
+ // match arms coming after the primary span because they aren't relevant:
+ // ```
+ // let x;
+ // match y {
+ // _ if { x = 2; true } => {}
+ // _ if {
+ // x; //~ ERROR
+ // false
+ // } => {}
+ // _ => {} // We don't want to point to this.
+ // };
+ // ```
+ err.span_label(sp, &label);
+ shown = true;
+ }
+ }
+ if !shown {
+ for sp in &spans {
+ if *sp < span && !sp.overlaps(span) {
+ err.span_label(*sp, "binding initialized here in some conditions");
+ }
+ }
+ }
+ err.span_label(decl_span, "binding declared here but left uninitialized");
+ err
+ }
+
+ fn suggest_borrow_fn_like(
+ &self,
+ err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>,
+ ty: Ty<'tcx>,
+ move_sites: &[MoveSite],
+ value_name: &str,
+ ) -> bool {
+ let tcx = self.infcx.tcx;
+
+ // Find out if the predicates show that the type is a Fn or FnMut
+ let find_fn_kind_from_did =
+ |predicates: ty::EarlyBinder<&[(ty::Predicate<'tcx>, Span)]>, substs| {
+ predicates.0.iter().find_map(|(pred, _)| {
+ let pred = if let Some(substs) = substs {
+ predicates.rebind(*pred).subst(tcx, substs).kind().skip_binder()
+ } else {
+ pred.kind().skip_binder()
+ };
+ if let ty::PredicateKind::Trait(pred) = pred && pred.self_ty() == ty {
+ if Some(pred.def_id()) == tcx.lang_items().fn_trait() {
+ return Some(hir::Mutability::Not);
+ } else if Some(pred.def_id()) == tcx.lang_items().fn_mut_trait() {
+ return Some(hir::Mutability::Mut);
+ }
+ }
+ None
+ })
+ };
+
+ // If the type is opaque/param/closure, and it is Fn or FnMut, let's suggest (mutably)
+ // borrowing the type, since `&mut F: FnMut` iff `F: FnMut` and similarly for `Fn`.
+ // These types seem reasonably opaque enough that they could be substituted with their
+ // borrowed variants in a function body when we see a move error.
+ let borrow_level = match ty.kind() {
+ ty::Param(_) => find_fn_kind_from_did(
+ tcx.bound_explicit_predicates_of(self.mir_def_id().to_def_id())
+ .map_bound(|p| p.predicates),
+ None,
+ ),
+ ty::Opaque(did, substs) => {
+ find_fn_kind_from_did(tcx.bound_explicit_item_bounds(*did), Some(*substs))
+ }
+ ty::Closure(_, substs) => match substs.as_closure().kind() {
+ ty::ClosureKind::Fn => Some(hir::Mutability::Not),
+ ty::ClosureKind::FnMut => Some(hir::Mutability::Mut),
+ _ => None,
+ },
+ _ => None,
+ };
+
+ let Some(borrow_level) = borrow_level else { return false; };
+ let sugg = move_sites
+ .iter()
+ .map(|move_site| {
+ 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 suggestion = if borrow_level == hir::Mutability::Mut {
+ "&mut ".to_string()
+ } else {
+ "&".to_string()
+ };
+ (move_span.shrink_to_lo(), suggestion)
+ })
+ .collect();
+ err.multipart_suggestion_verbose(
+ &format!(
+ "consider {}borrowing {value_name}",
+ if borrow_level == hir::Mutability::Mut { "mutably " } else { "" }
+ ),
+ sugg,
+ Applicability::MaybeIncorrect,
+ );
+ true
+ }
+
+ fn suggest_adding_copy_bounds(
+ &self,
+ err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>,
+ ty: Ty<'tcx>,
+ span: Span,
+ ) {
+ let tcx = self.infcx.tcx;
+ let generics = tcx.generics_of(self.mir_def_id());
+
+ let Some(hir_generics) = tcx
+ .typeck_root_def_id(self.mir_def_id().to_def_id())
+ .as_local()
+ .and_then(|def_id| tcx.hir().get_generics(def_id))
+ else { return; };
+ // Try to find predicates on *generic params* that would allow copying `ty`
+ let predicates: Result<Vec<_>, _> = tcx.infer_ctxt().enter(|infcx| {
+ let mut fulfill_cx = <dyn rustc_infer::traits::TraitEngine<'_>>::new(infcx.tcx);
+
+ let copy_did = infcx.tcx.lang_items().copy_trait().unwrap();
+ let cause = ObligationCause::new(
+ span,
+ self.mir_hir_id(),
+ rustc_infer::traits::ObligationCauseCode::MiscObligation,
+ );
+ fulfill_cx.register_bound(
+ &infcx,
+ self.param_env,
+ // Erase any region vids from the type, which may not be resolved
+ infcx.tcx.erase_regions(ty),
+ copy_did,
+ cause,
+ );
+ // Select all, including ambiguous predicates
+ let errors = fulfill_cx.select_all_or_error(&infcx);
+
+ // Only emit suggestion if all required predicates are on generic
+ errors
+ .into_iter()
+ .map(|err| match err.obligation.predicate.kind().skip_binder() {
+ PredicateKind::Trait(predicate) => match predicate.self_ty().kind() {
+ ty::Param(param_ty) => Ok((
+ generics.type_param(param_ty, tcx),
+ predicate.trait_ref.print_only_trait_path().to_string(),
+ )),
+ _ => Err(()),
+ },
+ _ => Err(()),
+ })
+ .collect()
+ });
+
+ if let Ok(predicates) = predicates {
+ suggest_constraining_type_params(
+ tcx,
+ hir_generics,
+ err,
+ predicates
+ .iter()
+ .map(|(param, constraint)| (param.name.as_str(), &**constraint, None)),
+ );
+ }
+ }
+
+ pub(crate) 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 = self.describe_any_place(place.as_ref());
+ let borrow_msg = self.describe_any_place(borrow.borrowed_place.as_ref());
+
+ 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_any_place(place.as_ref()));
+ 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_path_only(
+ &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()),
+ "moved",
+ );
+
+ 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),
+ None,
+ );
+ self.buffer_error(err);
+ }
+
+ pub(crate) fn report_use_while_mutably_borrowed(
+ &mut self,
+ location: Location,
+ (place, _span): (Place<'tcx>, Span),
+ borrow: &BorrowData<'tcx>,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ 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();
+
+ // If the attempted use is in a closure then we do not care about the path span of the place we are currently trying to use
+ // we call `var_span_label` on `borrow_spans` to annotate if the existing borrow was in a closure
+ let mut err = self.cannot_use_when_mutably_borrowed(
+ span,
+ &self.describe_any_place(place.as_ref()),
+ borrow_span,
+ &self.describe_any_place(borrow.borrowed_place.as_ref()),
+ );
+
+ borrow_spans.var_span_label(
+ &mut err,
+ {
+ let place = &borrow.borrowed_place;
+ let desc_place = self.describe_any_place(place.as_ref());
+ format!("borrow occurs due to use of {}{}", desc_place, borrow_spans.describe())
+ },
+ "mutable",
+ );
+
+ self.explain_why_borrow_contains_point(location, borrow, None)
+ .add_explanation_to_diagnostic(
+ self.infcx.tcx,
+ &self.body,
+ &self.local_names,
+ &mut err,
+ "",
+ None,
+ None,
+ );
+ err
+ }
+
+ pub(crate) fn report_conflicting_borrow(
+ &mut self,
+ location: Location,
+ (place, span): (Place<'tcx>, Span),
+ gen_borrow_kind: BorrowKind,
+ issued_borrow: &BorrowData<'tcx>,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ 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 ";
+ let mut err = self.cannot_mutably_borrow_multiply(
+ span,
+ &desc_place,
+ &msg_place,
+ issued_span,
+ &msg_borrow,
+ None,
+ );
+ self.suggest_split_at_mut_if_applicable(
+ &mut err,
+ place,
+ issued_borrow.borrowed_place,
+ );
+ err
+ }
+
+ (BorrowKind::Unique, BorrowKind::Unique) => {
+ first_borrow_desc = "first ";
+ self.cannot_uniquely_borrow_by_two_closures(span, &desc_place, issued_span, None)
+ }
+
+ (BorrowKind::Mut { .. } | 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(),
+ ),
+ "immutable",
+ );
+
+ 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::Shallow)
+ | (
+ BorrowKind::Shallow,
+ BorrowKind::Mut { .. }
+ | BorrowKind::Unique
+ | BorrowKind::Shared
+ | 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(),),
+ gen_borrow_kind.describe_mutability(),
+ );
+ } else {
+ let borrow_place = &issued_borrow.borrowed_place;
+ let borrow_place_desc = self.describe_any_place(borrow_place.as_ref());
+ issued_spans.var_span_label(
+ &mut err,
+ format!(
+ "first borrow occurs due to use of {}{}",
+ borrow_place_desc,
+ issued_spans.describe(),
+ ),
+ issued_borrow.kind.describe_mutability(),
+ );
+
+ borrow_spans.var_span_label(
+ &mut err,
+ format!(
+ "second borrow occurs due to use of {}{}",
+ desc_place,
+ borrow_spans.describe(),
+ ),
+ gen_borrow_kind.describe_mutability(),
+ );
+ }
+
+ 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,
+ Some((issued_span, span)),
+ );
+
+ self.suggest_using_local_if_applicable(&mut err, location, issued_borrow, explanation);
+
+ err
+ }
+
+ #[instrument(level = "debug", skip(self, err))]
+ fn suggest_using_local_if_applicable(
+ &self,
+ err: &mut Diagnostic,
+ location: Location,
+ issued_borrow: &BorrowData<'tcx>,
+ explanation: BorrowExplanation<'tcx>,
+ ) {
+ let used_in_call = matches!(
+ explanation,
+ BorrowExplanation::UsedLater(LaterUseKind::Call | LaterUseKind::Other, _call_span, _)
+ );
+ if !used_in_call {
+ debug!("not later used in call");
+ return;
+ }
+
+ let use_span =
+ if let BorrowExplanation::UsedLater(LaterUseKind::Other, use_span, _) = explanation {
+ Some(use_span)
+ } else {
+ None
+ };
+
+ let outer_call_loc =
+ if let TwoPhaseActivation::ActivatedAt(loc) = issued_borrow.activation_location {
+ loc
+ } else {
+ issued_borrow.reserve_location
+ };
+ let outer_call_stmt = self.body.stmt_at(outer_call_loc);
+
+ let inner_param_location = location;
+ let Some(inner_param_stmt) = self.body.stmt_at(inner_param_location).left() else {
+ debug!("`inner_param_location` {:?} is not for a statement", inner_param_location);
+ return;
+ };
+ let Some(&inner_param) = inner_param_stmt.kind.as_assign().map(|(p, _)| p) else {
+ debug!(
+ "`inner_param_location` {:?} is not for an assignment: {:?}",
+ inner_param_location, inner_param_stmt
+ );
+ return;
+ };
+ let inner_param_uses = find_all_local_uses::find(self.body, inner_param.local);
+ let Some((inner_call_loc, inner_call_term)) = inner_param_uses.into_iter().find_map(|loc| {
+ let Either::Right(term) = self.body.stmt_at(loc) else {
+ debug!("{:?} is a statement, so it can't be a call", loc);
+ return None;
+ };
+ let TerminatorKind::Call { args, .. } = &term.kind else {
+ debug!("not a call: {:?}", term);
+ return None;
+ };
+ debug!("checking call args for uses of inner_param: {:?}", args);
+ if args.contains(&Operand::Move(inner_param)) {
+ Some((loc, term))
+ } else {
+ None
+ }
+ }) else {
+ debug!("no uses of inner_param found as a by-move call arg");
+ return;
+ };
+ debug!("===> outer_call_loc = {:?}, inner_call_loc = {:?}", outer_call_loc, inner_call_loc);
+
+ let inner_call_span = inner_call_term.source_info.span;
+ let outer_call_span = match use_span {
+ Some(span) => span,
+ None => outer_call_stmt.either(|s| s.source_info, |t| t.source_info).span,
+ };
+ if outer_call_span == inner_call_span || !outer_call_span.contains(inner_call_span) {
+ // FIXME: This stops the suggestion in some cases where it should be emitted.
+ // Fix the spans for those cases so it's emitted correctly.
+ debug!(
+ "outer span {:?} does not strictly contain inner span {:?}",
+ outer_call_span, inner_call_span
+ );
+ return;
+ }
+ err.span_help(
+ inner_call_span,
+ &format!(
+ "try adding a local storing this{}...",
+ if use_span.is_some() { "" } else { " argument" }
+ ),
+ );
+ err.span_help(
+ outer_call_span,
+ &format!(
+ "...and then using that local {}",
+ if use_span.is_some() { "here" } else { "as the argument to this call" }
+ ),
+ );
+ }
+
+ fn suggest_split_at_mut_if_applicable(
+ &self,
+ err: &mut Diagnostic,
+ place: Place<'tcx>,
+ borrowed_place: Place<'tcx>,
+ ) {
+ if let ([ProjectionElem::Index(_)], [ProjectionElem::Index(_)]) =
+ (&place.projection[..], &borrowed_place.projection[..])
+ {
+ err.help(
+ "consider using `.split_at_mut(position)` or similar method to obtain \
+ two mutable non-overlapping sub-slices",
+ );
+ }
+ }
+
+ /// 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:
+ ///
+ /// ```text
+ /// 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(crate) 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| {
+ // Need to use fn call syntax `PlaceRef::ty` to determine the type of `place_base`;
+ // using a type annotation in the closure argument instead leads to a lifetime error.
+ let ty = PlaceRef::ty(&place_base, self.body, self.infcx.tcx).ty;
+ ty.ty_adt_def().filter(|adt| adt.is_union()).map(|_| ty)
+ };
+
+ // 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 an access to a different field.
+ for (place_base, elem) in first_borrowed_place.iter_projections().rev() {
+ match elem {
+ ProjectionElem::Field(field, _) if union_ty(place_base).is_some() => {
+ return Some((place_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 an access to a different field of the same union.
+ for (place_base, elem) in second_borrowed_place.iter_projections().rev() {
+ if let ProjectionElem::Field(field, _) = elem {
+ if let Some(union_ty) = union_ty(place_base) {
+ if field != target_field && place_base == target_base {
+ return Some((
+ self.describe_any_place(place_base),
+ self.describe_any_place(first_borrowed_place.as_ref()),
+ self.describe_any_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.
+ (
+ self.describe_any_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(crate) fn report_borrowed_value_does_not_live_long_enough(
+ &mut self,
+ location: Location,
+ borrow: &BorrowData<'tcx>,
+ place_span: (Place<'tcx>, Span),
+ kind: Option<WriteKind>,
+ ) {
+ 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_path_span();
+
+ assert!(root_place.projection.is_empty());
+ let proper_span = self.body.local_decls[root_place.local].source_info.span;
+
+ let root_place_projection = self.infcx.tcx.intern_place_elems(root_place.projection);
+
+ if self.access_place_error_reported.contains(&(
+ Place { local: root_place.local, 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 { local: root_place.local, projection: root_place_projection },
+ borrow_span,
+ ));
+
+ let borrowed_local = borrow.borrowed_place.local;
+ if self.body.local_decls[borrowed_local].is_ref_to_thread_local() {
+ let err =
+ self.report_thread_local_value_does_not_live_long_enough(drop_span, borrow_span);
+ self.buffer_error(err);
+ 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<Fn() -> &'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(_)
+ | ConstraintCategory::CallArgument(_)
+ | ConstraintCategory::OpaqueType),
+ from_closure: false,
+ ref region_name,
+ span,
+ ..
+ },
+ ) if borrow_spans.for_generator() | borrow_spans.for_closure() => 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, 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,
+ ),
+ };
+
+ self.buffer_error(err);
+ }
+
+ fn report_local_value_does_not_live_long_enough(
+ &mut self,
+ location: Location,
+ name: &str,
+ borrow: &BorrowData<'tcx>,
+ drop_span: Span,
+ borrow_spans: UseSpans<'tcx>,
+ explanation: BorrowExplanation<'tcx>,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ debug!(
+ "report_local_value_does_not_live_long_enough(\
+ {:?}, {:?}, {:?}, {:?}, {:?}\
+ )",
+ location, name, borrow, drop_span, borrow_spans
+ );
+
+ let borrow_span = borrow_spans.var_or_use_path_span();
+ 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),
+ );
+
+ let fn_hir_id = self.mir_hir_id();
+ err.span_label(
+ drop_span,
+ format!(
+ "...but `{}` will be dropped here, when the {} returns",
+ name,
+ self.infcx
+ .tcx
+ .hir()
+ .opt_name(fn_hir_id)
+ .map(|name| format!("function `{}`", name))
+ .unwrap_or_else(|| {
+ match &self
+ .infcx
+ .tcx
+ .typeck(self.mir_def_id())
+ .node_type(fn_hir_id)
+ .kind()
+ {
+ ty::Closure(..) => "enclosing closure",
+ ty::Generator(..) => "enclosing generator",
+ kind => bug!("expected closure or generator, found {:?}", kind),
+ }
+ .to_string()
+ })
+ ),
+ );
+
+ 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 <https://doc.rust-lang.org/book/ch04-02-\
+ references-and-borrowing.html#dangling-references>",
+ );
+
+ if let BorrowExplanation::MustBeValidFor { .. } = explanation {
+ } else {
+ explanation.add_explanation_to_diagnostic(
+ self.infcx.tcx,
+ &self.body,
+ &self.local_names,
+ &mut err,
+ "",
+ None,
+ 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,
+ None,
+ );
+ }
+
+ err
+ }
+
+ fn report_borrow_conflicts_with_destructor(
+ &mut self,
+ location: Location,
+ borrow: &BorrowData<'tcx>,
+ (place, drop_span): (Place<'tcx>, Span),
+ kind: Option<WriteKind>,
+ 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,
+ None,
+ );
+
+ self.buffer_error(err);
+ }
+
+ fn report_thread_local_value_does_not_live_long_enough(
+ &mut self,
+ drop_span: Span,
+ borrow_span: Span,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ 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<'tcx>,
+ proper_span: Span,
+ explanation: BorrowExplanation<'tcx>,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ 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.
+ let sm = self.infcx.tcx.sess.source_map();
+ let mut suggested = false;
+ let msg = "consider using a `let` binding to create a longer lived value";
+
+ /// We check that there's a single level of block nesting to ensure always correct
+ /// suggestions. If we don't, then we only provide a free-form message to avoid
+ /// misleading users in cases like `src/test/ui/nll/borrowed-temporary-error.rs`.
+ /// We could expand the analysis to suggest hoising all of the relevant parts of
+ /// the users' code to make the code compile, but that could be too much.
+ struct NestedStatementVisitor {
+ span: Span,
+ current: usize,
+ found: usize,
+ }
+
+ impl<'tcx> Visitor<'tcx> for NestedStatementVisitor {
+ fn visit_block(&mut self, block: &hir::Block<'tcx>) {
+ self.current += 1;
+ walk_block(self, block);
+ self.current -= 1;
+ }
+ fn visit_expr(&mut self, expr: &hir::Expr<'tcx>) {
+ if self.span == expr.span {
+ self.found = self.current;
+ }
+ walk_expr(self, expr);
+ }
+ }
+ let source_info = self.body.source_info(location);
+ if let Some(scope) = self.body.source_scopes.get(source_info.scope)
+ && let ClearCrossCrate::Set(scope_data) = &scope.local_data
+ && let Some(node) = self.infcx.tcx.hir().find(scope_data.lint_root)
+ && let Some(id) = node.body_id()
+ && let hir::ExprKind::Block(block, _) = self.infcx.tcx.hir().body(id).value.kind
+ {
+ for stmt in block.stmts {
+ let mut visitor = NestedStatementVisitor {
+ span: proper_span,
+ current: 0,
+ found: 0,
+ };
+ visitor.visit_stmt(stmt);
+ if visitor.found == 0
+ && stmt.span.contains(proper_span)
+ && let Some(p) = sm.span_to_margin(stmt.span)
+ && let Ok(s) = sm.span_to_snippet(proper_span)
+ {
+ let addition = format!("let binding = {};\n{}", s, " ".repeat(p));
+ err.multipart_suggestion_verbose(
+ msg,
+ vec![
+ (stmt.span.shrink_to_lo(), addition),
+ (proper_span, "binding".to_string()),
+ ],
+ Applicability::MaybeIncorrect,
+ );
+ suggested = true;
+ break;
+ }
+ }
+ }
+ if !suggested {
+ err.note(msg);
+ }
+ }
+ _ => {}
+ }
+ explanation.add_explanation_to_diagnostic(
+ self.infcx.tcx,
+ &self.body,
+ &self.local_names,
+ &mut err,
+ "",
+ None,
+ 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<'tcx>,
+ opt_place_desc: Option<&String>,
+ ) -> Option<DiagnosticBuilder<'cx, ErrorGuaranteed>> {
+ 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 == ty::CAPTURE_STRUCT_LOCAL =>
+ {
+ "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 = root_place.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);
+
+ let tcx = self.infcx.tcx;
+ let ty_params = ty::List::empty();
+
+ let return_ty = self.regioncx.universal_regions().unnormalized_output_ty;
+ let return_ty = tcx.erase_regions(return_ty);
+
+ // to avoid panics
+ if let Some(iter_trait) = tcx.get_diagnostic_item(sym::Iterator)
+ && self
+ .infcx
+ .type_implements_trait(iter_trait, return_ty, ty_params, self.param_env)
+ .must_apply_modulo_regions()
+ {
+ err.span_suggestion_hidden(
+ return_span.shrink_to_hi(),
+ "use `.collect()` to allocate the iterator",
+ ".collect::<Vec<_>>()",
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+
+ Some(err)
+ }
+
+ fn report_escaping_closure_capture(
+ &mut self,
+ use_span: UseSpans<'tcx>,
+ var_span: Span,
+ fr_name: &RegionName,
+ category: ConstraintCategory<'tcx>,
+ constraint_span: Span,
+ captured_var: &str,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ let tcx = self.infcx.tcx;
+ let args_span = use_span.args_or_use();
+
+ let (sugg_span, suggestion) = match tcx.sess.source_map().span_to_snippet(args_span) {
+ Ok(string) => {
+ if string.starts_with("async ") {
+ let pos = args_span.lo() + BytePos(6);
+ (args_span.with_lo(pos).with_hi(pos), "move ")
+ } else if string.starts_with("async|") {
+ let pos = args_span.lo() + BytePos(5);
+ (args_span.with_lo(pos).with_hi(pos), " move")
+ } else {
+ (args_span.shrink_to_lo(), "move ")
+ }
+ }
+ Err(_) => (args_span, "move |<args>| <body>"),
+ };
+ 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 function found."),
+ },
+ GeneratorKind::Gen => "generator",
+ },
+ None => "closure",
+ };
+
+ let mut err =
+ self.cannot_capture_in_long_lived_closure(args_span, kind, captured_var, var_span);
+ err.span_suggestion_verbose(
+ sugg_span,
+ &format!(
+ "to force the {} to take ownership of {} (and any \
+ other referenced variables), use the `move` keyword",
+ kind, captured_var
+ ),
+ suggestion,
+ Applicability::MachineApplicable,
+ );
+
+ match category {
+ ConstraintCategory::Return(_) | ConstraintCategory::OpaqueType => {
+ let msg = format!("{} is returned here", kind);
+ err.span_note(constraint_span, &msg);
+ }
+ ConstraintCategory::CallArgument(_) => {
+ fr_name.highlight_region_name(&mut err);
+ if matches!(use_span.generator_kind(), Some(GeneratorKind::Async(_))) {
+ err.note(
+ "async blocks are not executed immediately and must either take a \
+ reference or ownership of outside variables they use",
+ );
+ } else {
+ let msg = format!("function requires argument type to outlive `{}`", fr_name);
+ err.span_note(constraint_span, &msg);
+ }
+ }
+ _ => bug!(
+ "report_escaping_closure_capture called with unexpected constraint \
+ category: `{:?}`",
+ category
+ ),
+ }
+
+ err
+ }
+
+ fn report_escaping_data(
+ &mut self,
+ borrow_span: Span,
+ name: &Option<String>,
+ upvar_span: Span,
+ upvar_name: Symbol,
+ escape_span: Span,
+ ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
+ let tcx = self.infcx.tcx;
+
+ let (_, escapes_from) = tcx.article_and_description(self.mir_def_id().to_def_id());
+
+ let mut err =
+ borrowck_errors::borrowed_data_escapes_closure(tcx, escape_span, escapes_from);
+
+ err.span_label(
+ upvar_span,
+ format!("`{}` 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<MoveSite>, Vec<Location>) {
+ fn predecessor_locations<'tcx, 'a>(
+ body: &'a mir::Body<'tcx>,
+ location: Location,
+ ) -> impl Iterator<Item = Location> + Captures<'tcx> + 'a {
+ if location.statement_index == 0 {
+ let predecessors = body.basic_blocks.predecessors()[location.block].to_vec();
+ Either::Left(predecessors.into_iter().map(move |bb| body.terminator_loc(bb)))
+ } else {
+ Either::Right(std::iter::once(Location {
+ statement_index: location.statement_index - 1,
+ ..location
+ }))
+ }
+ }
+
+ let mut mpis = vec![mpi];
+ let move_paths = &self.move_data.move_paths;
+ mpis.extend(move_paths[mpi].parents(move_paths).map(|(mpi, _)| mpi));
+
+ let mut stack = Vec::new();
+ let mut back_edge_stack = Vec::new();
+
+ predecessor_locations(self.body, location).for_each(|predecessor| {
+ if location.dominates(predecessor, &self.dominators) {
+ back_edge_stack.push(predecessor)
+ } else {
+ stack.push(predecessor);
+ }
+ });
+
+ let mut reached_start = false;
+
+ /* Check if the mpi is initialized as an argument */
+ let mut is_argument = false;
+ for arg in self.body.args_iter() {
+ let path = self.move_data.rev_lookup.find_local(arg);
+ if mpis.contains(&path) {
+ is_argument = true;
+ }
+ }
+
+ let mut visited = FxHashSet::default();
+ let mut move_locations = FxHashSet::default();
+ let mut reinits = vec![];
+ let mut result = vec![];
+
+ let mut dfs_iter = |result: &mut Vec<MoveSite>, location: Location, is_back_edge: bool| {
+ debug!(
+ "report_use_of_moved_or_uninitialized: (current_location={:?}, back_edge={})",
+ location, is_back_edge
+ );
+
+ if !visited.insert(location) {
+ return true;
+ }
+
+ // 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.
+ for moi in &self.move_data.loc_map[location] {
+ debug!("report_use_of_moved_or_uninitialized: moi={:?}", moi);
+ let path = self.move_data.moves[*moi].path;
+ if mpis.contains(&path) {
+ debug!(
+ "report_use_of_moved_or_uninitialized: found {:?}",
+ move_paths[path].place
+ );
+ result.push(MoveSite { moi: *moi, traversed_back_edge: is_back_edge });
+ move_locations.insert(location);
+
+ // 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.
+ return true;
+ }
+ }
+ }
+
+ // check for inits
+ let mut any_match = false;
+ for ii in &self.move_data.init_loc_map[location] {
+ let init = self.move_data.inits[*ii];
+ match init.kind {
+ InitKind::Deep | InitKind::NonPanicPathOnly => {
+ if mpis.contains(&init.path) {
+ any_match = true;
+ }
+ }
+ InitKind::Shallow => {
+ if mpi == init.path {
+ any_match = true;
+ }
+ }
+ }
+ }
+ if any_match {
+ reinits.push(location);
+ return true;
+ }
+ return false;
+ };
+
+ while let Some(location) = stack.pop() {
+ if dfs_iter(&mut result, location, false) {
+ continue;
+ }
+
+ let mut has_predecessor = false;
+ predecessor_locations(self.body, location).for_each(|predecessor| {
+ if location.dominates(predecessor, &self.dominators) {
+ back_edge_stack.push(predecessor)
+ } else {
+ stack.push(predecessor);
+ }
+ has_predecessor = true;
+ });
+
+ if !has_predecessor {
+ reached_start = true;
+ }
+ }
+ if (is_argument || !reached_start) && result.is_empty() {
+ /* Process back edges (moves in future loop iterations) only if
+ the move path is definitely initialized upon loop entry,
+ to avoid spurious "in previous iteration" errors.
+ During DFS, if there's a path from the error back to the start
+ of the function with no intervening init or move, then the
+ move path may be uninitialized at loop entry.
+ */
+ while let Some(location) = back_edge_stack.pop() {
+ if dfs_iter(&mut result, location, true) {
+ continue;
+ }
+
+ predecessor_locations(self.body, location)
+ .for_each(|predecessor| back_edge_stack.push(predecessor));
+ }
+ }
+
+ // Check if we can reach these reinits from a move location.
+ let reinits_reachable = reinits
+ .into_iter()
+ .filter(|reinit| {
+ let mut visited = FxHashSet::default();
+ let mut stack = vec![*reinit];
+ while let Some(location) = stack.pop() {
+ if !visited.insert(location) {
+ continue;
+ }
+ if move_locations.contains(&location) {
+ return true;
+ }
+ stack.extend(predecessor_locations(self.body, location));
+ }
+ false
+ })
+ .collect::<Vec<Location>>();
+ (result, reinits_reachable)
+ }
+
+ pub(crate) 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();
+
+ let descr_place = self.describe_any_place(place.as_ref());
+ 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,
+ &descr_place,
+ section,
+ "assign",
+ );
+ loan_spans.var_span_label(
+ &mut err,
+ format!("borrow occurs due to use{}", loan_spans.describe()),
+ loan.kind.describe_mutability(),
+ );
+
+ self.buffer_error(err);
+
+ return;
+ }
+ }
+
+ let mut err = self.cannot_assign_to_borrowed(span, loan_span, &descr_place);
+
+ loan_spans.var_span_label(
+ &mut err,
+ format!("borrow occurs due to use{}", loan_spans.describe()),
+ loan.kind.describe_mutability(),
+ );
+
+ self.explain_why_borrow_contains_point(location, loan, None).add_explanation_to_diagnostic(
+ self.infcx.tcx,
+ &self.body,
+ &self.local_names,
+ &mut err,
+ "",
+ None,
+ None,
+ );
+
+ self.explain_deref_coercion(loan, &mut err);
+
+ self.buffer_error(err);
+ }
+
+ fn explain_deref_coercion(&mut self, loan: &BorrowData<'tcx>, err: &mut Diagnostic) {
+ let tcx = self.infcx.tcx;
+ if let (
+ Some(Terminator { kind: TerminatorKind::Call { from_hir_call: false, .. }, .. }),
+ Some((method_did, method_substs)),
+ ) = (
+ &self.body[loan.reserve_location.block].terminator,
+ rustc_const_eval::util::find_self_call(
+ tcx,
+ self.body,
+ loan.assigned_place.local,
+ loan.reserve_location.block,
+ ),
+ ) {
+ if tcx.is_diagnostic_item(sym::deref_method, method_did) {
+ let deref_target =
+ tcx.get_diagnostic_item(sym::deref_target).and_then(|deref_target| {
+ Instance::resolve(tcx, self.param_env, deref_target, method_substs)
+ .transpose()
+ });
+ if let Some(Ok(instance)) = deref_target {
+ let deref_target_ty = instance.ty(tcx, self.param_env);
+ err.note(&format!(
+ "borrow occurs due to deref coercion to `{}`",
+ deref_target_ty
+ ));
+ err.span_note(tcx.def_span(instance.def_id()), "deref defined here");
+ }
+ }
+ }
+ }
+
+ /// 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(crate) 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:
+ Some(box LocalInfo::User(
+ ClearCrossCrate::Clear
+ | ClearCrossCrate::Set(BindingForm::Var(VarBindingForm {
+ opt_match_place: None,
+ ..
+ })),
+ ))
+ | Some(box LocalInfo::StaticRef { .. })
+ | None,
+ ..
+ })
+ | None => (self.describe_any_place(place.as_ref()), assigned_span),
+ Some(decl) => (self.describe_any_place(err_place.as_ref()), decl.source_info.span),
+ };
+
+ let mut err = self.cannot_reassign_immutable(span, &place_description, from_arg);
+ let msg = if from_arg {
+ "cannot assign to immutable argument"
+ } else {
+ "cannot assign twice to immutable variable"
+ };
+ if span != assigned_span && !from_arg {
+ err.span_label(assigned_span, format!("first assignment to {}", place_description));
+ }
+ if let Some(decl) = local_decl
+ && let Some(name) = local_name
+ && decl.can_be_made_mutable()
+ {
+ err.span_suggestion(
+ decl.source_info.span,
+ "consider making this binding mutable",
+ format!("mut {}", name),
+ Applicability::MachineApplicable,
+ );
+ }
+ err.span_label(span, msg);
+ self.buffer_error(err);
+ }
+
+ fn classify_drop_access_kind(&self, place: PlaceRef<'tcx>) -> StorageDeadOrDrop<'tcx> {
+ let tcx = self.infcx.tcx;
+ let (kind, _place_ty) = place.projection.iter().fold(
+ (LocalStorageDead, PlaceTy::from_ty(self.body.local_decls[place.local].ty)),
+ |(kind, place_ty), &elem| {
+ (
+ match elem {
+ ProjectionElem::Deref => match kind {
+ StorageDeadOrDrop::LocalStorageDead
+ | StorageDeadOrDrop::BoxedStorageDead => {
+ assert!(
+ place_ty.ty.is_box(),
+ "Drop of value behind a reference or raw pointer"
+ );
+ StorageDeadOrDrop::BoxedStorageDead
+ }
+ StorageDeadOrDrop::Destructor(_) => kind,
+ },
+ ProjectionElem::Field(..) | ProjectionElem::Downcast(..) => {
+ match place_ty.ty.kind() {
+ ty::Adt(def, _) if def.has_dtor(tcx) => {
+ // Report the outermost adt with a destructor
+ match kind {
+ StorageDeadOrDrop::Destructor(_) => kind,
+ StorageDeadOrDrop::LocalStorageDead
+ | StorageDeadOrDrop::BoxedStorageDead => {
+ StorageDeadOrDrop::Destructor(place_ty.ty)
+ }
+ }
+ }
+ _ => kind,
+ }
+ }
+ ProjectionElem::ConstantIndex { .. }
+ | ProjectionElem::Subslice { .. }
+ | ProjectionElem::Index(_) => kind,
+ },
+ place_ty.projection_ty(tcx, elem),
+ )
+ },
+ );
+ kind
+ }
+
+ /// 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_middle::mir::visit::Visitor;
+ struct FakeReadCauseFinder<'tcx> {
+ place: Place<'tcx>,
+ cause: Option<FakeReadCause>,
+ }
+ impl<'tcx> Visitor<'tcx> for FakeReadCauseFinder<'tcx> {
+ fn visit_statement(&mut self, statement: &Statement<'tcx>, _: Location) {
+ match statement {
+ Statement { kind: StatementKind::FakeRead(box (cause, 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<AnnotatedBorrowFnSignature<'tcx>> {
+ // 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().to_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),
+ ref operands,
+ ) = *rvalue
+ {
+ for operand in operands {
+ let (Operand::Copy(assigned_from) | Operand::Move(assigned_from)) = operand else {
+ continue;
+ };
+ debug!(
+ "annotate_argument_and_return_for_borrow: assigned_from={:?}",
+ assigned_from
+ );
+
+ // Find the local from the operand.
+ let Some(assigned_from_local) = assigned_from.local_or_deref_local() else {
+ 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, substs.as_closure().sig());
+ 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 Some(assigned_from_local) = assigned_from.local_or_deref_local() else { 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, target: Some(_), args, .. } =
+ &terminator.kind
+ {
+ if let Some(assigned_to) = destination.as_local() {
+ debug!(
+ "annotate_argument_and_return_for_borrow: assigned_to={:?} args={:?}",
+ assigned_to, args
+ );
+ for operand in args {
+ let (Operand::Copy(assigned_from) | Operand::Move(assigned_from)) = operand else {
+ 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: LocalDefId,
+ sig: ty::PolyFnSig<'tcx>,
+ ) -> Option<AnnotatedBorrowFnSignature<'tcx>> {
+ debug!("annotate_fn_sig: did={:?} sig={:?}", did, sig);
+ let is_closure = self.infcx.tcx.is_closure(did.to_def_id());
+ let fn_hir_id = self.infcx.tcx.hir().local_def_id_to_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_middle::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::FnRetTy::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()?;
+ 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(crate) fn emit(&self, cx: &mut MirBorrowckCtxt<'_, 'tcx>, diag: &mut Diagnostic) -> 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 <https://doc.rust-lang.org/book/ch10-03-\
+ lifetime-syntax.html#lifetime-elision>",
+ );
+
+ 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
+ }
+ }
+ }
+}
+
+/// Detect whether one of the provided spans is a statement nested within the top-most visited expr
+struct ReferencedStatementsVisitor<'a>(&'a [Span], bool);
+
+impl<'a, 'v> Visitor<'v> for ReferencedStatementsVisitor<'a> {
+ fn visit_stmt(&mut self, s: &'v hir::Stmt<'v>) {
+ match s.kind {
+ hir::StmtKind::Semi(expr) if self.0.contains(&expr.span) => {
+ self.1 = true;
+ }
+ _ => {}
+ }
+ }
+}
+
+/// Given a set of spans representing statements initializing the relevant binding, visit all the
+/// function expressions looking for branching code paths that *do not* initialize the binding.
+struct ConditionVisitor<'b> {
+ spans: &'b [Span],
+ name: &'b str,
+ errors: Vec<(Span, String)>,
+}
+
+impl<'b, 'v> Visitor<'v> for ConditionVisitor<'b> {
+ fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
+ match ex.kind {
+ hir::ExprKind::If(cond, body, None) => {
+ // `if` expressions with no `else` that initialize the binding might be missing an
+ // `else` arm.
+ let mut v = ReferencedStatementsVisitor(self.spans, false);
+ v.visit_expr(body);
+ if v.1 {
+ self.errors.push((
+ cond.span,
+ format!(
+ "if this `if` condition is `false`, {} is not initialized",
+ self.name,
+ ),
+ ));
+ self.errors.push((
+ ex.span.shrink_to_hi(),
+ format!("an `else` arm might be missing here, initializing {}", self.name),
+ ));
+ }
+ }
+ hir::ExprKind::If(cond, body, Some(other)) => {
+ // `if` expressions where the binding is only initialized in one of the two arms
+ // might be missing a binding initialization.
+ let mut a = ReferencedStatementsVisitor(self.spans, false);
+ a.visit_expr(body);
+ let mut b = ReferencedStatementsVisitor(self.spans, false);
+ b.visit_expr(other);
+ match (a.1, b.1) {
+ (true, true) | (false, false) => {}
+ (true, false) => {
+ if other.span.is_desugaring(DesugaringKind::WhileLoop) {
+ self.errors.push((
+ cond.span,
+ format!(
+ "if this condition isn't met and the `while` loop runs 0 \
+ times, {} is not initialized",
+ self.name
+ ),
+ ));
+ } else {
+ self.errors.push((
+ body.span.shrink_to_hi().until(other.span),
+ format!(
+ "if the `if` condition is `false` and this `else` arm is \
+ executed, {} is not initialized",
+ self.name
+ ),
+ ));
+ }
+ }
+ (false, true) => {
+ self.errors.push((
+ cond.span,
+ format!(
+ "if this condition is `true`, {} is not initialized",
+ self.name
+ ),
+ ));
+ }
+ }
+ }
+ hir::ExprKind::Match(e, arms, loop_desugar) => {
+ // If the binding is initialized in one of the match arms, then the other match
+ // arms might be missing an initialization.
+ let results: Vec<bool> = arms
+ .iter()
+ .map(|arm| {
+ let mut v = ReferencedStatementsVisitor(self.spans, false);
+ v.visit_arm(arm);
+ v.1
+ })
+ .collect();
+ if results.iter().any(|x| *x) && !results.iter().all(|x| *x) {
+ for (arm, seen) in arms.iter().zip(results) {
+ if !seen {
+ if loop_desugar == hir::MatchSource::ForLoopDesugar {
+ self.errors.push((
+ e.span,
+ format!(
+ "if the `for` loop runs 0 times, {} is not initialized",
+ self.name
+ ),
+ ));
+ } else if let Some(guard) = &arm.guard {
+ self.errors.push((
+ arm.pat.span.to(guard.body().span),
+ format!(
+ "if this pattern and condition are matched, {} is not \
+ initialized",
+ self.name
+ ),
+ ));
+ } else {
+ self.errors.push((
+ arm.pat.span,
+ format!(
+ "if this pattern is matched, {} is not initialized",
+ self.name
+ ),
+ ));
+ }
+ }
+ }
+ }
+ }
+ // FIXME: should we also account for binops, particularly `&&` and `||`? `try` should
+ // also be accounted for. For now it is fine, as if we don't find *any* relevant
+ // branching code paths, we point at the places where the binding *is* initialized for
+ // *some* context.
+ _ => {}
+ }
+ walk_expr(self, ex);
+ }
+}
diff --git a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs
new file mode 100644
index 000000000..72aee0267
--- /dev/null
+++ b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs
@@ -0,0 +1,744 @@
+//! Print diagnostics to explain why values are borrowed.
+
+use std::collections::VecDeque;
+
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::{Applicability, Diagnostic};
+use rustc_index::vec::IndexVec;
+use rustc_infer::infer::NllRegionVariableOrigin;
+use rustc_middle::mir::{
+ Body, CastKind, ConstraintCategory, FakeReadCause, Local, Location, Operand, Place, Rvalue,
+ Statement, StatementKind, TerminatorKind,
+};
+use rustc_middle::ty::adjustment::PointerCast;
+use rustc_middle::ty::{self, RegionVid, TyCtxt};
+use rustc_span::symbol::{kw, Symbol};
+use rustc_span::{sym, DesugaringKind, Span};
+
+use crate::region_infer::BlameConstraint;
+use crate::{
+ borrow_set::BorrowData, nll::ConstraintDescription, region_infer::Cause, MirBorrowckCtxt,
+ WriteKind,
+};
+
+use super::{find_use, RegionName, UseSpans};
+
+#[derive(Debug)]
+pub(crate) enum BorrowExplanation<'tcx> {
+ UsedLater(LaterUseKind, Span, Option<Span>),
+ UsedLaterInLoop(LaterUseKind, Span, Option<Span>),
+ UsedLaterWhenDropped {
+ drop_loc: Location,
+ dropped_local: Local,
+ should_note_order: bool,
+ },
+ MustBeValidFor {
+ category: ConstraintCategory<'tcx>,
+ from_closure: bool,
+ span: Span,
+ region_name: RegionName,
+ opt_place_desc: Option<String>,
+ },
+ Unexplained,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub(crate) enum LaterUseKind {
+ TraitCapture,
+ ClosureCapture,
+ Call,
+ FakeLetRead,
+ Other,
+}
+
+impl<'tcx> BorrowExplanation<'tcx> {
+ pub(crate) fn is_explained(&self) -> bool {
+ !matches!(self, BorrowExplanation::Unexplained)
+ }
+ pub(crate) fn add_explanation_to_diagnostic(
+ &self,
+ tcx: TyCtxt<'tcx>,
+ body: &Body<'tcx>,
+ local_names: &IndexVec<Local, Option<Symbol>>,
+ err: &mut Diagnostic,
+ borrow_desc: &str,
+ borrow_span: Option<Span>,
+ multiple_borrow_span: Option<(Span, Span)>,
+ ) {
+ match *self {
+ BorrowExplanation::UsedLater(later_use_kind, var_or_use_span, path_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",
+ };
+ // We can use `var_or_use_span` if either `path_span` is not present, or both spans are the same
+ if path_span.map(|path_span| path_span == var_or_use_span).unwrap_or(true) {
+ if borrow_span.map(|sp| !sp.overlaps(var_or_use_span)).unwrap_or(true) {
+ err.span_label(
+ var_or_use_span,
+ format!("{}borrow later {}", borrow_desc, message),
+ );
+ }
+ } else {
+ // path_span must be `Some` as otherwise the if condition is true
+ let path_span = path_span.unwrap();
+ // path_span is only present in the case of closure capture
+ assert!(matches!(later_use_kind, LaterUseKind::ClosureCapture));
+ if !borrow_span.map_or(false, |sp| sp.overlaps(var_or_use_span)) {
+ let path_label = "used here by closure";
+ let capture_kind_label = message;
+ err.span_label(
+ var_or_use_span,
+ format!("{}borrow later {}", borrow_desc, capture_kind_label),
+ );
+ err.span_label(path_span, path_label);
+ }
+ }
+ }
+ BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span, path_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",
+ };
+ // We can use `var_or_use_span` if either `path_span` is not present, or both spans are the same
+ if path_span.map(|path_span| path_span == var_or_use_span).unwrap_or(true) {
+ err.span_label(var_or_use_span, format!("{}{}", borrow_desc, message));
+ } else {
+ // path_span must be `Some` as otherwise the if condition is true
+ let path_span = path_span.unwrap();
+ // path_span is only present in the case of closure capture
+ assert!(matches!(later_use_kind, LaterUseKind::ClosureCapture));
+ if borrow_span.map(|sp| !sp.overlaps(var_or_use_span)).unwrap_or(true) {
+ let path_label = "used here by closure";
+ let capture_kind_label = message;
+ err.span_label(
+ var_or_use_span,
+ format!("{}borrow later {}", borrow_desc, capture_kind_label),
+ );
+ err.span_label(path_span, path_label);
+ }
+ }
+ }
+ BorrowExplanation::UsedLaterWhenDropped {
+ drop_loc,
+ dropped_local,
+ should_note_order,
+ } => {
+ let local_decl = &body.local_decls[dropped_local];
+ let mut ty = local_decl.ty;
+ if local_decl.source_info.span.desugaring_kind() == Some(DesugaringKind::ForLoop) {
+ if let ty::Adt(adt, substs) = local_decl.ty.kind() {
+ if tcx.is_diagnostic_item(sym::Option, adt.did()) {
+ // in for loop desugaring, only look at the `Some(..)` inner type
+ ty = substs.type_at(0);
+ }
+ }
+ }
+ let (dtor_desc, type_desc) = match 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 {
+ if info.tail_result_is_ignored {
+ // #85581: If the first mutable borrow's scope contains
+ // the second borrow, this suggestion isn't helpful.
+ if !multiple_borrow_span
+ .map(|(old, new)| {
+ old.to(info.span.shrink_to_hi()).contains(new)
+ })
+ .unwrap_or(false)
+ {
+ err.span_suggestion_verbose(
+ info.span.shrink_to_hi(),
+ "consider adding semicolon after the expression so its \
+ temporaries are dropped sooner, before the local variables \
+ declared by the block are dropped",
+ ";",
+ Applicability::MaybeIncorrect,
+ );
+ }
+ } else {
+ err.note(
+ "the temporary is part of an expression at the end of a \
+ block;\nconsider forcing this temporary to be dropped sooner, \
+ before the block's local variables are dropped",
+ );
+ err.multipart_suggestion(
+ "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",
+ vec![
+ (info.span.shrink_to_lo(), "let x = ".to_string()),
+ (info.span.shrink_to_hi(), "; x".to_string()),
+ ],
+ Applicability::MaybeIncorrect,
+ );
+ };
+ }
+ }
+ }
+ }
+ 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,
+ ),
+ );
+ };
+
+ self.add_lifetime_bound_suggestion_to_diagnostic(err, &category, span, region_name);
+ }
+ _ => {}
+ }
+ }
+ pub(crate) fn add_lifetime_bound_suggestion_to_diagnostic(
+ &self,
+ err: &mut Diagnostic,
+ category: &ConstraintCategory<'tcx>,
+ span: Span,
+ region_name: &RegionName,
+ ) {
+ if let ConstraintCategory::OpaqueType = category {
+ let suggestable_name =
+ if region_name.was_named() { region_name.name } else { kw::UnderscoreLifetime };
+
+ let msg = format!(
+ "you can add a bound to the {}to make it last less than `'static` and match `{}`",
+ category.description(),
+ region_name,
+ );
+
+ err.span_suggestion_verbose(
+ span.shrink_to_hi(),
+ &msg,
+ format!(" + {}", suggestable_name),
+ Applicability::Unspecified,
+ );
+ }
+ }
+}
+
+impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
+ fn free_region_constraint_info(
+ &self,
+ borrow_region: RegionVid,
+ outlived_region: RegionVid,
+ ) -> (ConstraintCategory<'tcx>, bool, Span, Option<RegionName>) {
+ let BlameConstraint { category, from_closure, cause, variance_info: _ } =
+ self.regioncx.best_blame_constraint(
+ &self.body,
+ borrow_region,
+ NllRegionVariableOrigin::FreeRegion,
+ |r| self.regioncx.provides_universal_region(r, borrow_region, outlived_region),
+ );
+
+ let outlived_fr_name = self.give_region_a_name(outlived_region);
+
+ (category, from_closure, cause.span, outlived_fr_name)
+ }
+
+ /// 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(crate) fn explain_why_borrow_contains_point(
+ &self,
+ location: Location,
+ borrow: &BorrowData<'tcx>,
+ kind_place: Option<(WriteKind, Place<'tcx>)>,
+ ) -> BorrowExplanation<'tcx> {
+ debug!(
+ "explain_why_borrow_contains_point(location={:?}, borrow={:?}, kind_place={:?})",
+ location, borrow, kind_place
+ );
+
+ let regioncx = &self.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 = self.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, later_use.2)
+ } 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 = <expr>` patterns.
+ let later_use = self.later_use_kind(borrow, spans, location);
+ BorrowExplanation::UsedLater(later_use.0, later_use.1, later_use.2)
+ }
+ }
+
+ Some(Cause::DropVar(local, location)) => {
+ let mut should_note_order = false;
+ if self.local_names[local].is_some()
+ && let Some((WriteKind::StorageDeadOrDrop, place)) = kind_place
+ && let Some(borrowed_local) = place.as_local()
+ && 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) = self.to_error_region_vid(borrow_region_vid) {
+ let (category, from_closure, span, region_name) =
+ self.free_region_constraint_info(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<Location> {
+ 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<Location>,
+ ) -> 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.
+ /// First span returned points to the location of the conflicting use
+ /// Second span if `Some` is returned in the case of closures and points
+ /// to the use of the path
+ fn later_use_kind(
+ &self,
+ borrow: &BorrowData<'tcx>,
+ use_spans: UseSpans<'tcx>,
+ location: Location,
+ ) -> (LaterUseKind, Span, Option<Span>) {
+ match use_spans {
+ UseSpans::ClosureUse { capture_kind_span, path_span, .. } => {
+ // Used in a closure.
+ (LaterUseKind::ClosureCapture, capture_kind_span, Some(path_span))
+ }
+ UseSpans::PatUse(span)
+ | UseSpans::OtherUse(span)
+ | UseSpans::FnSelfUse { var_span: span, .. } => {
+ let block = &self.body.basic_blocks()[location.block];
+
+ let kind = if let Some(&Statement {
+ kind: StatementKind::FakeRead(box (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, None);
+ } else {
+ LaterUseKind::Other
+ }
+ } else {
+ LaterUseKind::Other
+ };
+
+ (kind, span, None)
+ }
+ }
+ }
+
+ /// 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
+ /// an 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 Some(into) = place.local_or_deref_local() else {
+ // 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 an 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<dyn Trait>`
+ _ 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, target: Some(block), args, .. } =
+ &terminator.kind
+ {
+ if let Some(dest) = destination.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/compiler/rustc_borrowck/src/diagnostics/find_all_local_uses.rs b/compiler/rustc_borrowck/src/diagnostics/find_all_local_uses.rs
new file mode 100644
index 000000000..b3edc35dc
--- /dev/null
+++ b/compiler/rustc_borrowck/src/diagnostics/find_all_local_uses.rs
@@ -0,0 +1,26 @@
+use std::collections::BTreeSet;
+
+use rustc_middle::mir::visit::{PlaceContext, Visitor};
+use rustc_middle::mir::{Body, Local, Location};
+
+/// Find all uses of (including assignments to) a [`Local`].
+///
+/// Uses `BTreeSet` so output is deterministic.
+pub(super) fn find<'tcx>(body: &Body<'tcx>, local: Local) -> BTreeSet<Location> {
+ let mut visitor = AllLocalUsesVisitor { for_local: local, uses: BTreeSet::default() };
+ visitor.visit_body(body);
+ visitor.uses
+}
+
+struct AllLocalUsesVisitor {
+ for_local: Local,
+ uses: BTreeSet<Location>,
+}
+
+impl<'tcx> Visitor<'tcx> for AllLocalUsesVisitor {
+ fn visit_local(&mut self, local: Local, _context: PlaceContext, location: Location) {
+ if local == self.for_local {
+ self.uses.insert(location);
+ }
+ }
+}
diff --git a/compiler/rustc_borrowck/src/diagnostics/find_use.rs b/compiler/rustc_borrowck/src/diagnostics/find_use.rs
new file mode 100644
index 000000000..b5a3081e5
--- /dev/null
+++ b/compiler/rustc_borrowck/src/diagnostics/find_use.rs
@@ -0,0 +1,128 @@
+use std::collections::VecDeque;
+use std::rc::Rc;
+
+use crate::{
+ def_use::{self, DefUse},
+ nll::ToRegionVid,
+ region_infer::{Cause, RegionInferenceContext},
+};
+use rustc_data_structures::fx::FxHashSet;
+use rustc_middle::mir::visit::{MirVisitable, PlaceContext, Visitor};
+use rustc_middle::mir::{Body, Local, Location};
+use rustc_middle::ty::{RegionVid, TyCtxt};
+
+pub(crate) fn find<'tcx>(
+ body: &Body<'tcx>,
+ regioncx: &Rc<RegionInferenceContext<'tcx>>,
+ tcx: TyCtxt<'tcx>,
+ region_vid: RegionVid,
+ start_point: Location,
+) -> Option<Cause> {
+ let mut uf = UseFinder { body, regioncx, tcx, region_vid, start_point };
+
+ uf.find()
+}
+
+struct UseFinder<'cx, 'tcx> {
+ body: &'cx Body<'tcx>,
+ regioncx: &'cx Rc<RegionInferenceContext<'tcx>>,
+ tcx: TyCtxt<'tcx>,
+ region_vid: RegionVid,
+ start_point: Location,
+}
+
+impl<'cx, 'tcx> UseFinder<'cx, 'tcx> {
+ fn find(&mut self) -> Option<Cause> {
+ 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<DefUseResult> {
+ 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<DefUseResult>,
+}
+
+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 def_use::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/compiler/rustc_borrowck/src/diagnostics/mod.rs b/compiler/rustc_borrowck/src/diagnostics/mod.rs
new file mode 100644
index 000000000..098e8de94
--- /dev/null
+++ b/compiler/rustc_borrowck/src/diagnostics/mod.rs
@@ -0,0 +1,1127 @@
+//! Borrow checker diagnostics.
+
+use itertools::Itertools;
+use rustc_const_eval::util::{call_kind, CallDesugaringKind};
+use rustc_errors::{Applicability, Diagnostic};
+use rustc_hir as hir;
+use rustc_hir::def::{CtorKind, Namespace};
+use rustc_hir::GeneratorKind;
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_middle::mir::tcx::PlaceTy;
+use rustc_middle::mir::{
+ AggregateKind, Constant, FakeReadCause, Field, Local, LocalInfo, LocalKind, Location, Operand,
+ Place, PlaceRef, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind,
+};
+use rustc_middle::ty::print::Print;
+use rustc_middle::ty::{self, DefIdTree, Instance, Ty, TyCtxt};
+use rustc_mir_dataflow::move_paths::{InitLocation, LookupResult};
+use rustc_span::def_id::LocalDefId;
+use rustc_span::{symbol::sym, Span, Symbol, DUMMY_SP};
+use rustc_target::abi::VariantIdx;
+use rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions;
+
+use super::borrow_set::BorrowData;
+use super::MirBorrowckCtxt;
+
+mod find_all_local_uses;
+mod find_use;
+mod outlives_suggestion;
+mod region_name;
+mod var_name;
+
+mod bound_region_errors;
+mod conflict_errors;
+mod explain_borrow;
+mod move_errors;
+mod mutability_errors;
+mod region_errors;
+
+pub(crate) use bound_region_errors::{ToUniverseInfo, UniverseInfo};
+pub(crate) use mutability_errors::AccessKind;
+pub(crate) use outlives_suggestion::OutlivesSuggestionBuilder;
+pub(crate) use region_errors::{ErrorConstraintInfo, RegionErrorKind, RegionErrors};
+pub(crate) use region_name::{RegionName, RegionNameSource};
+pub(crate) use rustc_const_eval::util::CallKind;
+
+pub(super) struct DescribePlaceOpt {
+ pub including_downcast: bool,
+
+ /// Enable/Disable tuple fields.
+ /// For example `x` tuple. if it's `true` `x.0`. Otherwise `x`
+ pub including_tuple_field: bool,
+}
+
+pub(super) struct IncludingTupleField(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<'tcx>,
+ diag: &mut Diagnostic,
+ ) {
+ 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, .. }),
+ args,
+ ..
+ } = &terminator.kind
+ {
+ if let ty::FnDef(id, _) = *literal.ty().kind() {
+ debug!("add_moved_or_invoked_closure_note: id={:?}", id);
+ if Some(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 did = did.expect_local();
+ let hir_id = self.infcx.tcx.hir().local_def_id_to_hir_id(did);
+
+ if let Some((span, hir_place)) =
+ self.infcx.tcx.typeck(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",
+ ty::place_to_string_for_capture(self.infcx.tcx, hir_place)
+ ),
+ );
+ 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 did = did.expect_local();
+ let hir_id = self.infcx.tcx.hir().local_def_id_to_hir_id(did);
+
+ if let Some((span, hir_place)) =
+ self.infcx.tcx.typeck(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",
+ ty::place_to_string_for_capture(self.infcx.tcx, hir_place)
+ ),
+ );
+ }
+ }
+ }
+ }
+
+ /// End-user visible description of `place` if one can be found.
+ /// If the place is a temporary for instance, `"value"` will be returned.
+ pub(super) fn describe_any_place(&self, place_ref: PlaceRef<'tcx>) -> String {
+ match self.describe_place(place_ref) {
+ Some(mut descr) => {
+ // Surround descr with `backticks`.
+ descr.reserve(2);
+ descr.insert(0, '`');
+ descr.push('`');
+ descr
+ }
+ None => "value".to_string(),
+ }
+ }
+
+ /// 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<'tcx>) -> Option<String> {
+ self.describe_place_with_options(
+ place_ref,
+ DescribePlaceOpt { including_downcast: false, including_tuple_field: true },
+ )
+ }
+
+ /// 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 `None` if `ProjectionElem` is
+ /// `Downcast` and `IncludingDowncast` is true
+ pub(super) fn describe_place_with_options(
+ &self,
+ place: PlaceRef<'tcx>,
+ opt: DescribePlaceOpt,
+ ) -> Option<String> {
+ let local = place.local;
+ let mut autoderef_index = None;
+ let mut buf = String::new();
+ let mut ok = self.append_local_to_string(local, &mut buf);
+
+ for (index, elem) in place.projection.into_iter().enumerate() {
+ match elem {
+ ProjectionElem::Deref => {
+ if index == 0 {
+ if self.body.local_decls[local].is_ref_for_guard() {
+ continue;
+ }
+ if let Some(box LocalInfo::StaticRef { def_id, .. }) =
+ &self.body.local_decls[local].local_info
+ {
+ buf.push_str(self.infcx.tcx.item_name(*def_id).as_str());
+ ok = Ok(());
+ continue;
+ }
+ }
+ if let Some(field) = self.is_upvar_field_projection(PlaceRef {
+ local,
+ projection: place.projection.split_at(index + 1).0,
+ }) {
+ let var_index = field.index();
+ buf = self.upvars[var_index].place.to_string(self.infcx.tcx);
+ ok = Ok(());
+ if !self.upvars[var_index].by_ref {
+ buf.insert(0, '*');
+ }
+ } else {
+ if autoderef_index.is_none() {
+ autoderef_index =
+ match place.projection.into_iter().rev().find_position(|elem| {
+ !matches!(
+ elem,
+ ProjectionElem::Deref | ProjectionElem::Downcast(..)
+ )
+ }) {
+ Some((index, _)) => Some(place.projection.len() - index),
+ None => Some(0),
+ };
+ }
+ if index >= autoderef_index.unwrap() {
+ buf.insert(0, '*');
+ }
+ }
+ }
+ ProjectionElem::Downcast(..) if opt.including_downcast => return None,
+ ProjectionElem::Downcast(..) => (),
+ ProjectionElem::Field(field, _ty) => {
+ // FIXME(project-rfc_2229#36): print capture precisely here.
+ if let Some(field) = self.is_upvar_field_projection(PlaceRef {
+ local,
+ projection: place.projection.split_at(index + 1).0,
+ }) {
+ buf = self.upvars[field.index()].place.to_string(self.infcx.tcx);
+ ok = Ok(());
+ } else {
+ let field_name = self.describe_field(
+ PlaceRef { local, projection: place.projection.split_at(index).0 },
+ *field,
+ IncludingTupleField(opt.including_tuple_field),
+ );
+ if let Some(field_name_str) = field_name {
+ buf.push('.');
+ buf.push_str(&field_name_str);
+ }
+ }
+ }
+ ProjectionElem::Index(index) => {
+ buf.push('[');
+ if self.append_local_to_string(*index, &mut buf).is_err() {
+ buf.push('_');
+ }
+ buf.push(']');
+ }
+ ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. } => {
+ // 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
+ buf.push_str("[..]");
+ }
+ }
+ }
+ ok.ok().map(|_| buf)
+ }
+
+ fn describe_name(&self, place: PlaceRef<'tcx>) -> Option<Symbol> {
+ for elem in place.projection.into_iter() {
+ match elem {
+ ProjectionElem::Downcast(Some(name), _) => {
+ return Some(*name);
+ }
+ _ => {}
+ }
+ }
+ None
+ }
+
+ /// 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<'tcx>,
+ field: Field,
+ including_tuple_field: IncludingTupleField,
+ ) -> Option<String> {
+ let place_ty = match place {
+ PlaceRef { local, projection: [] } => PlaceTy::from_ty(self.body.local_decls[local].ty),
+ PlaceRef { local, projection: [proj_base @ .., elem] } => match elem {
+ ProjectionElem::Deref
+ | ProjectionElem::Index(..)
+ | ProjectionElem::ConstantIndex { .. }
+ | ProjectionElem::Subslice { .. } => {
+ PlaceRef { local, projection: proj_base }.ty(self.body, self.infcx.tcx)
+ }
+ ProjectionElem::Downcast(..) => place.ty(self.body, self.infcx.tcx),
+ ProjectionElem::Field(_, field_type) => PlaceTy::from_ty(*field_type),
+ },
+ };
+ self.describe_field_from_ty(
+ place_ty.ty,
+ field,
+ place_ty.variant_index,
+ including_tuple_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<VariantIdx>,
+ including_tuple_field: IncludingTupleField,
+ ) -> 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, including_tuple_field)
+ } else {
+ match *ty.kind() {
+ ty::Adt(def, _) => {
+ let variant = if let Some(idx) = variant_index {
+ assert!(def.is_enum());
+ &def.variant(idx)
+ } else {
+ def.non_enum_variant()
+ };
+ if !including_tuple_field.0 && variant.ctor_kind == CtorKind::Fn {
+ return None;
+ }
+ Some(variant.fields[field.index()].name.to_string())
+ }
+ ty::Tuple(_) => Some(field.index().to_string()),
+ ty::Ref(_, ty, _) | ty::RawPtr(ty::TypeAndMut { ty, .. }) => {
+ self.describe_field_from_ty(ty, field, variant_index, including_tuple_field)
+ }
+ ty::Array(ty, _) | ty::Slice(ty) => {
+ self.describe_field_from_ty(ty, field, variant_index, including_tuple_field)
+ }
+ ty::Closure(def_id, _) | ty::Generator(def_id, _, _) => {
+ // We won't be borrowck'ing here if the closure came from another crate,
+ // so it's safe to call `expect_local`.
+ //
+ // We know the field exists so it's safe to call operator[] and `unwrap` here.
+ let def_id = def_id.expect_local();
+ let var_id = self
+ .infcx
+ .tcx
+ .typeck(def_id)
+ .closure_min_captures_flattened(def_id)
+ .nth(field.index())
+ .unwrap()
+ .get_root_variable();
+
+ Some(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 Diagnostic,
+ place_desc: &str,
+ ty: Ty<'tcx>,
+ span: Option<Span>,
+ move_prefix: &str,
+ ) {
+ let message = format!(
+ "{}move occurs because {} has type `{}`, which does not implement the `Copy` trait",
+ move_prefix, 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<'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 InitLocation::Statement(loc) = init.location else { 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 = deref_base.ty(self.body, tcx).ty;
+ if base_ty.is_unsafe_ptr() {
+ BorrowedContentSource::DerefRawPointer
+ } else if base_ty.is_mutable_ptr() {
+ BorrowedContentSource::DerefMutableRef
+ } else {
+ BorrowedContentSource::DerefSharedRef
+ }
+ }
+
+ /// 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 printer = ty::print::FmtPrinter::new(self.infcx.tcx, 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`.
+ if let ty::Ref(region, ..) = ty.kind() {
+ match **region {
+ ty::ReLateBound(_, ty::BoundRegion { kind: br, .. })
+ | ty::RePlaceholder(ty::PlaceholderRegion { name: br, .. }) => {
+ printer.region_highlight_mode.highlighting_bound_region(br, counter)
+ }
+ _ => {}
+ }
+ }
+
+ ty.print(printer).unwrap().into_buffer()
+ }
+
+ /// 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 printer = ty::print::FmtPrinter::new(self.infcx.tcx, Namespace::TypeNS);
+
+ let region = if let ty::Ref(region, ..) = ty.kind() {
+ match **region {
+ ty::ReLateBound(_, ty::BoundRegion { kind: br, .. })
+ | ty::RePlaceholder(ty::PlaceholderRegion { name: br, .. }) => {
+ printer.region_highlight_mode.highlighting_bound_region(br, counter)
+ }
+ _ => {}
+ }
+ region
+ } else {
+ bug!("ty for annotation of borrow region is not a reference");
+ };
+
+ region.print(printer).unwrap().into_buffer()
+ }
+}
+
+/// The span(s) associated to a use of a place.
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub(super) enum UseSpans<'tcx> {
+ /// 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<GeneratorKind>,
+ /// The span of the args of the closure, including the `move` keyword if
+ /// it's present.
+ args_span: Span,
+ /// The span of the use resulting in capture kind
+ /// Check `ty::CaptureInfo` for more details
+ capture_kind_span: Span,
+ /// The span of the use resulting in the captured path
+ /// Check `ty::CaptureInfo` for more details
+ path_span: Span,
+ },
+ /// The access is caused by using a variable as the receiver of a method
+ /// that takes 'self'
+ FnSelfUse {
+ /// The span of the variable being moved
+ var_span: Span,
+ /// The span of the method call on the variable
+ fn_call_span: Span,
+ /// The definition span of the method being called
+ fn_span: Span,
+ kind: CallKind<'tcx>,
+ },
+ /// This access is caused by a `match` or `if let` pattern.
+ PatUse(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::PatUse(span)
+ | UseSpans::OtherUse(span) => span,
+ UseSpans::FnSelfUse { fn_call_span, kind: CallKind::DerefCoercion { .. }, .. } => {
+ fn_call_span
+ }
+ UseSpans::FnSelfUse { var_span, .. } => var_span,
+ }
+ }
+
+ /// Returns the span of `self`, in the case of a `ClosureUse` returns the `path_span`
+ pub(super) fn var_or_use_path_span(self) -> Span {
+ match self {
+ UseSpans::ClosureUse { path_span: span, .. }
+ | UseSpans::PatUse(span)
+ | UseSpans::OtherUse(span) => span,
+ UseSpans::FnSelfUse { fn_call_span, kind: CallKind::DerefCoercion { .. }, .. } => {
+ fn_call_span
+ }
+ UseSpans::FnSelfUse { var_span, .. } => var_span,
+ }
+ }
+
+ /// Returns the span of `self`, in the case of a `ClosureUse` returns the `capture_kind_span`
+ pub(super) fn var_or_use(self) -> Span {
+ match self {
+ UseSpans::ClosureUse { capture_kind_span: span, .. }
+ | UseSpans::PatUse(span)
+ | UseSpans::OtherUse(span) => span,
+ UseSpans::FnSelfUse { fn_call_span, kind: CallKind::DerefCoercion { .. }, .. } => {
+ fn_call_span
+ }
+ UseSpans::FnSelfUse { var_span, .. } => var_span,
+ }
+ }
+
+ pub(super) fn generator_kind(self) -> Option<GeneratorKind> {
+ 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 Diagnostic, message: impl Into<String>) {
+ 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.
+ // only adds label to the `path_span`
+ pub(super) fn var_span_label_path_only(self, err: &mut Diagnostic, message: impl Into<String>) {
+ if let UseSpans::ClosureUse { path_span, .. } = self {
+ err.span_label(path_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 Diagnostic,
+ message: impl Into<String>,
+ kind_desc: impl Into<String>,
+ ) {
+ if let UseSpans::ClosureUse { capture_kind_span, path_span, .. } = self {
+ if capture_kind_span == path_span {
+ err.span_label(capture_kind_span, message);
+ } else {
+ let capture_kind_label =
+ format!("capture is {} because of use here", kind_desc.into());
+ let path_label = message;
+ err.span_label(capture_kind_span, capture_kind_label);
+ err.span_label(path_span, path_label);
+ }
+ }
+ }
+
+ /// 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) -> &str {
+ match *self {
+ UseSpans::ClosureUse { generator_kind, .. } => {
+ if generator_kind.is_some() {
+ " in generator"
+ } else {
+ " in closure"
+ }
+ }
+ _ => "",
+ }
+ }
+
+ pub(super) fn or_else<F>(self, if_other: F) -> Self
+ where
+ F: FnOnce() -> Self,
+ {
+ match self {
+ closure @ UseSpans::ClosureUse { .. } => closure,
+ UseSpans::PatUse(_) | UseSpans::OtherUse(_) => if_other(),
+ fn_self @ UseSpans::FnSelfUse { .. } => fn_self,
+ }
+ }
+}
+
+pub(super) enum BorrowedContentSource<'tcx> {
+ DerefRawPointer,
+ DerefMutableRef,
+ DerefSharedRef,
+ OverloadedDeref(Ty<'tcx>),
+ OverloadedIndex(Ty<'tcx>),
+}
+
+impl<'tcx> BorrowedContentSource<'tcx> {
+ pub(super) fn describe_for_unnamed_place(&self, tcx: TyCtxt<'_>) -> String {
+ match *self {
+ BorrowedContentSource::DerefRawPointer => "a raw pointer".to_string(),
+ BorrowedContentSource::DerefSharedRef => "a shared reference".to_string(),
+ BorrowedContentSource::DerefMutableRef => "a mutable reference".to_string(),
+ BorrowedContentSource::OverloadedDeref(ty) => ty
+ .ty_adt_def()
+ .and_then(|adt| match tcx.get_diagnostic_name(adt.did())? {
+ name @ (sym::Rc | sym::Arc) => Some(format!("an `{}`", name)),
+ _ => None,
+ })
+ .unwrap_or_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, tcx: TyCtxt<'_>) -> String {
+ match *self {
+ BorrowedContentSource::DerefRawPointer => "a `*const` pointer".to_string(),
+ BorrowedContentSource::DerefSharedRef => "a `&` reference".to_string(),
+ BorrowedContentSource::DerefMutableRef => {
+ bug!("describe_for_immutable_place: DerefMutableRef isn't immutable")
+ }
+ BorrowedContentSource::OverloadedDeref(ty) => ty
+ .ty_adt_def()
+ .and_then(|adt| match tcx.get_diagnostic_name(adt.did())? {
+ name @ (sym::Rc | sym::Arc) => Some(format!("an `{}`", name)),
+ _ => None,
+ })
+ .unwrap_or_else(|| format!("dereference of `{}`", ty)),
+ BorrowedContentSource::OverloadedIndex(ty) => format!("an index of `{}`", ty),
+ }
+ }
+
+ fn from_call(func: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Option<Self> {
+ 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<'tcx>, // Could also be an upvar.
+ location: Location,
+ ) -> UseSpans<'tcx> {
+ use self::UseSpans::*;
+
+ let Some(stmt) = self.body[location.block].statements.get(location.statement_index) else {
+ 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 {
+ match **kind {
+ AggregateKind::Closure(def_id, _) | AggregateKind::Generator(def_id, _, _) => {
+ debug!("move_spans: def_id={:?} places={:?}", def_id, places);
+ if let Some((args_span, generator_kind, capture_kind_span, path_span)) =
+ self.closure_span(def_id, moved_place, places)
+ {
+ return ClosureUse {
+ generator_kind,
+ args_span,
+ capture_kind_span,
+ path_span,
+ };
+ }
+ }
+ _ => {}
+ }
+ }
+
+ // StatementKind::FakeRead only contains a def_id if they are introduced as a result
+ // of pattern matching within a closure.
+ if let StatementKind::FakeRead(box (cause, ref place)) = stmt.kind {
+ match cause {
+ FakeReadCause::ForMatchedPlace(Some(closure_def_id))
+ | FakeReadCause::ForLet(Some(closure_def_id)) => {
+ debug!("move_spans: def_id={:?} place={:?}", closure_def_id, place);
+ let places = &[Operand::Move(*place)];
+ if let Some((args_span, generator_kind, capture_kind_span, path_span)) =
+ self.closure_span(closure_def_id, moved_place, places)
+ {
+ return ClosureUse {
+ generator_kind,
+ args_span,
+ capture_kind_span,
+ path_span,
+ };
+ }
+ }
+ _ => {}
+ }
+ }
+
+ let normal_ret =
+ if moved_place.projection.iter().any(|p| matches!(p, ProjectionElem::Downcast(..))) {
+ PatUse(stmt.source_info.span)
+ } else {
+ OtherUse(stmt.source_info.span)
+ };
+
+ // We are trying to find MIR of the form:
+ // ```
+ // _temp = _moved_val;
+ // ...
+ // FnSelfCall(_temp, ...)
+ // ```
+ //
+ // where `_moved_val` is the place we generated the move error for,
+ // `_temp` is some other local, and `FnSelfCall` is a function
+ // that has a `self` parameter.
+
+ let target_temp = match stmt.kind {
+ StatementKind::Assign(box (temp, _)) if temp.as_local().is_some() => {
+ temp.as_local().unwrap()
+ }
+ _ => return normal_ret,
+ };
+
+ debug!("move_spans: target_temp = {:?}", target_temp);
+
+ if let Some(Terminator {
+ kind: TerminatorKind::Call { fn_span, from_hir_call, .. }, ..
+ }) = &self.body[location.block].terminator
+ {
+ let Some((method_did, method_substs)) =
+ rustc_const_eval::util::find_self_call(
+ self.infcx.tcx,
+ &self.body,
+ target_temp,
+ location.block,
+ )
+ else {
+ return normal_ret;
+ };
+
+ let kind = call_kind(
+ self.infcx.tcx,
+ self.param_env,
+ method_did,
+ method_substs,
+ *fn_span,
+ *from_hir_call,
+ Some(self.infcx.tcx.fn_arg_names(method_did)[0]),
+ );
+
+ return FnSelfUse {
+ var_span: stmt.source_info.span,
+ fn_call_span: *fn_span,
+ fn_span: self.infcx.tcx.def_span(method_did),
+ kind,
+ };
+ }
+ normal_ret
+ }
+
+ /// 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<'tcx> {
+ 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, capture_kind_span, path_span)) =
+ self.closure_span(def_id, Place::from(target).as_ref(), places)
+ {
+ return ClosureUse { generator_kind, args_span, capture_kind_span, path_span };
+ } else {
+ return OtherUse(use_span);
+ }
+ }
+
+ if use_span != stmt.source_info.span {
+ break;
+ }
+ }
+
+ OtherUse(use_span)
+ }
+
+ /// Finds the spans of a captured place within a closure or generator.
+ /// The first span is the location of the use resulting in the capture kind of the capture
+ /// The second span is the location the use resulting in the captured path of the capture
+ fn closure_span(
+ &self,
+ def_id: LocalDefId,
+ target_place: PlaceRef<'tcx>,
+ places: &[Operand<'tcx>],
+ ) -> Option<(Span, Option<GeneratorKind>, Span, Span)> {
+ debug!(
+ "closure_span: def_id={:?} target_place={:?} places={:?}",
+ def_id, target_place, places
+ );
+ let hir_id = self.infcx.tcx.hir().local_def_id_to_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(&hir::Closure { body, fn_decl_span, .. }) = expr {
+ for (captured_place, place) in
+ self.infcx.tcx.typeck(def_id).closure_min_captures_flattened(def_id).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);
+ let generator_kind = body.generator_kind();
+
+ return Some((
+ fn_decl_span,
+ generator_kind,
+ captured_place.get_capture_kind_span(self.infcx.tcx),
+ captured_place.get_path_span(self.infcx.tcx),
+ ));
+ }
+ _ => {}
+ }
+ }
+ }
+ None
+ }
+
+ /// Helper to retrieve span(s) of given borrow from the current MIR
+ /// representation
+ pub(super) fn retrieve_borrow_spans(&self, borrow: &BorrowData<'_>) -> UseSpans<'tcx> {
+ let span = self.body.source_info(borrow.reserve_location).span;
+ self.borrow_spans(span, borrow.reserve_location)
+ }
+
+ fn explain_captures(
+ &mut self,
+ err: &mut Diagnostic,
+ span: Span,
+ move_span: Span,
+ move_spans: UseSpans<'tcx>,
+ moved_place: Place<'tcx>,
+ used_place: Option<PlaceRef<'tcx>>,
+ partially_str: &str,
+ loop_message: &str,
+ move_msg: &str,
+ is_loop_move: bool,
+ maybe_reinitialized_locations_is_empty: bool,
+ ) {
+ if let UseSpans::FnSelfUse { var_span, fn_call_span, fn_span, kind } = move_spans {
+ let place_name = self
+ .describe_place(moved_place.as_ref())
+ .map(|n| format!("`{}`", n))
+ .unwrap_or_else(|| "value".to_owned());
+ match kind {
+ CallKind::FnCall { fn_trait_id, .. }
+ if Some(fn_trait_id) == self.infcx.tcx.lang_items().fn_once_trait() =>
+ {
+ err.span_label(
+ fn_call_span,
+ &format!(
+ "{} {}moved due to this call{}",
+ place_name, partially_str, loop_message
+ ),
+ );
+ err.span_note(
+ var_span,
+ "this value implements `FnOnce`, which causes it to be moved when called",
+ );
+ }
+ CallKind::Operator { self_arg, .. } => {
+ let self_arg = self_arg.unwrap();
+ err.span_label(
+ fn_call_span,
+ &format!(
+ "{} {}moved due to usage in operator{}",
+ place_name, partially_str, loop_message
+ ),
+ );
+ if self.fn_self_span_reported.insert(fn_span) {
+ err.span_note(
+ // Check whether the source is accessible
+ if self.infcx.tcx.sess.source_map().is_span_accessible(self_arg.span) {
+ self_arg.span
+ } else {
+ fn_call_span
+ },
+ "calling this operator moves the left-hand side",
+ );
+ }
+ }
+ CallKind::Normal { self_arg, desugaring, is_option_or_result } => {
+ let self_arg = self_arg.unwrap();
+ if let Some((CallDesugaringKind::ForLoopIntoIter, _)) = desugaring {
+ let ty = moved_place.ty(self.body, self.infcx.tcx).ty;
+ let suggest = match self.infcx.tcx.get_diagnostic_item(sym::IntoIterator) {
+ Some(def_id) => self.infcx.tcx.infer_ctxt().enter(|infcx| {
+ type_known_to_meet_bound_modulo_regions(
+ &infcx,
+ self.param_env,
+ infcx.tcx.mk_imm_ref(
+ infcx.tcx.lifetimes.re_erased,
+ infcx.tcx.erase_regions(ty),
+ ),
+ def_id,
+ DUMMY_SP,
+ )
+ }),
+ _ => false,
+ };
+ if suggest {
+ err.span_suggestion_verbose(
+ move_span.shrink_to_lo(),
+ &format!(
+ "consider iterating over a slice of the `{}`'s content to \
+ avoid moving into the `for` loop",
+ ty,
+ ),
+ "&",
+ Applicability::MaybeIncorrect,
+ );
+ }
+
+ err.span_label(
+ fn_call_span,
+ &format!(
+ "{} {}moved due to this implicit call to `.into_iter()`{}",
+ place_name, partially_str, loop_message
+ ),
+ );
+ // If we have a `&mut` ref, we need to reborrow.
+ if let Some(ty::Ref(_, _, hir::Mutability::Mut)) = used_place
+ .map(|used_place| used_place.ty(self.body, self.infcx.tcx).ty.kind())
+ {
+ // If we are in a loop this will be suggested later.
+ if !is_loop_move {
+ err.span_suggestion_verbose(
+ move_span.shrink_to_lo(),
+ &format!(
+ "consider creating a fresh reborrow of {} here",
+ self.describe_place(moved_place.as_ref())
+ .map(|n| format!("`{}`", n))
+ .unwrap_or_else(|| "the mutable reference".to_string()),
+ ),
+ "&mut *",
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ } else {
+ err.span_label(
+ fn_call_span,
+ &format!(
+ "{} {}moved due to this method call{}",
+ place_name, partially_str, loop_message
+ ),
+ );
+ }
+ if is_option_or_result && maybe_reinitialized_locations_is_empty {
+ err.span_suggestion_verbose(
+ fn_call_span.shrink_to_lo(),
+ "consider calling `.as_ref()` to borrow the type's contents",
+ "as_ref().",
+ Applicability::MachineApplicable,
+ );
+ }
+ // Avoid pointing to the same function in multiple different
+ // error messages.
+ if span != DUMMY_SP && self.fn_self_span_reported.insert(self_arg.span) {
+ err.span_note(
+ self_arg.span,
+ &format!("this function takes ownership of the receiver `self`, which moves {}", place_name)
+ );
+ }
+ }
+ // Other desugarings takes &self, which cannot cause a move
+ _ => {}
+ }
+ } else {
+ if move_span != span || !loop_message.is_empty() {
+ err.span_label(
+ move_span,
+ format!("value {}moved{} here{}", partially_str, move_msg, loop_message),
+ );
+ }
+ // If the move error occurs due to a loop, don't show
+ // another message for the same span
+ if loop_message.is_empty() {
+ move_spans.var_span_label(
+ err,
+ format!("variable {}moved due to use{}", partially_str, move_spans.describe()),
+ "moved",
+ );
+ }
+ }
+ }
+}
diff --git a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs
new file mode 100644
index 000000000..cb3cd479a
--- /dev/null
+++ b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs
@@ -0,0 +1,529 @@
+use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed};
+use rustc_middle::mir::*;
+use rustc_middle::ty;
+use rustc_mir_dataflow::move_paths::{
+ IllegalMoveOrigin, IllegalMoveOriginKind, LookupResult, MoveError, MovePathIndex,
+};
+use rustc_span::Span;
+
+use crate::diagnostics::{DescribePlaceOpt, UseSpans};
+use crate::prefixes::PrefixSet;
+use crate::MirBorrowckCtxt;
+
+// Often when desugaring a pattern match we may have many individual moves in
+// MIR that are all part of one operation from the user's point-of-view. For
+// example:
+//
+// let (x, y) = foo()
+//
+// would move x from the 0 field of some temporary, and y from the 1 field. We
+// group such errors together for cleaner error reporting.
+//
+// Errors are kept separate if they are from places with different parent move
+// paths. For example, this generates two errors:
+//
+// let (&x, &y) = (&String::new(), &String::new());
+#[derive(Debug)]
+enum GroupedMoveError<'tcx> {
+ // Place expression can't be moved from,
+ // e.g., match x[0] { s => (), } where x: &[String]
+ MovesFromPlace {
+ original_path: Place<'tcx>,
+ span: Span,
+ move_from: Place<'tcx>,
+ kind: IllegalMoveOriginKind<'tcx>,
+ binds_to: Vec<Local>,
+ },
+ // 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<Local>,
+ },
+ // Everything that isn't from pattern matching.
+ OtherIllegalMove {
+ original_path: Place<'tcx>,
+ use_spans: UseSpans<'tcx>,
+ kind: IllegalMoveOriginKind<'tcx>,
+ },
+}
+
+impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
+ pub(crate) fn report_move_errors(&mut self, move_errors: Vec<(Place<'tcx>, MoveError<'tcx>)>) {
+ let grouped_errors = self.group_move_errors(move_errors);
+ for error in grouped_errors {
+ self.report(error);
+ }
+ }
+
+ fn group_move_errors(
+ &self,
+ errors: Vec<(Place<'tcx>, MoveError<'tcx>)>,
+ ) -> Vec<GroupedMoveError<'tcx>> {
+ 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<GroupedMoveError<'tcx>>,
+ original_path: Place<'tcx>,
+ error: MoveError<'tcx>,
+ ) {
+ match error {
+ MoveError::UnionMove { .. } => {
+ unimplemented!("don't know how to report union move errors yet.")
+ }
+ MoveError::IllegalMove { cannot_move_out_of: IllegalMoveOrigin { location, kind } } => {
+ // Note: that the only time we assign a place isn't a temporary
+ // to a user variable is when initializing it.
+ // If that ever stops being the case, then the ever initialized
+ // flow could be used.
+ if let Some(StatementKind::Assign(box (
+ place,
+ Rvalue::Use(Operand::Move(move_from)),
+ ))) = self.body.basic_blocks()[location.block]
+ .statements
+ .get(location.statement_index)
+ .map(|stmt| &stmt.kind)
+ {
+ if let Some(local) = place.as_local() {
+ let local_decl = &self.body.local_decls[local];
+ // opt_match_place is the
+ // match_span is the span of the expression being matched on
+ // match *x.y { ... } match_place is Some(*x.y)
+ // ^^^^ match_span is the span of *x.y
+ //
+ // opt_match_place is None for let [mut] x = ... statements,
+ // whether or not the right-hand side is a place expression
+ if let Some(box LocalInfo::User(ClearCrossCrate::Set(BindingForm::Var(
+ VarBindingForm {
+ opt_match_place: Some((opt_match_place, match_span)),
+ binding_mode: _,
+ opt_ty_info: _,
+ pat_span: _,
+ },
+ )))) = local_decl.local_info
+ {
+ let stmt_source_info = self.body.source_info(location);
+ self.append_binding_error(
+ grouped_errors,
+ kind,
+ original_path,
+ *move_from,
+ local,
+ opt_match_place,
+ match_span,
+ stmt_source_info.span,
+ );
+ return;
+ }
+ }
+ }
+
+ let move_spans = self.move_spans(original_path.as_ref(), location);
+ grouped_errors.push(GroupedMoveError::OtherIllegalMove {
+ use_spans: move_spans,
+ original_path,
+ kind,
+ });
+ }
+ }
+ }
+
+ fn append_binding_error(
+ &self,
+ grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,
+ kind: IllegalMoveOriginKind<'tcx>,
+ original_path: Place<'tcx>,
+ move_from: Place<'tcx>,
+ bind_to: Local,
+ match_place: Option<Place<'tcx>>,
+ match_span: Span,
+ statement_span: Span,
+ ) {
+ debug!("append_binding_error(match_place={:?}, match_span={:?})", match_place, match_span);
+
+ let from_simple_let = match_place.is_none();
+ let match_place = match_place.unwrap_or(move_from);
+
+ match self.move_data.rev_lookup.find(match_place.as_ref()) {
+ // Error with the match place
+ LookupResult::Parent(_) => {
+ for ge in &mut *grouped_errors {
+ if let GroupedMoveError::MovesFromPlace { span, binds_to, .. } = ge
+ && match_span == *span
+ {
+ debug!("appending local({:?}) to list", bind_to);
+ if !binds_to.is_empty() {
+ binds_to.push(bind_to);
+ }
+ return;
+ }
+ }
+ debug!("found a new move error location");
+
+ // Don't need to point to x in let x = ... .
+ let (binds_to, span) = if from_simple_let {
+ (vec![], statement_span)
+ } else {
+ (vec![bind_to], match_span)
+ };
+ grouped_errors.push(GroupedMoveError::MovesFromPlace {
+ span,
+ move_from,
+ original_path,
+ kind,
+ binds_to,
+ });
+ }
+ // Error with the pattern
+ LookupResult::Exact(_) => {
+ let LookupResult::Parent(Some(mpi)) = self.move_data.rev_lookup.find(move_from.as_ref()) else {
+ // move_from should be a projection from match_place.
+ unreachable!("Probably not unreachable...");
+ };
+ for ge in &mut *grouped_errors {
+ if let GroupedMoveError::MovesFromValue {
+ span,
+ move_from: other_mpi,
+ binds_to,
+ ..
+ } = ge
+ {
+ if match_span == *span && mpi == *other_mpi {
+ debug!("appending local({:?}) to list", bind_to);
+ binds_to.push(bind_to);
+ return;
+ }
+ }
+ }
+ debug!("found a new move error location");
+ grouped_errors.push(GroupedMoveError::MovesFromValue {
+ span: match_span,
+ move_from: mpi,
+ original_path,
+ kind,
+ binds_to: vec![bind_to],
+ });
+ }
+ };
+ }
+
+ fn report(&mut self, error: GroupedMoveError<'tcx>) {
+ let (mut err, err_span) = {
+ let (span, use_spans, original_path, kind) = match error {
+ GroupedMoveError::MovesFromPlace { span, original_path, ref kind, .. }
+ | GroupedMoveError::MovesFromValue { span, original_path, ref kind, .. } => {
+ (span, None, original_path, kind)
+ }
+ GroupedMoveError::OtherIllegalMove { use_spans, original_path, ref kind } => {
+ (use_spans.args_or_use(), Some(use_spans), original_path, kind)
+ }
+ };
+ debug!(
+ "report: original_path={:?} span={:?}, kind={:?} \
+ original_path.is_upvar_field_projection={:?}",
+ original_path,
+ span,
+ kind,
+ self.is_upvar_field_projection(original_path.as_ref())
+ );
+ (
+ match kind {
+ &IllegalMoveOriginKind::BorrowedContent { target_place } => self
+ .report_cannot_move_from_borrowed_content(
+ original_path,
+ target_place,
+ span,
+ use_spans,
+ ),
+ &IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => {
+ self.cannot_move_out_of_interior_of_drop(span, ty)
+ }
+ &IllegalMoveOriginKind::InteriorOfSliceOrArray { ty, is_index } => {
+ self.cannot_move_out_of_interior_noncopy(span, ty, Some(is_index))
+ }
+ },
+ span,
+ )
+ };
+
+ self.add_move_hints(error, &mut err, err_span);
+ self.buffer_error(err);
+ }
+
+ fn report_cannot_move_from_static(
+ &mut self,
+ place: Place<'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'a, ErrorGuaranteed> {
+ let description = if place.projection.len() == 1 {
+ format!("static item {}", self.describe_any_place(place.as_ref()))
+ } else {
+ let base_static = PlaceRef { local: place.local, projection: &[ProjectionElem::Deref] };
+
+ format!(
+ "{} as {} is a static item",
+ self.describe_any_place(place.as_ref()),
+ self.describe_any_place(base_static),
+ )
+ };
+
+ self.cannot_move_out_of(span, &description)
+ }
+
+ fn report_cannot_move_from_borrowed_content(
+ &mut self,
+ move_place: Place<'tcx>,
+ deref_target_place: Place<'tcx>,
+ span: Span,
+ use_spans: Option<UseSpans<'tcx>>,
+ ) -> DiagnosticBuilder<'a, ErrorGuaranteed> {
+ // Inspect the type of the content behind the
+ // borrow to provide feedback about why this
+ // was a move rather than a copy.
+ let ty = deref_target_place.ty(self.body, self.infcx.tcx).ty;
+ let upvar_field = self
+ .prefixes(move_place.as_ref(), PrefixSet::All)
+ .find_map(|p| self.is_upvar_field_projection(p));
+
+ let deref_base = match deref_target_place.projection.as_ref() {
+ [proj_base @ .., ProjectionElem::Deref] => {
+ PlaceRef { local: deref_target_place.local, projection: &proj_base }
+ }
+ _ => bug!("deref_target_place is not a deref projection"),
+ };
+
+ if let PlaceRef { local, projection: [] } = deref_base {
+ let decl = &self.body.local_decls[local];
+ if decl.is_ref_for_guard() {
+ let mut err = self.cannot_move_out_of(
+ span,
+ &format!("`{}` in pattern guard", self.local_names[local].unwrap()),
+ );
+ err.note(
+ "variables bound in patterns cannot be moved from \
+ until after the end of the pattern guard",
+ );
+ return err;
+ } else if decl.is_ref_to_static() {
+ return self.report_cannot_move_from_static(move_place, span);
+ }
+ }
+
+ debug!("report: ty={:?}", ty);
+ let mut err = match ty.kind() {
+ ty::Array(..) | ty::Slice(..) => {
+ self.cannot_move_out_of_interior_noncopy(span, ty, None)
+ }
+ ty::Closure(def_id, closure_substs)
+ if def_id.as_local() == Some(self.mir_def_id()) && upvar_field.is_some() =>
+ {
+ let closure_kind_ty = closure_substs.as_closure().kind_ty();
+ let closure_kind = match closure_kind_ty.to_opt_closure_kind() {
+ Some(kind @ (ty::ClosureKind::Fn | ty::ClosureKind::FnMut)) => kind,
+ Some(ty::ClosureKind::FnOnce) => {
+ bug!("closure kind does not match first argument type")
+ }
+ None => bug!("closure kind not inferred by borrowck"),
+ };
+ let capture_description =
+ format!("captured variable in an `{closure_kind}` closure");
+
+ let upvar = &self.upvars[upvar_field.unwrap().index()];
+ let upvar_hir_id = upvar.place.get_root_variable();
+ let upvar_name = upvar.place.to_string(self.infcx.tcx);
+ let upvar_span = self.infcx.tcx.hir().span(upvar_hir_id);
+
+ let place_name = self.describe_any_place(move_place.as_ref());
+
+ let place_description =
+ if self.is_upvar_field_projection(move_place.as_ref()).is_some() {
+ format!("{place_name}, a {capture_description}")
+ } else {
+ format!("{place_name}, as `{upvar_name}` is a {capture_description}")
+ };
+
+ debug!(
+ "report: closure_kind_ty={:?} closure_kind={:?} place_description={:?}",
+ closure_kind_ty, closure_kind, place_description,
+ );
+
+ let mut diag = self.cannot_move_out_of(span, &place_description);
+
+ diag.span_label(upvar_span, "captured outer variable");
+ diag.span_label(
+ self.body.span,
+ format!("captured by this `{closure_kind}` closure"),
+ );
+
+ diag
+ }
+ _ => {
+ let source = self.borrowed_content_source(deref_base);
+ let move_place_ref = move_place.as_ref();
+ match (
+ self.describe_place_with_options(
+ move_place_ref,
+ DescribePlaceOpt {
+ including_downcast: false,
+ including_tuple_field: false,
+ },
+ ),
+ self.describe_name(move_place_ref),
+ source.describe_for_named_place(),
+ ) {
+ (Some(place_desc), Some(name), Some(source_desc)) => self.cannot_move_out_of(
+ span,
+ &format!("`{place_desc}` as enum variant `{name}` which is behind a {source_desc}"),
+ ),
+ (Some(place_desc), Some(name), None) => self.cannot_move_out_of(
+ span,
+ &format!("`{place_desc}` as enum variant `{name}`"),
+ ),
+ (Some(place_desc), _, Some(source_desc)) => self.cannot_move_out_of(
+ span,
+ &format!("`{place_desc}` which is behind a {source_desc}"),
+ ),
+ (_, _, _) => self.cannot_move_out_of(
+ span,
+ &source.describe_for_unnamed_place(self.infcx.tcx),
+ ),
+ }
+ }
+ };
+ if let Some(use_spans) = use_spans {
+ self.explain_captures(
+ &mut err, span, span, use_spans, move_place, None, "", "", "", false, true,
+ );
+ }
+ err
+ }
+
+ fn add_move_hints(&self, error: GroupedMoveError<'tcx>, err: &mut Diagnostic, span: Span) {
+ match error {
+ GroupedMoveError::MovesFromPlace { mut binds_to, move_from, .. } => {
+ if let Ok(snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(span) {
+ err.span_suggestion(
+ span,
+ "consider borrowing here",
+ format!("&{snippet}"),
+ Applicability::Unspecified,
+ );
+ }
+
+ if binds_to.is_empty() {
+ let place_ty = move_from.ty(self.body, self.infcx.tcx).ty;
+ let place_desc = match self.describe_place(move_from.as_ref()) {
+ Some(desc) => format!("`{desc}`"),
+ None => "value".to_string(),
+ };
+
+ self.note_type_does_not_implement_copy(
+ err,
+ &place_desc,
+ place_ty,
+ Some(span),
+ "",
+ );
+ } else {
+ binds_to.sort();
+ binds_to.dedup();
+
+ self.add_move_error_details(err, &binds_to);
+ }
+ }
+ GroupedMoveError::MovesFromValue { mut binds_to, .. } => {
+ binds_to.sort();
+ binds_to.dedup();
+ self.add_move_error_suggestions(err, &binds_to);
+ self.add_move_error_details(err, &binds_to);
+ }
+ // No binding. Nothing to suggest.
+ GroupedMoveError::OtherIllegalMove { ref original_path, use_spans, .. } => {
+ let span = use_spans.var_or_use();
+ let place_ty = original_path.ty(self.body, self.infcx.tcx).ty;
+ let place_desc = match self.describe_place(original_path.as_ref()) {
+ Some(desc) => format!("`{desc}`"),
+ None => "value".to_string(),
+ };
+ self.note_type_does_not_implement_copy(err, &place_desc, place_ty, Some(span), "");
+
+ use_spans.args_span_label(err, format!("move out of {place_desc} occurs here"));
+ }
+ }
+ }
+
+ fn add_move_error_suggestions(&self, err: &mut Diagnostic, binds_to: &[Local]) {
+ let mut suggestions: Vec<(Span, &str, String)> = Vec::new();
+ for local in binds_to {
+ let bind_to = &self.body.local_decls[*local];
+ if let Some(box LocalInfo::User(ClearCrossCrate::Set(BindingForm::Var(
+ VarBindingForm { pat_span, .. },
+ )))) = bind_to.local_info
+ {
+ if let Ok(pat_snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(pat_span)
+ {
+ if let Some(stripped) = pat_snippet.strip_prefix('&') {
+ let pat_snippet = stripped.trim_start();
+ let (suggestion, to_remove) = if pat_snippet.starts_with("mut")
+ && pat_snippet["mut".len()..].starts_with(rustc_lexer::is_whitespace)
+ {
+ (pat_snippet["mut".len()..].trim_start(), "&mut")
+ } else {
+ (pat_snippet, "&")
+ };
+ suggestions.push((pat_span, to_remove, suggestion.to_owned()));
+ }
+ }
+ }
+ }
+ suggestions.sort_unstable_by_key(|&(span, _, _)| span);
+ suggestions.dedup_by_key(|&mut (span, _, _)| span);
+ for (span, to_remove, suggestion) in suggestions {
+ err.span_suggestion(
+ span,
+ &format!("consider removing the `{to_remove}`"),
+ suggestion,
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+
+ fn add_move_error_details(&self, err: &mut Diagnostic, binds_to: &[Local]) {
+ for (j, local) in binds_to.iter().enumerate() {
+ let bind_to = &self.body.local_decls[*local];
+ let binding_span = bind_to.source_info.span;
+
+ if j == 0 {
+ err.span_label(binding_span, "data moved here");
+ } else {
+ err.span_label(binding_span, "...and here");
+ }
+
+ if binds_to.len() == 1 {
+ self.note_type_does_not_implement_copy(
+ err,
+ &format!("`{}`", self.local_names[*local].unwrap()),
+ bind_to.ty,
+ Some(binding_span),
+ "",
+ );
+ }
+ }
+
+ if binds_to.len() > 1 {
+ err.note(
+ "move occurs because these variables have types that \
+ don't implement the `Copy` trait",
+ );
+ }
+ }
+}
diff --git a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs
new file mode 100644
index 000000000..0ad4abbce
--- /dev/null
+++ b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs
@@ -0,0 +1,1115 @@
+use rustc_hir as hir;
+use rustc_hir::Node;
+use rustc_middle::hir::map::Map;
+use rustc_middle::mir::{Mutability, Place, PlaceRef, ProjectionElem};
+use rustc_middle::ty::{self, Ty, TyCtxt};
+use rustc_middle::{
+ hir::place::PlaceBase,
+ mir::{
+ self, BindingForm, ClearCrossCrate, ImplicitSelfKind, Local, LocalDecl, LocalInfo,
+ LocalKind, Location,
+ },
+};
+use rustc_span::source_map::DesugaringKind;
+use rustc_span::symbol::{kw, Symbol};
+use rustc_span::{BytePos, Span};
+
+use crate::diagnostics::BorrowedContentSource;
+use crate::MirBorrowckCtxt;
+use rustc_const_eval::util::collect_writes::FindAssignments;
+use rustc_errors::{Applicability, Diagnostic};
+
+#[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<'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_any_place(access_place.as_ref());
+ debug!("report_mutability_error: access_place_desc={:?}", access_place_desc);
+
+ match the_place_err {
+ PlaceRef { local, projection: [] } => {
+ item_msg = access_place_desc;
+ 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 `{name}` is not declared as mutable");
+ }
+ }
+
+ PlaceRef {
+ local,
+ projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)],
+ } => {
+ debug_assert!(is_closure_or_generator(
+ Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty
+ ));
+
+ let imm_borrow_derefed = self.upvars[upvar_index.index()]
+ .place
+ .place
+ .deref_tys()
+ .any(|ty| matches!(ty.kind(), ty::Ref(.., hir::Mutability::Not)));
+
+ // If the place is immutable then:
+ //
+ // - Either we deref an immutable ref to get to our final place.
+ // - We don't capture derefs of raw ptrs
+ // - Or the final place is immut because the root variable of the capture
+ // isn't marked mut and we should suggest that to the user.
+ if imm_borrow_derefed {
+ // If we deref an immutable ref then the suggestion here doesn't help.
+ return;
+ } else {
+ item_msg = access_place_desc;
+ 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()].place.to_string(self.infcx.tcx);
+ reason = format!(", as `{name}` is not declared as mutable");
+ }
+ }
+ }
+
+ PlaceRef { local, projection: [ProjectionElem::Deref] }
+ if self.body.local_decls[local].is_ref_for_guard() =>
+ {
+ item_msg = access_place_desc;
+ reason = ", as it is immutable for the pattern guard".to_string();
+ }
+ PlaceRef { 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}");
+ reason = String::new();
+ } else {
+ item_msg = access_place_desc;
+ let local_info = &self.body.local_decls[local].local_info;
+ if let Some(box LocalInfo::StaticRef { def_id, .. }) = *local_info {
+ let static_name = &self.infcx.tcx.item_name(def_id);
+ reason = format!(", as `{static_name}` is an immutable static item");
+ } else {
+ bug!("is_ref_to_static return true, but not ref to static?");
+ }
+ }
+ }
+ PlaceRef { local: _, projection: [proj_base @ .., ProjectionElem::Deref] } => {
+ if the_place_err.local == ty::CAPTURE_STRUCT_LOCAL
+ && proj_base.is_empty()
+ && !self.upvars.is_empty()
+ {
+ item_msg = access_place_desc;
+ debug_assert!(
+ self.body.local_decls[ty::CAPTURE_STRUCT_LOCAL].ty.is_region_ptr()
+ );
+ debug_assert!(is_closure_or_generator(
+ Place::ty_from(
+ the_place_err.local,
+ 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 {
+ local: the_place_err.local,
+ projection: proj_base,
+ });
+ let pointer_type = source.describe_for_immutable_place(self.infcx.tcx);
+ opt_source = Some(source);
+ if let Some(desc) = self.describe_place(access_place.as_ref()) {
+ 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 {
+ local: _,
+ projection:
+ [
+ ..,
+ ProjectionElem::Index(_)
+ | ProjectionElem::ConstantIndex { .. }
+ | ProjectionElem::Subslice { .. }
+ | 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",
+ self.describe_any_place(access_place.as_ref()),
+ ),
+ "mutable",
+ );
+ 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 {
+ local,
+ projection:
+ &[
+ ref proj_base @ ..,
+ ProjectionElem::Deref,
+ ProjectionElem::Field(field, _),
+ ProjectionElem::Deref,
+ ],
+ } => {
+ err.span_label(span, format!("cannot {ACT}", ACT = act));
+
+ if let Some(span) = get_mut_span_in_struct_field(
+ self.infcx.tcx,
+ Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty,
+ field,
+ ) {
+ err.span_suggestion_verbose(
+ span,
+ "consider changing this to be mutable",
+ " mut ",
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+
+ // Suggest removing a `&mut` from the use of a mutable reference.
+ PlaceRef { local, projection: [] }
+ if self
+ .body
+ .local_decls
+ .get(local)
+ .map(|l| mut_borrow_of_mutable_ref(l, self.local_names[local]))
+ .unwrap_or(false) =>
+ {
+ let decl = &self.body.local_decls[local];
+ err.span_label(span, format!("cannot {ACT}", ACT = act));
+ if let Some(mir::Statement {
+ source_info,
+ kind:
+ mir::StatementKind::Assign(box (
+ _,
+ mir::Rvalue::Ref(
+ _,
+ mir::BorrowKind::Mut { allow_two_phase_borrow: false },
+ _,
+ ),
+ )),
+ ..
+ }) = &self.body[location.block].statements.get(location.statement_index)
+ {
+ match decl.local_info {
+ Some(box LocalInfo::User(ClearCrossCrate::Set(BindingForm::Var(
+ mir::VarBindingForm {
+ binding_mode: ty::BindingMode::BindByValue(Mutability::Not),
+ opt_ty_info: Some(sp),
+ opt_match_place: _,
+ pat_span: _,
+ },
+ )))) => {
+ err.span_note(sp, "the binding is already a mutable borrow");
+ }
+ _ => {
+ err.span_note(
+ decl.source_info.span,
+ "the binding is already a mutable borrow",
+ );
+ }
+ }
+ if let Ok(snippet) =
+ self.infcx.tcx.sess.source_map().span_to_snippet(source_info.span)
+ {
+ if snippet.starts_with("&mut ") {
+ // We don't have access to the HIR to get accurate spans, but we can
+ // give a best effort structured suggestion.
+ err.span_suggestion_verbose(
+ source_info.span.with_hi(source_info.span.lo() + BytePos(5)),
+ "try removing `&mut` here",
+ "",
+ Applicability::MachineApplicable,
+ );
+ } else {
+ // This can occur with things like `(&mut self).foo()`.
+ err.span_help(source_info.span, "try removing `&mut` here");
+ }
+ } else {
+ err.span_help(source_info.span, "try removing `&mut` here");
+ }
+ } else if decl.mutability == Mutability::Not
+ && !matches!(
+ decl.local_info,
+ Some(box LocalInfo::User(ClearCrossCrate::Set(BindingForm::ImplicitSelf(
+ ImplicitSelfKind::MutRef
+ ))))
+ )
+ {
+ err.span_suggestion_verbose(
+ decl.source_info.span.shrink_to_lo(),
+ "consider making the binding mutable",
+ "mut ",
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+
+ // We want to suggest users use `let mut` for local (user
+ // variable) mutations...
+ PlaceRef { 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,
+ );
+ let tcx = self.infcx.tcx;
+ if let ty::Closure(id, _) = *the_place_err.ty(self.body, tcx).ty.kind() {
+ self.show_mutating_upvar(tcx, id.expect_local(), the_place_err, &mut err);
+ }
+ }
+
+ // Also suggest adding mut for upvars
+ PlaceRef {
+ local,
+ projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)],
+ } => {
+ debug_assert!(is_closure_or_generator(
+ Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty
+ ));
+
+ let captured_place = &self.upvars[upvar_index.index()].place;
+
+ err.span_label(span, format!("cannot {ACT}", ACT = act));
+
+ let upvar_hir_id = captured_place.get_root_variable();
+
+ if let Some(Node::Pat(pat)) = self.infcx.tcx.hir().find(upvar_hir_id)
+ && 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,
+ );
+ }
+
+ let tcx = self.infcx.tcx;
+ if let ty::Ref(_, ty, Mutability::Mut) = the_place_err.ty(self.body, tcx).ty.kind()
+ && let ty::Closure(id, _) = *ty.kind()
+ {
+ self.show_mutating_upvar(tcx, id.expect_local(), the_place_err, &mut err);
+ }
+ }
+
+ // 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 { 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_suggestion(
+ span,
+ "try removing `&mut` here",
+ "",
+ Applicability::MaybeIncorrect,
+ );
+ }
+
+ PlaceRef { 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 { local, projection: [ProjectionElem::Deref] }
+ if self.body.local_decls[local].is_user_variable() =>
+ {
+ let local_decl = &self.body.local_decls[local];
+
+ let (pointer_sigil, pointer_desc) = if local_decl.ty.is_region_ptr() {
+ ("&", "reference")
+ } else {
+ ("*const", "pointer")
+ };
+
+ match self.local_names[local] {
+ Some(name) if !local_decl.from_compiler_desugaring() => {
+ let label = match local_decl.local_info.as_deref().unwrap() {
+ LocalInfo::User(ClearCrossCrate::Set(
+ mir::BindingForm::ImplicitSelf(_),
+ )) => {
+ let (span, suggestion) =
+ suggest_ampmut_self(self.infcx.tcx, local_decl);
+ Some((true, span, suggestion))
+ }
+
+ LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var(
+ mir::VarBindingForm {
+ binding_mode: ty::BindingMode::BindByValue(_),
+ opt_ty_info,
+ ..
+ },
+ ))) => {
+ // check if the RHS is from desugaring
+ let opt_assignment_rhs_span =
+ self.body.find_assignments(local).first().map(|&location| {
+ if let Some(mir::Statement {
+ source_info: _,
+ kind:
+ mir::StatementKind::Assign(box (
+ _,
+ mir::Rvalue::Use(mir::Operand::Copy(place)),
+ )),
+ }) = self.body[location.block]
+ .statements
+ .get(location.statement_index)
+ {
+ self.body.local_decls[place.local].source_info.span
+ } else {
+ self.body.source_info(location).span
+ }
+ });
+ match opt_assignment_rhs_span.and_then(|s| s.desugaring_kind()) {
+ // on for loops, RHS points to the iterator part
+ Some(DesugaringKind::ForLoop) => {
+ self.suggest_similar_mut_method_for_for_loop(&mut err);
+ err.span_label(opt_assignment_rhs_span.unwrap(), format!(
+ "this iterator yields `{pointer_sigil}` {pointer_desc}s",
+ ));
+ None
+ }
+ // don't create labels for compiler-generated spans
+ Some(_) => None,
+ None => {
+ let label = if name != kw::SelfLower {
+ suggest_ampmut(
+ self.infcx.tcx,
+ local_decl,
+ opt_assignment_rhs_span,
+ *opt_ty_info,
+ )
+ } else {
+ match local_decl.local_info.as_deref() {
+ Some(LocalInfo::User(ClearCrossCrate::Set(
+ mir::BindingForm::Var(mir::VarBindingForm {
+ opt_ty_info: None,
+ ..
+ }),
+ ))) => {
+ let (span, sugg) = suggest_ampmut_self(
+ self.infcx.tcx,
+ local_decl,
+ );
+ (true, span, sugg)
+ }
+ // explicit self (eg `self: &'a Self`)
+ _ => suggest_ampmut(
+ self.infcx.tcx,
+ local_decl,
+ opt_assignment_rhs_span,
+ *opt_ty_info,
+ ),
+ }
+ };
+ Some(label)
+ }
+ }
+ }
+
+ 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| (true, pattern_span, replacement))
+ }
+
+ LocalInfo::User(ClearCrossCrate::Clear) => {
+ bug!("saw cleared local state")
+ }
+
+ _ => unreachable!(),
+ };
+
+ match label {
+ Some((true, err_help_span, suggested_code)) => {
+ let (is_trait_sig, local_trait) = self.is_error_in_trait(local);
+ if !is_trait_sig {
+ err.span_suggestion(
+ err_help_span,
+ &format!(
+ "consider changing this to be a mutable {pointer_desc}"
+ ),
+ suggested_code,
+ Applicability::MachineApplicable,
+ );
+ } else if let Some(x) = local_trait {
+ err.span_suggestion(
+ x,
+ &format!(
+ "consider changing that to be a mutable {pointer_desc}"
+ ),
+ suggested_code,
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ Some((false, err_label_span, message)) => {
+ err.span_label(
+ err_label_span,
+ &format!(
+ "consider changing this binding's type to be: `{message}`"
+ ),
+ );
+ }
+ None => {}
+ }
+ 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 { local, projection: [ProjectionElem::Deref] }
+ if local == ty::CAPTURE_STRUCT_LOCAL && !self.upvars.is_empty() =>
+ {
+ self.expected_fn_found_fn_mut_call(&mut err, span, act);
+ }
+
+ PlaceRef { local: _, 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));
+ }
+ }
+
+ self.buffer_error(err);
+ }
+
+ /// User cannot make signature of a trait mutable without changing the
+ /// trait. So we find if this error belongs to a trait and if so we move
+ /// suggestion to the trait or disable it if it is out of scope of this crate
+ fn is_error_in_trait(&self, local: Local) -> (bool, Option<Span>) {
+ if self.body.local_kind(local) != LocalKind::Arg {
+ return (false, None);
+ }
+ let hir_map = self.infcx.tcx.hir();
+ let my_def = self.body.source.def_id();
+ let my_hir = hir_map.local_def_id_to_hir_id(my_def.as_local().unwrap());
+ let Some(td) =
+ self.infcx.tcx.impl_of_method(my_def).and_then(|x| self.infcx.tcx.trait_id_of_impl(x))
+ else {
+ return (false, None);
+ };
+ (
+ true,
+ td.as_local().and_then(|tld| match hir_map.find_by_def_id(tld) {
+ Some(Node::Item(hir::Item {
+ kind: hir::ItemKind::Trait(_, _, _, _, items),
+ ..
+ })) => {
+ let mut f_in_trait_opt = None;
+ for hir::TraitItemRef { id: fi, kind: k, .. } in *items {
+ let hi = fi.hir_id();
+ if !matches!(k, hir::AssocItemKind::Fn { .. }) {
+ continue;
+ }
+ if hir_map.name(hi) != hir_map.name(my_hir) {
+ continue;
+ }
+ f_in_trait_opt = Some(hi);
+ break;
+ }
+ f_in_trait_opt.and_then(|f_in_trait| match hir_map.find(f_in_trait) {
+ Some(Node::TraitItem(hir::TraitItem {
+ kind:
+ hir::TraitItemKind::Fn(
+ hir::FnSig { decl: hir::FnDecl { inputs, .. }, .. },
+ _,
+ ),
+ ..
+ })) => {
+ let hir::Ty { span, .. } = inputs[local.index() - 1];
+ Some(span)
+ }
+ _ => None,
+ })
+ }
+ _ => None,
+ }),
+ )
+ }
+
+ // point to span of upvar making closure call require mutable borrow
+ fn show_mutating_upvar(
+ &self,
+ tcx: TyCtxt<'_>,
+ closure_local_def_id: hir::def_id::LocalDefId,
+ the_place_err: PlaceRef<'tcx>,
+ err: &mut Diagnostic,
+ ) {
+ let tables = tcx.typeck(closure_local_def_id);
+ let closure_hir_id = tcx.hir().local_def_id_to_hir_id(closure_local_def_id);
+ if let Some((span, closure_kind_origin)) =
+ &tables.closure_kind_origins().get(closure_hir_id)
+ {
+ let reason = if let PlaceBase::Upvar(upvar_id) = closure_kind_origin.base {
+ let upvar = ty::place_to_string_for_capture(tcx, closure_kind_origin);
+ let root_hir_id = upvar_id.var_path.hir_id;
+ // we have an origin for this closure kind starting at this root variable so it's safe to unwrap here
+ let captured_places =
+ tables.closure_min_captures[&closure_local_def_id].get(&root_hir_id).unwrap();
+
+ let origin_projection = closure_kind_origin
+ .projections
+ .iter()
+ .map(|proj| proj.kind)
+ .collect::<Vec<_>>();
+ let mut capture_reason = String::new();
+ for captured_place in captured_places {
+ let captured_place_kinds = captured_place
+ .place
+ .projections
+ .iter()
+ .map(|proj| proj.kind)
+ .collect::<Vec<_>>();
+ if rustc_middle::ty::is_ancestor_or_same_capture(
+ &captured_place_kinds,
+ &origin_projection,
+ ) {
+ match captured_place.info.capture_kind {
+ ty::UpvarCapture::ByRef(
+ ty::BorrowKind::MutBorrow | ty::BorrowKind::UniqueImmBorrow,
+ ) => {
+ capture_reason = format!("mutable borrow of `{upvar}`");
+ }
+ ty::UpvarCapture::ByValue => {
+ capture_reason = format!("possible mutation of `{upvar}`");
+ }
+ _ => bug!("upvar `{upvar}` borrowed, but not mutably"),
+ }
+ break;
+ }
+ }
+ if capture_reason.is_empty() {
+ bug!("upvar `{upvar}` borrowed, but cannot find reason");
+ }
+ capture_reason
+ } else {
+ bug!("not an upvar")
+ };
+ err.span_label(
+ *span,
+ format!(
+ "calling `{}` requires mutable binding due to {}",
+ self.describe_place(the_place_err).unwrap(),
+ reason
+ ),
+ );
+ }
+ }
+
+ // Attempt to search similar mutable associated items for suggestion.
+ // In the future, attempt in all path but initially for RHS of for_loop
+ fn suggest_similar_mut_method_for_for_loop(&self, err: &mut Diagnostic) {
+ use hir::{
+ BodyId, Expr,
+ ExprKind::{Block, Call, DropTemps, Match, MethodCall},
+ HirId, ImplItem, ImplItemKind, Item, ItemKind,
+ };
+
+ fn maybe_body_id_of_fn(hir_map: Map<'_>, id: HirId) -> Option<BodyId> {
+ match hir_map.find(id) {
+ Some(Node::Item(Item { kind: ItemKind::Fn(_, _, body_id), .. }))
+ | Some(Node::ImplItem(ImplItem { kind: ImplItemKind::Fn(_, body_id), .. })) => {
+ Some(*body_id)
+ }
+ _ => None,
+ }
+ }
+ let hir_map = self.infcx.tcx.hir();
+ let mir_body_hir_id = self.mir_hir_id();
+ if let Some(fn_body_id) = maybe_body_id_of_fn(hir_map, mir_body_hir_id) {
+ if let Block(
+ hir::Block {
+ expr:
+ Some(Expr {
+ kind:
+ DropTemps(Expr {
+ kind:
+ Match(
+ Expr {
+ kind:
+ Call(
+ _,
+ [
+ Expr {
+ kind:
+ MethodCall(
+ path_segment,
+ _args,
+ span,
+ ),
+ hir_id,
+ ..
+ },
+ ..,
+ ],
+ ),
+ ..
+ },
+ ..,
+ ),
+ ..
+ }),
+ ..
+ }),
+ ..
+ },
+ _,
+ ) = hir_map.body(fn_body_id).value.kind
+ {
+ let opt_suggestions = path_segment
+ .hir_id
+ .map(|path_hir_id| self.infcx.tcx.typeck(path_hir_id.owner))
+ .and_then(|typeck| typeck.type_dependent_def_id(*hir_id))
+ .and_then(|def_id| self.infcx.tcx.impl_of_method(def_id))
+ .map(|def_id| self.infcx.tcx.associated_items(def_id))
+ .map(|assoc_items| {
+ assoc_items
+ .in_definition_order()
+ .map(|assoc_item_def| assoc_item_def.ident(self.infcx.tcx))
+ .filter(|&ident| {
+ let original_method_ident = path_segment.ident;
+ original_method_ident != ident
+ && ident
+ .as_str()
+ .starts_with(&original_method_ident.name.to_string())
+ })
+ .map(|ident| format!("{ident}()"))
+ .peekable()
+ });
+
+ if let Some(mut suggestions) = opt_suggestions
+ && suggestions.peek().is_some()
+ {
+ err.span_suggestions(
+ *span,
+ "use mutable method",
+ suggestions,
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ };
+ }
+
+ /// Targeted error when encountering an `FnMut` closure where an `Fn` closure was expected.
+ fn expected_fn_found_fn_mut_call(&self, err: &mut Diagnostic, sp: Span, act: &str) {
+ err.span_label(sp, format!("cannot {act}"));
+
+ let hir = self.infcx.tcx.hir();
+ let closure_id = self.mir_hir_id();
+ let fn_call_id = hir.get_parent_node(closure_id);
+ let node = hir.get(fn_call_id);
+ let def_id = hir.enclosing_body_owner(fn_call_id);
+ let mut look_at_return = true;
+ // If we can detect the expression to be an `fn` call where the closure was an argument,
+ // we point at the `fn` definition argument...
+ if let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Call(func, args), .. }) = node {
+ let arg_pos = args
+ .iter()
+ .enumerate()
+ .filter(|(_, arg)| arg.hir_id == closure_id)
+ .map(|(pos, _)| pos)
+ .next();
+ let tables = self.infcx.tcx.typeck(def_id);
+ if let Some(ty::FnDef(def_id, _)) =
+ tables.node_type_opt(func.hir_id).as_ref().map(|ty| ty.kind())
+ {
+ let arg = match hir.get_if_local(*def_id) {
+ Some(
+ hir::Node::Item(hir::Item {
+ ident, kind: hir::ItemKind::Fn(sig, ..), ..
+ })
+ | hir::Node::TraitItem(hir::TraitItem {
+ ident,
+ kind: hir::TraitItemKind::Fn(sig, _),
+ ..
+ })
+ | hir::Node::ImplItem(hir::ImplItem {
+ ident,
+ kind: hir::ImplItemKind::Fn(sig, _),
+ ..
+ }),
+ ) => Some(
+ arg_pos
+ .and_then(|pos| {
+ sig.decl.inputs.get(
+ pos + if sig.decl.implicit_self.has_implicit_self() {
+ 1
+ } else {
+ 0
+ },
+ )
+ })
+ .map(|arg| arg.span)
+ .unwrap_or(ident.span),
+ ),
+ _ => None,
+ };
+ if let Some(span) = arg {
+ err.span_label(span, "change this to accept `FnMut` instead of `Fn`");
+ err.span_label(func.span, "expects `Fn` instead of `FnMut`");
+ err.span_label(self.body.span, "in this closure");
+ look_at_return = false;
+ }
+ }
+ }
+
+ if look_at_return && hir.get_return_block(closure_id).is_some() {
+ // ...otherwise we are probably in the tail expression of the function, point at the
+ // return type.
+ match hir.get_by_def_id(hir.get_parent_item(fn_call_id)) {
+ hir::Node::Item(hir::Item { ident, kind: hir::ItemKind::Fn(sig, ..), .. })
+ | hir::Node::TraitItem(hir::TraitItem {
+ ident,
+ kind: hir::TraitItemKind::Fn(sig, _),
+ ..
+ })
+ | hir::Node::ImplItem(hir::ImplItem {
+ ident,
+ kind: hir::ImplItemKind::Fn(sig, _),
+ ..
+ }) => {
+ err.span_label(ident.span, "");
+ err.span_label(
+ sig.decl.output.span(),
+ "change this to return `FnMut` instead of `Fn`",
+ );
+ err.span_label(self.body.span, "in this closure");
+ }
+ _ => {}
+ }
+ }
+ }
+}
+
+fn mut_borrow_of_mutable_ref(local_decl: &LocalDecl<'_>, local_name: Option<Symbol>) -> bool {
+ debug!("local_info: {:?}, ty.kind(): {:?}", local_decl.local_info, local_decl.ty.kind());
+
+ match local_decl.local_info.as_deref() {
+ // Check if mutably borrowing a mutable reference.
+ Some(LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var(
+ mir::VarBindingForm {
+ binding_mode: ty::BindingMode::BindByValue(Mutability::Not), ..
+ },
+ )))) => matches!(local_decl.ty.kind(), ty::Ref(_, _, hir::Mutability::Mut)),
+ Some(LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::ImplicitSelf(kind)))) => {
+ // 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
+ }
+ _ if Some(kw::SelfLower) == local_name => {
+ // Otherwise, check if the name is the `self` keyword - 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`.
+ matches!(local_decl.ty.kind(), ty::Ref(_, _, hir::Mutability::Mut))
+ }
+ _ => false,
+ }
+}
+
+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>,
+ local_decl: &mir::LocalDecl<'tcx>,
+ opt_assignment_rhs_span: Option<Span>,
+ opt_ty_info: Option<Span>,
+) -> (bool, Span, String) {
+ if let Some(assignment_rhs_span) = opt_assignment_rhs_span
+ && let Ok(src) = tcx.sess.source_map().span_to_snippet(assignment_rhs_span)
+ {
+ let is_mutbl = |ty: &str| -> bool {
+ if let Some(rest) = ty.strip_prefix("mut") {
+ match rest.chars().next() {
+ // e.g. `&mut x`
+ Some(c) if c.is_whitespace() => true,
+ // e.g. `&mut(x)`
+ Some('(') => true,
+ // e.g. `&mut{x}`
+ Some('{') => true,
+ // e.g. `&mutablevar`
+ _ => false,
+ }
+ } else {
+ false
+ }
+ };
+ if let (true, Some(ws_pos)) = (src.starts_with("&'"), src.find(char::is_whitespace)) {
+ let lt_name = &src[1..ws_pos];
+ let ty = src[ws_pos..].trim_start();
+ if !is_mutbl(ty) {
+ return (true, assignment_rhs_span, format!("&{lt_name} mut {ty}"));
+ }
+ } else if let Some(stripped) = src.strip_prefix('&') {
+ let stripped = stripped.trim_start();
+ if !is_mutbl(stripped) {
+ return (true, assignment_rhs_span, format!("&mut {stripped}"));
+ }
+ }
+ }
+
+ let (suggestability, 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) => (true, ty_span),
+
+ // otherwise, just highlight the span associated with
+ // the (MIR) LocalDecl.
+ None => (false, local_decl.source_info.span),
+ };
+
+ if let Ok(src) = tcx.sess.source_map().span_to_snippet(highlight_span)
+ && let (true, Some(ws_pos)) = (src.starts_with("&'"), src.find(char::is_whitespace))
+ {
+ let lt_name = &src[1..ws_pos];
+ let ty = &src[ws_pos..];
+ return (true, highlight_span, format!("&{} mut{}", lt_name, ty));
+ }
+
+ let ty_mut = local_decl.ty.builtin_deref(true).unwrap();
+ assert_eq!(ty_mut.mutbl, hir::Mutability::Not);
+ (
+ suggestability,
+ 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()
+}
+
+/// Given a field that needs to be mutable, returns a span where the " mut " could go.
+/// This function expects the local to be a reference to a struct in order to produce a span.
+///
+/// ```text
+/// LL | s: &'a String
+/// | ^^^ returns a span taking up the space here
+/// ```
+fn get_mut_span_in_struct_field<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ ty: Ty<'tcx>,
+ field: mir::Field,
+) -> Option<Span> {
+ // Expect our local to be a reference to a struct of some kind.
+ if let ty::Ref(_, ty, _) = ty.kind()
+ && let ty::Adt(def, _) = ty.kind()
+ && let field = def.all_fields().nth(field.index())?
+ // Use the HIR types to construct the diagnostic message.
+ && let node = tcx.hir().find_by_def_id(field.did.as_local()?)?
+ // 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.
+ && let hir::Node::Field(field) = node
+ && let hir::TyKind::Rptr(lt, hir::MutTy { mutbl: hir::Mutability::Not, ty }) = field.ty.kind
+ {
+ return Some(lt.span.between(ty.span));
+ }
+
+ None
+}
+
+/// If possible, suggest replacing `ref` with `ref mut`.
+fn suggest_ref_mut(tcx: TyCtxt<'_>, binding_span: Span) -> Option<String> {
+ 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/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs b/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs
new file mode 100644
index 000000000..d359d7efb
--- /dev/null
+++ b/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs
@@ -0,0 +1,261 @@
+//! Contains utilities for generating suggestions for borrowck errors related to unsatisfied
+//! outlives constraints.
+
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Diagnostic;
+use rustc_middle::ty::RegionVid;
+use smallvec::SmallVec;
+use std::collections::BTreeMap;
+use tracing::debug;
+
+use crate::MirBorrowckCtxt;
+
+use super::{ErrorConstraintInfo, RegionName, RegionNameSource};
+
+/// 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.
+#[derive(Default)]
+pub struct OutlivesSuggestionBuilder {
+ /// 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<RegionVid, Vec<RegionVid>>,
+}
+
+impl OutlivesSuggestionBuilder {
+ /// 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 unnameable
+ // regions.
+ RegionNameSource::SynthesizedFreeEnvRegion(..)
+ | RegionNameSource::AnonRegionFromArgument(..)
+ | RegionNameSource::AnonRegionFromUpvar(..)
+ | RegionNameSource::AnonRegionFromOutput(..)
+ | RegionNameSource::AnonRegionFromYieldTy(..)
+ | RegionNameSource::AnonRegionFromAsyncFn(..)
+ | RegionNameSource::AnonRegionFromImplSignature(..) => {
+ 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,
+ mbcx: &MirBorrowckCtxt<'_, '_>,
+ region: RegionVid,
+ ) -> Option<RegionName> {
+ mbcx.give_region_a_name(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(
+ &self,
+ mbcx: &MirBorrowckCtxt<'_, '_>,
+ ) -> 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();
+
+ for (fr, outlived) in &self.constraints_to_add {
+ let Some(fr_name) = self.region_vid_to_name(mbcx, *fr) 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(mbcx, *fr).map(|rname| (fr, rname)))
+ .collect::<Vec<_>>();
+
+ // 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)| matches!(outlived_name.source, RegionNameSource::Static))
+ {
+ 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::<SmallVec<_>>();
+ suggested.push(SuggestedConstraint::Outlives(fr_name, other))
+ }
+ }
+ }
+
+ suggested
+ }
+
+ /// Add the outlives constraint `fr: outlived_fr` to the set of constraints we need to suggest.
+ pub(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_default().push(outlived_fr);
+ }
+
+ /// Emit an intermediate note on the given `Diagnostic` if the involved regions are
+ /// suggestable.
+ pub(crate) fn intermediate_suggestion(
+ &mut self,
+ mbcx: &MirBorrowckCtxt<'_, '_>,
+ errci: &ErrorConstraintInfo<'_>,
+ diag: &mut Diagnostic,
+ ) {
+ // Emit an intermediate note.
+ let fr_name = self.region_vid_to_name(mbcx, errci.fr);
+ let outlived_fr_name = self.region_vid_to_name(mbcx, errci.outlived_fr);
+
+ if let (Some(fr_name), Some(outlived_fr_name)) = (fr_name, outlived_fr_name)
+ && !matches!(outlived_fr_name.source, RegionNameSource::Static)
+ {
+ 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.
+ pub(crate) fn add_suggestion(&self, mbcx: &mut MirBorrowckCtxt<'_, '_>) {
+ // 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
+ && self.constraints_to_add.values().next().unwrap().len() == 1
+ {
+ debug!("Only 1 suggestion. Skipping.");
+ return;
+ }
+
+ // Get all suggestable constraints.
+ let suggested = self.compile_all_suggestions(mbcx);
+
+ // 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 {
+ mbcx.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 = mbcx
+ .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 = mbcx.body.span;
+ diag.sort_span = mir_span.shrink_to_hi();
+
+ // Buffer the diagnostic
+ mbcx.buffer_non_error_diag(diag);
+ }
+}
diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs
new file mode 100644
index 000000000..176090c3b
--- /dev/null
+++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs
@@ -0,0 +1,904 @@
+//! Error reporting machinery for lifetime errors.
+
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan};
+use rustc_hir::def_id::DefId;
+use rustc_hir::intravisit::Visitor;
+use rustc_hir::{self as hir, Item, ItemKind, Node};
+use rustc_infer::infer::{
+ error_reporting::nice_region_error::{
+ self, find_anon_type, find_param_with_region, suggest_adding_lifetime_params,
+ HirTraitObjectVisitor, NiceRegionError, TraitObjectVisitor,
+ },
+ error_reporting::unexpected_hidden_region_diagnostic,
+ NllRegionVariableOrigin, RelateParamBound,
+};
+use rustc_middle::hir::place::PlaceBase;
+use rustc_middle::mir::{ConstraintCategory, ReturnConstraint};
+use rustc_middle::ty::subst::InternalSubsts;
+use rustc_middle::ty::Region;
+use rustc_middle::ty::TypeVisitor;
+use rustc_middle::ty::{self, RegionVid, Ty};
+use rustc_span::symbol::{kw, sym, Ident};
+use rustc_span::Span;
+
+use crate::borrowck_errors;
+use crate::session_diagnostics::GenericDoesNotLiveLongEnough;
+
+use super::{OutlivesSuggestionBuilder, RegionName};
+use crate::region_infer::BlameConstraint;
+use crate::{
+ nll::ConstraintDescription,
+ region_infer::{values::RegionElement, TypeTest},
+ universal_regions::DefiningTy,
+ MirBorrowckCtxt,
+};
+
+impl<'tcx> ConstraintDescription for ConstraintCategory<'tcx> {
+ 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::ClosureUpvar(_) => "closure capture ",
+ ConstraintCategory::Usage => "this usage ",
+ ConstraintCategory::Predicate(_)
+ | ConstraintCategory::Boring
+ | ConstraintCategory::BoringNoLocation
+ | ConstraintCategory::Internal => "",
+ }
+ }
+}
+
+/// A collection of errors encountered during region inference. This is needed to efficiently
+/// report errors after borrow checking.
+///
+/// Usually we expect this to either be empty or contain a small number of items, so we can avoid
+/// allocation most of the time.
+pub(crate) type RegionErrors<'tcx> = Vec<RegionErrorKind<'tcx>>;
+
+#[derive(Clone, Debug)]
+pub(crate) enum RegionErrorKind<'tcx> {
+ /// A generic bound failure for a type test (`T: 'a`).
+ TypeTestError { type_test: TypeTest<'tcx> },
+
+ /// An unexpected hidden region for an opaque type.
+ UnexpectedHiddenRegion {
+ /// The span for the member constraint.
+ span: Span,
+ /// The hidden type.
+ hidden_ty: Ty<'tcx>,
+ /// The opaque type.
+ key: ty::OpaqueTypeKey<'tcx>,
+ /// The unexpected region.
+ member_region: ty::Region<'tcx>,
+ },
+
+ /// Higher-ranked subtyping error.
+ BoundUniversalRegionError {
+ /// The placeholder free region.
+ longer_fr: RegionVid,
+ /// The region element that erroneously must be outlived by `longer_fr`.
+ error_element: RegionElement,
+ /// The placeholder region.
+ placeholder: ty::PlaceholderRegion,
+ },
+
+ /// Any other lifetime error.
+ RegionError {
+ /// The origin of the region.
+ fr_origin: NllRegionVariableOrigin,
+ /// The region that should outlive `shorter_fr`.
+ longer_fr: RegionVid,
+ /// The region that should be shorter, but we can't prove it.
+ shorter_fr: RegionVid,
+ /// Indicates whether this is a reported error. We currently only report the first error
+ /// encountered and leave the rest unreported so as not to overwhelm the user.
+ is_reported: bool,
+ },
+}
+
+/// Information about the various region constraints involved in a borrow checker error.
+#[derive(Clone, Debug)]
+pub struct ErrorConstraintInfo<'tcx> {
+ // 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<'tcx>,
+ pub(super) span: Span,
+}
+
+impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
+ /// Converts a region inference variable into a `ty::Region` that
+ /// we can use for error reporting. If `r` is universally bound,
+ /// then we use the name that we have on record for it. If `r` is
+ /// existentially bound, then we check its inferred value and try
+ /// to find a good name from that. Returns `None` if we can't find
+ /// one (e.g., this is just some random part of the CFG).
+ pub(super) fn to_error_region(&self, r: RegionVid) -> Option<ty::Region<'tcx>> {
+ self.to_error_region_vid(r).and_then(|r| self.regioncx.region_definition(r).external_name)
+ }
+
+ /// Returns the `RegionVid` corresponding to the region returned by
+ /// `to_error_region`.
+ pub(super) fn to_error_region_vid(&self, r: RegionVid) -> Option<RegionVid> {
+ if self.regioncx.universal_regions().is_universal_region(r) {
+ Some(r)
+ } else {
+ // We just want something nameable, even if it's not
+ // actually an upper bound.
+ let upper_bound = self.regioncx.approx_universal_upper_bound(r);
+
+ if self.regioncx.upper_bound_in_region_scc(r, upper_bound) {
+ self.to_error_region_vid(upper_bound)
+ } else {
+ None
+ }
+ }
+ }
+
+ /// Returns `true` if a closure is inferred to be an `FnMut` closure.
+ fn is_closure_fn_mut(&self, fr: RegionVid) -> bool {
+ if let Some(ty::ReFree(free_region)) = self.to_error_region(fr).as_deref()
+ && let ty::BoundRegionKind::BrEnv = free_region.bound_region
+ && let DefiningTy::Closure(_, substs) = self.regioncx.universal_regions().defining_ty
+ {
+ return substs.as_closure().kind() == ty::ClosureKind::FnMut;
+ }
+
+ false
+ }
+
+ /// Produces nice borrowck error diagnostics for all the errors collected in `nll_errors`.
+ pub(crate) fn report_region_errors(&mut self, nll_errors: RegionErrors<'tcx>) {
+ // Iterate through all the errors, producing a diagnostic for each one. The diagnostics are
+ // buffered in the `MirBorrowckCtxt`.
+
+ let mut outlives_suggestion = OutlivesSuggestionBuilder::default();
+
+ for nll_error in nll_errors.into_iter() {
+ match nll_error {
+ RegionErrorKind::TypeTestError { type_test } => {
+ // Try to convert the lower-bound region into something named we can print for the user.
+ let lower_bound_region = self.to_error_region(type_test.lower_bound);
+
+ let type_test_span = type_test.locations.span(&self.body);
+
+ if let Some(lower_bound_region) = lower_bound_region {
+ let generic_ty = type_test.generic_kind.to_ty(self.infcx.tcx);
+ let origin = RelateParamBound(type_test_span, generic_ty, None);
+ self.buffer_error(self.infcx.construct_generic_bound_failure(
+ self.body.source.def_id().expect_local(),
+ type_test_span,
+ Some(origin),
+ type_test.generic_kind,
+ lower_bound_region,
+ ));
+ } else {
+ // FIXME. We should handle this case better. It
+ // indicates that we have e.g., some region variable
+ // whose value is like `'a+'b` where `'a` and `'b` are
+ // distinct unrelated universal regions that are not
+ // known to outlive one another. It'd be nice to have
+ // some examples where this arises to decide how best
+ // to report it; we could probably handle it by
+ // iterating over the universal regions and reporting
+ // an error that multiple bounds are required.
+ self.buffer_error(self.infcx.tcx.sess.create_err(
+ GenericDoesNotLiveLongEnough {
+ kind: type_test.generic_kind.to_string(),
+ span: type_test_span,
+ },
+ ));
+ }
+ }
+
+ RegionErrorKind::UnexpectedHiddenRegion { span, hidden_ty, key, member_region } => {
+ let named_ty = self.regioncx.name_regions(self.infcx.tcx, hidden_ty);
+ let named_key = self.regioncx.name_regions(self.infcx.tcx, key);
+ let named_region = self.regioncx.name_regions(self.infcx.tcx, member_region);
+ self.buffer_error(unexpected_hidden_region_diagnostic(
+ self.infcx.tcx,
+ span,
+ named_ty,
+ named_region,
+ named_key,
+ ));
+ }
+
+ RegionErrorKind::BoundUniversalRegionError {
+ longer_fr,
+ placeholder,
+ error_element,
+ } => {
+ let error_vid = self.regioncx.region_from_element(longer_fr, &error_element);
+
+ // Find the code to blame for the fact that `longer_fr` outlives `error_fr`.
+ let (_, cause) = self.regioncx.find_outlives_blame_span(
+ &self.body,
+ longer_fr,
+ NllRegionVariableOrigin::Placeholder(placeholder),
+ error_vid,
+ );
+
+ let universe = placeholder.universe;
+ let universe_info = self.regioncx.universe_info(universe);
+
+ universe_info.report_error(self, placeholder, error_element, cause);
+ }
+
+ RegionErrorKind::RegionError { fr_origin, longer_fr, shorter_fr, is_reported } => {
+ if is_reported {
+ self.report_region_error(
+ longer_fr,
+ fr_origin,
+ shorter_fr,
+ &mut outlives_suggestion,
+ );
+ } else {
+ // We only report the first error, so as not to overwhelm the user. See
+ // `RegRegionErrorKind` docs.
+ //
+ // FIXME: currently we do nothing with these, but perhaps we can do better?
+ // FIXME: try collecting these constraints on the outlives suggestion
+ // builder. Does it make the suggestions any better?
+ debug!(
+ "Unreported region error: can't prove that {:?}: {:?}",
+ longer_fr, shorter_fr
+ );
+ }
+ }
+ }
+ }
+
+ // Emit one outlives suggestions for each MIR def we borrowck
+ outlives_suggestion.add_suggestion(self);
+ }
+
+ fn get_impl_ident_and_self_ty_from_trait(
+ &self,
+ def_id: DefId,
+ trait_objects: &FxHashSet<DefId>,
+ ) -> Option<(Ident, &'tcx hir::Ty<'tcx>)> {
+ let tcx = self.infcx.tcx;
+ match tcx.hir().get_if_local(def_id) {
+ Some(Node::ImplItem(impl_item)) => {
+ match tcx.hir().find_by_def_id(tcx.hir().get_parent_item(impl_item.hir_id())) {
+ Some(Node::Item(Item {
+ kind: ItemKind::Impl(hir::Impl { self_ty, .. }),
+ ..
+ })) => Some((impl_item.ident, self_ty)),
+ _ => None,
+ }
+ }
+ Some(Node::TraitItem(trait_item)) => {
+ let trait_did = tcx.hir().get_parent_item(trait_item.hir_id());
+ match tcx.hir().find_by_def_id(trait_did) {
+ Some(Node::Item(Item { kind: ItemKind::Trait(..), .. })) => {
+ // The method being called is defined in the `trait`, but the `'static`
+ // obligation comes from the `impl`. Find that `impl` so that we can point
+ // at it in the suggestion.
+ let trait_did = trait_did.to_def_id();
+ match tcx
+ .hir()
+ .trait_impls(trait_did)
+ .iter()
+ .filter_map(|&impl_did| {
+ match tcx.hir().get_if_local(impl_did.to_def_id()) {
+ Some(Node::Item(Item {
+ kind: ItemKind::Impl(hir::Impl { self_ty, .. }),
+ ..
+ })) if trait_objects.iter().all(|did| {
+ // FIXME: we should check `self_ty` against the receiver
+ // type in the `UnifyReceiver` context, but for now, use
+ // this imperfect proxy. This will fail if there are
+ // multiple `impl`s for the same trait like
+ // `impl Foo for Box<dyn Bar>` and `impl Foo for dyn Bar`.
+ // In that case, only the first one will get suggestions.
+ let mut traits = vec![];
+ let mut hir_v = HirTraitObjectVisitor(&mut traits, *did);
+ hir_v.visit_ty(self_ty);
+ !traits.is_empty()
+ }) =>
+ {
+ Some(self_ty)
+ }
+ _ => None,
+ }
+ })
+ .next()
+ {
+ Some(self_ty) => Some((trait_item.ident, self_ty)),
+ _ => None,
+ }
+ }
+ _ => None,
+ }
+ }
+ _ => 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:
+ ///
+ /// ```compile_fail,E0312
+ /// fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x }
+ /// ```
+ ///
+ /// Here we would be invoked with `fr = 'a` and `outlived_fr = `'b`.
+ pub(crate) fn report_region_error(
+ &mut self,
+ fr: RegionVid,
+ fr_origin: NllRegionVariableOrigin,
+ outlived_fr: RegionVid,
+ outlives_suggestion: &mut OutlivesSuggestionBuilder,
+ ) {
+ debug!("report_region_error(fr={:?}, outlived_fr={:?})", fr, outlived_fr);
+
+ let BlameConstraint { category, cause, variance_info, from_closure: _ } =
+ self.regioncx.best_blame_constraint(&self.body, fr, fr_origin, |r| {
+ self.regioncx.provides_universal_region(r, fr, outlived_fr)
+ });
+
+ debug!("report_region_error: category={:?} {:?} {:?}", category, cause, variance_info);
+
+ // 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 nice = NiceRegionError::new_from_span(self.infcx, cause.span, o, f);
+ if let Some(diag) = nice.try_report_from_nll() {
+ self.buffer_error(diag);
+ return;
+ }
+ }
+
+ let (fr_is_local, outlived_fr_is_local): (bool, bool) = (
+ self.regioncx.universal_regions().is_local_free_region(fr),
+ self.regioncx.universal_regions().is_local_free_region(outlived_fr),
+ );
+
+ debug!(
+ "report_region_error: fr_is_local={:?} outlived_fr_is_local={:?} category={:?}",
+ fr_is_local, outlived_fr_is_local, category
+ );
+
+ let errci = ErrorConstraintInfo {
+ fr,
+ outlived_fr,
+ fr_is_local,
+ outlived_fr_is_local,
+ category,
+ span: cause.span,
+ };
+
+ let mut diag = match (category, fr_is_local, outlived_fr_is_local) {
+ (ConstraintCategory::Return(kind), true, false) if self.is_closure_fn_mut(fr) => {
+ self.report_fnmut_error(&errci, kind)
+ }
+ (ConstraintCategory::Assignment, true, false)
+ | (ConstraintCategory::CallArgument(_), true, false) => {
+ let mut db = self.report_escaping_data_error(&errci);
+
+ outlives_suggestion.intermediate_suggestion(self, &errci, &mut db);
+ outlives_suggestion.collect_constraint(fr, outlived_fr);
+
+ db
+ }
+ _ => {
+ let mut db = self.report_general_error(&errci);
+
+ outlives_suggestion.intermediate_suggestion(self, &errci, &mut db);
+ outlives_suggestion.collect_constraint(fr, outlived_fr);
+
+ db
+ }
+ };
+
+ match variance_info {
+ ty::VarianceDiagInfo::None => {}
+ ty::VarianceDiagInfo::Invariant { ty, param_index } => {
+ let (desc, note) = match ty.kind() {
+ ty::RawPtr(ty_mut) => {
+ assert_eq!(ty_mut.mutbl, rustc_hir::Mutability::Mut);
+ (
+ format!("a mutable pointer to `{}`", ty_mut.ty),
+ "mutable pointers are invariant over their type parameter".to_string(),
+ )
+ }
+ ty::Ref(_, inner_ty, mutbl) => {
+ assert_eq!(*mutbl, rustc_hir::Mutability::Mut);
+ (
+ format!("a mutable reference to `{inner_ty}`"),
+ "mutable references are invariant over their type parameter"
+ .to_string(),
+ )
+ }
+ ty::Adt(adt, substs) => {
+ let generic_arg = substs[param_index as usize];
+ let identity_substs =
+ InternalSubsts::identity_for_item(self.infcx.tcx, adt.did());
+ let base_ty = self.infcx.tcx.mk_adt(*adt, identity_substs);
+ let base_generic_arg = identity_substs[param_index as usize];
+ let adt_desc = adt.descr();
+
+ let desc = format!(
+ "the type `{ty}`, which makes the generic argument `{generic_arg}` invariant"
+ );
+ let note = format!(
+ "the {adt_desc} `{base_ty}` is invariant over the parameter `{base_generic_arg}`"
+ );
+ (desc, note)
+ }
+ ty::FnDef(def_id, _) => {
+ let name = self.infcx.tcx.item_name(*def_id);
+ let identity_substs =
+ InternalSubsts::identity_for_item(self.infcx.tcx, *def_id);
+ let desc = format!("a function pointer to `{name}`");
+ let note = format!(
+ "the function `{name}` is invariant over the parameter `{}`",
+ identity_substs[param_index as usize]
+ );
+ (desc, note)
+ }
+ _ => panic!("Unexpected type {:?}", ty),
+ };
+ diag.note(&format!("requirement occurs because of {desc}",));
+ diag.note(&note);
+ diag.help("see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance");
+ }
+ }
+
+ self.buffer_error(diag);
+ }
+
+ /// 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,
+ errci: &ErrorConstraintInfo<'tcx>,
+ kind: ReturnConstraint,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ let ErrorConstraintInfo { outlived_fr, span, .. } = errci;
+
+ let mut diag = self
+ .infcx
+ .tcx
+ .sess
+ .struct_span_err(*span, "captured variable cannot escape `FnMut` closure body");
+
+ let mut output_ty = self.regioncx.universal_regions().unnormalized_output_ty;
+ if let ty::Opaque(def_id, _) = *output_ty.kind() {
+ output_ty = self.infcx.tcx.type_of(def_id)
+ };
+
+ debug!("report_fnmut_error: output_ty={:?}", output_ty);
+
+ let message = match output_ty.kind() {
+ ty::Closure(_, _) => {
+ "returns a closure that contains a reference to a captured variable, which then \
+ escapes the closure body"
+ }
+ ty::Adt(def, _) if self.infcx.tcx.is_diagnostic_item(sym::gen_future, def.did()) => {
+ "returns an `async` block that contains a reference to a captured variable, which then \
+ escapes the closure body"
+ }
+ _ => "returns a reference to a captured variable which escapes the closure body",
+ };
+
+ diag.span_label(*span, message);
+
+ if let ReturnConstraint::ClosureUpvar(upvar_field) = kind {
+ let def_id = match self.regioncx.universal_regions().defining_ty {
+ DefiningTy::Closure(def_id, _) => def_id,
+ ty => bug!("unexpected DefiningTy {:?}", ty),
+ };
+
+ let captured_place = &self.upvars[upvar_field.index()].place;
+ let defined_hir = match captured_place.place.base {
+ PlaceBase::Local(hirid) => Some(hirid),
+ PlaceBase::Upvar(upvar) => Some(upvar.var_path.hir_id),
+ _ => None,
+ };
+
+ if let Some(def_hir) = defined_hir {
+ let upvars_map = self.infcx.tcx.upvars_mentioned(def_id).unwrap();
+ let upvar_def_span = self.infcx.tcx.hir().span(def_hir);
+ let upvar_span = upvars_map.get(&def_hir).unwrap().span;
+ diag.span_label(upvar_def_span, "variable defined here");
+ diag.span_label(upvar_span, "variable captured here");
+ }
+ }
+
+ if let Some(fr_span) = self.give_region_a_name(*outlived_fr).unwrap().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 an 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<Fn()+'a>) {
+ /// | - `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,
+ errci: &ErrorConstraintInfo<'tcx>,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ let ErrorConstraintInfo { span, category, .. } = errci;
+
+ let fr_name_and_span = self.regioncx.get_var_name_and_span_for_region(
+ self.infcx.tcx,
+ &self.body,
+ &self.local_names,
+ &self.upvars,
+ errci.fr,
+ );
+ let outlived_fr_name_and_span = self.regioncx.get_var_name_and_span_for_region(
+ self.infcx.tcx,
+ &self.body,
+ &self.local_names,
+ &self.upvars,
+ errci.outlived_fr,
+ );
+
+ let (_, escapes_from) = self
+ .infcx
+ .tcx
+ .article_and_description(self.regioncx.universal_regions().defining_ty.def_id());
+
+ // 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
+ && self.regioncx.universal_regions().defining_ty.is_fn_def())
+ || self.regioncx.universal_regions().defining_ty.is_const()
+ {
+ return self.report_general_error(&ErrorConstraintInfo {
+ fr_is_local: true,
+ outlived_fr_is_local: false,
+ ..*errci
+ });
+ }
+
+ let mut diag =
+ borrowck_errors::borrowed_data_escapes_closure(self.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!("`{outlived_fr_name}` declared here, outside of the {escapes_from} body",),
+ );
+ }
+
+ if let Some((Some(fr_name), fr_span)) = fr_name_and_span {
+ diag.span_label(
+ fr_span,
+ format!(
+ "`{fr_name}` is a reference that is only valid in the {escapes_from} body",
+ ),
+ );
+
+ diag.span_label(*span, format!("`{fr_name}` escapes the {escapes_from} body here"));
+ }
+
+ // Only show an extra note if we can find an 'error region' for both of the region
+ // variables. This avoids showing a noisy note that just mentions 'synthetic' regions
+ // that don't help the user understand the error.
+ match (self.to_error_region(errci.fr), self.to_error_region(errci.outlived_fr)) {
+ (Some(f), Some(o)) => {
+ self.maybe_suggest_constrain_dyn_trait_impl(&mut diag, f, o, category);
+
+ let fr_region_name = self.give_region_a_name(errci.fr).unwrap();
+ fr_region_name.highlight_region_name(&mut diag);
+ let outlived_fr_region_name = self.give_region_a_name(errci.outlived_fr).unwrap();
+ outlived_fr_region_name.highlight_region_name(&mut diag);
+
+ diag.span_label(
+ *span,
+ format!(
+ "{}requires that `{}` must outlive `{}`",
+ category.description(),
+ fr_region_name,
+ outlived_fr_region_name,
+ ),
+ );
+ }
+ _ => {}
+ }
+
+ 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,
+ errci: &ErrorConstraintInfo<'tcx>,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ let ErrorConstraintInfo {
+ fr,
+ fr_is_local,
+ outlived_fr,
+ outlived_fr_is_local,
+ span,
+ category,
+ ..
+ } = errci;
+
+ let mut diag =
+ self.infcx.tcx.sess.struct_span_err(*span, "lifetime may not live long enough");
+
+ let (_, mir_def_name) =
+ self.infcx.tcx.article_and_description(self.mir_def_id().to_def_id());
+
+ let fr_name = self.give_region_a_name(*fr).unwrap();
+ fr_name.highlight_region_name(&mut diag);
+ let outlived_fr_name = self.give_region_a_name(*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!(
+ "{mir_def_name} was supposed to return data with lifetime `{outlived_fr_name}` but it is returning \
+ data with lifetime `{fr_name}`",
+ ),
+ );
+ }
+ _ => {
+ diag.span_label(
+ *span,
+ format!(
+ "{}requires that `{}` must outlive `{}`",
+ category.description(),
+ fr_name,
+ outlived_fr_name,
+ ),
+ );
+ }
+ }
+
+ self.add_static_impl_trait_suggestion(&mut diag, *fr, fr_name, *outlived_fr);
+ self.suggest_adding_lifetime_params(&mut diag, *fr, *outlived_fr);
+
+ diag
+ }
+
+ /// Adds a suggestion to errors where an `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<Item=u32> + 'a {
+ /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ /// ```
+ fn add_static_impl_trait_suggestion(
+ &self,
+ diag: &mut Diagnostic,
+ 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(outlived_f)) =
+ (self.to_error_region(fr), self.to_error_region(outlived_fr))
+ {
+ if *outlived_f != ty::ReStatic {
+ return;
+ }
+
+ let fn_returns = self
+ .infcx
+ .tcx
+ .is_suitable_region(f)
+ .map(|r| self.infcx.tcx.return_type_impl_or_dyn_traits(r.def_id))
+ .unwrap_or_default();
+
+ if fn_returns.is_empty() {
+ return;
+ }
+
+ let param = if let Some(param) = find_param_with_region(self.infcx.tcx, f, outlived_f) {
+ param
+ } else {
+ return;
+ };
+
+ let lifetime = if f.has_name() { fr_name.name } else { kw::UnderscoreLifetime };
+
+ let arg = match param.param.pat.simple_ident() {
+ Some(simple_ident) => format!("argument `{}`", simple_ident),
+ None => "the argument".to_string(),
+ };
+ let captures = format!("captures data from {}", arg);
+
+ return nice_region_error::suggest_new_region_bound(
+ self.infcx.tcx,
+ diag,
+ fn_returns,
+ lifetime.to_string(),
+ Some(arg),
+ captures,
+ Some((param.param_ty_span, param.param_ty.to_string())),
+ );
+ }
+ }
+
+ fn maybe_suggest_constrain_dyn_trait_impl(
+ &self,
+ diag: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>,
+ f: Region<'tcx>,
+ o: Region<'tcx>,
+ category: &ConstraintCategory<'tcx>,
+ ) {
+ if !o.is_static() {
+ return;
+ }
+
+ let tcx = self.infcx.tcx;
+
+ let instance = if let ConstraintCategory::CallArgument(Some(func_ty)) = category {
+ let (fn_did, substs) = match func_ty.kind() {
+ ty::FnDef(fn_did, substs) => (fn_did, substs),
+ _ => return,
+ };
+ debug!(?fn_did, ?substs);
+
+ // Only suggest this on function calls, not closures
+ let ty = tcx.type_of(fn_did);
+ debug!("ty: {:?}, ty.kind: {:?}", ty, ty.kind());
+ if let ty::Closure(_, _) = ty.kind() {
+ return;
+ }
+
+ if let Ok(Some(instance)) = ty::Instance::resolve(
+ tcx,
+ self.param_env,
+ *fn_did,
+ self.infcx.resolve_vars_if_possible(substs),
+ ) {
+ instance
+ } else {
+ return;
+ }
+ } else {
+ return;
+ };
+
+ let param = match find_param_with_region(tcx, f, o) {
+ Some(param) => param,
+ None => return,
+ };
+ debug!(?param);
+
+ let mut visitor = TraitObjectVisitor(FxHashSet::default());
+ visitor.visit_ty(param.param_ty);
+
+ let Some((ident, self_ty)) =
+ self.get_impl_ident_and_self_ty_from_trait(instance.def_id(), &visitor.0) else {return};
+
+ self.suggest_constrain_dyn_trait_in_impl(diag, &visitor.0, ident, self_ty);
+ }
+
+ #[instrument(skip(self, err), level = "debug")]
+ fn suggest_constrain_dyn_trait_in_impl(
+ &self,
+ err: &mut Diagnostic,
+ found_dids: &FxHashSet<DefId>,
+ ident: Ident,
+ self_ty: &hir::Ty<'_>,
+ ) -> bool {
+ debug!("err: {:#?}", err);
+ let mut suggested = false;
+ for found_did in found_dids {
+ let mut traits = vec![];
+ let mut hir_v = HirTraitObjectVisitor(&mut traits, *found_did);
+ hir_v.visit_ty(&self_ty);
+ debug!("trait spans found: {:?}", traits);
+ for span in &traits {
+ let mut multi_span: MultiSpan = vec![*span].into();
+ multi_span
+ .push_span_label(*span, "this has an implicit `'static` lifetime requirement");
+ multi_span.push_span_label(
+ ident.span,
+ "calling this method introduces the `impl`'s 'static` requirement",
+ );
+ err.span_note(multi_span, "the used `impl` has a `'static` requirement");
+ err.span_suggestion_verbose(
+ span.shrink_to_hi(),
+ "consider relaxing the implicit `'static` requirement",
+ " + '_",
+ Applicability::MaybeIncorrect,
+ );
+ suggested = true;
+ }
+ }
+ suggested
+ }
+
+ fn suggest_adding_lifetime_params(
+ &self,
+ diag: &mut Diagnostic,
+ sub: RegionVid,
+ sup: RegionVid,
+ ) {
+ let (Some(sub), Some(sup)) = (self.to_error_region(sub), self.to_error_region(sup)) else {
+ return
+ };
+
+ let Some((ty_sub, _)) = self
+ .infcx
+ .tcx
+ .is_suitable_region(sub)
+ .and_then(|anon_reg| find_anon_type(self.infcx.tcx, sub, &anon_reg.boundregion)) else {
+ return
+ };
+
+ let Some((ty_sup, _)) = self
+ .infcx
+ .tcx
+ .is_suitable_region(sup)
+ .and_then(|anon_reg| find_anon_type(self.infcx.tcx, sup, &anon_reg.boundregion)) else {
+ return
+ };
+
+ suggest_adding_lifetime_params(self.infcx.tcx, sub, ty_sup, ty_sub, diag);
+ }
+}
diff --git a/compiler/rustc_borrowck/src/diagnostics/region_name.rs b/compiler/rustc_borrowck/src/diagnostics/region_name.rs
new file mode 100644
index 000000000..a87e8bd5b
--- /dev/null
+++ b/compiler/rustc_borrowck/src/diagnostics/region_name.rs
@@ -0,0 +1,896 @@
+use std::fmt::{self, Display};
+use std::iter;
+
+use rustc_errors::Diagnostic;
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_middle::ty::print::RegionHighlightMode;
+use rustc_middle::ty::subst::{GenericArgKind, SubstsRef};
+use rustc_middle::ty::{self, DefIdTree, RegionVid, Ty};
+use rustc_span::symbol::{kw, sym, Ident, Symbol};
+use rustc_span::{Span, DUMMY_SP};
+
+use crate::{nll::ToRegionVid, universal_regions::DefiningTy, MirBorrowckCtxt};
+
+/// 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)]
+pub(crate) struct RegionName {
+ /// The name of the region (interned).
+ pub(crate) name: Symbol,
+ /// Where the region comes from.
+ pub(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)]
+pub(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, &'static str),
+ /// The region corresponding to an argument.
+ AnonRegionFromArgument(RegionNameHighlight),
+ /// The region corresponding to a closure upvar.
+ AnonRegionFromUpvar(Span, Symbol),
+ /// The region corresponding to the return type of a closure.
+ AnonRegionFromOutput(RegionNameHighlight, &'static str),
+ /// The region from a type yielded by a generator.
+ AnonRegionFromYieldTy(Span, String),
+ /// An anonymous region from an async fn.
+ AnonRegionFromAsyncFn(Span),
+ /// An anonymous region from an impl self type or trait
+ AnonRegionFromImplSignature(Span, &'static str),
+}
+
+/// Describes what to highlight to explain to the user that we're giving an anonymous region a
+/// synthesized name, and how to highlight it.
+#[derive(Debug, Clone)]
+pub(crate) enum RegionNameHighlight {
+ /// The anonymous region corresponds to a reference that was found by traversing the type in the HIR.
+ MatchedHirTy(Span),
+ /// The anonymous region corresponds to a `'_` in the generics list of a struct/enum/union.
+ MatchedAdtAndSegment(Span),
+ /// The anonymous region 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 anonymous region corresponds to a region where the type annotation is completely missing
+ /// from the code, and *even if* we print out the full name of the type, the region name won't
+ /// be included. This currently occurs for opaque types like `impl Future`.
+ Occluded(Span, String),
+}
+
+impl RegionName {
+ pub(crate) fn was_named(&self) -> bool {
+ match self.source {
+ RegionNameSource::NamedEarlyBoundRegion(..)
+ | RegionNameSource::NamedFreeRegion(..)
+ | RegionNameSource::Static => true,
+ RegionNameSource::SynthesizedFreeEnvRegion(..)
+ | RegionNameSource::AnonRegionFromArgument(..)
+ | RegionNameSource::AnonRegionFromUpvar(..)
+ | RegionNameSource::AnonRegionFromOutput(..)
+ | RegionNameSource::AnonRegionFromYieldTy(..)
+ | RegionNameSource::AnonRegionFromAsyncFn(..)
+ | RegionNameSource::AnonRegionFromImplSignature(..) => false,
+ }
+ }
+
+ pub(crate) fn span(&self) -> Option<Span> {
+ match self.source {
+ RegionNameSource::Static => None,
+ RegionNameSource::NamedEarlyBoundRegion(span)
+ | RegionNameSource::NamedFreeRegion(span)
+ | RegionNameSource::SynthesizedFreeEnvRegion(span, _)
+ | RegionNameSource::AnonRegionFromUpvar(span, _)
+ | RegionNameSource::AnonRegionFromYieldTy(span, _)
+ | RegionNameSource::AnonRegionFromAsyncFn(span)
+ | RegionNameSource::AnonRegionFromImplSignature(span, _) => Some(span),
+ RegionNameSource::AnonRegionFromArgument(ref highlight)
+ | RegionNameSource::AnonRegionFromOutput(ref highlight, _) => match *highlight {
+ RegionNameHighlight::MatchedHirTy(span)
+ | RegionNameHighlight::MatchedAdtAndSegment(span)
+ | RegionNameHighlight::CannotMatchHirTy(span, _)
+ | RegionNameHighlight::Occluded(span, _) => Some(span),
+ },
+ }
+ }
+
+ pub(crate) fn highlight_region_name(&self, diag: &mut Diagnostic) {
+ match &self.source {
+ RegionNameSource::NamedFreeRegion(span)
+ | RegionNameSource::NamedEarlyBoundRegion(span) => {
+ diag.span_label(*span, format!("lifetime `{self}` defined here"));
+ }
+ RegionNameSource::SynthesizedFreeEnvRegion(span, note) => {
+ diag.span_label(*span, format!("lifetime `{self}` represents this closure's body"));
+ diag.note(*note);
+ }
+ RegionNameSource::AnonRegionFromArgument(RegionNameHighlight::CannotMatchHirTy(
+ span,
+ type_name,
+ )) => {
+ diag.span_label(*span, format!("has type `{type_name}`"));
+ }
+ RegionNameSource::AnonRegionFromArgument(RegionNameHighlight::MatchedHirTy(span))
+ | RegionNameSource::AnonRegionFromOutput(RegionNameHighlight::MatchedHirTy(span), _)
+ | RegionNameSource::AnonRegionFromAsyncFn(span) => {
+ diag.span_label(
+ *span,
+ format!("let's call the lifetime of this reference `{self}`"),
+ );
+ }
+ RegionNameSource::AnonRegionFromArgument(
+ RegionNameHighlight::MatchedAdtAndSegment(span),
+ )
+ | RegionNameSource::AnonRegionFromOutput(
+ RegionNameHighlight::MatchedAdtAndSegment(span),
+ _,
+ ) => {
+ diag.span_label(*span, format!("let's call this `{self}`"));
+ }
+ RegionNameSource::AnonRegionFromArgument(RegionNameHighlight::Occluded(
+ span,
+ type_name,
+ )) => {
+ diag.span_label(
+ *span,
+ format!("lifetime `{self}` appears in the type {type_name}"),
+ );
+ }
+ RegionNameSource::AnonRegionFromOutput(
+ RegionNameHighlight::Occluded(span, type_name),
+ mir_description,
+ ) => {
+ diag.span_label(
+ *span,
+ format!(
+ "return type{mir_description} `{type_name}` contains a lifetime `{self}`"
+ ),
+ );
+ }
+ RegionNameSource::AnonRegionFromUpvar(span, upvar_name) => {
+ diag.span_label(
+ *span,
+ format!("lifetime `{self}` appears in the type of `{upvar_name}`"),
+ );
+ }
+ RegionNameSource::AnonRegionFromOutput(
+ RegionNameHighlight::CannotMatchHirTy(span, type_name),
+ mir_description,
+ ) => {
+ diag.span_label(*span, format!("return type{mir_description} is {type_name}"));
+ }
+ RegionNameSource::AnonRegionFromYieldTy(span, type_name) => {
+ diag.span_label(*span, format!("yield type is {type_name}"));
+ }
+ RegionNameSource::AnonRegionFromImplSignature(span, location) => {
+ diag.span_label(
+ *span,
+ format!("lifetime `{self}` appears in the `impl`'s {location}"),
+ );
+ }
+ RegionNameSource::Static => {}
+ }
+ }
+}
+
+impl Display for RegionName {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.name)
+ }
+}
+
+impl<'tcx> MirBorrowckCtxt<'_, 'tcx> {
+ pub(crate) fn mir_def_id(&self) -> hir::def_id::LocalDefId {
+ self.body.source.def_id().expect_local()
+ }
+
+ pub(crate) fn mir_hir_id(&self) -> hir::HirId {
+ self.infcx.tcx.hir().local_def_id_to_hir_id(self.mir_def_id())
+ }
+
+ /// Generate a synthetic region named `'N`, where `N` is the next value of the counter. Then,
+ /// increment the counter.
+ ///
+ /// This is _not_ idempotent. Call `give_region_a_name` when possible.
+ fn synthesize_region_name(&self) -> Symbol {
+ let c = self.next_region_name.replace_with(|counter| *counter + 1);
+ Symbol::intern(&format!("'{:?}", c))
+ }
+
+ /// 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.
+ ///
+ /// The names are memoized, so this is both cheap to recompute and idempotent.
+ ///
+ /// Example (function arguments):
+ ///
+ /// Suppose we are trying to give a name to the lifetime of the
+ /// reference `x`:
+ ///
+ /// ```ignore (pseudo-rust)
+ /// fn foo(x: &u32) { .. }
+ /// ```
+ ///
+ /// This function would create a label like this:
+ ///
+ /// ```text
+ /// | fn foo(x: &u32) { .. }
+ /// ------- fully elaborated type of `x` is `&'1 u32`
+ /// ```
+ ///
+ /// and then return the name `'1` for us to use.
+ pub(crate) fn give_region_a_name(&self, fr: RegionVid) -> Option<RegionName> {
+ debug!(
+ "give_region_a_name(fr={:?}, counter={:?})",
+ fr,
+ self.next_region_name.try_borrow().unwrap()
+ );
+
+ assert!(self.regioncx.universal_regions().is_universal_region(fr));
+
+ if let Some(value) = self.region_names.try_borrow_mut().unwrap().get(&fr) {
+ return Some(value.clone());
+ }
+
+ let value = self
+ .give_name_from_error_region(fr)
+ .or_else(|| self.give_name_if_anonymous_region_appears_in_arguments(fr))
+ .or_else(|| self.give_name_if_anonymous_region_appears_in_upvars(fr))
+ .or_else(|| self.give_name_if_anonymous_region_appears_in_output(fr))
+ .or_else(|| self.give_name_if_anonymous_region_appears_in_yield_ty(fr))
+ .or_else(|| self.give_name_if_anonymous_region_appears_in_impl_signature(fr));
+
+ if let Some(ref value) = value {
+ self.region_names.try_borrow_mut().unwrap().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.
+ #[tracing::instrument(level = "trace", skip(self))]
+ fn give_name_from_error_region(&self, fr: RegionVid) -> Option<RegionName> {
+ let error_region = self.to_error_region(fr)?;
+
+ let tcx = self.infcx.tcx;
+
+ 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::BoundRegionKind::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 let hir::IsAsync::Async = tcx.asyncness(self.mir_hir_id().owner) {
+ // 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 = self.synthesize_region_name();
+ Some(RegionName {
+ name,
+ source: RegionNameSource::AnonRegionFromAsyncFn(span),
+ })
+ } else {
+ None
+ }
+ }
+
+ ty::BoundRegionKind::BrEnv => {
+ let def_ty = self.regioncx.universal_regions().defining_ty;
+
+ let DefiningTy::Closure(_, substs) = def_ty else {
+ // Can't have BrEnv in functions, constants or generators.
+ bug!("BrEnv outside of closure.");
+ };
+ let hir::ExprKind::Closure(&hir::Closure { fn_decl_span, .. })
+ = tcx.hir().expect_expr(self.mir_hir_id()).kind
+ else {
+ bug!("Closure is not defined by a closure expr");
+ };
+ let region_name = self.synthesize_region_name();
+
+ let closure_kind_ty = substs.as_closure().kind_ty();
+ 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(fn_decl_span, note),
+ })
+ }
+
+ ty::BoundRegionKind::BrAnon(_) => None,
+ },
+
+ ty::ReLateBound(..)
+ | ty::ReVar(..)
+ | ty::RePlaceholder(..)
+ | ty::ReEmpty(_)
+ | ty::ReErased => None,
+ }
+ }
+
+ /// Finds an argument that contains `fr` and label it with a fully
+ /// elaborated type, returning something like `'1`. Result looks
+ /// like:
+ ///
+ /// ```text
+ /// | fn foo(x: &u32) { .. }
+ /// ------- fully elaborated type of `x` is `&'1 u32`
+ /// ```
+ #[tracing::instrument(level = "trace", skip(self))]
+ fn give_name_if_anonymous_region_appears_in_arguments(
+ &self,
+ fr: RegionVid,
+ ) -> Option<RegionName> {
+ let implicit_inputs = self.regioncx.universal_regions().defining_ty.implicit_inputs();
+ let argument_index = self.regioncx.get_argument_index_for_region(self.infcx.tcx, fr)?;
+
+ let arg_ty = self.regioncx.universal_regions().unnormalized_input_tys
+ [implicit_inputs + argument_index];
+ let (_, span) = self.regioncx.get_argument_name_and_span_for_region(
+ &self.body,
+ &self.local_names,
+ argument_index,
+ );
+
+ let highlight = self
+ .get_argument_hir_ty_for_highlighting(argument_index)
+ .and_then(|arg_hir_ty| self.highlight_if_we_can_match_hir_ty(fr, arg_ty, arg_hir_ty))
+ .unwrap_or_else(|| {
+ // `highlight_if_we_cannot_match_hir_ty` needs to know the number we will give to
+ // the anonymous region. If it succeeds, the `synthesize_region_name` call below
+ // will increment the counter, "reserving" the number we just used.
+ let counter = *self.next_region_name.try_borrow().unwrap();
+ self.highlight_if_we_cannot_match_hir_ty(fr, arg_ty, span, counter)
+ });
+
+ Some(RegionName {
+ name: self.synthesize_region_name(),
+ source: RegionNameSource::AnonRegionFromArgument(highlight),
+ })
+ }
+
+ fn get_argument_hir_ty_for_highlighting(
+ &self,
+ argument_index: usize,
+ ) -> Option<&hir::Ty<'tcx>> {
+ let fn_decl = self.infcx.tcx.hir().fn_decl_by_hir_id(self.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,
+
+ _ => Some(argument_hir_ty),
+ }
+ }
+
+ /// 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:
+ ///
+ /// ```text
+ /// | foo(|a, b| b)
+ /// | - -
+ /// | | |
+ /// | | has type `&'1 u32`
+ /// | has type `&'2 u32`
+ /// ```
+ fn highlight_if_we_cannot_match_hir_ty(
+ &self,
+ needle_fr: RegionVid,
+ ty: Ty<'tcx>,
+ span: Span,
+ counter: usize,
+ ) -> RegionNameHighlight {
+ let mut highlight = RegionHighlightMode::new(self.infcx.tcx);
+ highlight.highlighting_region_vid(needle_fr, counter);
+ let type_name =
+ self.infcx.extract_inference_diagnostics_data(ty.into(), Some(highlight)).name;
+
+ debug!(
+ "highlight_if_we_cannot_match_hir_ty: type_name={:?} needle_fr={:?}",
+ type_name, needle_fr
+ );
+ if type_name.contains(&format!("'{counter}")) {
+ // Only add a label if we can confirm that a region was labelled.
+ RegionNameHighlight::CannotMatchHirTy(span, type_name)
+ } else {
+ RegionNameHighlight::Occluded(span, type_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:
+ ///
+ /// ```text
+ /// | fn a<T>(items: &[T]) -> Box<dyn Iterator<Item = &T>> {
+ /// | - let's call the lifetime of this reference `'1`
+ /// ```
+ ///
+ /// the way this works is that we match up `ty`, which is
+ /// a `Ty<'tcx>` (the internal form of the type) with
+ /// `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 highlight_if_we_can_match_hir_ty(
+ &self,
+ needle_fr: RegionVid,
+ ty: Ty<'tcx>,
+ hir_ty: &hir::Ty<'_>,
+ ) -> Option<RegionNameHighlight> {
+ let search_stack: &mut Vec<(Ty<'tcx>, &hir::Ty<'_>)> = &mut vec![(ty, hir_ty)];
+
+ while let Some((ty, hir_ty)) = search_stack.pop() {
+ match (ty.kind(), &hir_ty.kind) {
+ // Check if the `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 {
+ // Just grab the first character, the `&`.
+ let source_map = self.infcx.tcx.sess.source_map();
+ let ampersand_span = source_map.start_point(hir_ty.span);
+
+ return Some(RegionNameHighlight::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(highlight) = self.match_adt_and_segment(
+ substs,
+ needle_fr,
+ last_segment,
+ search_stack,
+ ) {
+ return Some(highlight);
+ }
+ }
+ }
+ }
+ }
+
+ // 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(iter::zip(elem_tys, *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
+ }
+ }
+ }
+
+ 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<'hir>,
+ search_stack: &mut Vec<(Ty<'tcx>, &'hir hir::Ty<'hir>)>,
+ ) -> Option<RegionNameHighlight> {
+ // 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::ParamName::Plain(_) | hir::ParamName::Error)
+ | hir::LifetimeName::Error
+ | hir::LifetimeName::Static => {
+ let lifetime_span = lifetime.span;
+ Some(RegionNameHighlight::MatchedAdtAndSegment(lifetime_span))
+ }
+
+ hir::LifetimeName::Param(_, hir::ParamName::Fresh)
+ | hir::LifetimeName::ImplicitObjectLifetimeDefault
+ | hir::LifetimeName::Infer => {
+ // In this case, the user left off the lifetime; so
+ // they wrote something like:
+ //
+ // ```
+ // x: Foo<T>
+ // ```
+ //
+ // 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<'hir>,
+ search_stack: &mut Vec<(Ty<'tcx>, &'hir hir::Ty<'hir>)>,
+ ) -> Option<&'hir hir::Lifetime> {
+ for (kind, hir_arg) in iter::zip(substs, 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(_),
+ _,
+ ) => {
+ // HIR lowering sometimes doesn't catch this in erroneous
+ // programs, so we need to use delay_span_bug here. See #82126.
+ self.infcx.tcx.sess.delay_span_bug(
+ hir_arg.span(),
+ &format!("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:
+ ///
+ /// ```text
+ /// | let x = Some(&22);
+ /// - fully elaborated type of `x` is `Option<&'1 u32>`
+ /// ```
+ #[tracing::instrument(level = "trace", skip(self))]
+ fn give_name_if_anonymous_region_appears_in_upvars(&self, fr: RegionVid) -> Option<RegionName> {
+ let upvar_index = self.regioncx.get_upvar_index_for_region(self.infcx.tcx, fr)?;
+ let (upvar_name, upvar_span) = self.regioncx.get_upvar_name_and_span_for_region(
+ self.infcx.tcx,
+ &self.upvars,
+ upvar_index,
+ );
+ let region_name = self.synthesize_region_name();
+
+ Some(RegionName {
+ name: region_name,
+ source: RegionNameSource::AnonRegionFromUpvar(upvar_span, upvar_name),
+ })
+ }
+
+ /// 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).
+ #[tracing::instrument(level = "trace", skip(self))]
+ fn give_name_if_anonymous_region_appears_in_output(&self, fr: RegionVid) -> Option<RegionName> {
+ let tcx = self.infcx.tcx;
+ let hir = tcx.hir();
+
+ let return_ty = self.regioncx.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 mir_hir_id = self.mir_hir_id();
+
+ let (return_span, mir_description, hir_ty) = match hir.get(mir_hir_id) {
+ hir::Node::Expr(hir::Expr {
+ kind: hir::ExprKind::Closure(&hir::Closure { fn_decl, body, fn_decl_span, .. }),
+ ..
+ }) => {
+ let (mut span, mut hir_ty) = match fn_decl.output {
+ hir::FnRetTy::DefaultReturn(_) => {
+ (tcx.sess.source_map().end_point(fn_decl_span), None)
+ }
+ hir::FnRetTy::Return(hir_ty) => (fn_decl.output.span(), Some(hir_ty)),
+ };
+ let mir_description = match hir.body(body).generator_kind {
+ Some(hir::GeneratorKind::Async(gen)) => match gen {
+ hir::AsyncGeneratorKind::Block => " of async block",
+ hir::AsyncGeneratorKind::Closure => " of async closure",
+ hir::AsyncGeneratorKind::Fn => {
+ let parent_item = hir.get_by_def_id(hir.get_parent_item(mir_hir_id));
+ let output = &parent_item
+ .fn_decl()
+ .expect("generator lowered from async fn should be in fn")
+ .output;
+ span = output.span();
+ if let hir::FnRetTy::Return(ret) = output {
+ hir_ty = Some(self.get_future_inner_return_ty(*ret));
+ }
+ " of async function"
+ }
+ },
+ Some(hir::GeneratorKind::Gen) => " of generator",
+ None => " of closure",
+ };
+ (span, mir_description, hir_ty)
+ }
+ node => match node.fn_decl() {
+ Some(fn_decl) => {
+ let hir_ty = match fn_decl.output {
+ hir::FnRetTy::DefaultReturn(_) => None,
+ hir::FnRetTy::Return(ty) => Some(ty),
+ };
+ (fn_decl.output.span(), "", hir_ty)
+ }
+ None => (self.body.span, "", None),
+ },
+ };
+
+ let highlight = hir_ty
+ .and_then(|hir_ty| self.highlight_if_we_can_match_hir_ty(fr, return_ty, hir_ty))
+ .unwrap_or_else(|| {
+ // `highlight_if_we_cannot_match_hir_ty` needs to know the number we will give to
+ // the anonymous region. If it succeeds, the `synthesize_region_name` call below
+ // will increment the counter, "reserving" the number we just used.
+ let counter = *self.next_region_name.try_borrow().unwrap();
+ self.highlight_if_we_cannot_match_hir_ty(fr, return_ty, return_span, counter)
+ });
+
+ Some(RegionName {
+ name: self.synthesize_region_name(),
+ source: RegionNameSource::AnonRegionFromOutput(highlight, mir_description),
+ })
+ }
+
+ /// From the [`hir::Ty`] of an async function's lowered return type,
+ /// retrieve the `hir::Ty` representing the type the user originally wrote.
+ ///
+ /// e.g. given the function:
+ ///
+ /// ```
+ /// async fn foo() -> i32 { 2 }
+ /// ```
+ ///
+ /// this function, given the lowered return type of `foo`, an [`OpaqueDef`] that implements `Future<Output=i32>`,
+ /// returns the `i32`.
+ ///
+ /// [`OpaqueDef`]: hir::TyKind::OpaqueDef
+ fn get_future_inner_return_ty(&self, hir_ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> {
+ let hir = self.infcx.tcx.hir();
+
+ let hir::TyKind::OpaqueDef(id, _) = hir_ty.kind else {
+ span_bug!(
+ hir_ty.span,
+ "lowered return type of async fn is not OpaqueDef: {:?}",
+ hir_ty
+ );
+ };
+ let opaque_ty = hir.item(id);
+ if let hir::ItemKind::OpaqueTy(hir::OpaqueTy {
+ bounds:
+ [
+ hir::GenericBound::LangItemTrait(
+ hir::LangItem::Future,
+ _,
+ _,
+ hir::GenericArgs {
+ bindings:
+ [
+ hir::TypeBinding {
+ ident: Ident { name: sym::Output, .. },
+ kind:
+ hir::TypeBindingKind::Equality { term: hir::Term::Ty(ty) },
+ ..
+ },
+ ],
+ ..
+ },
+ ),
+ ],
+ ..
+ }) = opaque_ty.kind
+ {
+ ty
+ } else {
+ span_bug!(
+ hir_ty.span,
+ "bounds from lowered return type of async fn did not match expected format: {:?}",
+ opaque_ty
+ );
+ }
+ }
+
+ #[tracing::instrument(level = "trace", skip(self))]
+ fn give_name_if_anonymous_region_appears_in_yield_ty(
+ &self,
+ fr: RegionVid,
+ ) -> Option<RegionName> {
+ // Note: generators from `async fn` yield `()`, so we don't have to
+ // worry about them here.
+ let yield_ty = self.regioncx.universal_regions().yield_ty?;
+ debug!("give_name_if_anonymous_region_appears_in_yield_ty: yield_ty = {:?}", yield_ty);
+
+ let tcx = self.infcx.tcx;
+
+ if !tcx.any_free_region_meets(&yield_ty, |r| r.to_region_vid() == fr) {
+ return None;
+ }
+
+ let mut highlight = RegionHighlightMode::new(tcx);
+ highlight.highlighting_region_vid(fr, *self.next_region_name.try_borrow().unwrap());
+ let type_name =
+ self.infcx.extract_inference_diagnostics_data(yield_ty.into(), Some(highlight)).name;
+
+ let yield_span = match tcx.hir().get(self.mir_hir_id()) {
+ hir::Node::Expr(hir::Expr {
+ kind: hir::ExprKind::Closure(&hir::Closure { fn_decl_span, .. }),
+ ..
+ }) => tcx.sess.source_map().end_point(fn_decl_span),
+ _ => self.body.span,
+ };
+
+ debug!(
+ "give_name_if_anonymous_region_appears_in_yield_ty: \
+ type_name = {:?}, yield_span = {:?}",
+ yield_span, type_name,
+ );
+
+ Some(RegionName {
+ name: self.synthesize_region_name(),
+ source: RegionNameSource::AnonRegionFromYieldTy(yield_span, type_name),
+ })
+ }
+
+ fn give_name_if_anonymous_region_appears_in_impl_signature(
+ &self,
+ fr: RegionVid,
+ ) -> Option<RegionName> {
+ let ty::ReEarlyBound(region) = *self.to_error_region(fr)? else {
+ return None;
+ };
+ if region.has_name() {
+ return None;
+ };
+
+ let tcx = self.infcx.tcx;
+ let body_parent_did = tcx.opt_parent(self.mir_def_id().to_def_id())?;
+ if tcx.parent(region.def_id) != body_parent_did
+ || tcx.def_kind(body_parent_did) != DefKind::Impl
+ {
+ return None;
+ }
+
+ let mut found = false;
+ tcx.fold_regions(tcx.type_of(body_parent_did), |r: ty::Region<'tcx>, _| {
+ if *r == ty::ReEarlyBound(region) {
+ found = true;
+ }
+ r
+ });
+
+ Some(RegionName {
+ name: self.synthesize_region_name(),
+ source: RegionNameSource::AnonRegionFromImplSignature(
+ tcx.def_span(region.def_id),
+ // FIXME(compiler-errors): Does this ever actually show up
+ // anywhere other than the self type? I couldn't create an
+ // example of a `'_` in the impl's trait being referenceable.
+ if found { "self type" } else { "header" },
+ ),
+ })
+ }
+}
diff --git a/compiler/rustc_borrowck/src/diagnostics/var_name.rs b/compiler/rustc_borrowck/src/diagnostics/var_name.rs
new file mode 100644
index 000000000..9ba29f04b
--- /dev/null
+++ b/compiler/rustc_borrowck/src/diagnostics/var_name.rs
@@ -0,0 +1,133 @@
+use crate::Upvar;
+use crate::{nll::ToRegionVid, region_infer::RegionInferenceContext};
+use rustc_index::vec::{Idx, IndexVec};
+use rustc_middle::mir::{Body, Local};
+use rustc_middle::ty::{RegionVid, TyCtxt};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::Symbol;
+
+impl<'tcx> RegionInferenceContext<'tcx> {
+ pub(crate) fn get_var_name_and_span_for_region(
+ &self,
+ tcx: TyCtxt<'tcx>,
+ body: &Body<'tcx>,
+ local_names: &IndexVec<Local, Option<Symbol>>,
+ upvars: &[Upvar<'tcx>],
+ fr: RegionVid,
+ ) -> Option<(Option<Symbol>, 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| {
+ // FIXME(project-rfc-2229#8): Use place span for diagnostics
+ 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.
+ pub(crate) fn get_upvar_index_for_region(
+ &self,
+ tcx: TyCtxt<'tcx>,
+ fr: RegionVid,
+ ) -> Option<usize> {
+ let upvar_index =
+ self.universal_regions().defining_ty.upvar_tys().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().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.
+ pub(crate) fn get_upvar_name_and_span_for_region(
+ &self,
+ tcx: TyCtxt<'tcx>,
+ upvars: &[Upvar<'tcx>],
+ upvar_index: usize,
+ ) -> (Symbol, Span) {
+ let upvar_hir_id = upvars[upvar_index].place.get_root_variable();
+ 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.
+ pub(crate) fn get_argument_index_for_region(
+ &self,
+ tcx: TyCtxt<'tcx>,
+ fr: RegionVid,
+ ) -> Option<usize> {
+ 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.
+ pub(crate) fn get_argument_name_and_span_for_region(
+ &self,
+ body: &Body<'tcx>,
+ local_names: &IndexVec<Local, Option<Symbol>>,
+ argument_index: usize,
+ ) -> (Option<Symbol>, 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/compiler/rustc_borrowck/src/facts.rs b/compiler/rustc_borrowck/src/facts.rs
new file mode 100644
index 000000000..22134d5a7
--- /dev/null
+++ b/compiler/rustc_borrowck/src/facts.rs
@@ -0,0 +1,212 @@
+use crate::location::{LocationIndex, LocationTable};
+use crate::BorrowIndex;
+use polonius_engine::AllFacts as PoloniusFacts;
+use polonius_engine::Atom;
+use rustc_index::vec::Idx;
+use rustc_middle::mir::Local;
+use rustc_middle::ty::{RegionVid, TyCtxt};
+use rustc_mir_dataflow::move_paths::MovePathIndex;
+use std::error::Error;
+use std::fmt::Debug;
+use std::fs::{self, File};
+use std::io::{BufWriter, Write};
+use std::path::Path;
+
+#[derive(Copy, Clone, Debug)]
+pub struct RustcFacts;
+
+impl polonius_engine::FactTypes for RustcFacts {
+ type Origin = RegionVid;
+ type Loan = BorrowIndex;
+ type Point = LocationIndex;
+ type Variable = Local;
+ type Path = MovePathIndex;
+}
+
+pub type AllFacts = PoloniusFacts<RustcFacts>;
+
+pub(crate) trait AllFactsExt {
+ /// Returns `true` if there is a need to gather `AllFacts` given the
+ /// current `-Z` flags.
+ fn enabled(tcx: TyCtxt<'_>) -> bool;
+
+ fn write_to_dir(
+ &self,
+ dir: impl AsRef<Path>,
+ location_table: &LocationTable,
+ ) -> Result<(), Box<dyn Error>>;
+}
+
+impl AllFactsExt for AllFacts {
+ /// Return
+ fn enabled(tcx: TyCtxt<'_>) -> bool {
+ tcx.sess.opts.unstable_opts.nll_facts || tcx.sess.opts.unstable_opts.polonius
+ }
+
+ fn write_to_dir(
+ &self,
+ dir: impl AsRef<Path>,
+ location_table: &LocationTable,
+ ) -> Result<(), Box<dyn Error>> {
+ let dir: &Path = dir.as_ref();
+ fs::create_dir_all(dir)?;
+ let wr = FactWriter { location_table, dir };
+ macro_rules! write_facts_to_path {
+ ($wr:ident . write_facts_to_path($this:ident . [
+ $($field:ident,)*
+ ])) => {
+ $(
+ $wr.write_facts_to_path(
+ &$this.$field,
+ &format!("{}.facts", stringify!($field))
+ )?;
+ )*
+ }
+ }
+ write_facts_to_path! {
+ wr.write_facts_to_path(self.[
+ loan_issued_at,
+ universal_region,
+ cfg_edge,
+ loan_killed_at,
+ subset_base,
+ loan_invalidated_at,
+ var_used_at,
+ var_defined_at,
+ var_dropped_at,
+ use_of_var_derefs_origin,
+ drop_of_var_derefs_origin,
+ child_path,
+ path_is_var,
+ path_assigned_at_base,
+ path_moved_at_base,
+ path_accessed_at_base,
+ known_placeholder_subset,
+ placeholder,
+ ])
+ }
+ Ok(())
+ }
+}
+
+impl Atom for BorrowIndex {
+ fn index(self) -> usize {
+ Idx::index(self)
+ }
+}
+
+impl Atom for LocationIndex {
+ fn index(self) -> usize {
+ Idx::index(self)
+ }
+}
+
+struct FactWriter<'w> {
+ location_table: &'w LocationTable,
+ dir: &'w Path,
+}
+
+impl<'w> FactWriter<'w> {
+ fn write_facts_to_path<T>(&self, rows: &[T], file_name: &str) -> Result<(), Box<dyn Error>>
+ where
+ T: FactRow,
+ {
+ let file = &self.dir.join(file_name);
+ let mut file = BufWriter::new(File::create(file)?);
+ for row in rows {
+ row.write(&mut file, self.location_table)?;
+ }
+ Ok(())
+ }
+}
+
+trait FactRow {
+ fn write(
+ &self,
+ out: &mut dyn Write,
+ location_table: &LocationTable,
+ ) -> Result<(), Box<dyn Error>>;
+}
+
+impl FactRow for RegionVid {
+ fn write(
+ &self,
+ out: &mut dyn Write,
+ location_table: &LocationTable,
+ ) -> Result<(), Box<dyn Error>> {
+ write_row(out, location_table, &[self])
+ }
+}
+
+impl<A, B> FactRow for (A, B)
+where
+ A: FactCell,
+ B: FactCell,
+{
+ fn write(
+ &self,
+ out: &mut dyn Write,
+ location_table: &LocationTable,
+ ) -> Result<(), Box<dyn Error>> {
+ write_row(out, location_table, &[&self.0, &self.1])
+ }
+}
+
+impl<A, B, C> FactRow for (A, B, C)
+where
+ A: FactCell,
+ B: FactCell,
+ C: FactCell,
+{
+ fn write(
+ &self,
+ out: &mut dyn Write,
+ location_table: &LocationTable,
+ ) -> Result<(), Box<dyn Error>> {
+ write_row(out, location_table, &[&self.0, &self.1, &self.2])
+ }
+}
+
+impl<A, B, C, D> FactRow for (A, B, C, D)
+where
+ A: FactCell,
+ B: FactCell,
+ C: FactCell,
+ D: FactCell,
+{
+ fn write(
+ &self,
+ out: &mut dyn Write,
+ location_table: &LocationTable,
+ ) -> Result<(), Box<dyn Error>> {
+ write_row(out, location_table, &[&self.0, &self.1, &self.2, &self.3])
+ }
+}
+
+fn write_row(
+ out: &mut dyn Write,
+ location_table: &LocationTable,
+ columns: &[&dyn FactCell],
+) -> Result<(), Box<dyn Error>> {
+ for (index, c) in columns.iter().enumerate() {
+ let tail = if index == columns.len() - 1 { "\n" } else { "\t" };
+ write!(out, "{:?}{}", c.to_string(location_table), tail)?;
+ }
+ Ok(())
+}
+
+trait FactCell {
+ fn to_string(&self, location_table: &LocationTable) -> String;
+}
+
+impl<A: Debug> FactCell for A {
+ default fn to_string(&self, _location_table: &LocationTable) -> String {
+ format!("{:?}", self)
+ }
+}
+
+impl FactCell for LocationIndex {
+ fn to_string(&self, location_table: &LocationTable) -> String {
+ format!("{:?}", location_table.to_location(*self))
+ }
+}
diff --git a/compiler/rustc_borrowck/src/invalidation.rs b/compiler/rustc_borrowck/src/invalidation.rs
new file mode 100644
index 000000000..ec521b1cf
--- /dev/null
+++ b/compiler/rustc_borrowck/src/invalidation.rs
@@ -0,0 +1,442 @@
+use rustc_data_structures::graph::dominators::Dominators;
+use rustc_middle::mir::visit::Visitor;
+use rustc_middle::mir::{BasicBlock, Body, Location, Place, Rvalue};
+use rustc_middle::mir::{BorrowKind, Mutability, Operand};
+use rustc_middle::mir::{InlineAsmOperand, Terminator, TerminatorKind};
+use rustc_middle::mir::{Statement, StatementKind};
+use rustc_middle::ty::TyCtxt;
+
+use crate::{
+ borrow_set::BorrowSet, facts::AllFacts, location::LocationTable, path_utils::*, AccessDepth,
+ Activation, ArtificialField, BorrowIndex, Deep, LocalMutationIsAllowed, Read, ReadKind,
+ ReadOrWrite, Reservation, Shallow, Write, WriteKind,
+};
+
+pub(super) fn generate_invalidates<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ all_facts: &mut Option<AllFacts>,
+ location_table: &LocationTable,
+ body: &Body<'tcx>,
+ borrow_set: &BorrowSet<'tcx>,
+) {
+ if all_facts.is_none() {
+ // Nothing to do if we don't have any facts
+ return;
+ }
+
+ if let Some(all_facts) = all_facts {
+ let _prof_timer = tcx.prof.generic_activity("polonius_fact_generation");
+ let dominators = body.basic_blocks.dominators();
+ let mut ig = InvalidationGenerator {
+ all_facts,
+ borrow_set,
+ tcx,
+ location_table,
+ body: &body,
+ dominators,
+ };
+ ig.visit_body(body);
+ }
+}
+
+struct InvalidationGenerator<'cx, 'tcx> {
+ tcx: TyCtxt<'tcx>,
+ all_facts: &'cx mut AllFacts,
+ location_table: &'cx LocationTable,
+ body: &'cx Body<'tcx>,
+ dominators: Dominators<BasicBlock>,
+ borrow_set: &'cx BorrowSet<'tcx>,
+}
+
+/// Visits the whole MIR and generates `invalidates()` facts.
+/// Most of the code implementing this was stolen from `borrow_check/mod.rs`.
+impl<'cx, 'tcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx> {
+ fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
+ self.check_activations(location);
+
+ match &statement.kind {
+ StatementKind::Assign(box (lhs, rhs)) => {
+ self.consume_rvalue(location, rhs);
+
+ self.mutate_place(location, *lhs, Shallow(None));
+ }
+ StatementKind::FakeRead(box (_, _)) => {
+ // Only relevant for initialized/liveness/safety checks.
+ }
+ StatementKind::CopyNonOverlapping(box rustc_middle::mir::CopyNonOverlapping {
+ ref src,
+ ref dst,
+ ref count,
+ }) => {
+ self.consume_operand(location, src);
+ self.consume_operand(location, dst);
+ self.consume_operand(location, count);
+ }
+ StatementKind::Nop
+ | StatementKind::Coverage(..)
+ | StatementKind::AscribeUserType(..)
+ | StatementKind::Retag { .. }
+ | StatementKind::StorageLive(..) => {
+ // `Nop`, `AscribeUserType`, `Retag`, and `StorageLive` are irrelevant
+ // to borrow check.
+ }
+ StatementKind::StorageDead(local) => {
+ self.access_place(
+ location,
+ Place::from(*local),
+ (Shallow(None), Write(WriteKind::StorageDeadOrDrop)),
+ LocalMutationIsAllowed::Yes,
+ );
+ }
+ StatementKind::Deinit(..) | StatementKind::SetDiscriminant { .. } => {
+ bug!("Statement not allowed in this MIR phase")
+ }
+ }
+
+ self.super_statement(statement, location);
+ }
+
+ fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
+ self.check_activations(location);
+
+ match &terminator.kind {
+ TerminatorKind::SwitchInt { ref discr, switch_ty: _, targets: _ } => {
+ self.consume_operand(location, discr);
+ }
+ TerminatorKind::Drop { place: drop_place, target: _, unwind: _ } => {
+ self.access_place(
+ location,
+ *drop_place,
+ (AccessDepth::Drop, Write(WriteKind::StorageDeadOrDrop)),
+ LocalMutationIsAllowed::Yes,
+ );
+ }
+ TerminatorKind::DropAndReplace {
+ place: drop_place,
+ value: ref new_value,
+ target: _,
+ unwind: _,
+ } => {
+ self.mutate_place(location, *drop_place, Deep);
+ self.consume_operand(location, new_value);
+ }
+ TerminatorKind::Call {
+ ref func,
+ ref args,
+ destination,
+ target: _,
+ cleanup: _,
+ from_hir_call: _,
+ fn_span: _,
+ } => {
+ self.consume_operand(location, func);
+ for arg in args {
+ self.consume_operand(location, arg);
+ }
+ self.mutate_place(location, *destination, Deep);
+ }
+ TerminatorKind::Assert { ref cond, expected: _, ref msg, target: _, cleanup: _ } => {
+ self.consume_operand(location, cond);
+ use rustc_middle::mir::AssertKind;
+ if let AssertKind::BoundsCheck { ref len, ref index } = *msg {
+ self.consume_operand(location, len);
+ self.consume_operand(location, index);
+ }
+ }
+ TerminatorKind::Yield { ref value, resume, resume_arg, drop: _ } => {
+ self.consume_operand(location, value);
+
+ // Invalidate all borrows of local places
+ let borrow_set = self.borrow_set;
+ let resume = self.location_table.start_index(resume.start_location());
+ for (i, data) in borrow_set.iter_enumerated() {
+ if borrow_of_local_data(data.borrowed_place) {
+ self.all_facts.loan_invalidated_at.push((resume, i));
+ }
+ }
+
+ self.mutate_place(location, *resume_arg, Deep);
+ }
+ TerminatorKind::Resume | TerminatorKind::Return | TerminatorKind::GeneratorDrop => {
+ // Invalidate all borrows of local places
+ let borrow_set = self.borrow_set;
+ let start = self.location_table.start_index(location);
+ for (i, data) in borrow_set.iter_enumerated() {
+ if borrow_of_local_data(data.borrowed_place) {
+ self.all_facts.loan_invalidated_at.push((start, i));
+ }
+ }
+ }
+ TerminatorKind::InlineAsm {
+ template: _,
+ ref operands,
+ options: _,
+ line_spans: _,
+ destination: _,
+ cleanup: _,
+ } => {
+ for op in operands {
+ match *op {
+ InlineAsmOperand::In { reg: _, ref value } => {
+ self.consume_operand(location, value);
+ }
+ InlineAsmOperand::Out { reg: _, late: _, place, .. } => {
+ if let Some(place) = place {
+ self.mutate_place(location, place, Shallow(None));
+ }
+ }
+ InlineAsmOperand::InOut { reg: _, late: _, ref in_value, out_place } => {
+ self.consume_operand(location, in_value);
+ if let Some(out_place) = out_place {
+ self.mutate_place(location, out_place, Shallow(None));
+ }
+ }
+ InlineAsmOperand::Const { value: _ }
+ | InlineAsmOperand::SymFn { value: _ }
+ | InlineAsmOperand::SymStatic { def_id: _ } => {}
+ }
+ }
+ }
+ TerminatorKind::Goto { target: _ }
+ | TerminatorKind::Abort
+ | TerminatorKind::Unreachable
+ | TerminatorKind::FalseEdge { real_target: _, imaginary_target: _ }
+ | TerminatorKind::FalseUnwind { real_target: _, unwind: _ } => {
+ // no data used, thus irrelevant to borrowck
+ }
+ }
+
+ self.super_terminator(terminator, location);
+ }
+}
+
+impl<'cx, 'tcx> InvalidationGenerator<'cx, 'tcx> {
+ /// Simulates mutation of a place.
+ fn mutate_place(&mut self, location: Location, place: Place<'tcx>, kind: AccessDepth) {
+ self.access_place(
+ location,
+ place,
+ (kind, Write(WriteKind::Mutate)),
+ LocalMutationIsAllowed::ExceptUpvars,
+ );
+ }
+
+ /// Simulates consumption of an operand.
+ fn consume_operand(&mut self, location: Location, operand: &Operand<'tcx>) {
+ match *operand {
+ Operand::Copy(place) => {
+ self.access_place(
+ location,
+ place,
+ (Deep, Read(ReadKind::Copy)),
+ LocalMutationIsAllowed::No,
+ );
+ }
+ Operand::Move(place) => {
+ self.access_place(
+ location,
+ place,
+ (Deep, Write(WriteKind::Move)),
+ LocalMutationIsAllowed::Yes,
+ );
+ }
+ Operand::Constant(_) => {}
+ }
+ }
+
+ // Simulates consumption of an rvalue
+ fn consume_rvalue(&mut self, location: Location, rvalue: &Rvalue<'tcx>) {
+ match *rvalue {
+ Rvalue::Ref(_ /*rgn*/, bk, place) => {
+ let access_kind = match bk {
+ BorrowKind::Shallow => {
+ (Shallow(Some(ArtificialField::ShallowBorrow)), Read(ReadKind::Borrow(bk)))
+ }
+ BorrowKind::Shared => (Deep, Read(ReadKind::Borrow(bk))),
+ BorrowKind::Unique | BorrowKind::Mut { .. } => {
+ let wk = WriteKind::MutableBorrow(bk);
+ if allow_two_phase_borrow(bk) {
+ (Deep, Reservation(wk))
+ } else {
+ (Deep, Write(wk))
+ }
+ }
+ };
+
+ self.access_place(location, place, access_kind, LocalMutationIsAllowed::No);
+ }
+
+ Rvalue::AddressOf(mutability, place) => {
+ let access_kind = match mutability {
+ Mutability::Mut => (
+ Deep,
+ Write(WriteKind::MutableBorrow(BorrowKind::Mut {
+ allow_two_phase_borrow: false,
+ })),
+ ),
+ Mutability::Not => (Deep, Read(ReadKind::Borrow(BorrowKind::Shared))),
+ };
+
+ self.access_place(location, place, access_kind, LocalMutationIsAllowed::No);
+ }
+
+ Rvalue::ThreadLocalRef(_) => {}
+
+ Rvalue::Use(ref operand)
+ | Rvalue::Repeat(ref operand, _)
+ | Rvalue::UnaryOp(_ /*un_op*/, ref operand)
+ | Rvalue::Cast(_ /*cast_kind*/, ref operand, _ /*ty*/)
+ | Rvalue::ShallowInitBox(ref operand, _ /*ty*/) => {
+ self.consume_operand(location, operand)
+ }
+ Rvalue::CopyForDeref(ref place) => {
+ let op = &Operand::Copy(*place);
+ self.consume_operand(location, op);
+ }
+
+ Rvalue::Len(place) | Rvalue::Discriminant(place) => {
+ let af = match *rvalue {
+ Rvalue::Len(..) => Some(ArtificialField::ArrayLength),
+ Rvalue::Discriminant(..) => None,
+ _ => unreachable!(),
+ };
+ self.access_place(
+ location,
+ place,
+ (Shallow(af), Read(ReadKind::Copy)),
+ LocalMutationIsAllowed::No,
+ );
+ }
+
+ Rvalue::BinaryOp(_bin_op, box (ref operand1, ref operand2))
+ | Rvalue::CheckedBinaryOp(_bin_op, box (ref operand1, ref operand2)) => {
+ self.consume_operand(location, operand1);
+ self.consume_operand(location, operand2);
+ }
+
+ Rvalue::NullaryOp(_op, _ty) => {}
+
+ Rvalue::Aggregate(_, ref operands) => {
+ for operand in operands {
+ self.consume_operand(location, operand);
+ }
+ }
+ }
+ }
+
+ /// Simulates an access to a place.
+ fn access_place(
+ &mut self,
+ location: Location,
+ place: Place<'tcx>,
+ kind: (AccessDepth, ReadOrWrite),
+ _is_local_mutation_allowed: LocalMutationIsAllowed,
+ ) {
+ let (sd, rw) = kind;
+ // note: not doing check_access_permissions checks because they don't generate invalidates
+ self.check_access_for_conflict(location, place, sd, rw);
+ }
+
+ fn check_access_for_conflict(
+ &mut self,
+ location: Location,
+ place: Place<'tcx>,
+ sd: AccessDepth,
+ rw: ReadOrWrite,
+ ) {
+ debug!(
+ "invalidation::check_access_for_conflict(location={:?}, place={:?}, sd={:?}, \
+ rw={:?})",
+ location, place, sd, rw,
+ );
+ let tcx = self.tcx;
+ let body = self.body;
+ let borrow_set = self.borrow_set;
+ let indices = self.borrow_set.indices();
+ each_borrow_involving_path(
+ self,
+ tcx,
+ body,
+ location,
+ (sd, place),
+ borrow_set,
+ indices,
+ |this, borrow_index, borrow| {
+ match (rw, borrow.kind) {
+ // Obviously an activation is compatible with its own
+ // reservation (or even prior activating uses of same
+ // borrow); so don't check if they interfere.
+ //
+ // NOTE: *reservations* do conflict with themselves;
+ // thus aren't injecting unsoundness w/ this check.)
+ (Activation(_, activating), _) if activating == borrow_index => {
+ // Activating a borrow doesn't generate any invalidations, since we
+ // have already taken the reservation
+ }
+
+ (Read(_), BorrowKind::Shallow | BorrowKind::Shared)
+ | (
+ Read(ReadKind::Borrow(BorrowKind::Shallow)),
+ BorrowKind::Unique | BorrowKind::Mut { .. },
+ ) => {
+ // Reads don't invalidate shared or shallow borrows
+ }
+
+ (Read(_), BorrowKind::Unique | BorrowKind::Mut { .. }) => {
+ // Reading from mere reservations of mutable-borrows is OK.
+ if !is_active(&this.dominators, borrow, location) {
+ // If the borrow isn't active yet, reads don't invalidate it
+ assert!(allow_two_phase_borrow(borrow.kind));
+ return Control::Continue;
+ }
+
+ // Unique and mutable borrows are invalidated by reads from any
+ // involved path
+ this.emit_loan_invalidated_at(borrow_index, location);
+ }
+
+ (Reservation(_) | Activation(_, _) | Write(_), _) => {
+ // unique or mutable borrows are invalidated by writes.
+ // Reservations count as writes since we need to check
+ // that activating the borrow will be OK
+ // FIXME(bob_twinkles) is this actually the right thing to do?
+ this.emit_loan_invalidated_at(borrow_index, location);
+ }
+ }
+ Control::Continue
+ },
+ );
+ }
+
+ /// Generates a new `loan_invalidated_at(L, B)` fact.
+ fn emit_loan_invalidated_at(&mut self, b: BorrowIndex, l: Location) {
+ let lidx = self.location_table.start_index(l);
+ self.all_facts.loan_invalidated_at.push((lidx, b));
+ }
+
+ fn check_activations(&mut self, location: Location) {
+ // Two-phase borrow support: For each activation that is newly
+ // generated at this statement, check if it interferes with
+ // another borrow.
+ for &borrow_index in self.borrow_set.activations_at_location(location) {
+ let borrow = &self.borrow_set[borrow_index];
+
+ // only mutable borrows should be 2-phase
+ assert!(match borrow.kind {
+ BorrowKind::Shared | BorrowKind::Shallow => false,
+ BorrowKind::Unique | BorrowKind::Mut { .. } => true,
+ });
+
+ self.access_place(
+ location,
+ borrow.borrowed_place,
+ (Deep, Activation(WriteKind::MutableBorrow(borrow.kind), borrow_index)),
+ LocalMutationIsAllowed::No,
+ );
+
+ // We do not need to call `check_if_path_or_subpath_is_moved`
+ // again, as we already called it when we made the
+ // initial reservation.
+ }
+ }
+}
diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs
new file mode 100644
index 000000000..3d8b07382
--- /dev/null
+++ b/compiler/rustc_borrowck/src/lib.rs
@@ -0,0 +1,2380 @@
+//! This query borrow-checks the MIR to (further) ensure it is not broken.
+
+#![allow(rustc::potential_query_instability)]
+#![feature(box_patterns)]
+#![feature(let_chains)]
+#![feature(let_else)]
+#![feature(min_specialization)]
+#![feature(never_type)]
+#![feature(rustc_attrs)]
+#![feature(stmt_expr_attributes)]
+#![feature(trusted_step)]
+#![feature(try_blocks)]
+#![recursion_limit = "256"]
+
+#[macro_use]
+extern crate rustc_middle;
+#[macro_use]
+extern crate tracing;
+
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::graph::dominators::Dominators;
+use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed};
+use rustc_hir as hir;
+use rustc_hir::def_id::LocalDefId;
+use rustc_index::bit_set::ChunkedBitSet;
+use rustc_index::vec::IndexVec;
+use rustc_infer::infer::{DefiningAnchor, InferCtxt, TyCtxtInferExt};
+use rustc_middle::mir::{
+ traversal, Body, ClearCrossCrate, Local, Location, Mutability, Operand, Place, PlaceElem,
+ PlaceRef, VarDebugInfoContents,
+};
+use rustc_middle::mir::{AggregateKind, BasicBlock, BorrowCheckResult, BorrowKind};
+use rustc_middle::mir::{Field, ProjectionElem, Promoted, Rvalue, Statement, StatementKind};
+use rustc_middle::mir::{InlineAsmOperand, Terminator, TerminatorKind};
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::{self, CapturedPlace, ParamEnv, RegionVid, TyCtxt};
+use rustc_session::lint::builtin::UNUSED_MUT;
+use rustc_span::{Span, Symbol};
+
+use either::Either;
+use smallvec::SmallVec;
+use std::cell::RefCell;
+use std::collections::BTreeMap;
+use std::rc::Rc;
+
+use rustc_mir_dataflow::impls::{
+ EverInitializedPlaces, MaybeInitializedPlaces, MaybeUninitializedPlaces,
+};
+use rustc_mir_dataflow::move_paths::{InitIndex, MoveOutIndex, MovePathIndex};
+use rustc_mir_dataflow::move_paths::{InitLocation, LookupResult, MoveData, MoveError};
+use rustc_mir_dataflow::Analysis;
+use rustc_mir_dataflow::MoveDataParamEnv;
+
+use self::diagnostics::{AccessKind, RegionName};
+use self::location::LocationTable;
+use self::prefixes::PrefixSet;
+use facts::AllFacts;
+
+use self::path_utils::*;
+
+pub mod borrow_set;
+mod borrowck_errors;
+mod constraint_generation;
+mod constraints;
+mod dataflow;
+mod def_use;
+mod diagnostics;
+mod facts;
+mod invalidation;
+mod location;
+mod member_constraints;
+mod nll;
+mod path_utils;
+mod place_ext;
+mod places_conflict;
+mod prefixes;
+mod region_infer;
+mod renumber;
+mod session_diagnostics;
+mod type_check;
+mod universal_regions;
+mod used_muts;
+
+// A public API provided for the Rust compiler consumers.
+pub mod consumers;
+
+use borrow_set::{BorrowData, BorrowSet};
+use dataflow::{BorrowIndex, BorrowckFlowState as Flows, BorrowckResults, Borrows};
+use nll::{PoloniusOutput, ToRegionVid};
+use place_ext::PlaceExt;
+use places_conflict::{places_conflict, PlaceConflictBias};
+use region_infer::RegionInferenceContext;
+
+// FIXME(eddyb) perhaps move this somewhere more centrally.
+#[derive(Debug)]
+struct Upvar<'tcx> {
+ place: CapturedPlace<'tcx>,
+
+ /// If true, the capture is behind a reference.
+ by_ref: bool,
+}
+
+/// Associate some local constants with the `'tcx` lifetime
+struct TyCtxtConsts<'tcx>(TyCtxt<'tcx>);
+impl<'tcx> TyCtxtConsts<'tcx> {
+ const DEREF_PROJECTION: &'tcx [PlaceElem<'tcx>; 1] = &[ProjectionElem::Deref];
+}
+
+pub fn provide(providers: &mut Providers) {
+ *providers = Providers {
+ mir_borrowck: |tcx, did| {
+ if let Some(def) = ty::WithOptConstParam::try_lookup(did, tcx) {
+ tcx.mir_borrowck_const_arg(def)
+ } else {
+ mir_borrowck(tcx, ty::WithOptConstParam::unknown(did))
+ }
+ },
+ mir_borrowck_const_arg: |tcx, (did, param_did)| {
+ mir_borrowck(tcx, ty::WithOptConstParam { did, const_param_did: Some(param_did) })
+ },
+ ..*providers
+ };
+}
+
+fn mir_borrowck<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ def: ty::WithOptConstParam<LocalDefId>,
+) -> &'tcx BorrowCheckResult<'tcx> {
+ let (input_body, promoted) = tcx.mir_promoted(def);
+ debug!("run query mir_borrowck: {}", tcx.def_path_str(def.did.to_def_id()));
+ let hir_owner = tcx.hir().local_def_id_to_hir_id(def.did).owner;
+
+ let opt_closure_req = tcx
+ .infer_ctxt()
+ .with_opaque_type_inference(DefiningAnchor::Bind(hir_owner))
+ .enter(|infcx| {
+ let input_body: &Body<'_> = &input_body.borrow();
+ let promoted: &IndexVec<_, _> = &promoted.borrow();
+ do_mir_borrowck(&infcx, input_body, promoted, false).0
+ });
+ debug!("mir_borrowck done");
+
+ tcx.arena.alloc(opt_closure_req)
+}
+
+/// Perform the actual borrow checking.
+///
+/// If `return_body_with_facts` is true, then return the body with non-erased
+/// region ids on which the borrow checking was performed together with Polonius
+/// facts.
+#[instrument(skip(infcx, input_body, input_promoted), fields(id=?input_body.source.with_opt_param().as_local().unwrap()), level = "debug")]
+fn do_mir_borrowck<'a, 'tcx>(
+ infcx: &InferCtxt<'a, 'tcx>,
+ input_body: &Body<'tcx>,
+ input_promoted: &IndexVec<Promoted, Body<'tcx>>,
+ return_body_with_facts: bool,
+) -> (BorrowCheckResult<'tcx>, Option<Box<BodyWithBorrowckFacts<'tcx>>>) {
+ let def = input_body.source.with_opt_param().as_local().unwrap();
+
+ debug!(?def);
+
+ let tcx = infcx.tcx;
+ let param_env = tcx.param_env(def.did);
+
+ let mut local_names = IndexVec::from_elem(None, &input_body.local_decls);
+ for var_debug_info in &input_body.var_debug_info {
+ if let VarDebugInfoContents::Place(place) = var_debug_info.value {
+ if let Some(local) = place.as_local() {
+ if let Some(prev_name) = local_names[local] && var_debug_info.name != prev_name {
+ span_bug!(
+ var_debug_info.source_info.span,
+ "local {:?} has many names (`{}` vs `{}`)",
+ local,
+ prev_name,
+ var_debug_info.name
+ );
+ }
+ local_names[local] = Some(var_debug_info.name);
+ }
+ }
+ }
+
+ let mut errors = error::BorrowckErrors::new();
+
+ // Gather the upvars of a closure, if any.
+ let tables = tcx.typeck_opt_const_arg(def);
+ if let Some(ErrorGuaranteed { .. }) = tables.tainted_by_errors {
+ infcx.set_tainted_by_errors();
+ errors.set_tainted_by_errors();
+ }
+ let upvars: Vec<_> = tables
+ .closure_min_captures_flattened(def.did)
+ .map(|captured_place| {
+ let capture = captured_place.info.capture_kind;
+ let by_ref = match capture {
+ ty::UpvarCapture::ByValue => false,
+ ty::UpvarCapture::ByRef(..) => true,
+ };
+ Upvar { place: captured_place.clone(), by_ref }
+ })
+ .collect();
+
+ // Replace all regions with fresh inference variables. This
+ // requires first making our own copy of the MIR. This copy will
+ // be modified (in place) to contain non-lexical lifetimes. It
+ // will have a lifetime tied to the inference context.
+ let mut body_owned = input_body.clone();
+ let mut promoted = input_promoted.clone();
+ let free_regions =
+ nll::replace_regions_in_mir(infcx, param_env, &mut body_owned, &mut promoted);
+ let body = &body_owned; // no further changes
+
+ let location_table_owned = LocationTable::new(body);
+ let location_table = &location_table_owned;
+
+ let (move_data, move_errors): (MoveData<'tcx>, Vec<(Place<'tcx>, MoveError<'tcx>)>) =
+ match MoveData::gather_moves(&body, tcx, param_env) {
+ Ok((_, move_data)) => (move_data, Vec::new()),
+ Err((move_data, move_errors)) => (move_data, move_errors),
+ };
+ let promoted_errors = promoted
+ .iter_enumerated()
+ .map(|(idx, body)| (idx, MoveData::gather_moves(&body, tcx, param_env)));
+
+ let mdpe = MoveDataParamEnv { move_data, param_env };
+
+ let mut flow_inits = MaybeInitializedPlaces::new(tcx, &body, &mdpe)
+ .into_engine(tcx, &body)
+ .pass_name("borrowck")
+ .iterate_to_fixpoint()
+ .into_results_cursor(&body);
+
+ let locals_are_invalidated_at_exit = tcx.hir().body_owner_kind(def.did).is_fn_or_closure();
+ let borrow_set =
+ Rc::new(BorrowSet::build(tcx, body, locals_are_invalidated_at_exit, &mdpe.move_data));
+
+ let use_polonius = return_body_with_facts || infcx.tcx.sess.opts.unstable_opts.polonius;
+
+ // Compute non-lexical lifetimes.
+ let nll::NllOutput {
+ regioncx,
+ opaque_type_values,
+ polonius_input,
+ polonius_output,
+ opt_closure_req,
+ nll_errors,
+ } = nll::compute_regions(
+ infcx,
+ free_regions,
+ body,
+ &promoted,
+ location_table,
+ param_env,
+ &mut flow_inits,
+ &mdpe.move_data,
+ &borrow_set,
+ &upvars,
+ use_polonius,
+ );
+
+ // Dump MIR results into a file, if that is enabled. This let us
+ // write unit-tests, as well as helping with debugging.
+ nll::dump_mir_results(infcx, &body, &regioncx, &opt_closure_req);
+
+ // We also have a `#[rustc_regions]` annotation that causes us to dump
+ // information.
+ nll::dump_annotation(
+ infcx,
+ &body,
+ &regioncx,
+ &opt_closure_req,
+ &opaque_type_values,
+ &mut errors,
+ );
+
+ // The various `flow_*` structures can be large. We drop `flow_inits` here
+ // so it doesn't overlap with the others below. This reduces peak memory
+ // usage significantly on some benchmarks.
+ drop(flow_inits);
+
+ let regioncx = Rc::new(regioncx);
+
+ let flow_borrows = Borrows::new(tcx, body, &regioncx, &borrow_set)
+ .into_engine(tcx, body)
+ .pass_name("borrowck")
+ .iterate_to_fixpoint();
+ let flow_uninits = MaybeUninitializedPlaces::new(tcx, body, &mdpe)
+ .into_engine(tcx, body)
+ .pass_name("borrowck")
+ .iterate_to_fixpoint();
+ let flow_ever_inits = EverInitializedPlaces::new(tcx, body, &mdpe)
+ .into_engine(tcx, body)
+ .pass_name("borrowck")
+ .iterate_to_fixpoint();
+
+ let movable_generator =
+ // The first argument is the generator type passed by value
+ if let Some(local) = body.local_decls.raw.get(1)
+ // Get the interior types and substs which typeck computed
+ && let ty::Generator(_, _, hir::Movability::Static) = local.ty.kind()
+ {
+ false
+ } else {
+ true
+ };
+
+ for (idx, move_data_results) in promoted_errors {
+ let promoted_body = &promoted[idx];
+
+ if let Err((move_data, move_errors)) = move_data_results {
+ let mut promoted_mbcx = MirBorrowckCtxt {
+ infcx,
+ param_env,
+ body: promoted_body,
+ move_data: &move_data,
+ location_table, // no need to create a real one for the promoted, it is not used
+ movable_generator,
+ fn_self_span_reported: Default::default(),
+ locals_are_invalidated_at_exit,
+ access_place_error_reported: Default::default(),
+ reservation_error_reported: Default::default(),
+ uninitialized_error_reported: Default::default(),
+ regioncx: regioncx.clone(),
+ used_mut: Default::default(),
+ used_mut_upvars: SmallVec::new(),
+ borrow_set: Rc::clone(&borrow_set),
+ dominators: Dominators::dummy(), // not used
+ upvars: Vec::new(),
+ local_names: IndexVec::from_elem(None, &promoted_body.local_decls),
+ region_names: RefCell::default(),
+ next_region_name: RefCell::new(1),
+ polonius_output: None,
+ errors,
+ };
+ promoted_mbcx.report_move_errors(move_errors);
+ errors = promoted_mbcx.errors;
+ };
+ }
+
+ let dominators = body.basic_blocks.dominators();
+
+ let mut mbcx = MirBorrowckCtxt {
+ infcx,
+ param_env,
+ body,
+ move_data: &mdpe.move_data,
+ location_table,
+ movable_generator,
+ locals_are_invalidated_at_exit,
+ fn_self_span_reported: Default::default(),
+ access_place_error_reported: Default::default(),
+ reservation_error_reported: Default::default(),
+ uninitialized_error_reported: Default::default(),
+ regioncx: Rc::clone(&regioncx),
+ used_mut: Default::default(),
+ used_mut_upvars: SmallVec::new(),
+ borrow_set: Rc::clone(&borrow_set),
+ dominators,
+ upvars,
+ local_names,
+ region_names: RefCell::default(),
+ next_region_name: RefCell::new(1),
+ polonius_output,
+ errors,
+ };
+
+ // Compute and report region errors, if any.
+ mbcx.report_region_errors(nll_errors);
+
+ let results = BorrowckResults {
+ ever_inits: flow_ever_inits,
+ uninits: flow_uninits,
+ borrows: flow_borrows,
+ };
+
+ mbcx.report_move_errors(move_errors);
+
+ rustc_mir_dataflow::visit_results(
+ body,
+ traversal::reverse_postorder(body).map(|(bb, _)| bb),
+ &results,
+ &mut mbcx,
+ );
+
+ // For each non-user used mutable variable, check if it's been assigned from
+ // a user-declared local. If so, then put that local into the used_mut set.
+ // Note that this set is expected to be small - only upvars from closures
+ // would have a chance of erroneously adding non-user-defined mutable vars
+ // to the set.
+ let temporary_used_locals: FxHashSet<Local> = mbcx
+ .used_mut
+ .iter()
+ .filter(|&local| !mbcx.body.local_decls[*local].is_user_variable())
+ .cloned()
+ .collect();
+ // For the remaining unused locals that are marked as mutable, we avoid linting any that
+ // were never initialized. These locals may have been removed as unreachable code; or will be
+ // linted as unused variables.
+ let unused_mut_locals =
+ mbcx.body.mut_vars_iter().filter(|local| !mbcx.used_mut.contains(local)).collect();
+ mbcx.gather_used_muts(temporary_used_locals, unused_mut_locals);
+
+ debug!("mbcx.used_mut: {:?}", mbcx.used_mut);
+ let used_mut = std::mem::take(&mut mbcx.used_mut);
+ for local in mbcx.body.mut_vars_and_args_iter().filter(|local| !used_mut.contains(local)) {
+ let local_decl = &mbcx.body.local_decls[local];
+ let lint_root = match &mbcx.body.source_scopes[local_decl.source_info.scope].local_data {
+ ClearCrossCrate::Set(data) => data.lint_root,
+ _ => continue,
+ };
+
+ // Skip over locals that begin with an underscore or have no name
+ match mbcx.local_names[local] {
+ Some(name) => {
+ if name.as_str().starts_with('_') {
+ continue;
+ }
+ }
+ None => continue,
+ }
+
+ let span = local_decl.source_info.span;
+ if span.desugaring_kind().is_some() {
+ // If the `mut` arises as part of a desugaring, we should ignore it.
+ continue;
+ }
+
+ tcx.struct_span_lint_hir(UNUSED_MUT, lint_root, span, |lint| {
+ let mut_span = tcx.sess.source_map().span_until_non_whitespace(span);
+ lint.build("variable does not need to be mutable")
+ .span_suggestion_short(
+ mut_span,
+ "remove this `mut`",
+ "",
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ })
+ }
+
+ let tainted_by_errors = mbcx.emit_errors();
+
+ let result = BorrowCheckResult {
+ concrete_opaque_types: opaque_type_values,
+ closure_requirements: opt_closure_req,
+ used_mut_upvars: mbcx.used_mut_upvars,
+ tainted_by_errors,
+ };
+
+ let body_with_facts = if return_body_with_facts {
+ let output_facts = mbcx.polonius_output.expect("Polonius output was not computed");
+ Some(Box::new(BodyWithBorrowckFacts {
+ body: body_owned,
+ input_facts: *polonius_input.expect("Polonius input facts were not generated"),
+ output_facts,
+ location_table: location_table_owned,
+ }))
+ } else {
+ None
+ };
+
+ debug!("do_mir_borrowck: result = {:#?}", result);
+
+ (result, body_with_facts)
+}
+
+/// A `Body` with information computed by the borrow checker. This struct is
+/// intended to be consumed by compiler consumers.
+///
+/// We need to include the MIR body here because the region identifiers must
+/// match the ones in the Polonius facts.
+pub struct BodyWithBorrowckFacts<'tcx> {
+ /// A mir body that contains region identifiers.
+ pub body: Body<'tcx>,
+ /// Polonius input facts.
+ pub input_facts: AllFacts,
+ /// Polonius output facts.
+ pub output_facts: Rc<self::nll::PoloniusOutput>,
+ /// The table that maps Polonius points to locations in the table.
+ pub location_table: LocationTable,
+}
+
+struct MirBorrowckCtxt<'cx, 'tcx> {
+ infcx: &'cx InferCtxt<'cx, 'tcx>,
+ param_env: ParamEnv<'tcx>,
+ body: &'cx Body<'tcx>,
+ move_data: &'cx MoveData<'tcx>,
+
+ /// Map from MIR `Location` to `LocationIndex`; created
+ /// when MIR borrowck begins.
+ location_table: &'cx LocationTable,
+
+ movable_generator: bool,
+ /// This keeps track of whether local variables are free-ed when the function
+ /// exits even without a `StorageDead`, which appears to be the case for
+ /// constants.
+ ///
+ /// I'm not sure this is the right approach - @eddyb could you try and
+ /// figure this out?
+ locals_are_invalidated_at_exit: bool,
+ /// This field keeps track of when borrow errors are reported in the access_place function
+ /// so that there is no duplicate reporting. This field cannot also be used for the conflicting
+ /// borrow errors that is handled by the `reservation_error_reported` field as the inclusion
+ /// of the `Span` type (while required to mute some errors) stops the muting of the reservation
+ /// errors.
+ access_place_error_reported: FxHashSet<(Place<'tcx>, Span)>,
+ /// This field keeps track of when borrow conflict errors are reported
+ /// for reservations, so that we don't report seemingly duplicate
+ /// errors for corresponding activations.
+ //
+ // FIXME: ideally this would be a set of `BorrowIndex`, not `Place`s,
+ // but it is currently inconvenient to track down the `BorrowIndex`
+ // at the time we detect and report a reservation error.
+ reservation_error_reported: FxHashSet<Place<'tcx>>,
+ /// This fields keeps track of the `Span`s that we have
+ /// used to report extra information for `FnSelfUse`, to avoid
+ /// unnecessarily verbose errors.
+ fn_self_span_reported: FxHashSet<Span>,
+ /// This field keeps track of errors reported in the checking of uninitialized variables,
+ /// so that we don't report seemingly duplicate errors.
+ uninitialized_error_reported: FxHashSet<PlaceRef<'tcx>>,
+ /// This field keeps track of all the local variables that are declared mut and are mutated.
+ /// Used for the warning issued by an unused mutable local variable.
+ used_mut: FxHashSet<Local>,
+ /// If the function we're checking is a closure, then we'll need to report back the list of
+ /// mutable upvars that have been used. This field keeps track of them.
+ used_mut_upvars: SmallVec<[Field; 8]>,
+ /// Region inference context. This contains the results from region inference and lets us e.g.
+ /// find out which CFG points are contained in each borrow region.
+ regioncx: Rc<RegionInferenceContext<'tcx>>,
+
+ /// The set of borrows extracted from the MIR
+ borrow_set: Rc<BorrowSet<'tcx>>,
+
+ /// Dominators for MIR
+ dominators: Dominators<BasicBlock>,
+
+ /// Information about upvars not necessarily preserved in types or MIR
+ upvars: Vec<Upvar<'tcx>>,
+
+ /// Names of local (user) variables (extracted from `var_debug_info`).
+ local_names: IndexVec<Local, Option<Symbol>>,
+
+ /// Record the region names generated for each region in the given
+ /// MIR def so that we can reuse them later in help/error messages.
+ region_names: RefCell<FxHashMap<RegionVid, RegionName>>,
+
+ /// The counter for generating new region names.
+ next_region_name: RefCell<usize>,
+
+ /// Results of Polonius analysis.
+ polonius_output: Option<Rc<PoloniusOutput>>,
+
+ errors: error::BorrowckErrors<'tcx>,
+}
+
+// Check that:
+// 1. assignments are always made to mutable locations (FIXME: does that still really go here?)
+// 2. loans made in overlapping scopes do not conflict
+// 3. assignments do not affect things loaned out as immutable
+// 4. moves do not affect things loaned out in any way
+impl<'cx, 'tcx> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx> for MirBorrowckCtxt<'cx, 'tcx> {
+ type FlowState = Flows<'cx, 'tcx>;
+
+ fn visit_statement_before_primary_effect(
+ &mut self,
+ flow_state: &Flows<'cx, 'tcx>,
+ stmt: &'cx Statement<'tcx>,
+ location: Location,
+ ) {
+ debug!("MirBorrowckCtxt::process_statement({:?}, {:?}): {:?}", location, stmt, flow_state);
+ let span = stmt.source_info.span;
+
+ self.check_activations(location, span, flow_state);
+
+ match &stmt.kind {
+ StatementKind::Assign(box (lhs, ref rhs)) => {
+ self.consume_rvalue(location, (rhs, span), flow_state);
+
+ self.mutate_place(location, (*lhs, span), Shallow(None), flow_state);
+ }
+ StatementKind::FakeRead(box (_, ref place)) => {
+ // Read for match doesn't access any memory and is used to
+ // assert that a place is safe and live. So we don't have to
+ // do any checks here.
+ //
+ // FIXME: Remove check that the place is initialized. This is
+ // needed for now because matches don't have never patterns yet.
+ // So this is the only place we prevent
+ // let x: !;
+ // match x {};
+ // from compiling.
+ self.check_if_path_or_subpath_is_moved(
+ location,
+ InitializationRequiringAction::Use,
+ (place.as_ref(), span),
+ flow_state,
+ );
+ }
+ StatementKind::CopyNonOverlapping(box rustc_middle::mir::CopyNonOverlapping {
+ ..
+ }) => {
+ span_bug!(
+ span,
+ "Unexpected CopyNonOverlapping, should only appear after lower_intrinsics",
+ )
+ }
+ StatementKind::Nop
+ | StatementKind::Coverage(..)
+ | StatementKind::AscribeUserType(..)
+ | StatementKind::Retag { .. }
+ | StatementKind::StorageLive(..) => {
+ // `Nop`, `AscribeUserType`, `Retag`, and `StorageLive` are irrelevant
+ // to borrow check.
+ }
+ StatementKind::StorageDead(local) => {
+ self.access_place(
+ location,
+ (Place::from(*local), span),
+ (Shallow(None), Write(WriteKind::StorageDeadOrDrop)),
+ LocalMutationIsAllowed::Yes,
+ flow_state,
+ );
+ }
+ StatementKind::Deinit(..) | StatementKind::SetDiscriminant { .. } => {
+ bug!("Statement not allowed in this MIR phase")
+ }
+ }
+ }
+
+ fn visit_terminator_before_primary_effect(
+ &mut self,
+ flow_state: &Flows<'cx, 'tcx>,
+ term: &'cx Terminator<'tcx>,
+ loc: Location,
+ ) {
+ debug!("MirBorrowckCtxt::process_terminator({:?}, {:?}): {:?}", loc, term, flow_state);
+ let span = term.source_info.span;
+
+ self.check_activations(loc, span, flow_state);
+
+ match term.kind {
+ TerminatorKind::SwitchInt { ref discr, switch_ty: _, targets: _ } => {
+ self.consume_operand(loc, (discr, span), flow_state);
+ }
+ TerminatorKind::Drop { place, target: _, unwind: _ } => {
+ debug!(
+ "visit_terminator_drop \
+ loc: {:?} term: {:?} place: {:?} span: {:?}",
+ loc, term, place, span
+ );
+
+ self.access_place(
+ loc,
+ (place, span),
+ (AccessDepth::Drop, Write(WriteKind::StorageDeadOrDrop)),
+ LocalMutationIsAllowed::Yes,
+ flow_state,
+ );
+ }
+ TerminatorKind::DropAndReplace {
+ place: drop_place,
+ value: ref new_value,
+ target: _,
+ unwind: _,
+ } => {
+ self.mutate_place(loc, (drop_place, span), Deep, flow_state);
+ self.consume_operand(loc, (new_value, span), flow_state);
+ }
+ TerminatorKind::Call {
+ ref func,
+ ref args,
+ destination,
+ target: _,
+ cleanup: _,
+ from_hir_call: _,
+ fn_span: _,
+ } => {
+ self.consume_operand(loc, (func, span), flow_state);
+ for arg in args {
+ self.consume_operand(loc, (arg, span), flow_state);
+ }
+ self.mutate_place(loc, (destination, span), Deep, flow_state);
+ }
+ TerminatorKind::Assert { ref cond, expected: _, ref msg, target: _, cleanup: _ } => {
+ self.consume_operand(loc, (cond, span), flow_state);
+ use rustc_middle::mir::AssertKind;
+ if let AssertKind::BoundsCheck { ref len, ref index } = *msg {
+ self.consume_operand(loc, (len, span), flow_state);
+ self.consume_operand(loc, (index, span), flow_state);
+ }
+ }
+
+ TerminatorKind::Yield { ref value, resume: _, resume_arg, drop: _ } => {
+ self.consume_operand(loc, (value, span), flow_state);
+ self.mutate_place(loc, (resume_arg, span), Deep, flow_state);
+ }
+
+ TerminatorKind::InlineAsm {
+ template: _,
+ ref operands,
+ options: _,
+ line_spans: _,
+ destination: _,
+ cleanup: _,
+ } => {
+ for op in operands {
+ match *op {
+ InlineAsmOperand::In { reg: _, ref value } => {
+ self.consume_operand(loc, (value, span), flow_state);
+ }
+ InlineAsmOperand::Out { reg: _, late: _, place, .. } => {
+ if let Some(place) = place {
+ self.mutate_place(loc, (place, span), Shallow(None), flow_state);
+ }
+ }
+ InlineAsmOperand::InOut { reg: _, late: _, ref in_value, out_place } => {
+ self.consume_operand(loc, (in_value, span), flow_state);
+ if let Some(out_place) = out_place {
+ self.mutate_place(
+ loc,
+ (out_place, span),
+ Shallow(None),
+ flow_state,
+ );
+ }
+ }
+ InlineAsmOperand::Const { value: _ }
+ | InlineAsmOperand::SymFn { value: _ }
+ | InlineAsmOperand::SymStatic { def_id: _ } => {}
+ }
+ }
+ }
+
+ TerminatorKind::Goto { target: _ }
+ | TerminatorKind::Abort
+ | TerminatorKind::Unreachable
+ | TerminatorKind::Resume
+ | TerminatorKind::Return
+ | TerminatorKind::GeneratorDrop
+ | TerminatorKind::FalseEdge { real_target: _, imaginary_target: _ }
+ | TerminatorKind::FalseUnwind { real_target: _, unwind: _ } => {
+ // no data used, thus irrelevant to borrowck
+ }
+ }
+ }
+
+ fn visit_terminator_after_primary_effect(
+ &mut self,
+ flow_state: &Flows<'cx, 'tcx>,
+ term: &'cx Terminator<'tcx>,
+ loc: Location,
+ ) {
+ let span = term.source_info.span;
+
+ match term.kind {
+ TerminatorKind::Yield { value: _, resume: _, resume_arg: _, drop: _ } => {
+ if self.movable_generator {
+ // Look for any active borrows to locals
+ let borrow_set = self.borrow_set.clone();
+ for i in flow_state.borrows.iter() {
+ let borrow = &borrow_set[i];
+ self.check_for_local_borrow(borrow, span);
+ }
+ }
+ }
+
+ TerminatorKind::Resume | TerminatorKind::Return | TerminatorKind::GeneratorDrop => {
+ // Returning from the function implicitly kills storage for all locals and statics.
+ // Often, the storage will already have been killed by an explicit
+ // StorageDead, but we don't always emit those (notably on unwind paths),
+ // so this "extra check" serves as a kind of backup.
+ let borrow_set = self.borrow_set.clone();
+ for i in flow_state.borrows.iter() {
+ let borrow = &borrow_set[i];
+ self.check_for_invalidation_at_exit(loc, borrow, span);
+ }
+ }
+
+ TerminatorKind::Abort
+ | TerminatorKind::Assert { .. }
+ | TerminatorKind::Call { .. }
+ | TerminatorKind::Drop { .. }
+ | TerminatorKind::DropAndReplace { .. }
+ | TerminatorKind::FalseEdge { real_target: _, imaginary_target: _ }
+ | TerminatorKind::FalseUnwind { real_target: _, unwind: _ }
+ | TerminatorKind::Goto { .. }
+ | TerminatorKind::SwitchInt { .. }
+ | TerminatorKind::Unreachable
+ | TerminatorKind::InlineAsm { .. } => {}
+ }
+ }
+}
+
+use self::AccessDepth::{Deep, Shallow};
+use self::ReadOrWrite::{Activation, Read, Reservation, Write};
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+enum ArtificialField {
+ ArrayLength,
+ ShallowBorrow,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+enum AccessDepth {
+ /// From the RFC: "A *shallow* access means that the immediate
+ /// fields reached at P are accessed, but references or pointers
+ /// found within are not dereferenced. Right now, the only access
+ /// that is shallow is an assignment like `x = ...;`, which would
+ /// be a *shallow write* of `x`."
+ Shallow(Option<ArtificialField>),
+
+ /// From the RFC: "A *deep* access means that all data reachable
+ /// through the given place may be invalidated or accesses by
+ /// this action."
+ Deep,
+
+ /// Access is Deep only when there is a Drop implementation that
+ /// can reach the data behind the reference.
+ Drop,
+}
+
+/// Kind of access to a value: read or write
+/// (For informational purposes only)
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+enum ReadOrWrite {
+ /// From the RFC: "A *read* means that the existing data may be
+ /// read, but will not be changed."
+ Read(ReadKind),
+
+ /// From the RFC: "A *write* means that the data may be mutated to
+ /// new values or otherwise invalidated (for example, it could be
+ /// de-initialized, as in a move operation).
+ Write(WriteKind),
+
+ /// For two-phase borrows, we distinguish a reservation (which is treated
+ /// like a Read) from an activation (which is treated like a write), and
+ /// each of those is furthermore distinguished from Reads/Writes above.
+ Reservation(WriteKind),
+ Activation(WriteKind, BorrowIndex),
+}
+
+/// Kind of read access to a value
+/// (For informational purposes only)
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+enum ReadKind {
+ Borrow(BorrowKind),
+ Copy,
+}
+
+/// Kind of write access to a value
+/// (For informational purposes only)
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+enum WriteKind {
+ StorageDeadOrDrop,
+ MutableBorrow(BorrowKind),
+ Mutate,
+ Move,
+}
+
+/// When checking permissions for a place access, this flag is used to indicate that an immutable
+/// local place can be mutated.
+//
+// FIXME: @nikomatsakis suggested that this flag could be removed with the following modifications:
+// - Merge `check_access_permissions()` and `check_if_reassignment_to_immutable_state()`.
+// - Split `is_mutable()` into `is_assignable()` (can be directly assigned) and
+// `is_declared_mutable()`.
+// - Take flow state into consideration in `is_assignable()` for local variables.
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+enum LocalMutationIsAllowed {
+ Yes,
+ /// We want use of immutable upvars to cause a "write to immutable upvar"
+ /// error, not an "reassignment" error.
+ ExceptUpvars,
+ No,
+}
+
+#[derive(Copy, Clone, Debug)]
+enum InitializationRequiringAction {
+ Borrow,
+ MatchOn,
+ Use,
+ Assignment,
+ PartialAssignment,
+}
+
+struct RootPlace<'tcx> {
+ place_local: Local,
+ place_projection: &'tcx [PlaceElem<'tcx>],
+ is_local_mutation_allowed: LocalMutationIsAllowed,
+}
+
+impl InitializationRequiringAction {
+ fn as_noun(self) -> &'static str {
+ match self {
+ InitializationRequiringAction::Borrow => "borrow",
+ InitializationRequiringAction::MatchOn => "use", // no good noun
+ InitializationRequiringAction::Use => "use",
+ InitializationRequiringAction::Assignment => "assign",
+ InitializationRequiringAction::PartialAssignment => "assign to part",
+ }
+ }
+
+ fn as_verb_in_past_tense(self) -> &'static str {
+ match self {
+ InitializationRequiringAction::Borrow => "borrowed",
+ InitializationRequiringAction::MatchOn => "matched on",
+ InitializationRequiringAction::Use => "used",
+ InitializationRequiringAction::Assignment => "assigned",
+ InitializationRequiringAction::PartialAssignment => "partially assigned",
+ }
+ }
+
+ fn as_general_verb_in_past_tense(self) -> &'static str {
+ match self {
+ InitializationRequiringAction::Borrow
+ | InitializationRequiringAction::MatchOn
+ | InitializationRequiringAction::Use => "used",
+ InitializationRequiringAction::Assignment => "assigned",
+ InitializationRequiringAction::PartialAssignment => "partially assigned",
+ }
+ }
+}
+
+impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
+ fn body(&self) -> &'cx Body<'tcx> {
+ self.body
+ }
+
+ /// Checks an access to the given place to see if it is allowed. Examines the set of borrows
+ /// that are in scope, as well as which paths have been initialized, to ensure that (a) the
+ /// place is initialized and (b) it is not borrowed in some way that would prevent this
+ /// access.
+ ///
+ /// Returns `true` if an error is reported.
+ fn access_place(
+ &mut self,
+ location: Location,
+ place_span: (Place<'tcx>, Span),
+ kind: (AccessDepth, ReadOrWrite),
+ is_local_mutation_allowed: LocalMutationIsAllowed,
+ flow_state: &Flows<'cx, 'tcx>,
+ ) {
+ let (sd, rw) = kind;
+
+ if let Activation(_, borrow_index) = rw {
+ if self.reservation_error_reported.contains(&place_span.0) {
+ debug!(
+ "skipping access_place for activation of invalid reservation \
+ place: {:?} borrow_index: {:?}",
+ place_span.0, borrow_index
+ );
+ return;
+ }
+ }
+
+ // Check is_empty() first because it's the common case, and doing that
+ // way we avoid the clone() call.
+ if !self.access_place_error_reported.is_empty()
+ && self.access_place_error_reported.contains(&(place_span.0, place_span.1))
+ {
+ debug!(
+ "access_place: suppressing error place_span=`{:?}` kind=`{:?}`",
+ place_span, kind
+ );
+ return;
+ }
+
+ let mutability_error = self.check_access_permissions(
+ place_span,
+ rw,
+ is_local_mutation_allowed,
+ flow_state,
+ location,
+ );
+ let conflict_error =
+ self.check_access_for_conflict(location, place_span, sd, rw, flow_state);
+
+ if conflict_error || mutability_error {
+ debug!("access_place: logging error place_span=`{:?}` kind=`{:?}`", place_span, kind);
+ self.access_place_error_reported.insert((place_span.0, place_span.1));
+ }
+ }
+
+ fn check_access_for_conflict(
+ &mut self,
+ location: Location,
+ place_span: (Place<'tcx>, Span),
+ sd: AccessDepth,
+ rw: ReadOrWrite,
+ flow_state: &Flows<'cx, 'tcx>,
+ ) -> bool {
+ debug!(
+ "check_access_for_conflict(location={:?}, place_span={:?}, sd={:?}, rw={:?})",
+ location, place_span, sd, rw,
+ );
+
+ let mut error_reported = false;
+ let tcx = self.infcx.tcx;
+ let body = self.body;
+ let borrow_set = self.borrow_set.clone();
+
+ // Use polonius output if it has been enabled.
+ let polonius_output = self.polonius_output.clone();
+ let borrows_in_scope = if let Some(polonius) = &polonius_output {
+ let location = self.location_table.start_index(location);
+ Either::Left(polonius.errors_at(location).iter().copied())
+ } else {
+ Either::Right(flow_state.borrows.iter())
+ };
+
+ each_borrow_involving_path(
+ self,
+ tcx,
+ body,
+ location,
+ (sd, place_span.0),
+ &borrow_set,
+ borrows_in_scope,
+ |this, borrow_index, borrow| match (rw, borrow.kind) {
+ // Obviously an activation is compatible with its own
+ // reservation (or even prior activating uses of same
+ // borrow); so don't check if they interfere.
+ //
+ // NOTE: *reservations* do conflict with themselves;
+ // thus aren't injecting unsoundness w/ this check.)
+ (Activation(_, activating), _) if activating == borrow_index => {
+ debug!(
+ "check_access_for_conflict place_span: {:?} sd: {:?} rw: {:?} \
+ skipping {:?} b/c activation of same borrow_index",
+ place_span,
+ sd,
+ rw,
+ (borrow_index, borrow),
+ );
+ Control::Continue
+ }
+
+ (Read(_), BorrowKind::Shared | BorrowKind::Shallow)
+ | (
+ Read(ReadKind::Borrow(BorrowKind::Shallow)),
+ BorrowKind::Unique | BorrowKind::Mut { .. },
+ ) => Control::Continue,
+
+ (Reservation(_), BorrowKind::Shallow | BorrowKind::Shared) => {
+ // This used to be a future compatibility warning (to be
+ // disallowed on NLL). See rust-lang/rust#56254
+ Control::Continue
+ }
+
+ (Write(WriteKind::Move), BorrowKind::Shallow) => {
+ // Handled by initialization checks.
+ Control::Continue
+ }
+
+ (Read(kind), BorrowKind::Unique | BorrowKind::Mut { .. }) => {
+ // Reading from mere reservations of mutable-borrows is OK.
+ if !is_active(&this.dominators, borrow, location) {
+ assert!(allow_two_phase_borrow(borrow.kind));
+ return Control::Continue;
+ }
+
+ error_reported = true;
+ match kind {
+ ReadKind::Copy => {
+ let err = this
+ .report_use_while_mutably_borrowed(location, place_span, borrow);
+ this.buffer_error(err);
+ }
+ ReadKind::Borrow(bk) => {
+ let err =
+ this.report_conflicting_borrow(location, place_span, bk, borrow);
+ this.buffer_error(err);
+ }
+ }
+ Control::Break
+ }
+
+ (Reservation(kind) | Activation(kind, _) | Write(kind), _) => {
+ match rw {
+ Reservation(..) => {
+ debug!(
+ "recording invalid reservation of \
+ place: {:?}",
+ place_span.0
+ );
+ this.reservation_error_reported.insert(place_span.0);
+ }
+ Activation(_, activating) => {
+ debug!(
+ "observing check_place for activation of \
+ borrow_index: {:?}",
+ activating
+ );
+ }
+ Read(..) | Write(..) => {}
+ }
+
+ error_reported = true;
+ match kind {
+ WriteKind::MutableBorrow(bk) => {
+ let err =
+ this.report_conflicting_borrow(location, place_span, bk, borrow);
+ this.buffer_error(err);
+ }
+ WriteKind::StorageDeadOrDrop => this
+ .report_borrowed_value_does_not_live_long_enough(
+ location,
+ borrow,
+ place_span,
+ Some(kind),
+ ),
+ WriteKind::Mutate => {
+ this.report_illegal_mutation_of_borrowed(location, place_span, borrow)
+ }
+ WriteKind::Move => {
+ this.report_move_out_while_borrowed(location, place_span, borrow)
+ }
+ }
+ Control::Break
+ }
+ },
+ );
+
+ error_reported
+ }
+
+ fn mutate_place(
+ &mut self,
+ location: Location,
+ place_span: (Place<'tcx>, Span),
+ kind: AccessDepth,
+ flow_state: &Flows<'cx, 'tcx>,
+ ) {
+ // Write of P[i] or *P requires P init'd.
+ self.check_if_assigned_path_is_moved(location, place_span, flow_state);
+
+ // Special case: you can assign an immutable local variable
+ // (e.g., `x = ...`) so long as it has never been initialized
+ // before (at this point in the flow).
+ if let Some(local) = place_span.0.as_local() {
+ if let Mutability::Not = self.body.local_decls[local].mutability {
+ // check for reassignments to immutable local variables
+ self.check_if_reassignment_to_immutable_state(
+ location, local, place_span, flow_state,
+ );
+ return;
+ }
+ }
+
+ // Otherwise, use the normal access permission rules.
+ self.access_place(
+ location,
+ place_span,
+ (kind, Write(WriteKind::Mutate)),
+ LocalMutationIsAllowed::No,
+ flow_state,
+ );
+ }
+
+ fn consume_rvalue(
+ &mut self,
+ location: Location,
+ (rvalue, span): (&'cx Rvalue<'tcx>, Span),
+ flow_state: &Flows<'cx, 'tcx>,
+ ) {
+ match *rvalue {
+ Rvalue::Ref(_ /*rgn*/, bk, place) => {
+ let access_kind = match bk {
+ BorrowKind::Shallow => {
+ (Shallow(Some(ArtificialField::ShallowBorrow)), Read(ReadKind::Borrow(bk)))
+ }
+ BorrowKind::Shared => (Deep, Read(ReadKind::Borrow(bk))),
+ BorrowKind::Unique | BorrowKind::Mut { .. } => {
+ let wk = WriteKind::MutableBorrow(bk);
+ if allow_two_phase_borrow(bk) {
+ (Deep, Reservation(wk))
+ } else {
+ (Deep, Write(wk))
+ }
+ }
+ };
+
+ self.access_place(
+ location,
+ (place, span),
+ access_kind,
+ LocalMutationIsAllowed::No,
+ flow_state,
+ );
+
+ let action = if bk == BorrowKind::Shallow {
+ InitializationRequiringAction::MatchOn
+ } else {
+ InitializationRequiringAction::Borrow
+ };
+
+ self.check_if_path_or_subpath_is_moved(
+ location,
+ action,
+ (place.as_ref(), span),
+ flow_state,
+ );
+ }
+
+ Rvalue::AddressOf(mutability, place) => {
+ let access_kind = match mutability {
+ Mutability::Mut => (
+ Deep,
+ Write(WriteKind::MutableBorrow(BorrowKind::Mut {
+ allow_two_phase_borrow: false,
+ })),
+ ),
+ Mutability::Not => (Deep, Read(ReadKind::Borrow(BorrowKind::Shared))),
+ };
+
+ self.access_place(
+ location,
+ (place, span),
+ access_kind,
+ LocalMutationIsAllowed::No,
+ flow_state,
+ );
+
+ self.check_if_path_or_subpath_is_moved(
+ location,
+ InitializationRequiringAction::Borrow,
+ (place.as_ref(), span),
+ flow_state,
+ );
+ }
+
+ Rvalue::ThreadLocalRef(_) => {}
+
+ Rvalue::Use(ref operand)
+ | Rvalue::Repeat(ref operand, _)
+ | Rvalue::UnaryOp(_ /*un_op*/, ref operand)
+ | Rvalue::Cast(_ /*cast_kind*/, ref operand, _ /*ty*/)
+ | Rvalue::ShallowInitBox(ref operand, _ /*ty*/) => {
+ self.consume_operand(location, (operand, span), flow_state)
+ }
+ Rvalue::CopyForDeref(place) => {
+ self.access_place(
+ location,
+ (place, span),
+ (Deep, Read(ReadKind::Copy)),
+ LocalMutationIsAllowed::No,
+ flow_state,
+ );
+
+ // Finally, check if path was already moved.
+ self.check_if_path_or_subpath_is_moved(
+ location,
+ InitializationRequiringAction::Use,
+ (place.as_ref(), span),
+ flow_state,
+ );
+ }
+
+ Rvalue::Len(place) | Rvalue::Discriminant(place) => {
+ let af = match *rvalue {
+ Rvalue::Len(..) => Some(ArtificialField::ArrayLength),
+ Rvalue::Discriminant(..) => None,
+ _ => unreachable!(),
+ };
+ self.access_place(
+ location,
+ (place, span),
+ (Shallow(af), Read(ReadKind::Copy)),
+ LocalMutationIsAllowed::No,
+ flow_state,
+ );
+ self.check_if_path_or_subpath_is_moved(
+ location,
+ InitializationRequiringAction::Use,
+ (place.as_ref(), span),
+ flow_state,
+ );
+ }
+
+ Rvalue::BinaryOp(_bin_op, box (ref operand1, ref operand2))
+ | Rvalue::CheckedBinaryOp(_bin_op, box (ref operand1, ref operand2)) => {
+ self.consume_operand(location, (operand1, span), flow_state);
+ self.consume_operand(location, (operand2, span), flow_state);
+ }
+
+ Rvalue::NullaryOp(_op, _ty) => {
+ // nullary ops take no dynamic input; no borrowck effect.
+ }
+
+ Rvalue::Aggregate(ref aggregate_kind, ref operands) => {
+ // We need to report back the list of mutable upvars that were
+ // moved into the closure and subsequently used by the closure,
+ // in order to populate our used_mut set.
+ match **aggregate_kind {
+ AggregateKind::Closure(def_id, _) | AggregateKind::Generator(def_id, _, _) => {
+ let BorrowCheckResult { used_mut_upvars, .. } =
+ self.infcx.tcx.mir_borrowck(def_id);
+ debug!("{:?} used_mut_upvars={:?}", def_id, used_mut_upvars);
+ for field in used_mut_upvars {
+ self.propagate_closure_used_mut_upvar(&operands[field.index()]);
+ }
+ }
+ AggregateKind::Adt(..)
+ | AggregateKind::Array(..)
+ | AggregateKind::Tuple { .. } => (),
+ }
+
+ for operand in operands {
+ self.consume_operand(location, (operand, span), flow_state);
+ }
+ }
+ }
+ }
+
+ fn propagate_closure_used_mut_upvar(&mut self, operand: &Operand<'tcx>) {
+ let propagate_closure_used_mut_place = |this: &mut Self, place: Place<'tcx>| {
+ // We have three possibilities here:
+ // a. We are modifying something through a mut-ref
+ // b. We are modifying something that is local to our parent
+ // c. Current body is a nested closure, and we are modifying path starting from
+ // a Place captured by our parent closure.
+
+ // Handle (c), the path being modified is exactly the path captured by our parent
+ if let Some(field) = this.is_upvar_field_projection(place.as_ref()) {
+ this.used_mut_upvars.push(field);
+ return;
+ }
+
+ for (place_ref, proj) in place.iter_projections().rev() {
+ // Handle (a)
+ if proj == ProjectionElem::Deref {
+ match place_ref.ty(this.body(), this.infcx.tcx).ty.kind() {
+ // We aren't modifying a variable directly
+ ty::Ref(_, _, hir::Mutability::Mut) => return,
+
+ _ => {}
+ }
+ }
+
+ // Handle (c)
+ if let Some(field) = this.is_upvar_field_projection(place_ref) {
+ this.used_mut_upvars.push(field);
+ return;
+ }
+ }
+
+ // Handle(b)
+ this.used_mut.insert(place.local);
+ };
+
+ // This relies on the current way that by-value
+ // captures of a closure are copied/moved directly
+ // when generating MIR.
+ match *operand {
+ Operand::Move(place) | Operand::Copy(place) => {
+ match place.as_local() {
+ Some(local) if !self.body.local_decls[local].is_user_variable() => {
+ if self.body.local_decls[local].ty.is_mutable_ptr() {
+ // The variable will be marked as mutable by the borrow.
+ return;
+ }
+ // This is an edge case where we have a `move` closure
+ // inside a non-move closure, and the inner closure
+ // contains a mutation:
+ //
+ // let mut i = 0;
+ // || { move || { i += 1; }; };
+ //
+ // In this case our usual strategy of assuming that the
+ // variable will be captured by mutable reference is
+ // wrong, since `i` can be copied into the inner
+ // closure from a shared reference.
+ //
+ // As such we have to search for the local that this
+ // capture comes from and mark it as being used as mut.
+
+ let temp_mpi = self.move_data.rev_lookup.find_local(local);
+ let init = if let [init_index] = *self.move_data.init_path_map[temp_mpi] {
+ &self.move_data.inits[init_index]
+ } else {
+ bug!("temporary should be initialized exactly once")
+ };
+
+ let InitLocation::Statement(loc) = init.location else {
+ bug!("temporary initialized in arguments")
+ };
+
+ let body = self.body;
+ let bbd = &body[loc.block];
+ let stmt = &bbd.statements[loc.statement_index];
+ debug!("temporary assigned in: stmt={:?}", stmt);
+
+ if let StatementKind::Assign(box (_, Rvalue::Ref(_, _, source))) = stmt.kind
+ {
+ propagate_closure_used_mut_place(self, source);
+ } else {
+ bug!(
+ "closures should only capture user variables \
+ or references to user variables"
+ );
+ }
+ }
+ _ => propagate_closure_used_mut_place(self, place),
+ }
+ }
+ Operand::Constant(..) => {}
+ }
+ }
+
+ fn consume_operand(
+ &mut self,
+ location: Location,
+ (operand, span): (&'cx Operand<'tcx>, Span),
+ flow_state: &Flows<'cx, 'tcx>,
+ ) {
+ match *operand {
+ Operand::Copy(place) => {
+ // copy of place: check if this is "copy of frozen path"
+ // (FIXME: see check_loans.rs)
+ self.access_place(
+ location,
+ (place, span),
+ (Deep, Read(ReadKind::Copy)),
+ LocalMutationIsAllowed::No,
+ flow_state,
+ );
+
+ // Finally, check if path was already moved.
+ self.check_if_path_or_subpath_is_moved(
+ location,
+ InitializationRequiringAction::Use,
+ (place.as_ref(), span),
+ flow_state,
+ );
+ }
+ Operand::Move(place) => {
+ // move of place: check if this is move of already borrowed path
+ self.access_place(
+ location,
+ (place, span),
+ (Deep, Write(WriteKind::Move)),
+ LocalMutationIsAllowed::Yes,
+ flow_state,
+ );
+
+ // Finally, check if path was already moved.
+ self.check_if_path_or_subpath_is_moved(
+ location,
+ InitializationRequiringAction::Use,
+ (place.as_ref(), span),
+ flow_state,
+ );
+ }
+ Operand::Constant(_) => {}
+ }
+ }
+
+ /// Checks whether a borrow of this place is invalidated when the function
+ /// exits
+ fn check_for_invalidation_at_exit(
+ &mut self,
+ location: Location,
+ borrow: &BorrowData<'tcx>,
+ span: Span,
+ ) {
+ debug!("check_for_invalidation_at_exit({:?})", borrow);
+ let place = borrow.borrowed_place;
+ let mut root_place = PlaceRef { local: place.local, projection: &[] };
+
+ // FIXME(nll-rfc#40): do more precise destructor tracking here. For now
+ // we just know that all locals are dropped at function exit (otherwise
+ // we'll have a memory leak) and assume that all statics have a destructor.
+ //
+ // FIXME: allow thread-locals to borrow other thread locals?
+
+ let (might_be_alive, will_be_dropped) =
+ if self.body.local_decls[root_place.local].is_ref_to_thread_local() {
+ // Thread-locals might be dropped after the function exits
+ // We have to dereference the outer reference because
+ // borrows don't conflict behind shared references.
+ root_place.projection = TyCtxtConsts::DEREF_PROJECTION;
+ (true, true)
+ } else {
+ (false, self.locals_are_invalidated_at_exit)
+ };
+
+ if !will_be_dropped {
+ debug!("place_is_invalidated_at_exit({:?}) - won't be dropped", place);
+ return;
+ }
+
+ let sd = if might_be_alive { Deep } else { Shallow(None) };
+
+ if places_conflict::borrow_conflicts_with_place(
+ self.infcx.tcx,
+ &self.body,
+ place,
+ borrow.kind,
+ root_place,
+ sd,
+ places_conflict::PlaceConflictBias::Overlap,
+ ) {
+ debug!("check_for_invalidation_at_exit({:?}): INVALID", place);
+ // FIXME: should be talking about the region lifetime instead
+ // of just a span here.
+ let span = self.infcx.tcx.sess.source_map().end_point(span);
+ self.report_borrowed_value_does_not_live_long_enough(
+ location,
+ borrow,
+ (place, span),
+ None,
+ )
+ }
+ }
+
+ /// Reports an error if this is a borrow of local data.
+ /// This is called for all Yield expressions on movable generators
+ fn check_for_local_borrow(&mut self, borrow: &BorrowData<'tcx>, yield_span: Span) {
+ debug!("check_for_local_borrow({:?})", borrow);
+
+ if borrow_of_local_data(borrow.borrowed_place) {
+ let err = self.cannot_borrow_across_generator_yield(
+ self.retrieve_borrow_spans(borrow).var_or_use(),
+ yield_span,
+ );
+
+ self.buffer_error(err);
+ }
+ }
+
+ fn check_activations(&mut self, location: Location, span: Span, flow_state: &Flows<'cx, 'tcx>) {
+ // Two-phase borrow support: For each activation that is newly
+ // generated at this statement, check if it interferes with
+ // another borrow.
+ let borrow_set = self.borrow_set.clone();
+ for &borrow_index in borrow_set.activations_at_location(location) {
+ let borrow = &borrow_set[borrow_index];
+
+ // only mutable borrows should be 2-phase
+ assert!(match borrow.kind {
+ BorrowKind::Shared | BorrowKind::Shallow => false,
+ BorrowKind::Unique | BorrowKind::Mut { .. } => true,
+ });
+
+ self.access_place(
+ location,
+ (borrow.borrowed_place, span),
+ (Deep, Activation(WriteKind::MutableBorrow(borrow.kind), borrow_index)),
+ LocalMutationIsAllowed::No,
+ flow_state,
+ );
+ // We do not need to call `check_if_path_or_subpath_is_moved`
+ // again, as we already called it when we made the
+ // initial reservation.
+ }
+ }
+
+ fn check_if_reassignment_to_immutable_state(
+ &mut self,
+ location: Location,
+ local: Local,
+ place_span: (Place<'tcx>, Span),
+ flow_state: &Flows<'cx, 'tcx>,
+ ) {
+ debug!("check_if_reassignment_to_immutable_state({:?})", local);
+
+ // Check if any of the initializations of `local` have happened yet:
+ if let Some(init_index) = self.is_local_ever_initialized(local, flow_state) {
+ // And, if so, report an error.
+ let init = &self.move_data.inits[init_index];
+ let span = init.span(&self.body);
+ self.report_illegal_reassignment(location, place_span, span, place_span.0);
+ }
+ }
+
+ fn check_if_full_path_is_moved(
+ &mut self,
+ location: Location,
+ desired_action: InitializationRequiringAction,
+ place_span: (PlaceRef<'tcx>, Span),
+ flow_state: &Flows<'cx, 'tcx>,
+ ) {
+ let maybe_uninits = &flow_state.uninits;
+
+ // Bad scenarios:
+ //
+ // 1. Move of `a.b.c`, use of `a.b.c`
+ // 2. Move of `a.b.c`, use of `a.b.c.d` (without first reinitializing `a.b.c.d`)
+ // 3. Uninitialized `(a.b.c: &_)`, use of `*a.b.c`; note that with
+ // partial initialization support, one might have `a.x`
+ // initialized but not `a.b`.
+ //
+ // OK scenarios:
+ //
+ // 4. Move of `a.b.c`, use of `a.b.d`
+ // 5. Uninitialized `a.x`, initialized `a.b`, use of `a.b`
+ // 6. Copied `(a.b: &_)`, use of `*(a.b).c`; note that `a.b`
+ // must have been initialized for the use to be sound.
+ // 7. Move of `a.b.c` then reinit of `a.b.c.d`, use of `a.b.c.d`
+
+ // The dataflow tracks shallow prefixes distinctly (that is,
+ // field-accesses on P distinctly from P itself), in order to
+ // track substructure initialization separately from the whole
+ // structure.
+ //
+ // E.g., when looking at (*a.b.c).d, if the closest prefix for
+ // which we have a MovePath is `a.b`, then that means that the
+ // initialization state of `a.b` is all we need to inspect to
+ // know if `a.b.c` is valid (and from that we infer that the
+ // dereference and `.d` access is also valid, since we assume
+ // `a.b.c` is assigned a reference to an initialized and
+ // well-formed record structure.)
+
+ // Therefore, if we seek out the *closest* prefix for which we
+ // have a MovePath, that should capture the initialization
+ // state for the place scenario.
+ //
+ // This code covers scenarios 1, 2, and 3.
+
+ debug!("check_if_full_path_is_moved place: {:?}", place_span.0);
+ let (prefix, mpi) = self.move_path_closest_to(place_span.0);
+ if maybe_uninits.contains(mpi) {
+ self.report_use_of_moved_or_uninitialized(
+ location,
+ desired_action,
+ (prefix, place_span.0, place_span.1),
+ mpi,
+ );
+ } // Only query longest prefix with a MovePath, not further
+ // ancestors; dataflow recurs on children when parents
+ // move (to support partial (re)inits).
+ //
+ // (I.e., querying parents breaks scenario 7; but may want
+ // to do such a query based on partial-init feature-gate.)
+ }
+
+ /// Subslices correspond to multiple move paths, so we iterate through the
+ /// elements of the base array. For each element we check
+ ///
+ /// * Does this element overlap with our slice.
+ /// * Is any part of it uninitialized.
+ fn check_if_subslice_element_is_moved(
+ &mut self,
+ location: Location,
+ desired_action: InitializationRequiringAction,
+ place_span: (PlaceRef<'tcx>, Span),
+ maybe_uninits: &ChunkedBitSet<MovePathIndex>,
+ from: u64,
+ to: u64,
+ ) {
+ if let Some(mpi) = self.move_path_for_place(place_span.0) {
+ let move_paths = &self.move_data.move_paths;
+
+ let root_path = &move_paths[mpi];
+ for (child_mpi, child_move_path) in root_path.children(move_paths) {
+ let last_proj = child_move_path.place.projection.last().unwrap();
+ if let ProjectionElem::ConstantIndex { offset, from_end, .. } = last_proj {
+ debug_assert!(!from_end, "Array constant indexing shouldn't be `from_end`.");
+
+ if (from..to).contains(offset) {
+ let uninit_child =
+ self.move_data.find_in_move_path_or_its_descendants(child_mpi, |mpi| {
+ maybe_uninits.contains(mpi)
+ });
+
+ if let Some(uninit_child) = uninit_child {
+ self.report_use_of_moved_or_uninitialized(
+ location,
+ desired_action,
+ (place_span.0, place_span.0, place_span.1),
+ uninit_child,
+ );
+ return; // don't bother finding other problems.
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fn check_if_path_or_subpath_is_moved(
+ &mut self,
+ location: Location,
+ desired_action: InitializationRequiringAction,
+ place_span: (PlaceRef<'tcx>, Span),
+ flow_state: &Flows<'cx, 'tcx>,
+ ) {
+ let maybe_uninits = &flow_state.uninits;
+
+ // Bad scenarios:
+ //
+ // 1. Move of `a.b.c`, use of `a` or `a.b`
+ // partial initialization support, one might have `a.x`
+ // initialized but not `a.b`.
+ // 2. All bad scenarios from `check_if_full_path_is_moved`
+ //
+ // OK scenarios:
+ //
+ // 3. Move of `a.b.c`, use of `a.b.d`
+ // 4. Uninitialized `a.x`, initialized `a.b`, use of `a.b`
+ // 5. Copied `(a.b: &_)`, use of `*(a.b).c`; note that `a.b`
+ // must have been initialized for the use to be sound.
+ // 6. Move of `a.b.c` then reinit of `a.b.c.d`, use of `a.b.c.d`
+
+ self.check_if_full_path_is_moved(location, desired_action, place_span, flow_state);
+
+ if let Some((place_base, ProjectionElem::Subslice { from, to, from_end: false })) =
+ place_span.0.last_projection()
+ {
+ let place_ty = place_base.ty(self.body(), self.infcx.tcx);
+ if let ty::Array(..) = place_ty.ty.kind() {
+ self.check_if_subslice_element_is_moved(
+ location,
+ desired_action,
+ (place_base, place_span.1),
+ maybe_uninits,
+ from,
+ to,
+ );
+ return;
+ }
+ }
+
+ // A move of any shallow suffix of `place` also interferes
+ // with an attempt to use `place`. This is scenario 3 above.
+ //
+ // (Distinct from handling of scenarios 1+2+4 above because
+ // `place` does not interfere with suffixes of its prefixes,
+ // e.g., `a.b.c` does not interfere with `a.b.d`)
+ //
+ // This code covers scenario 1.
+
+ debug!("check_if_path_or_subpath_is_moved place: {:?}", place_span.0);
+ if let Some(mpi) = self.move_path_for_place(place_span.0) {
+ let uninit_mpi = self
+ .move_data
+ .find_in_move_path_or_its_descendants(mpi, |mpi| maybe_uninits.contains(mpi));
+
+ if let Some(uninit_mpi) = uninit_mpi {
+ self.report_use_of_moved_or_uninitialized(
+ location,
+ desired_action,
+ (place_span.0, place_span.0, place_span.1),
+ uninit_mpi,
+ );
+ return; // don't bother finding other problems.
+ }
+ }
+ }
+
+ /// Currently MoveData does not store entries for all places in
+ /// the input MIR. For example it will currently filter out
+ /// places that are Copy; thus we do not track places of shared
+ /// reference type. This routine will walk up a place along its
+ /// prefixes, searching for a foundational place that *is*
+ /// tracked in the MoveData.
+ ///
+ /// An Err result includes a tag indicated why the search failed.
+ /// Currently this can only occur if the place is built off of a
+ /// static variable, as we do not track those in the MoveData.
+ fn move_path_closest_to(&mut self, place: PlaceRef<'tcx>) -> (PlaceRef<'tcx>, MovePathIndex) {
+ match self.move_data.rev_lookup.find(place) {
+ LookupResult::Parent(Some(mpi)) | LookupResult::Exact(mpi) => {
+ (self.move_data.move_paths[mpi].place.as_ref(), mpi)
+ }
+ LookupResult::Parent(None) => panic!("should have move path for every Local"),
+ }
+ }
+
+ fn move_path_for_place(&mut self, place: PlaceRef<'tcx>) -> Option<MovePathIndex> {
+ // If returns None, then there is no move path corresponding
+ // to a direct owner of `place` (which means there is nothing
+ // that borrowck tracks for its analysis).
+
+ match self.move_data.rev_lookup.find(place) {
+ LookupResult::Parent(_) => None,
+ LookupResult::Exact(mpi) => Some(mpi),
+ }
+ }
+
+ fn check_if_assigned_path_is_moved(
+ &mut self,
+ location: Location,
+ (place, span): (Place<'tcx>, Span),
+ flow_state: &Flows<'cx, 'tcx>,
+ ) {
+ debug!("check_if_assigned_path_is_moved place: {:?}", place);
+
+ // None case => assigning to `x` does not require `x` be initialized.
+ for (place_base, elem) in place.iter_projections().rev() {
+ match elem {
+ ProjectionElem::Index(_/*operand*/) |
+ ProjectionElem::ConstantIndex { .. } |
+ // assigning to P[i] requires P to be valid.
+ ProjectionElem::Downcast(_/*adt_def*/, _/*variant_idx*/) =>
+ // assigning to (P->variant) is okay if assigning to `P` is okay
+ //
+ // FIXME: is this true even if P is an adt with a dtor?
+ { }
+
+ // assigning to (*P) requires P to be initialized
+ ProjectionElem::Deref => {
+ self.check_if_full_path_is_moved(
+ location, InitializationRequiringAction::Use,
+ (place_base, span), flow_state);
+ // (base initialized; no need to
+ // recur further)
+ break;
+ }
+
+ ProjectionElem::Subslice { .. } => {
+ panic!("we don't allow assignments to subslices, location: {:?}",
+ location);
+ }
+
+ ProjectionElem::Field(..) => {
+ // if type of `P` has a dtor, then
+ // assigning to `P.f` requires `P` itself
+ // be already initialized
+ let tcx = self.infcx.tcx;
+ let base_ty = place_base.ty(self.body(), tcx).ty;
+ match base_ty.kind() {
+ ty::Adt(def, _) if def.has_dtor(tcx) => {
+ self.check_if_path_or_subpath_is_moved(
+ location, InitializationRequiringAction::Assignment,
+ (place_base, span), flow_state);
+
+ // (base initialized; no need to
+ // recur further)
+ break;
+ }
+
+ // Once `let s; s.x = V; read(s.x);`,
+ // is allowed, remove this match arm.
+ ty::Adt(..) | ty::Tuple(..) => {
+ check_parent_of_field(self, location, place_base, span, flow_state);
+
+ // rust-lang/rust#21232, #54499, #54986: during period where we reject
+ // partial initialization, do not complain about unnecessary `mut` on
+ // an attempt to do a partial initialization.
+ self.used_mut.insert(place.local);
+ }
+
+ _ => {}
+ }
+ }
+ }
+ }
+
+ fn check_parent_of_field<'cx, 'tcx>(
+ this: &mut MirBorrowckCtxt<'cx, 'tcx>,
+ location: Location,
+ base: PlaceRef<'tcx>,
+ span: Span,
+ flow_state: &Flows<'cx, 'tcx>,
+ ) {
+ // rust-lang/rust#21232: Until Rust allows reads from the
+ // initialized parts of partially initialized structs, we
+ // will, starting with the 2018 edition, reject attempts
+ // to write to structs that are not fully initialized.
+ //
+ // In other words, *until* we allow this:
+ //
+ // 1. `let mut s; s.x = Val; read(s.x);`
+ //
+ // we will for now disallow this:
+ //
+ // 2. `let mut s; s.x = Val;`
+ //
+ // and also this:
+ //
+ // 3. `let mut s = ...; drop(s); s.x=Val;`
+ //
+ // This does not use check_if_path_or_subpath_is_moved,
+ // because we want to *allow* reinitializations of fields:
+ // e.g., want to allow
+ //
+ // `let mut s = ...; drop(s.x); s.x=Val;`
+ //
+ // This does not use check_if_full_path_is_moved on
+ // `base`, because that would report an error about the
+ // `base` as a whole, but in this scenario we *really*
+ // want to report an error about the actual thing that was
+ // moved, which may be some prefix of `base`.
+
+ // Shallow so that we'll stop at any dereference; we'll
+ // report errors about issues with such bases elsewhere.
+ let maybe_uninits = &flow_state.uninits;
+
+ // Find the shortest uninitialized prefix you can reach
+ // without going over a Deref.
+ let mut shortest_uninit_seen = None;
+ for prefix in this.prefixes(base, PrefixSet::Shallow) {
+ let Some(mpi) = this.move_path_for_place(prefix) else { continue };
+
+ if maybe_uninits.contains(mpi) {
+ debug!(
+ "check_parent_of_field updating shortest_uninit_seen from {:?} to {:?}",
+ shortest_uninit_seen,
+ Some((prefix, mpi))
+ );
+ shortest_uninit_seen = Some((prefix, mpi));
+ } else {
+ debug!("check_parent_of_field {:?} is definitely initialized", (prefix, mpi));
+ }
+ }
+
+ if let Some((prefix, mpi)) = shortest_uninit_seen {
+ // Check for a reassignment into an uninitialized field of a union (for example,
+ // after a move out). In this case, do not report an error here. There is an
+ // exception, if this is the first assignment into the union (that is, there is
+ // no move out from an earlier location) then this is an attempt at initialization
+ // of the union - we should error in that case.
+ let tcx = this.infcx.tcx;
+ if base.ty(this.body(), tcx).ty.is_union() {
+ if this.move_data.path_map[mpi].iter().any(|moi| {
+ this.move_data.moves[*moi].source.is_predecessor_of(location, this.body)
+ }) {
+ return;
+ }
+ }
+
+ this.report_use_of_moved_or_uninitialized(
+ location,
+ InitializationRequiringAction::PartialAssignment,
+ (prefix, base, span),
+ mpi,
+ );
+ }
+ }
+ }
+
+ /// Checks the permissions for the given place and read or write kind
+ ///
+ /// Returns `true` if an error is reported.
+ fn check_access_permissions(
+ &mut self,
+ (place, span): (Place<'tcx>, Span),
+ kind: ReadOrWrite,
+ is_local_mutation_allowed: LocalMutationIsAllowed,
+ flow_state: &Flows<'cx, 'tcx>,
+ location: Location,
+ ) -> bool {
+ debug!(
+ "check_access_permissions({:?}, {:?}, is_local_mutation_allowed: {:?})",
+ place, kind, is_local_mutation_allowed
+ );
+
+ let error_access;
+ let the_place_err;
+
+ match kind {
+ Reservation(WriteKind::MutableBorrow(
+ borrow_kind @ (BorrowKind::Unique | BorrowKind::Mut { .. }),
+ ))
+ | Write(WriteKind::MutableBorrow(
+ borrow_kind @ (BorrowKind::Unique | BorrowKind::Mut { .. }),
+ )) => {
+ let is_local_mutation_allowed = match borrow_kind {
+ BorrowKind::Unique => LocalMutationIsAllowed::Yes,
+ BorrowKind::Mut { .. } => is_local_mutation_allowed,
+ BorrowKind::Shared | BorrowKind::Shallow => unreachable!(),
+ };
+ match self.is_mutable(place.as_ref(), is_local_mutation_allowed) {
+ Ok(root_place) => {
+ self.add_used_mut(root_place, flow_state);
+ return false;
+ }
+ Err(place_err) => {
+ error_access = AccessKind::MutableBorrow;
+ the_place_err = place_err;
+ }
+ }
+ }
+ Reservation(WriteKind::Mutate) | Write(WriteKind::Mutate) => {
+ match self.is_mutable(place.as_ref(), is_local_mutation_allowed) {
+ Ok(root_place) => {
+ self.add_used_mut(root_place, flow_state);
+ return false;
+ }
+ Err(place_err) => {
+ error_access = AccessKind::Mutate;
+ the_place_err = place_err;
+ }
+ }
+ }
+
+ Reservation(
+ WriteKind::Move
+ | WriteKind::StorageDeadOrDrop
+ | WriteKind::MutableBorrow(BorrowKind::Shared)
+ | WriteKind::MutableBorrow(BorrowKind::Shallow),
+ )
+ | Write(
+ WriteKind::Move
+ | WriteKind::StorageDeadOrDrop
+ | WriteKind::MutableBorrow(BorrowKind::Shared)
+ | WriteKind::MutableBorrow(BorrowKind::Shallow),
+ ) => {
+ if self.is_mutable(place.as_ref(), is_local_mutation_allowed).is_err()
+ && !self.has_buffered_errors()
+ {
+ // rust-lang/rust#46908: In pure NLL mode this code path should be
+ // unreachable, but we use `delay_span_bug` because we can hit this when
+ // dereferencing a non-Copy raw pointer *and* have `-Ztreat-err-as-bug`
+ // enabled. We don't want to ICE for that case, as other errors will have
+ // been emitted (#52262).
+ self.infcx.tcx.sess.delay_span_bug(
+ span,
+ &format!(
+ "Accessing `{:?}` with the kind `{:?}` shouldn't be possible",
+ place, kind,
+ ),
+ );
+ }
+ return false;
+ }
+ Activation(..) => {
+ // permission checks are done at Reservation point.
+ return false;
+ }
+ Read(
+ ReadKind::Borrow(
+ BorrowKind::Unique
+ | BorrowKind::Mut { .. }
+ | BorrowKind::Shared
+ | BorrowKind::Shallow,
+ )
+ | ReadKind::Copy,
+ ) => {
+ // Access authorized
+ return false;
+ }
+ }
+
+ // rust-lang/rust#21232, #54986: during period where we reject
+ // partial initialization, do not complain about mutability
+ // errors except for actual mutation (as opposed to an attempt
+ // to do a partial initialization).
+ let previously_initialized =
+ self.is_local_ever_initialized(place.local, flow_state).is_some();
+
+ // at this point, we have set up the error reporting state.
+ if previously_initialized {
+ self.report_mutability_error(place, span, the_place_err, error_access, location);
+ true
+ } else {
+ false
+ }
+ }
+
+ fn is_local_ever_initialized(
+ &self,
+ local: Local,
+ flow_state: &Flows<'cx, 'tcx>,
+ ) -> Option<InitIndex> {
+ let mpi = self.move_data.rev_lookup.find_local(local);
+ let ii = &self.move_data.init_path_map[mpi];
+ for &index in ii {
+ if flow_state.ever_inits.contains(index) {
+ return Some(index);
+ }
+ }
+ None
+ }
+
+ /// Adds the place into the used mutable variables set
+ fn add_used_mut(&mut self, root_place: RootPlace<'tcx>, flow_state: &Flows<'cx, 'tcx>) {
+ match root_place {
+ RootPlace { place_local: local, place_projection: [], is_local_mutation_allowed } => {
+ // If the local may have been initialized, and it is now currently being
+ // mutated, then it is justified to be annotated with the `mut`
+ // keyword, since the mutation may be a possible reassignment.
+ if is_local_mutation_allowed != LocalMutationIsAllowed::Yes
+ && self.is_local_ever_initialized(local, flow_state).is_some()
+ {
+ self.used_mut.insert(local);
+ }
+ }
+ RootPlace {
+ place_local: _,
+ place_projection: _,
+ is_local_mutation_allowed: LocalMutationIsAllowed::Yes,
+ } => {}
+ RootPlace {
+ place_local,
+ place_projection: place_projection @ [.., _],
+ is_local_mutation_allowed: _,
+ } => {
+ if let Some(field) = self.is_upvar_field_projection(PlaceRef {
+ local: place_local,
+ projection: place_projection,
+ }) {
+ self.used_mut_upvars.push(field);
+ }
+ }
+ }
+ }
+
+ /// Whether this value can be written or borrowed mutably.
+ /// Returns the root place if the place passed in is a projection.
+ fn is_mutable(
+ &self,
+ place: PlaceRef<'tcx>,
+ is_local_mutation_allowed: LocalMutationIsAllowed,
+ ) -> Result<RootPlace<'tcx>, PlaceRef<'tcx>> {
+ debug!("is_mutable: place={:?}, is_local...={:?}", place, is_local_mutation_allowed);
+ match place.last_projection() {
+ None => {
+ let local = &self.body.local_decls[place.local];
+ match local.mutability {
+ Mutability::Not => match is_local_mutation_allowed {
+ LocalMutationIsAllowed::Yes => Ok(RootPlace {
+ place_local: place.local,
+ place_projection: place.projection,
+ is_local_mutation_allowed: LocalMutationIsAllowed::Yes,
+ }),
+ LocalMutationIsAllowed::ExceptUpvars => Ok(RootPlace {
+ place_local: place.local,
+ place_projection: place.projection,
+ is_local_mutation_allowed: LocalMutationIsAllowed::ExceptUpvars,
+ }),
+ LocalMutationIsAllowed::No => Err(place),
+ },
+ Mutability::Mut => Ok(RootPlace {
+ place_local: place.local,
+ place_projection: place.projection,
+ is_local_mutation_allowed,
+ }),
+ }
+ }
+ Some((place_base, elem)) => {
+ match elem {
+ ProjectionElem::Deref => {
+ let base_ty = place_base.ty(self.body(), self.infcx.tcx).ty;
+
+ // Check the kind of deref to decide
+ match base_ty.kind() {
+ ty::Ref(_, _, mutbl) => {
+ match mutbl {
+ // Shared borrowed data is never mutable
+ hir::Mutability::Not => Err(place),
+ // Mutably borrowed data is mutable, but only if we have a
+ // unique path to the `&mut`
+ hir::Mutability::Mut => {
+ let mode = match self.is_upvar_field_projection(place) {
+ Some(field) if self.upvars[field.index()].by_ref => {
+ is_local_mutation_allowed
+ }
+ _ => LocalMutationIsAllowed::Yes,
+ };
+
+ self.is_mutable(place_base, mode)
+ }
+ }
+ }
+ ty::RawPtr(tnm) => {
+ match tnm.mutbl {
+ // `*const` raw pointers are not mutable
+ hir::Mutability::Not => Err(place),
+ // `*mut` raw pointers are always mutable, regardless of
+ // context. The users have to check by themselves.
+ hir::Mutability::Mut => Ok(RootPlace {
+ place_local: place.local,
+ place_projection: place.projection,
+ is_local_mutation_allowed,
+ }),
+ }
+ }
+ // `Box<T>` owns its content, so mutable if its location is mutable
+ _ if base_ty.is_box() => {
+ self.is_mutable(place_base, is_local_mutation_allowed)
+ }
+ // Deref should only be for reference, pointers or boxes
+ _ => bug!("Deref of unexpected type: {:?}", base_ty),
+ }
+ }
+ // All other projections are owned by their base path, so mutable if
+ // base path is mutable
+ ProjectionElem::Field(..)
+ | ProjectionElem::Index(..)
+ | ProjectionElem::ConstantIndex { .. }
+ | ProjectionElem::Subslice { .. }
+ | ProjectionElem::Downcast(..) => {
+ let upvar_field_projection = self.is_upvar_field_projection(place);
+ if let Some(field) = upvar_field_projection {
+ let upvar = &self.upvars[field.index()];
+ debug!(
+ "is_mutable: upvar.mutability={:?} local_mutation_is_allowed={:?} \
+ place={:?}, place_base={:?}",
+ upvar, is_local_mutation_allowed, place, place_base
+ );
+ match (upvar.place.mutability, is_local_mutation_allowed) {
+ (
+ Mutability::Not,
+ LocalMutationIsAllowed::No
+ | LocalMutationIsAllowed::ExceptUpvars,
+ ) => Err(place),
+ (Mutability::Not, LocalMutationIsAllowed::Yes)
+ | (Mutability::Mut, _) => {
+ // Subtle: this is an upvar
+ // reference, so it looks like
+ // `self.foo` -- we want to double
+ // check that the location `*self`
+ // is mutable (i.e., this is not a
+ // `Fn` closure). But if that
+ // check succeeds, we want to
+ // *blame* the mutability on
+ // `place` (that is,
+ // `self.foo`). This is used to
+ // propagate the info about
+ // whether mutability declarations
+ // are used outwards, so that we register
+ // the outer variable as mutable. Otherwise a
+ // test like this fails to record the `mut`
+ // as needed:
+ //
+ // ```
+ // fn foo<F: FnOnce()>(_f: F) { }
+ // fn main() {
+ // let var = Vec::new();
+ // foo(move || {
+ // var.push(1);
+ // });
+ // }
+ // ```
+ let _ =
+ self.is_mutable(place_base, is_local_mutation_allowed)?;
+ Ok(RootPlace {
+ place_local: place.local,
+ place_projection: place.projection,
+ is_local_mutation_allowed,
+ })
+ }
+ }
+ } else {
+ self.is_mutable(place_base, is_local_mutation_allowed)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /// If `place` is a field projection, and the field is being projected from a closure type,
+ /// then returns the index of the field being projected. Note that this closure will always
+ /// be `self` in the current MIR, because that is the only time we directly access the fields
+ /// of a closure type.
+ fn is_upvar_field_projection(&self, place_ref: PlaceRef<'tcx>) -> Option<Field> {
+ path_utils::is_upvar_field_projection(self.infcx.tcx, &self.upvars, place_ref, self.body())
+ }
+}
+
+mod error {
+ use rustc_errors::ErrorGuaranteed;
+
+ use super::*;
+
+ pub struct BorrowckErrors<'tcx> {
+ /// This field keeps track of move errors that are to be reported for given move indices.
+ ///
+ /// There are situations where many errors can be reported for a single move out (see #53807)
+ /// and we want only the best of those errors.
+ ///
+ /// The `report_use_of_moved_or_uninitialized` function checks this map and replaces the
+ /// diagnostic (if there is one) if the `Place` of the error being reported is a prefix of the
+ /// `Place` of the previous most diagnostic. This happens instead of buffering the error. Once
+ /// all move errors have been reported, any diagnostics in this map are added to the buffer
+ /// to be emitted.
+ ///
+ /// `BTreeMap` is used to preserve the order of insertions when iterating. This is necessary
+ /// when errors in the map are being re-added to the error buffer so that errors with the
+ /// same primary span come out in a consistent order.
+ buffered_move_errors:
+ BTreeMap<Vec<MoveOutIndex>, (PlaceRef<'tcx>, DiagnosticBuilder<'tcx, ErrorGuaranteed>)>,
+ /// Diagnostics to be reported buffer.
+ buffered: Vec<Diagnostic>,
+ /// Set to Some if we emit an error during borrowck
+ tainted_by_errors: Option<ErrorGuaranteed>,
+ }
+
+ impl BorrowckErrors<'_> {
+ pub fn new() -> Self {
+ BorrowckErrors {
+ buffered_move_errors: BTreeMap::new(),
+ buffered: Default::default(),
+ tainted_by_errors: None,
+ }
+ }
+
+ // FIXME(eddyb) this is a suboptimal API because `tainted_by_errors` is
+ // set before any emission actually happens (weakening the guarantee).
+ pub fn buffer_error(&mut self, t: DiagnosticBuilder<'_, ErrorGuaranteed>) {
+ self.tainted_by_errors = Some(ErrorGuaranteed::unchecked_claim_error_was_emitted());
+ t.buffer(&mut self.buffered);
+ }
+
+ pub fn buffer_non_error_diag(&mut self, t: DiagnosticBuilder<'_, ()>) {
+ t.buffer(&mut self.buffered);
+ }
+
+ pub fn set_tainted_by_errors(&mut self) {
+ self.tainted_by_errors = Some(ErrorGuaranteed::unchecked_claim_error_was_emitted());
+ }
+ }
+
+ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
+ pub fn buffer_error(&mut self, t: DiagnosticBuilder<'_, ErrorGuaranteed>) {
+ self.errors.buffer_error(t);
+ }
+
+ pub fn buffer_non_error_diag(&mut self, t: DiagnosticBuilder<'_, ()>) {
+ self.errors.buffer_non_error_diag(t);
+ }
+
+ pub fn buffer_move_error(
+ &mut self,
+ move_out_indices: Vec<MoveOutIndex>,
+ place_and_err: (PlaceRef<'tcx>, DiagnosticBuilder<'tcx, ErrorGuaranteed>),
+ ) -> bool {
+ if let Some((_, diag)) =
+ self.errors.buffered_move_errors.insert(move_out_indices, place_and_err)
+ {
+ // Cancel the old diagnostic so we don't ICE
+ diag.cancel();
+ false
+ } else {
+ true
+ }
+ }
+
+ pub fn emit_errors(&mut self) -> Option<ErrorGuaranteed> {
+ // Buffer any move errors that we collected and de-duplicated.
+ for (_, (_, diag)) in std::mem::take(&mut self.errors.buffered_move_errors) {
+ // We have already set tainted for this error, so just buffer it.
+ diag.buffer(&mut self.errors.buffered);
+ }
+
+ if !self.errors.buffered.is_empty() {
+ self.errors.buffered.sort_by_key(|diag| diag.sort_span);
+
+ for mut diag in self.errors.buffered.drain(..) {
+ self.infcx.tcx.sess.diagnostic().emit_diagnostic(&mut diag);
+ }
+ }
+
+ self.errors.tainted_by_errors
+ }
+
+ pub fn has_buffered_errors(&self) -> bool {
+ self.errors.buffered.is_empty()
+ }
+
+ pub fn has_move_error(
+ &self,
+ move_out_indices: &[MoveOutIndex],
+ ) -> Option<&(PlaceRef<'tcx>, DiagnosticBuilder<'cx, ErrorGuaranteed>)> {
+ self.errors.buffered_move_errors.get(move_out_indices)
+ }
+ }
+}
+
+/// The degree of overlap between 2 places for borrow-checking.
+enum Overlap {
+ /// The places might partially overlap - in this case, we give
+ /// up and say that they might conflict. This occurs when
+ /// different fields of a union are borrowed. For example,
+ /// if `u` is a union, we have no way of telling how disjoint
+ /// `u.a.x` and `a.b.y` are.
+ Arbitrary,
+ /// The places have the same type, and are either completely disjoint
+ /// or equal - i.e., they can't "partially" overlap as can occur with
+ /// unions. This is the "base case" on which we recur for extensions
+ /// of the place.
+ EqualOrDisjoint,
+ /// The places are disjoint, so we know all extensions of them
+ /// will also be disjoint.
+ Disjoint,
+}
diff --git a/compiler/rustc_borrowck/src/location.rs b/compiler/rustc_borrowck/src/location.rs
new file mode 100644
index 000000000..70a311694
--- /dev/null
+++ b/compiler/rustc_borrowck/src/location.rs
@@ -0,0 +1,107 @@
+use rustc_index::vec::{Idx, IndexVec};
+use rustc_middle::mir::{BasicBlock, Body, Location};
+
+/// Maps between a MIR Location, which identifies a particular
+/// statement within a basic block, to a "rich location", which
+/// identifies at a finer granularity. In particular, we distinguish
+/// the *start* of a statement and the *mid-point*. The mid-point is
+/// the point *just* before the statement takes effect; in particular,
+/// for an assignment `A = B`, it is the point where B is about to be
+/// written into A. This mid-point is a kind of hack to work around
+/// our inability to track the position information at sufficient
+/// granularity through outlives relations; however, the rich location
+/// table serves another purpose: it compresses locations from
+/// multiple words into a single u32.
+pub struct LocationTable {
+ num_points: usize,
+ statements_before_block: IndexVec<BasicBlock, usize>,
+}
+
+rustc_index::newtype_index! {
+ pub struct LocationIndex {
+ DEBUG_FORMAT = "LocationIndex({})"
+ }
+}
+
+#[derive(Copy, Clone, Debug)]
+pub enum RichLocation {
+ Start(Location),
+ Mid(Location),
+}
+
+impl LocationTable {
+ pub(crate) fn new(body: &Body<'_>) -> Self {
+ let mut num_points = 0;
+ let statements_before_block = body
+ .basic_blocks()
+ .iter()
+ .map(|block_data| {
+ let v = num_points;
+ num_points += (block_data.statements.len() + 1) * 2;
+ v
+ })
+ .collect();
+
+ debug!("LocationTable(statements_before_block={:#?})", statements_before_block);
+ debug!("LocationTable: num_points={:#?}", num_points);
+
+ Self { num_points, statements_before_block }
+ }
+
+ pub fn all_points(&self) -> impl Iterator<Item = LocationIndex> {
+ (0..self.num_points).map(LocationIndex::new)
+ }
+
+ pub fn start_index(&self, location: Location) -> LocationIndex {
+ let Location { block, statement_index } = location;
+ let start_index = self.statements_before_block[block];
+ LocationIndex::new(start_index + statement_index * 2)
+ }
+
+ pub fn mid_index(&self, location: Location) -> LocationIndex {
+ let Location { block, statement_index } = location;
+ let start_index = self.statements_before_block[block];
+ LocationIndex::new(start_index + statement_index * 2 + 1)
+ }
+
+ pub fn to_location(&self, index: LocationIndex) -> RichLocation {
+ let point_index = index.index();
+
+ // Find the basic block. We have a vector with the
+ // starting index of the statement in each block. Imagine
+ // we have statement #22, and we have a vector like:
+ //
+ // [0, 10, 20]
+ //
+ // In that case, this represents point_index 2 of
+ // basic block BB2. We know this because BB0 accounts for
+ // 0..10, BB1 accounts for 11..20, and BB2 accounts for
+ // 20...
+ //
+ // To compute this, we could do a binary search, but
+ // because I am lazy we instead iterate through to find
+ // the last point where the "first index" (0, 10, or 20)
+ // was less than the statement index (22). In our case, this will
+ // be (BB2, 20).
+ let (block, &first_index) = self
+ .statements_before_block
+ .iter_enumerated()
+ .filter(|(_, first_index)| **first_index <= point_index)
+ .last()
+ .unwrap();
+
+ let statement_index = (point_index - first_index) / 2;
+ if index.is_start() {
+ RichLocation::Start(Location { block, statement_index })
+ } else {
+ RichLocation::Mid(Location { block, statement_index })
+ }
+ }
+}
+
+impl LocationIndex {
+ fn is_start(self) -> bool {
+ // even indices are start points; odd indices are mid points
+ (self.index() % 2) == 0
+ }
+}
diff --git a/compiler/rustc_borrowck/src/member_constraints.rs b/compiler/rustc_borrowck/src/member_constraints.rs
new file mode 100644
index 000000000..43253a2aa
--- /dev/null
+++ b/compiler/rustc_borrowck/src/member_constraints.rs
@@ -0,0 +1,230 @@
+use rustc_data_structures::captures::Captures;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_index::vec::IndexVec;
+use rustc_middle::infer::MemberConstraint;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::Span;
+use std::hash::Hash;
+use std::ops::Index;
+
+/// Compactly stores a set of `R0 member of [R1...Rn]` constraints,
+/// indexed by the region `R0`.
+pub(crate) struct MemberConstraintSet<'tcx, R>
+where
+ R: Copy + Eq,
+{
+ /// Stores the first "member" constraint for a given `R0`. This is an
+ /// index into the `constraints` vector below.
+ first_constraints: FxHashMap<R, NllMemberConstraintIndex>,
+
+ /// Stores the data about each `R0 member of [R1..Rn]` constraint.
+ /// These are organized into a linked list, so each constraint
+ /// contains the index of the next constraint with the same `R0`.
+ constraints: IndexVec<NllMemberConstraintIndex, NllMemberConstraint<'tcx>>,
+
+ /// Stores the `R1..Rn` regions for *all* sets. For any given
+ /// constraint, we keep two indices so that we can pull out a
+ /// slice.
+ choice_regions: Vec<ty::RegionVid>,
+}
+
+/// Represents a `R0 member of [R1..Rn]` constraint
+pub(crate) struct NllMemberConstraint<'tcx> {
+ next_constraint: Option<NllMemberConstraintIndex>,
+
+ /// The span where the hidden type was instantiated.
+ pub(crate) definition_span: Span,
+
+ /// The hidden type in which `R0` appears. (Used in error reporting.)
+ pub(crate) hidden_ty: Ty<'tcx>,
+
+ pub(crate) key: ty::OpaqueTypeKey<'tcx>,
+
+ /// The region `R0`.
+ pub(crate) member_region_vid: ty::RegionVid,
+
+ /// Index of `R1` in `choice_regions` vector from `MemberConstraintSet`.
+ start_index: usize,
+
+ /// Index of `Rn` in `choice_regions` vector from `MemberConstraintSet`.
+ end_index: usize,
+}
+
+rustc_index::newtype_index! {
+ pub(crate) struct NllMemberConstraintIndex {
+ DEBUG_FORMAT = "MemberConstraintIndex({})"
+ }
+}
+
+impl Default for MemberConstraintSet<'_, ty::RegionVid> {
+ fn default() -> Self {
+ Self {
+ first_constraints: Default::default(),
+ constraints: Default::default(),
+ choice_regions: Default::default(),
+ }
+ }
+}
+
+impl<'tcx> MemberConstraintSet<'tcx, ty::RegionVid> {
+ /// Pushes a member constraint into the set.
+ ///
+ /// The input member constraint `m_c` is in the form produced by
+ /// the `rustc_middle::infer` code.
+ ///
+ /// The `to_region_vid` callback fn is used to convert the regions
+ /// within into `RegionVid` format -- it typically consults the
+ /// `UniversalRegions` data structure that is known to the caller
+ /// (but which this code is unaware of).
+ pub(crate) fn push_constraint(
+ &mut self,
+ m_c: &MemberConstraint<'tcx>,
+ mut to_region_vid: impl FnMut(ty::Region<'tcx>) -> ty::RegionVid,
+ ) {
+ debug!("push_constraint(m_c={:?})", m_c);
+ let member_region_vid: ty::RegionVid = to_region_vid(m_c.member_region);
+ let next_constraint = self.first_constraints.get(&member_region_vid).cloned();
+ let start_index = self.choice_regions.len();
+ let end_index = start_index + m_c.choice_regions.len();
+ debug!("push_constraint: member_region_vid={:?}", member_region_vid);
+ let constraint_index = self.constraints.push(NllMemberConstraint {
+ next_constraint,
+ member_region_vid,
+ definition_span: m_c.definition_span,
+ hidden_ty: m_c.hidden_ty,
+ key: m_c.key,
+ start_index,
+ end_index,
+ });
+ self.first_constraints.insert(member_region_vid, constraint_index);
+ self.choice_regions.extend(m_c.choice_regions.iter().map(|&r| to_region_vid(r)));
+ }
+}
+
+impl<'tcx, R1> MemberConstraintSet<'tcx, R1>
+where
+ R1: Copy + Hash + Eq,
+{
+ /// Remap the "member region" key using `map_fn`, producing a new
+ /// member constraint set. This is used in the NLL code to map from
+ /// the original `RegionVid` to an scc index. In some cases, we
+ /// may have multiple `R1` values mapping to the same `R2` key -- that
+ /// is ok, the two sets will be merged.
+ pub(crate) fn into_mapped<R2>(
+ self,
+ mut map_fn: impl FnMut(R1) -> R2,
+ ) -> MemberConstraintSet<'tcx, R2>
+ where
+ R2: Copy + Hash + Eq,
+ {
+ // We can re-use most of the original data, just tweaking the
+ // linked list links a bit.
+ //
+ // For example if we had two keys `Ra` and `Rb` that both now
+ // wind up mapped to the same key `S`, we would append the
+ // linked list for `Ra` onto the end of the linked list for
+ // `Rb` (or vice versa) -- this basically just requires
+ // rewriting the final link from one list to point at the other
+ // other (see `append_list`).
+
+ let MemberConstraintSet { first_constraints, mut constraints, choice_regions } = self;
+
+ let mut first_constraints2 = FxHashMap::default();
+ first_constraints2.reserve(first_constraints.len());
+
+ for (r1, start1) in first_constraints {
+ let r2 = map_fn(r1);
+ if let Some(&start2) = first_constraints2.get(&r2) {
+ append_list(&mut constraints, start1, start2);
+ }
+ first_constraints2.insert(r2, start1);
+ }
+
+ MemberConstraintSet { first_constraints: first_constraints2, constraints, choice_regions }
+ }
+}
+
+impl<'tcx, R> MemberConstraintSet<'tcx, R>
+where
+ R: Copy + Hash + Eq,
+{
+ pub(crate) fn all_indices(
+ &self,
+ ) -> impl Iterator<Item = NllMemberConstraintIndex> + Captures<'tcx> + '_ {
+ self.constraints.indices()
+ }
+
+ /// Iterate down the constraint indices associated with a given
+ /// peek-region. You can then use `choice_regions` and other
+ /// methods to access data.
+ pub(crate) fn indices(
+ &self,
+ member_region_vid: R,
+ ) -> impl Iterator<Item = NllMemberConstraintIndex> + Captures<'tcx> + '_ {
+ let mut next = self.first_constraints.get(&member_region_vid).cloned();
+ std::iter::from_fn(move || -> Option<NllMemberConstraintIndex> {
+ if let Some(current) = next {
+ next = self.constraints[current].next_constraint;
+ Some(current)
+ } else {
+ None
+ }
+ })
+ }
+
+ /// Returns the "choice regions" for a given member
+ /// constraint. This is the `R1..Rn` from a constraint like:
+ ///
+ /// ```text
+ /// R0 member of [R1..Rn]
+ /// ```
+ pub(crate) fn choice_regions(&self, pci: NllMemberConstraintIndex) -> &[ty::RegionVid] {
+ let NllMemberConstraint { start_index, end_index, .. } = &self.constraints[pci];
+ &self.choice_regions[*start_index..*end_index]
+ }
+}
+
+impl<'tcx, R> Index<NllMemberConstraintIndex> for MemberConstraintSet<'tcx, R>
+where
+ R: Copy + Eq,
+{
+ type Output = NllMemberConstraint<'tcx>;
+
+ fn index(&self, i: NllMemberConstraintIndex) -> &NllMemberConstraint<'tcx> {
+ &self.constraints[i]
+ }
+}
+
+/// Given a linked list starting at `source_list` and another linked
+/// list starting at `target_list`, modify `target_list` so that it is
+/// followed by `source_list`.
+///
+/// Before:
+///
+/// ```text
+/// target_list: A -> B -> C -> (None)
+/// source_list: D -> E -> F -> (None)
+/// ```
+///
+/// After:
+///
+/// ```text
+/// target_list: A -> B -> C -> D -> E -> F -> (None)
+/// ```
+fn append_list(
+ constraints: &mut IndexVec<NllMemberConstraintIndex, NllMemberConstraint<'_>>,
+ target_list: NllMemberConstraintIndex,
+ source_list: NllMemberConstraintIndex,
+) {
+ let mut p = target_list;
+ loop {
+ let mut r = &mut constraints[p];
+ match r.next_constraint {
+ Some(q) => p = q,
+ None => {
+ r.next_constraint = Some(source_list);
+ return;
+ }
+ }
+ }
+}
diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs
new file mode 100644
index 000000000..0961203d7
--- /dev/null
+++ b/compiler/rustc_borrowck/src/nll.rs
@@ -0,0 +1,462 @@
+//! The entry point of the NLL borrow checker.
+
+use rustc_data_structures::vec_map::VecMap;
+use rustc_hir::def_id::LocalDefId;
+use rustc_index::vec::IndexVec;
+use rustc_infer::infer::InferCtxt;
+use rustc_middle::mir::{create_dump_file, dump_enabled, dump_mir, PassWhere};
+use rustc_middle::mir::{
+ BasicBlock, Body, ClosureOutlivesSubject, ClosureRegionRequirements, LocalKind, Location,
+ Promoted,
+};
+use rustc_middle::ty::{self, OpaqueHiddenType, Region, RegionVid};
+use rustc_span::symbol::sym;
+use std::env;
+use std::fmt::Debug;
+use std::io;
+use std::path::PathBuf;
+use std::rc::Rc;
+use std::str::FromStr;
+
+use polonius_engine::{Algorithm, Output};
+
+use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
+use rustc_mir_dataflow::move_paths::{InitKind, InitLocation, MoveData};
+use rustc_mir_dataflow::ResultsCursor;
+
+use crate::{
+ borrow_set::BorrowSet,
+ constraint_generation,
+ diagnostics::RegionErrors,
+ facts::{AllFacts, AllFactsExt, RustcFacts},
+ invalidation,
+ location::LocationTable,
+ region_infer::{values::RegionValueElements, RegionInferenceContext},
+ renumber,
+ type_check::{self, MirTypeckRegionConstraints, MirTypeckResults},
+ universal_regions::UniversalRegions,
+ Upvar,
+};
+
+pub type PoloniusOutput = Output<RustcFacts>;
+
+/// The output of `nll::compute_regions`. This includes the computed `RegionInferenceContext`, any
+/// closure requirements to propagate, and any generated errors.
+pub(crate) struct NllOutput<'tcx> {
+ pub regioncx: RegionInferenceContext<'tcx>,
+ pub opaque_type_values: VecMap<LocalDefId, OpaqueHiddenType<'tcx>>,
+ pub polonius_input: Option<Box<AllFacts>>,
+ pub polonius_output: Option<Rc<PoloniusOutput>>,
+ pub opt_closure_req: Option<ClosureRegionRequirements<'tcx>>,
+ pub nll_errors: RegionErrors<'tcx>,
+}
+
+/// Rewrites the regions in the MIR to use NLL variables, also scraping out the set of universal
+/// regions (e.g., region parameters) declared on the function. That set will need to be given to
+/// `compute_regions`.
+#[instrument(skip(infcx, param_env, body, promoted), level = "debug")]
+pub(crate) fn replace_regions_in_mir<'cx, 'tcx>(
+ infcx: &InferCtxt<'cx, 'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ body: &mut Body<'tcx>,
+ promoted: &mut IndexVec<Promoted, Body<'tcx>>,
+) -> UniversalRegions<'tcx> {
+ let def = body.source.with_opt_param().as_local().unwrap();
+
+ debug!(?def);
+
+ // Compute named region information. This also renumbers the inputs/outputs.
+ let universal_regions = UniversalRegions::new(infcx, def, param_env);
+
+ // Replace all remaining regions with fresh inference variables.
+ renumber::renumber_mir(infcx, body, promoted);
+
+ dump_mir(infcx.tcx, None, "renumber", &0, body, |_, _| Ok(()));
+
+ universal_regions
+}
+
+// This function populates an AllFacts instance with base facts related to
+// MovePaths and needed for the move analysis.
+fn populate_polonius_move_facts(
+ all_facts: &mut AllFacts,
+ move_data: &MoveData<'_>,
+ location_table: &LocationTable,
+ body: &Body<'_>,
+) {
+ all_facts
+ .path_is_var
+ .extend(move_data.rev_lookup.iter_locals_enumerated().map(|(l, r)| (r, l)));
+
+ for (child, move_path) in move_data.move_paths.iter_enumerated() {
+ if let Some(parent) = move_path.parent {
+ all_facts.child_path.push((child, parent));
+ }
+ }
+
+ let fn_entry_start = location_table
+ .start_index(Location { block: BasicBlock::from_u32(0u32), statement_index: 0 });
+
+ // initialized_at
+ for init in move_data.inits.iter() {
+ match init.location {
+ InitLocation::Statement(location) => {
+ let block_data = &body[location.block];
+ let is_terminator = location.statement_index == block_data.statements.len();
+
+ if is_terminator && init.kind == InitKind::NonPanicPathOnly {
+ // We are at the terminator of an init that has a panic path,
+ // and where the init should not happen on panic
+
+ for successor in block_data.terminator().successors() {
+ if body[successor].is_cleanup {
+ continue;
+ }
+
+ // The initialization happened in (or rather, when arriving at)
+ // the successors, but not in the unwind block.
+ let first_statement = Location { block: successor, statement_index: 0 };
+ all_facts
+ .path_assigned_at_base
+ .push((init.path, location_table.start_index(first_statement)));
+ }
+ } else {
+ // In all other cases, the initialization just happens at the
+ // midpoint, like any other effect.
+ all_facts
+ .path_assigned_at_base
+ .push((init.path, location_table.mid_index(location)));
+ }
+ }
+ // Arguments are initialized on function entry
+ InitLocation::Argument(local) => {
+ assert!(body.local_kind(local) == LocalKind::Arg);
+ all_facts.path_assigned_at_base.push((init.path, fn_entry_start));
+ }
+ }
+ }
+
+ for (local, path) in move_data.rev_lookup.iter_locals_enumerated() {
+ if body.local_kind(local) != LocalKind::Arg {
+ // Non-arguments start out deinitialised; we simulate this with an
+ // initial move:
+ all_facts.path_moved_at_base.push((path, fn_entry_start));
+ }
+ }
+
+ // moved_out_at
+ // deinitialisation is assumed to always happen!
+ all_facts
+ .path_moved_at_base
+ .extend(move_data.moves.iter().map(|mo| (mo.path, location_table.mid_index(mo.source))));
+}
+
+/// Computes the (non-lexical) regions from the input MIR.
+///
+/// This may result in errors being reported.
+pub(crate) fn compute_regions<'cx, 'tcx>(
+ infcx: &InferCtxt<'cx, 'tcx>,
+ universal_regions: UniversalRegions<'tcx>,
+ body: &Body<'tcx>,
+ promoted: &IndexVec<Promoted, Body<'tcx>>,
+ location_table: &LocationTable,
+ param_env: ty::ParamEnv<'tcx>,
+ flow_inits: &mut ResultsCursor<'cx, 'tcx, MaybeInitializedPlaces<'cx, 'tcx>>,
+ move_data: &MoveData<'tcx>,
+ borrow_set: &BorrowSet<'tcx>,
+ upvars: &[Upvar<'tcx>],
+ use_polonius: bool,
+) -> NllOutput<'tcx> {
+ let mut all_facts =
+ (use_polonius || AllFacts::enabled(infcx.tcx)).then_some(AllFacts::default());
+
+ let universal_regions = Rc::new(universal_regions);
+
+ let elements = &Rc::new(RegionValueElements::new(&body));
+
+ // Run the MIR type-checker.
+ let MirTypeckResults { constraints, universal_region_relations, opaque_type_values } =
+ type_check::type_check(
+ infcx,
+ param_env,
+ body,
+ promoted,
+ &universal_regions,
+ location_table,
+ borrow_set,
+ &mut all_facts,
+ flow_inits,
+ move_data,
+ elements,
+ upvars,
+ use_polonius,
+ );
+
+ if let Some(all_facts) = &mut all_facts {
+ let _prof_timer = infcx.tcx.prof.generic_activity("polonius_fact_generation");
+ all_facts.universal_region.extend(universal_regions.universal_regions());
+ populate_polonius_move_facts(all_facts, move_data, location_table, &body);
+
+ // Emit universal regions facts, and their relations, for Polonius.
+ //
+ // 1: universal regions are modeled in Polonius as a pair:
+ // - the universal region vid itself.
+ // - a "placeholder loan" associated to this universal region. Since they don't exist in
+ // the `borrow_set`, their `BorrowIndex` are synthesized as the universal region index
+ // added to the existing number of loans, as if they succeeded them in the set.
+ //
+ let borrow_count = borrow_set.len();
+ debug!(
+ "compute_regions: polonius placeholders, num_universals={}, borrow_count={}",
+ universal_regions.len(),
+ borrow_count
+ );
+
+ for universal_region in universal_regions.universal_regions() {
+ let universal_region_idx = universal_region.index();
+ let placeholder_loan_idx = borrow_count + universal_region_idx;
+ all_facts.placeholder.push((universal_region, placeholder_loan_idx.into()));
+ }
+
+ // 2: the universal region relations `outlives` constraints are emitted as
+ // `known_placeholder_subset` facts.
+ for (fr1, fr2) in universal_region_relations.known_outlives() {
+ if fr1 != fr2 {
+ debug!(
+ "compute_regions: emitting polonius `known_placeholder_subset` \
+ fr1={:?}, fr2={:?}",
+ fr1, fr2
+ );
+ all_facts.known_placeholder_subset.push((fr1, fr2));
+ }
+ }
+ }
+
+ // Create the region inference context, taking ownership of the
+ // region inference data that was contained in `infcx`, and the
+ // base constraints generated by the type-check.
+ let var_origins = infcx.take_region_var_origins();
+ let MirTypeckRegionConstraints {
+ placeholder_indices,
+ placeholder_index_to_region: _,
+ mut liveness_constraints,
+ outlives_constraints,
+ member_constraints,
+ closure_bounds_mapping,
+ universe_causes,
+ type_tests,
+ } = constraints;
+ let placeholder_indices = Rc::new(placeholder_indices);
+
+ constraint_generation::generate_constraints(
+ infcx,
+ &mut liveness_constraints,
+ &mut all_facts,
+ location_table,
+ &body,
+ borrow_set,
+ );
+
+ let mut regioncx = RegionInferenceContext::new(
+ var_origins,
+ universal_regions,
+ placeholder_indices,
+ universal_region_relations,
+ outlives_constraints,
+ member_constraints,
+ closure_bounds_mapping,
+ universe_causes,
+ type_tests,
+ liveness_constraints,
+ elements,
+ );
+
+ // Generate various additional constraints.
+ invalidation::generate_invalidates(infcx.tcx, &mut all_facts, location_table, body, borrow_set);
+
+ let def_id = body.source.def_id();
+
+ // Dump facts if requested.
+ let polonius_output = all_facts.as_ref().and_then(|all_facts| {
+ if infcx.tcx.sess.opts.unstable_opts.nll_facts {
+ let def_path = infcx.tcx.def_path(def_id);
+ let dir_path = PathBuf::from(&infcx.tcx.sess.opts.unstable_opts.nll_facts_dir)
+ .join(def_path.to_filename_friendly_no_crate());
+ all_facts.write_to_dir(dir_path, location_table).unwrap();
+ }
+
+ if use_polonius {
+ let algorithm =
+ env::var("POLONIUS_ALGORITHM").unwrap_or_else(|_| String::from("Hybrid"));
+ let algorithm = Algorithm::from_str(&algorithm).unwrap();
+ debug!("compute_regions: using polonius algorithm {:?}", algorithm);
+ let _prof_timer = infcx.tcx.prof.generic_activity("polonius_analysis");
+ Some(Rc::new(Output::compute(&all_facts, algorithm, false)))
+ } else {
+ None
+ }
+ });
+
+ // Solve the region constraints.
+ let (closure_region_requirements, nll_errors) =
+ regioncx.solve(infcx, param_env, &body, polonius_output.clone());
+
+ if !nll_errors.is_empty() {
+ // Suppress unhelpful extra errors in `infer_opaque_types`.
+ infcx.set_tainted_by_errors();
+ }
+
+ let remapped_opaque_tys = regioncx.infer_opaque_types(&infcx, opaque_type_values);
+
+ NllOutput {
+ regioncx,
+ opaque_type_values: remapped_opaque_tys,
+ polonius_input: all_facts.map(Box::new),
+ polonius_output,
+ opt_closure_req: closure_region_requirements,
+ nll_errors,
+ }
+}
+
+pub(super) fn dump_mir_results<'a, 'tcx>(
+ infcx: &InferCtxt<'a, 'tcx>,
+ body: &Body<'tcx>,
+ regioncx: &RegionInferenceContext<'tcx>,
+ closure_region_requirements: &Option<ClosureRegionRequirements<'_>>,
+) {
+ if !dump_enabled(infcx.tcx, "nll", body.source.def_id()) {
+ return;
+ }
+
+ dump_mir(infcx.tcx, None, "nll", &0, body, |pass_where, out| {
+ match pass_where {
+ // Before the CFG, dump out the values for each region variable.
+ PassWhere::BeforeCFG => {
+ regioncx.dump_mir(infcx.tcx, out)?;
+ writeln!(out, "|")?;
+
+ if let Some(closure_region_requirements) = closure_region_requirements {
+ writeln!(out, "| Free Region Constraints")?;
+ for_each_region_constraint(closure_region_requirements, &mut |msg| {
+ writeln!(out, "| {}", msg)
+ })?;
+ writeln!(out, "|")?;
+ }
+ }
+
+ PassWhere::BeforeLocation(_) => {}
+
+ PassWhere::AfterTerminator(_) => {}
+
+ PassWhere::BeforeBlock(_) | PassWhere::AfterLocation(_) | PassWhere::AfterCFG => {}
+ }
+ Ok(())
+ });
+
+ // Also dump the inference graph constraints as a graphviz file.
+ let _: io::Result<()> = try {
+ let mut file =
+ create_dump_file(infcx.tcx, "regioncx.all.dot", None, "nll", &0, body.source)?;
+ regioncx.dump_graphviz_raw_constraints(&mut file)?;
+ };
+
+ // Also dump the inference graph constraints as a graphviz file.
+ let _: io::Result<()> = try {
+ let mut file =
+ create_dump_file(infcx.tcx, "regioncx.scc.dot", None, "nll", &0, body.source)?;
+ regioncx.dump_graphviz_scc_constraints(&mut file)?;
+ };
+}
+
+pub(super) fn dump_annotation<'a, 'tcx>(
+ infcx: &InferCtxt<'a, 'tcx>,
+ body: &Body<'tcx>,
+ regioncx: &RegionInferenceContext<'tcx>,
+ closure_region_requirements: &Option<ClosureRegionRequirements<'_>>,
+ opaque_type_values: &VecMap<LocalDefId, OpaqueHiddenType<'tcx>>,
+ errors: &mut crate::error::BorrowckErrors<'tcx>,
+) {
+ let tcx = infcx.tcx;
+ let base_def_id = tcx.typeck_root_def_id(body.source.def_id());
+ if !tcx.has_attr(base_def_id, sym::rustc_regions) {
+ return;
+ }
+
+ // When the enclosing function is tagged with `#[rustc_regions]`,
+ // we dump out various bits of state as warnings. This is useful
+ // for verifying that the compiler is behaving as expected. These
+ // warnings focus on the closure region requirements -- for
+ // viewing the intraprocedural state, the -Zdump-mir output is
+ // better.
+
+ let mut err = if let Some(closure_region_requirements) = closure_region_requirements {
+ let mut err = tcx.sess.diagnostic().span_note_diag(body.span, "external requirements");
+
+ regioncx.annotate(tcx, &mut err);
+
+ err.note(&format!(
+ "number of external vids: {}",
+ closure_region_requirements.num_external_vids
+ ));
+
+ // Dump the region constraints we are imposing *between* those
+ // newly created variables.
+ for_each_region_constraint(closure_region_requirements, &mut |msg| {
+ err.note(msg);
+ Ok(())
+ })
+ .unwrap();
+
+ err
+ } else {
+ let mut err = tcx.sess.diagnostic().span_note_diag(body.span, "no external requirements");
+ regioncx.annotate(tcx, &mut err);
+
+ err
+ };
+
+ if !opaque_type_values.is_empty() {
+ err.note(&format!("Inferred opaque type values:\n{:#?}", opaque_type_values));
+ }
+
+ errors.buffer_non_error_diag(err);
+}
+
+fn for_each_region_constraint(
+ closure_region_requirements: &ClosureRegionRequirements<'_>,
+ with_msg: &mut dyn FnMut(&str) -> io::Result<()>,
+) -> io::Result<()> {
+ for req in &closure_region_requirements.outlives_requirements {
+ let subject: &dyn Debug = match &req.subject {
+ ClosureOutlivesSubject::Region(subject) => subject,
+ ClosureOutlivesSubject::Ty(ty) => ty,
+ };
+ with_msg(&format!("where {:?}: {:?}", subject, req.outlived_free_region,))?;
+ }
+ Ok(())
+}
+
+/// Right now, we piggy back on the `ReVar` to store our NLL inference
+/// regions. These are indexed with `RegionVid`. This method will
+/// assert that the region is a `ReVar` and extract its internal index.
+/// This is reasonable because in our MIR we replace all universal regions
+/// with inference variables.
+pub trait ToRegionVid {
+ fn to_region_vid(self) -> RegionVid;
+}
+
+impl<'tcx> ToRegionVid for Region<'tcx> {
+ fn to_region_vid(self) -> RegionVid {
+ if let ty::ReVar(vid) = *self { vid } else { bug!("region is not an ReVar: {:?}", self) }
+ }
+}
+
+impl ToRegionVid for RegionVid {
+ fn to_region_vid(self) -> RegionVid {
+ self
+ }
+}
+
+pub(crate) trait ConstraintDescription {
+ fn description(&self) -> &'static str;
+}
diff --git a/compiler/rustc_borrowck/src/path_utils.rs b/compiler/rustc_borrowck/src/path_utils.rs
new file mode 100644
index 000000000..b2c8dfc82
--- /dev/null
+++ b/compiler/rustc_borrowck/src/path_utils.rs
@@ -0,0 +1,171 @@
+use crate::borrow_set::{BorrowData, BorrowSet, TwoPhaseActivation};
+use crate::places_conflict;
+use crate::AccessDepth;
+use crate::BorrowIndex;
+use crate::Upvar;
+use rustc_data_structures::graph::dominators::Dominators;
+use rustc_middle::mir::BorrowKind;
+use rustc_middle::mir::{BasicBlock, Body, Field, Location, Place, PlaceRef, ProjectionElem};
+use rustc_middle::ty::TyCtxt;
+
+/// Returns `true` if the borrow represented by `kind` is
+/// allowed to be split into separate Reservation and
+/// Activation phases.
+pub(super) fn allow_two_phase_borrow(kind: BorrowKind) -> bool {
+ kind.allows_two_phase_borrow()
+}
+
+/// Control for the path borrow checking code
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub(super) enum Control {
+ Continue,
+ Break,
+}
+
+/// Encapsulates the idea of iterating over every borrow that involves a particular path
+pub(super) fn each_borrow_involving_path<'tcx, F, I, S>(
+ s: &mut S,
+ tcx: TyCtxt<'tcx>,
+ body: &Body<'tcx>,
+ _location: Location,
+ access_place: (AccessDepth, Place<'tcx>),
+ borrow_set: &BorrowSet<'tcx>,
+ candidates: I,
+ mut op: F,
+) where
+ F: FnMut(&mut S, BorrowIndex, &BorrowData<'tcx>) -> Control,
+ I: Iterator<Item = BorrowIndex>,
+{
+ let (access, place) = access_place;
+
+ // FIXME: analogous code in check_loans first maps `place` to
+ // its base_path.
+
+ // check for loan restricting path P being used. Accounts for
+ // borrows of P, P.a.b, etc.
+ for i in candidates {
+ let borrowed = &borrow_set[i];
+
+ if places_conflict::borrow_conflicts_with_place(
+ tcx,
+ body,
+ borrowed.borrowed_place,
+ borrowed.kind,
+ place.as_ref(),
+ access,
+ places_conflict::PlaceConflictBias::Overlap,
+ ) {
+ debug!(
+ "each_borrow_involving_path: {:?} @ {:?} vs. {:?}/{:?}",
+ i, borrowed, place, access
+ );
+ let ctrl = op(s, i, borrowed);
+ if ctrl == Control::Break {
+ return;
+ }
+ }
+ }
+}
+
+pub(super) fn is_active<'tcx>(
+ dominators: &Dominators<BasicBlock>,
+ borrow_data: &BorrowData<'tcx>,
+ location: Location,
+) -> bool {
+ debug!("is_active(borrow_data={:?}, location={:?})", borrow_data, location);
+
+ let activation_location = match borrow_data.activation_location {
+ // If this is not a 2-phase borrow, it is always active.
+ TwoPhaseActivation::NotTwoPhase => return true,
+ // And if the unique 2-phase use is not an activation, then it is *never* active.
+ TwoPhaseActivation::NotActivated => return false,
+ // Otherwise, we derive info from the activation point `loc`:
+ TwoPhaseActivation::ActivatedAt(loc) => loc,
+ };
+
+ // Otherwise, it is active for every location *except* in between
+ // the reservation and the activation:
+ //
+ // X
+ // /
+ // R <--+ Except for this
+ // / \ | diamond
+ // \ / |
+ // A <------+
+ // |
+ // Z
+ //
+ // Note that we assume that:
+ // - the reservation R dominates the activation A
+ // - the activation A post-dominates the reservation R (ignoring unwinding edges).
+ //
+ // This means that there can't be an edge that leaves A and
+ // comes back into that diamond unless it passes through R.
+ //
+ // Suboptimal: In some cases, this code walks the dominator
+ // tree twice when it only has to be walked once. I am
+ // lazy. -nmatsakis
+
+ // If dominated by the activation A, then it is active. The
+ // activation occurs upon entering the point A, so this is
+ // also true if location == activation_location.
+ if activation_location.dominates(location, dominators) {
+ return true;
+ }
+
+ // The reservation starts *on exiting* the reservation block,
+ // so check if the location is dominated by R.successor. If so,
+ // this point falls in between the reservation and location.
+ let reserve_location = borrow_data.reserve_location.successor_within_block();
+ if reserve_location.dominates(location, dominators) {
+ false
+ } else {
+ // Otherwise, this point is outside the diamond, so
+ // consider the borrow active. This could happen for
+ // example if the borrow remains active around a loop (in
+ // which case it would be active also for the point R,
+ // which would generate an error).
+ true
+ }
+}
+
+/// Determines if a given borrow is borrowing local data
+/// This is called for all Yield expressions on movable generators
+pub(super) fn borrow_of_local_data(place: Place<'_>) -> bool {
+ // Reborrow of already borrowed data is ignored
+ // Any errors will be caught on the initial borrow
+ !place.is_indirect()
+}
+
+/// If `place` is a field projection, and the field is being projected from a closure type,
+/// then returns the index of the field being projected. Note that this closure will always
+/// be `self` in the current MIR, because that is the only time we directly access the fields
+/// of a closure type.
+pub(crate) fn is_upvar_field_projection<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ upvars: &[Upvar<'tcx>],
+ place_ref: PlaceRef<'tcx>,
+ body: &Body<'tcx>,
+) -> Option<Field> {
+ let mut place_ref = place_ref;
+ let mut by_ref = false;
+
+ if let Some((place_base, ProjectionElem::Deref)) = place_ref.last_projection() {
+ place_ref = place_base;
+ by_ref = true;
+ }
+
+ match place_ref.last_projection() {
+ Some((place_base, ProjectionElem::Field(field, _ty))) => {
+ let base_ty = place_base.ty(body, tcx).ty;
+ if (base_ty.is_closure() || base_ty.is_generator())
+ && (!by_ref || upvars[field.index()].by_ref)
+ {
+ Some(field)
+ } else {
+ None
+ }
+ }
+ _ => None,
+ }
+}
diff --git a/compiler/rustc_borrowck/src/place_ext.rs b/compiler/rustc_borrowck/src/place_ext.rs
new file mode 100644
index 000000000..93d202e49
--- /dev/null
+++ b/compiler/rustc_borrowck/src/place_ext.rs
@@ -0,0 +1,81 @@
+use crate::borrow_set::LocalsStateAtExit;
+use rustc_hir as hir;
+use rustc_middle::mir::ProjectionElem;
+use rustc_middle::mir::{Body, Mutability, Place};
+use rustc_middle::ty::{self, TyCtxt};
+
+/// Extension methods for the `Place` type.
+pub(crate) trait PlaceExt<'tcx> {
+ /// Returns `true` if we can safely ignore borrows of this place.
+ /// This is true whenever there is no action that the user can do
+ /// to the place `self` that would invalidate the borrow. This is true
+ /// for borrows of raw pointer dereferents as well as shared references.
+ fn ignore_borrow(
+ &self,
+ tcx: TyCtxt<'tcx>,
+ body: &Body<'tcx>,
+ locals_state_at_exit: &LocalsStateAtExit,
+ ) -> bool;
+}
+
+impl<'tcx> PlaceExt<'tcx> for Place<'tcx> {
+ fn ignore_borrow(
+ &self,
+ tcx: TyCtxt<'tcx>,
+ body: &Body<'tcx>,
+ locals_state_at_exit: &LocalsStateAtExit,
+ ) -> bool {
+ // If a local variable is immutable, then we only need to track borrows to guard
+ // against two kinds of errors:
+ // * The variable being dropped while still borrowed (e.g., because the fn returns
+ // a reference to a local variable)
+ // * The variable being moved while still borrowed
+ //
+ // In particular, the variable cannot be mutated -- the "access checks" will fail --
+ // so we don't have to worry about mutation while borrowed.
+ if let LocalsStateAtExit::SomeAreInvalidated { has_storage_dead_or_moved } =
+ locals_state_at_exit
+ {
+ let ignore = !has_storage_dead_or_moved.contains(self.local)
+ && body.local_decls[self.local].mutability == Mutability::Not;
+ debug!("ignore_borrow: local {:?} => {:?}", self.local, ignore);
+ if ignore {
+ return true;
+ }
+ }
+
+ for (i, elem) in self.projection.iter().enumerate() {
+ let proj_base = &self.projection[..i];
+
+ if elem == ProjectionElem::Deref {
+ let ty = Place::ty_from(self.local, proj_base, body, tcx).ty;
+ match ty.kind() {
+ ty::Ref(_, _, hir::Mutability::Not) if i == 0 => {
+ // For references to thread-local statics, we do need
+ // to track the borrow.
+ if body.local_decls[self.local].is_ref_to_thread_local() {
+ continue;
+ }
+ return true;
+ }
+ ty::RawPtr(..) | ty::Ref(_, _, hir::Mutability::Not) => {
+ // For both derefs of raw pointers and `&T`
+ // references, the original path is `Copy` and
+ // therefore not significant. In particular,
+ // there is nothing the user can do to the
+ // original path that would invalidate the
+ // newly created reference -- and if there
+ // were, then the user could have copied the
+ // original path into a new variable and
+ // borrowed *that* one, leaving the original
+ // path unborrowed.
+ return true;
+ }
+ _ => {}
+ }
+ }
+ }
+
+ false
+ }
+}
diff --git a/compiler/rustc_borrowck/src/places_conflict.rs b/compiler/rustc_borrowck/src/places_conflict.rs
new file mode 100644
index 000000000..97335fd0d
--- /dev/null
+++ b/compiler/rustc_borrowck/src/places_conflict.rs
@@ -0,0 +1,537 @@
+use crate::ArtificialField;
+use crate::Overlap;
+use crate::{AccessDepth, Deep, Shallow};
+use rustc_hir as hir;
+use rustc_middle::mir::{Body, BorrowKind, Local, Place, PlaceElem, PlaceRef, ProjectionElem};
+use rustc_middle::ty::{self, TyCtxt};
+use std::cmp::max;
+use std::iter;
+
+/// When checking if a place conflicts with another place, this enum is used to influence decisions
+/// where a place might be equal or disjoint with another place, such as if `a[i] == a[j]`.
+/// `PlaceConflictBias::Overlap` would bias toward assuming that `i` might equal `j` and that these
+/// places overlap. `PlaceConflictBias::NoOverlap` assumes that for the purposes of the predicate
+/// being run in the calling context, the conservative choice is to assume the compared indices
+/// are disjoint (and therefore, do not overlap).
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub(crate) enum PlaceConflictBias {
+ Overlap,
+ NoOverlap,
+}
+
+/// Helper function for checking if places conflict with a mutable borrow and deep access depth.
+/// This is used to check for places conflicting outside of the borrow checking code (such as in
+/// dataflow).
+pub(crate) fn places_conflict<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ body: &Body<'tcx>,
+ borrow_place: Place<'tcx>,
+ access_place: Place<'tcx>,
+ bias: PlaceConflictBias,
+) -> bool {
+ borrow_conflicts_with_place(
+ tcx,
+ body,
+ borrow_place,
+ BorrowKind::Mut { allow_two_phase_borrow: true },
+ access_place.as_ref(),
+ AccessDepth::Deep,
+ bias,
+ )
+}
+
+/// Checks whether the `borrow_place` conflicts with the `access_place` given a borrow kind and
+/// access depth. The `bias` parameter is used to determine how the unknowable (comparing runtime
+/// array indices, for example) should be interpreted - this depends on what the caller wants in
+/// order to make the conservative choice and preserve soundness.
+pub(super) fn borrow_conflicts_with_place<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ body: &Body<'tcx>,
+ borrow_place: Place<'tcx>,
+ borrow_kind: BorrowKind,
+ access_place: PlaceRef<'tcx>,
+ access: AccessDepth,
+ bias: PlaceConflictBias,
+) -> bool {
+ debug!(
+ "borrow_conflicts_with_place({:?}, {:?}, {:?}, {:?})",
+ borrow_place, access_place, access, bias,
+ );
+
+ // This Local/Local case is handled by the more general code below, but
+ // it's so common that it's a speed win to check for it first.
+ if let Some(l1) = borrow_place.as_local() && let Some(l2) = access_place.as_local() {
+ return l1 == l2;
+ }
+
+ place_components_conflict(tcx, body, borrow_place, borrow_kind, access_place, access, bias)
+}
+
+fn place_components_conflict<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ body: &Body<'tcx>,
+ borrow_place: Place<'tcx>,
+ borrow_kind: BorrowKind,
+ access_place: PlaceRef<'tcx>,
+ access: AccessDepth,
+ bias: PlaceConflictBias,
+) -> bool {
+ // The borrowck rules for proving disjointness are applied from the "root" of the
+ // borrow forwards, iterating over "similar" projections in lockstep until
+ // we can prove overlap one way or another. Essentially, we treat `Overlap` as
+ // a monoid and report a conflict if the product ends up not being `Disjoint`.
+ //
+ // At each step, if we didn't run out of borrow or place, we know that our elements
+ // have the same type, and that they only overlap if they are the identical.
+ //
+ // For example, if we are comparing these:
+ // BORROW: (*x1[2].y).z.a
+ // ACCESS: (*x1[i].y).w.b
+ //
+ // Then our steps are:
+ // x1 | x1 -- places are the same
+ // x1[2] | x1[i] -- equal or disjoint (disjoint if indexes differ)
+ // x1[2].y | x1[i].y -- equal or disjoint
+ // *x1[2].y | *x1[i].y -- equal or disjoint
+ // (*x1[2].y).z | (*x1[i].y).w -- we are disjoint and don't need to check more!
+ //
+ // Because `zip` does potentially bad things to the iterator inside, this loop
+ // also handles the case where the access might be a *prefix* of the borrow, e.g.
+ //
+ // BORROW: (*x1[2].y).z.a
+ // ACCESS: x1[i].y
+ //
+ // Then our steps are:
+ // x1 | x1 -- places are the same
+ // x1[2] | x1[i] -- equal or disjoint (disjoint if indexes differ)
+ // x1[2].y | x1[i].y -- equal or disjoint
+ //
+ // -- here we run out of access - the borrow can access a part of it. If this
+ // is a full deep access, then we *know* the borrow conflicts with it. However,
+ // if the access is shallow, then we can proceed:
+ //
+ // x1[2].y | (*x1[i].y) -- a deref! the access can't get past this, so we
+ // are disjoint
+ //
+ // Our invariant is, that at each step of the iteration:
+ // - If we didn't run out of access to match, our borrow and access are comparable
+ // and either equal or disjoint.
+ // - If we did run out of access, the borrow can access a part of it.
+
+ let borrow_local = borrow_place.local;
+ let access_local = access_place.local;
+
+ match place_base_conflict(borrow_local, access_local) {
+ Overlap::Arbitrary => {
+ bug!("Two base can't return Arbitrary");
+ }
+ Overlap::EqualOrDisjoint => {
+ // This is the recursive case - proceed to the next element.
+ }
+ Overlap::Disjoint => {
+ // We have proven the borrow disjoint - further
+ // projections will remain disjoint.
+ debug!("borrow_conflicts_with_place: disjoint");
+ return false;
+ }
+ }
+
+ // loop invariant: borrow_c is always either equal to access_c or disjoint from it.
+ for (i, (borrow_c, &access_c)) in
+ iter::zip(borrow_place.projection, access_place.projection).enumerate()
+ {
+ debug!("borrow_conflicts_with_place: borrow_c = {:?}", borrow_c);
+ let borrow_proj_base = &borrow_place.projection[..i];
+
+ debug!("borrow_conflicts_with_place: access_c = {:?}", access_c);
+
+ // Borrow and access path both have more components.
+ //
+ // Examples:
+ //
+ // - borrow of `a.(...)`, access to `a.(...)`
+ // - borrow of `a.(...)`, access to `b.(...)`
+ //
+ // Here we only see the components we have checked so
+ // far (in our examples, just the first component). We
+ // check whether the components being borrowed vs
+ // accessed are disjoint (as in the second example,
+ // but not the first).
+ match place_projection_conflict(
+ tcx,
+ body,
+ borrow_local,
+ borrow_proj_base,
+ borrow_c,
+ access_c,
+ bias,
+ ) {
+ Overlap::Arbitrary => {
+ // We have encountered different fields of potentially
+ // the same union - the borrow now partially overlaps.
+ //
+ // There is no *easy* way of comparing the fields
+ // further on, because they might have different types
+ // (e.g., borrows of `u.a.0` and `u.b.y` where `.0` and
+ // `.y` come from different structs).
+ //
+ // We could try to do some things here - e.g., count
+ // dereferences - but that's probably not a good
+ // idea, at least for now, so just give up and
+ // report a conflict. This is unsafe code anyway so
+ // the user could always use raw pointers.
+ debug!("borrow_conflicts_with_place: arbitrary -> conflict");
+ return true;
+ }
+ Overlap::EqualOrDisjoint => {
+ // This is the recursive case - proceed to the next element.
+ }
+ Overlap::Disjoint => {
+ // We have proven the borrow disjoint - further
+ // projections will remain disjoint.
+ debug!("borrow_conflicts_with_place: disjoint");
+ return false;
+ }
+ }
+ }
+
+ if borrow_place.projection.len() > access_place.projection.len() {
+ for (i, elem) in borrow_place.projection[access_place.projection.len()..].iter().enumerate()
+ {
+ // Borrow path is longer than the access path. Examples:
+ //
+ // - borrow of `a.b.c`, access to `a.b`
+ //
+ // Here, we know that the borrow can access a part of
+ // our place. This is a conflict if that is a part our
+ // access cares about.
+
+ let proj_base = &borrow_place.projection[..access_place.projection.len() + i];
+ let base_ty = Place::ty_from(borrow_local, proj_base, body, tcx).ty;
+
+ match (elem, &base_ty.kind(), access) {
+ (_, _, Shallow(Some(ArtificialField::ArrayLength)))
+ | (_, _, Shallow(Some(ArtificialField::ShallowBorrow))) => {
+ // The array length is like additional fields on the
+ // type; it does not overlap any existing data there.
+ // Furthermore, if cannot actually be a prefix of any
+ // borrowed place (at least in MIR as it is currently.)
+ //
+ // e.g., a (mutable) borrow of `a[5]` while we read the
+ // array length of `a`.
+ debug!("borrow_conflicts_with_place: implicit field");
+ return false;
+ }
+
+ (ProjectionElem::Deref, _, Shallow(None)) => {
+ // e.g., a borrow of `*x.y` while we shallowly access `x.y` or some
+ // prefix thereof - the shallow access can't touch anything behind
+ // the pointer.
+ debug!("borrow_conflicts_with_place: shallow access behind ptr");
+ return false;
+ }
+ (ProjectionElem::Deref, ty::Ref(_, _, hir::Mutability::Not), _) => {
+ // Shouldn't be tracked
+ bug!("Tracking borrow behind shared reference.");
+ }
+ (ProjectionElem::Deref, ty::Ref(_, _, hir::Mutability::Mut), AccessDepth::Drop) => {
+ // Values behind a mutable reference are not access either by dropping a
+ // value, or by StorageDead
+ debug!("borrow_conflicts_with_place: drop access behind ptr");
+ return false;
+ }
+
+ (ProjectionElem::Field { .. }, ty::Adt(def, _), AccessDepth::Drop) => {
+ // Drop can read/write arbitrary projections, so places
+ // conflict regardless of further projections.
+ if def.has_dtor(tcx) {
+ return true;
+ }
+ }
+
+ (ProjectionElem::Deref, _, Deep)
+ | (ProjectionElem::Deref, _, AccessDepth::Drop)
+ | (ProjectionElem::Field { .. }, _, _)
+ | (ProjectionElem::Index { .. }, _, _)
+ | (ProjectionElem::ConstantIndex { .. }, _, _)
+ | (ProjectionElem::Subslice { .. }, _, _)
+ | (ProjectionElem::Downcast { .. }, _, _) => {
+ // Recursive case. This can still be disjoint on a
+ // further iteration if this a shallow access and
+ // there's a deref later on, e.g., a borrow
+ // of `*x.y` while accessing `x`.
+ }
+ }
+ }
+ }
+
+ // Borrow path ran out but access path may not
+ // have. Examples:
+ //
+ // - borrow of `a.b`, access to `a.b.c`
+ // - borrow of `a.b`, access to `a.b`
+ //
+ // In the first example, where we didn't run out of
+ // access, the borrow can access all of our place, so we
+ // have a conflict.
+ //
+ // If the second example, where we did, then we still know
+ // that the borrow can access a *part* of our place that
+ // our access cares about, so we still have a conflict.
+ if borrow_kind == BorrowKind::Shallow
+ && borrow_place.projection.len() < access_place.projection.len()
+ {
+ debug!("borrow_conflicts_with_place: shallow borrow");
+ false
+ } else {
+ debug!("borrow_conflicts_with_place: full borrow, CONFLICT");
+ true
+ }
+}
+
+// Given that the bases of `elem1` and `elem2` are always either equal
+// or disjoint (and have the same type!), return the overlap situation
+// between `elem1` and `elem2`.
+fn place_base_conflict(l1: Local, l2: Local) -> Overlap {
+ if l1 == l2 {
+ // the same local - base case, equal
+ debug!("place_element_conflict: DISJOINT-OR-EQ-LOCAL");
+ Overlap::EqualOrDisjoint
+ } else {
+ // different locals - base case, disjoint
+ debug!("place_element_conflict: DISJOINT-LOCAL");
+ Overlap::Disjoint
+ }
+}
+
+// Given that the bases of `elem1` and `elem2` are always either equal
+// or disjoint (and have the same type!), return the overlap situation
+// between `elem1` and `elem2`.
+fn place_projection_conflict<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ body: &Body<'tcx>,
+ pi1_local: Local,
+ pi1_proj_base: &[PlaceElem<'tcx>],
+ pi1_elem: PlaceElem<'tcx>,
+ pi2_elem: PlaceElem<'tcx>,
+ bias: PlaceConflictBias,
+) -> Overlap {
+ match (pi1_elem, pi2_elem) {
+ (ProjectionElem::Deref, ProjectionElem::Deref) => {
+ // derefs (e.g., `*x` vs. `*x`) - recur.
+ debug!("place_element_conflict: DISJOINT-OR-EQ-DEREF");
+ Overlap::EqualOrDisjoint
+ }
+ (ProjectionElem::Field(f1, _), ProjectionElem::Field(f2, _)) => {
+ if f1 == f2 {
+ // same field (e.g., `a.y` vs. `a.y`) - recur.
+ debug!("place_element_conflict: DISJOINT-OR-EQ-FIELD");
+ Overlap::EqualOrDisjoint
+ } else {
+ let ty = Place::ty_from(pi1_local, pi1_proj_base, body, tcx).ty;
+ if ty.is_union() {
+ // Different fields of a union, we are basically stuck.
+ debug!("place_element_conflict: STUCK-UNION");
+ Overlap::Arbitrary
+ } else {
+ // Different fields of a struct (`a.x` vs. `a.y`). Disjoint!
+ debug!("place_element_conflict: DISJOINT-FIELD");
+ Overlap::Disjoint
+ }
+ }
+ }
+ (ProjectionElem::Downcast(_, v1), ProjectionElem::Downcast(_, v2)) => {
+ // different variants are treated as having disjoint fields,
+ // even if they occupy the same "space", because it's
+ // impossible for 2 variants of the same enum to exist
+ // (and therefore, to be borrowed) at the same time.
+ //
+ // Note that this is different from unions - we *do* allow
+ // this code to compile:
+ //
+ // ```
+ // fn foo(x: &mut Result<i32, i32>) {
+ // let mut v = None;
+ // if let Ok(ref mut a) = *x {
+ // v = Some(a);
+ // }
+ // // here, you would *think* that the
+ // // *entirety* of `x` would be borrowed,
+ // // but in fact only the `Ok` variant is,
+ // // so the `Err` variant is *entirely free*:
+ // if let Err(ref mut a) = *x {
+ // v = Some(a);
+ // }
+ // drop(v);
+ // }
+ // ```
+ if v1 == v2 {
+ debug!("place_element_conflict: DISJOINT-OR-EQ-FIELD");
+ Overlap::EqualOrDisjoint
+ } else {
+ debug!("place_element_conflict: DISJOINT-FIELD");
+ Overlap::Disjoint
+ }
+ }
+ (
+ ProjectionElem::Index(..),
+ ProjectionElem::Index(..)
+ | ProjectionElem::ConstantIndex { .. }
+ | ProjectionElem::Subslice { .. },
+ )
+ | (
+ ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. },
+ ProjectionElem::Index(..),
+ ) => {
+ // Array indexes (`a[0]` vs. `a[i]`). These can either be disjoint
+ // (if the indexes differ) or equal (if they are the same).
+ match bias {
+ PlaceConflictBias::Overlap => {
+ // If we are biased towards overlapping, then this is the recursive
+ // case that gives "equal *or* disjoint" its meaning.
+ debug!("place_element_conflict: DISJOINT-OR-EQ-ARRAY-INDEX");
+ Overlap::EqualOrDisjoint
+ }
+ PlaceConflictBias::NoOverlap => {
+ // If we are biased towards no overlapping, then this is disjoint.
+ debug!("place_element_conflict: DISJOINT-ARRAY-INDEX");
+ Overlap::Disjoint
+ }
+ }
+ }
+ (
+ ProjectionElem::ConstantIndex { offset: o1, min_length: _, from_end: false },
+ ProjectionElem::ConstantIndex { offset: o2, min_length: _, from_end: false },
+ )
+ | (
+ ProjectionElem::ConstantIndex { offset: o1, min_length: _, from_end: true },
+ ProjectionElem::ConstantIndex { offset: o2, min_length: _, from_end: true },
+ ) => {
+ if o1 == o2 {
+ debug!("place_element_conflict: DISJOINT-OR-EQ-ARRAY-CONSTANT-INDEX");
+ Overlap::EqualOrDisjoint
+ } else {
+ debug!("place_element_conflict: DISJOINT-ARRAY-CONSTANT-INDEX");
+ Overlap::Disjoint
+ }
+ }
+ (
+ ProjectionElem::ConstantIndex {
+ offset: offset_from_begin,
+ min_length: min_length1,
+ from_end: false,
+ },
+ ProjectionElem::ConstantIndex {
+ offset: offset_from_end,
+ min_length: min_length2,
+ from_end: true,
+ },
+ )
+ | (
+ ProjectionElem::ConstantIndex {
+ offset: offset_from_end,
+ min_length: min_length1,
+ from_end: true,
+ },
+ ProjectionElem::ConstantIndex {
+ offset: offset_from_begin,
+ min_length: min_length2,
+ from_end: false,
+ },
+ ) => {
+ // both patterns matched so it must be at least the greater of the two
+ let min_length = max(min_length1, min_length2);
+ // `offset_from_end` can be in range `[1..min_length]`, 1 indicates the last
+ // element (like -1 in Python) and `min_length` the first.
+ // Therefore, `min_length - offset_from_end` gives the minimal possible
+ // offset from the beginning
+ if offset_from_begin >= min_length - offset_from_end {
+ debug!("place_element_conflict: DISJOINT-OR-EQ-ARRAY-CONSTANT-INDEX-FE");
+ Overlap::EqualOrDisjoint
+ } else {
+ debug!("place_element_conflict: DISJOINT-ARRAY-CONSTANT-INDEX-FE");
+ Overlap::Disjoint
+ }
+ }
+ (
+ ProjectionElem::ConstantIndex { offset, min_length: _, from_end: false },
+ ProjectionElem::Subslice { from, to, from_end: false },
+ )
+ | (
+ ProjectionElem::Subslice { from, to, from_end: false },
+ ProjectionElem::ConstantIndex { offset, min_length: _, from_end: false },
+ ) => {
+ if (from..to).contains(&offset) {
+ debug!("place_element_conflict: DISJOINT-OR-EQ-ARRAY-CONSTANT-INDEX-SUBSLICE");
+ Overlap::EqualOrDisjoint
+ } else {
+ debug!("place_element_conflict: DISJOINT-ARRAY-CONSTANT-INDEX-SUBSLICE");
+ Overlap::Disjoint
+ }
+ }
+ (
+ ProjectionElem::ConstantIndex { offset, min_length: _, from_end: false },
+ ProjectionElem::Subslice { from, .. },
+ )
+ | (
+ ProjectionElem::Subslice { from, .. },
+ ProjectionElem::ConstantIndex { offset, min_length: _, from_end: false },
+ ) => {
+ if offset >= from {
+ debug!("place_element_conflict: DISJOINT-OR-EQ-SLICE-CONSTANT-INDEX-SUBSLICE");
+ Overlap::EqualOrDisjoint
+ } else {
+ debug!("place_element_conflict: DISJOINT-SLICE-CONSTANT-INDEX-SUBSLICE");
+ Overlap::Disjoint
+ }
+ }
+ (
+ ProjectionElem::ConstantIndex { offset, min_length: _, from_end: true },
+ ProjectionElem::Subslice { to, from_end: true, .. },
+ )
+ | (
+ ProjectionElem::Subslice { to, from_end: true, .. },
+ ProjectionElem::ConstantIndex { offset, min_length: _, from_end: true },
+ ) => {
+ if offset > to {
+ debug!(
+ "place_element_conflict: \
+ DISJOINT-OR-EQ-SLICE-CONSTANT-INDEX-SUBSLICE-FE"
+ );
+ Overlap::EqualOrDisjoint
+ } else {
+ debug!("place_element_conflict: DISJOINT-SLICE-CONSTANT-INDEX-SUBSLICE-FE");
+ Overlap::Disjoint
+ }
+ }
+ (
+ ProjectionElem::Subslice { from: f1, to: t1, from_end: false },
+ ProjectionElem::Subslice { from: f2, to: t2, from_end: false },
+ ) => {
+ if f2 >= t1 || f1 >= t2 {
+ debug!("place_element_conflict: DISJOINT-ARRAY-SUBSLICES");
+ Overlap::Disjoint
+ } else {
+ debug!("place_element_conflict: DISJOINT-OR-EQ-ARRAY-SUBSLICES");
+ Overlap::EqualOrDisjoint
+ }
+ }
+ (ProjectionElem::Subslice { .. }, ProjectionElem::Subslice { .. }) => {
+ debug!("place_element_conflict: DISJOINT-OR-EQ-SLICE-SUBSLICES");
+ Overlap::EqualOrDisjoint
+ }
+ (
+ ProjectionElem::Deref
+ | ProjectionElem::Field(..)
+ | ProjectionElem::Index(..)
+ | ProjectionElem::ConstantIndex { .. }
+ | ProjectionElem::Subslice { .. }
+ | ProjectionElem::Downcast(..),
+ _,
+ ) => bug!(
+ "mismatched projections in place_element_conflict: {:?} and {:?}",
+ pi1_elem,
+ pi2_elem
+ ),
+ }
+}
diff --git a/compiler/rustc_borrowck/src/prefixes.rs b/compiler/rustc_borrowck/src/prefixes.rs
new file mode 100644
index 000000000..bdf2becb7
--- /dev/null
+++ b/compiler/rustc_borrowck/src/prefixes.rs
@@ -0,0 +1,145 @@
+//! From the NLL RFC: "The deep [aka 'supporting'] prefixes for an
+//! place are formed by stripping away fields and derefs, except that
+//! we stop when we reach the deref of a shared reference. [...] "
+//!
+//! "Shallow prefixes are found by stripping away fields, but stop at
+//! any dereference. So: writing a path like `a` is illegal if `a.b`
+//! is borrowed. But: writing `a` is legal if `*a` is borrowed,
+//! whether or not `a` is a shared or mutable reference. [...] "
+
+use super::MirBorrowckCtxt;
+
+use rustc_hir as hir;
+use rustc_middle::mir::{Body, PlaceRef, ProjectionElem};
+use rustc_middle::ty::{self, TyCtxt};
+
+pub trait IsPrefixOf<'tcx> {
+ fn is_prefix_of(&self, other: PlaceRef<'tcx>) -> bool;
+}
+
+impl<'tcx> IsPrefixOf<'tcx> for PlaceRef<'tcx> {
+ fn is_prefix_of(&self, other: PlaceRef<'tcx>) -> bool {
+ self.local == other.local
+ && self.projection.len() <= other.projection.len()
+ && self.projection == &other.projection[..self.projection.len()]
+ }
+}
+
+pub(super) struct Prefixes<'cx, 'tcx> {
+ body: &'cx Body<'tcx>,
+ tcx: TyCtxt<'tcx>,
+ kind: PrefixSet,
+ next: Option<PlaceRef<'tcx>>,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub(super) enum PrefixSet {
+ /// Doesn't stop until it returns the base case (a Local or
+ /// Static prefix).
+ All,
+ /// Stops at any dereference.
+ Shallow,
+ /// Stops at the deref of a shared reference.
+ Supporting,
+}
+
+impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
+ /// Returns an iterator over the prefixes of `place`
+ /// (inclusive) from longest to smallest, potentially
+ /// terminating the iteration early based on `kind`.
+ pub(super) fn prefixes(
+ &self,
+ place_ref: PlaceRef<'tcx>,
+ kind: PrefixSet,
+ ) -> Prefixes<'cx, 'tcx> {
+ Prefixes { next: Some(place_ref), kind, body: self.body, tcx: self.infcx.tcx }
+ }
+}
+
+impl<'cx, 'tcx> Iterator for Prefixes<'cx, 'tcx> {
+ type Item = PlaceRef<'tcx>;
+ fn next(&mut self) -> Option<Self::Item> {
+ let mut cursor = self.next?;
+
+ // Post-processing `place`: Enqueue any remaining
+ // work. Also, `place` may not be a prefix itself, but
+ // may hold one further down (e.g., we never return
+ // downcasts here, but may return a base of a downcast).
+
+ 'cursor: loop {
+ match cursor.last_projection() {
+ None => {
+ self.next = None;
+ return Some(cursor);
+ }
+ Some((cursor_base, elem)) => {
+ match elem {
+ ProjectionElem::Field(_ /*field*/, _ /*ty*/) => {
+ // FIXME: add union handling
+ self.next = Some(cursor_base);
+ return Some(cursor);
+ }
+ ProjectionElem::Downcast(..)
+ | ProjectionElem::Subslice { .. }
+ | ProjectionElem::ConstantIndex { .. }
+ | ProjectionElem::Index(_) => {
+ cursor = cursor_base;
+ continue 'cursor;
+ }
+ ProjectionElem::Deref => {
+ // (handled below)
+ }
+ }
+
+ assert_eq!(elem, ProjectionElem::Deref);
+
+ match self.kind {
+ PrefixSet::Shallow => {
+ // Shallow prefixes are found by stripping away
+ // fields, but stop at *any* dereference.
+ // So we can just stop the traversal now.
+ self.next = None;
+ return Some(cursor);
+ }
+ PrefixSet::All => {
+ // All prefixes: just blindly enqueue the base
+ // of the projection.
+ self.next = Some(cursor_base);
+ return Some(cursor);
+ }
+ PrefixSet::Supporting => {
+ // Fall through!
+ }
+ }
+
+ assert_eq!(self.kind, PrefixSet::Supporting);
+ // Supporting prefixes: strip away fields and
+ // derefs, except we stop at the deref of a shared
+ // reference.
+
+ let ty = cursor_base.ty(self.body, self.tcx).ty;
+ match ty.kind() {
+ ty::RawPtr(_) | ty::Ref(_ /*rgn*/, _ /*ty*/, hir::Mutability::Not) => {
+ // don't continue traversing over derefs of raw pointers or shared
+ // borrows.
+ self.next = None;
+ return Some(cursor);
+ }
+
+ ty::Ref(_ /*rgn*/, _ /*ty*/, hir::Mutability::Mut) => {
+ self.next = Some(cursor_base);
+ return Some(cursor);
+ }
+
+ ty::Adt(..) if ty.is_box() => {
+ self.next = Some(cursor_base);
+ return Some(cursor);
+ }
+
+ _ => panic!("unknown type fed to Projection Deref."),
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/compiler/rustc_borrowck/src/region_infer/dump_mir.rs b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs
new file mode 100644
index 000000000..fe5193102
--- /dev/null
+++ b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs
@@ -0,0 +1,93 @@
+//! As part of generating the regions, if you enable `-Zdump-mir=nll`,
+//! we will generate an annotated copy of the MIR that includes the
+//! state of region inference. This code handles emitting the region
+//! context internal state.
+
+use super::{OutlivesConstraint, RegionInferenceContext};
+use crate::type_check::Locations;
+use rustc_infer::infer::NllRegionVariableOrigin;
+use rustc_middle::ty::TyCtxt;
+use std::io::{self, Write};
+
+// Room for "'_#NNNNr" before things get misaligned.
+// Easy enough to fix if this ever doesn't seem like
+// enough.
+const REGION_WIDTH: usize = 8;
+
+impl<'tcx> RegionInferenceContext<'tcx> {
+ /// Write out our state into the `.mir` files.
+ pub(crate) fn dump_mir(&self, tcx: TyCtxt<'tcx>, out: &mut dyn Write) -> io::Result<()> {
+ writeln!(out, "| Free Region Mapping")?;
+
+ for region in self.regions() {
+ if let NllRegionVariableOrigin::FreeRegion = self.definitions[region].origin {
+ let classification = self.universal_regions.region_classification(region).unwrap();
+ let outlived_by = self.universal_region_relations.regions_outlived_by(region);
+ writeln!(
+ out,
+ "| {r:rw$?} | {c:cw$?} | {ob:?}",
+ r = region,
+ rw = REGION_WIDTH,
+ c = classification,
+ cw = 8, // "External" at most
+ ob = outlived_by
+ )?;
+ }
+ }
+
+ writeln!(out, "|")?;
+ writeln!(out, "| Inferred Region Values")?;
+ for region in self.regions() {
+ writeln!(
+ out,
+ "| {r:rw$?} | {ui:4?} | {v}",
+ r = region,
+ rw = REGION_WIDTH,
+ ui = self.region_universe(region),
+ v = self.region_value_str(region),
+ )?;
+ }
+
+ writeln!(out, "|")?;
+ writeln!(out, "| Inference Constraints")?;
+ self.for_each_constraint(tcx, &mut |msg| writeln!(out, "| {}", msg))?;
+
+ Ok(())
+ }
+
+ /// Debugging aid: Invokes the `with_msg` callback repeatedly with
+ /// our internal region constraints. These are dumped into the
+ /// -Zdump-mir file so that we can figure out why the region
+ /// inference resulted in the values that it did when debugging.
+ fn for_each_constraint(
+ &self,
+ tcx: TyCtxt<'tcx>,
+ with_msg: &mut dyn FnMut(&str) -> io::Result<()>,
+ ) -> io::Result<()> {
+ for region in self.definitions.indices() {
+ let value = self.liveness_constraints.region_value_str(region);
+ if value != "{}" {
+ with_msg(&format!("{:?} live at {}", region, value))?;
+ }
+ }
+
+ let mut constraints: Vec<_> = self.constraints.outlives().iter().collect();
+ constraints.sort_by_key(|c| (c.sup, c.sub));
+ for constraint in &constraints {
+ let OutlivesConstraint { sup, sub, locations, category, span, variance_info: _ } =
+ constraint;
+ let (name, arg) = match locations {
+ Locations::All(span) => {
+ ("All", tcx.sess.source_map().span_to_embeddable_string(*span))
+ }
+ Locations::Single(loc) => ("Single", format!("{:?}", loc)),
+ };
+ with_msg(&format!(
+ "{:?}: {:?} due to {:?} at {}({}) ({:?}",
+ sup, sub, category, name, arg, span
+ ))?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/compiler/rustc_borrowck/src/region_infer/graphviz.rs b/compiler/rustc_borrowck/src/region_infer/graphviz.rs
new file mode 100644
index 000000000..f31ccd74c
--- /dev/null
+++ b/compiler/rustc_borrowck/src/region_infer/graphviz.rs
@@ -0,0 +1,140 @@
+//! This module provides linkage between RegionInferenceContext and
+//! `rustc_graphviz` traits, specialized to attaching borrowck analysis
+//! data to rendered labels.
+
+use std::borrow::Cow;
+use std::io::{self, Write};
+
+use super::*;
+use crate::constraints::OutlivesConstraint;
+use rustc_graphviz as dot;
+
+impl<'tcx> RegionInferenceContext<'tcx> {
+ /// Write out the region constraint graph.
+ pub(crate) fn dump_graphviz_raw_constraints(&self, mut w: &mut dyn Write) -> io::Result<()> {
+ dot::render(&RawConstraints { regioncx: self }, &mut w)
+ }
+
+ /// Write out the region constraint graph.
+ pub(crate) fn dump_graphviz_scc_constraints(&self, mut w: &mut dyn Write) -> io::Result<()> {
+ let mut nodes_per_scc: IndexVec<ConstraintSccIndex, _> =
+ self.constraint_sccs.all_sccs().map(|_| Vec::new()).collect();
+
+ for region in self.definitions.indices() {
+ let scc = self.constraint_sccs.scc(region);
+ nodes_per_scc[scc].push(region);
+ }
+
+ dot::render(&SccConstraints { regioncx: self, nodes_per_scc }, &mut w)
+ }
+}
+
+struct RawConstraints<'a, 'tcx> {
+ regioncx: &'a RegionInferenceContext<'tcx>,
+}
+
+impl<'a, 'this, 'tcx> dot::Labeller<'this> for RawConstraints<'a, 'tcx> {
+ type Node = RegionVid;
+ type Edge = OutlivesConstraint<'tcx>;
+
+ fn graph_id(&'this self) -> dot::Id<'this> {
+ dot::Id::new("RegionInferenceContext").unwrap()
+ }
+ fn node_id(&'this self, n: &RegionVid) -> dot::Id<'this> {
+ dot::Id::new(format!("r{}", n.index())).unwrap()
+ }
+ fn node_shape(&'this self, _node: &RegionVid) -> Option<dot::LabelText<'this>> {
+ Some(dot::LabelText::LabelStr(Cow::Borrowed("box")))
+ }
+ fn node_label(&'this self, n: &RegionVid) -> dot::LabelText<'this> {
+ dot::LabelText::LabelStr(format!("{:?}", n).into())
+ }
+ fn edge_label(&'this self, e: &OutlivesConstraint<'tcx>) -> dot::LabelText<'this> {
+ dot::LabelText::LabelStr(format!("{:?}", e.locations).into())
+ }
+}
+
+impl<'a, 'this, 'tcx> dot::GraphWalk<'this> for RawConstraints<'a, 'tcx> {
+ type Node = RegionVid;
+ type Edge = OutlivesConstraint<'tcx>;
+
+ fn nodes(&'this self) -> dot::Nodes<'this, RegionVid> {
+ let vids: Vec<RegionVid> = self.regioncx.definitions.indices().collect();
+ vids.into()
+ }
+ fn edges(&'this self) -> dot::Edges<'this, OutlivesConstraint<'tcx>> {
+ (&self.regioncx.constraints.outlives().raw[..]).into()
+ }
+
+ // Render `a: b` as `a -> b`, indicating the flow
+ // of data during inference.
+
+ fn source(&'this self, edge: &OutlivesConstraint<'tcx>) -> RegionVid {
+ edge.sup
+ }
+
+ fn target(&'this self, edge: &OutlivesConstraint<'tcx>) -> RegionVid {
+ edge.sub
+ }
+}
+
+struct SccConstraints<'a, 'tcx> {
+ regioncx: &'a RegionInferenceContext<'tcx>,
+ nodes_per_scc: IndexVec<ConstraintSccIndex, Vec<RegionVid>>,
+}
+
+impl<'a, 'this, 'tcx> dot::Labeller<'this> for SccConstraints<'a, 'tcx> {
+ type Node = ConstraintSccIndex;
+ type Edge = (ConstraintSccIndex, ConstraintSccIndex);
+
+ fn graph_id(&'this self) -> dot::Id<'this> {
+ dot::Id::new("RegionInferenceContext".to_string()).unwrap()
+ }
+ fn node_id(&'this self, n: &ConstraintSccIndex) -> dot::Id<'this> {
+ dot::Id::new(format!("r{}", n.index())).unwrap()
+ }
+ fn node_shape(&'this self, _node: &ConstraintSccIndex) -> Option<dot::LabelText<'this>> {
+ Some(dot::LabelText::LabelStr(Cow::Borrowed("box")))
+ }
+ fn node_label(&'this self, n: &ConstraintSccIndex) -> dot::LabelText<'this> {
+ let nodes = &self.nodes_per_scc[*n];
+ dot::LabelText::LabelStr(format!("{:?} = {:?}", n, nodes).into())
+ }
+}
+
+impl<'a, 'this, 'tcx> dot::GraphWalk<'this> for SccConstraints<'a, 'tcx> {
+ type Node = ConstraintSccIndex;
+ type Edge = (ConstraintSccIndex, ConstraintSccIndex);
+
+ fn nodes(&'this self) -> dot::Nodes<'this, ConstraintSccIndex> {
+ let vids: Vec<ConstraintSccIndex> = self.regioncx.constraint_sccs.all_sccs().collect();
+ vids.into()
+ }
+ fn edges(&'this self) -> dot::Edges<'this, (ConstraintSccIndex, ConstraintSccIndex)> {
+ let edges: Vec<_> = self
+ .regioncx
+ .constraint_sccs
+ .all_sccs()
+ .flat_map(|scc_a| {
+ self.regioncx
+ .constraint_sccs
+ .successors(scc_a)
+ .iter()
+ .map(move |&scc_b| (scc_a, scc_b))
+ })
+ .collect();
+
+ edges.into()
+ }
+
+ // Render `a: b` as `a -> b`, indicating the flow
+ // of data during inference.
+
+ fn source(&'this self, edge: &(ConstraintSccIndex, ConstraintSccIndex)) -> ConstraintSccIndex {
+ edge.0
+ }
+
+ fn target(&'this self, edge: &(ConstraintSccIndex, ConstraintSccIndex)) -> ConstraintSccIndex {
+ edge.1
+ }
+}
diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs
new file mode 100644
index 000000000..2894c6d29
--- /dev/null
+++ b/compiler/rustc_borrowck/src/region_infer/mod.rs
@@ -0,0 +1,2365 @@
+use std::collections::VecDeque;
+use std::rc::Rc;
+
+use rustc_data_structures::binary_search_util;
+use rustc_data_structures::frozen::Frozen;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::graph::scc::Sccs;
+use rustc_errors::Diagnostic;
+use rustc_hir::def_id::{DefId, CRATE_DEF_ID};
+use rustc_hir::CRATE_HIR_ID;
+use rustc_index::vec::IndexVec;
+use rustc_infer::infer::canonical::QueryOutlivesConstraint;
+use rustc_infer::infer::outlives::test_type_match;
+use rustc_infer::infer::region_constraints::{GenericKind, VarInfos, VerifyBound, VerifyIfEq};
+use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin, RegionVariableOrigin};
+use rustc_middle::mir::{
+ Body, ClosureOutlivesRequirement, ClosureOutlivesSubject, ClosureRegionRequirements,
+ ConstraintCategory, Local, Location, ReturnConstraint,
+};
+use rustc_middle::traits::ObligationCause;
+use rustc_middle::traits::ObligationCauseCode;
+use rustc_middle::ty::{
+ self, subst::SubstsRef, RegionVid, Ty, TyCtxt, TypeFoldable, TypeVisitable,
+};
+use rustc_span::Span;
+
+use crate::{
+ constraints::{
+ graph::NormalConstraintGraph, ConstraintSccIndex, OutlivesConstraint, OutlivesConstraintSet,
+ },
+ diagnostics::{RegionErrorKind, RegionErrors, UniverseInfo},
+ member_constraints::{MemberConstraintSet, NllMemberConstraintIndex},
+ nll::{PoloniusOutput, ToRegionVid},
+ region_infer::reverse_sccs::ReverseSccGraph,
+ region_infer::values::{
+ LivenessValues, PlaceholderIndices, RegionElement, RegionValueElements, RegionValues,
+ ToElementIndex,
+ },
+ type_check::{free_region_relations::UniversalRegionRelations, Locations},
+ universal_regions::UniversalRegions,
+};
+
+mod dump_mir;
+mod graphviz;
+mod opaque_types;
+mod reverse_sccs;
+
+pub mod values;
+
+pub struct RegionInferenceContext<'tcx> {
+ pub var_infos: VarInfos,
+
+ /// Contains the definition for every region variable. Region
+ /// 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<RegionVid, RegionDefinition<'tcx>>,
+
+ /// 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<RegionVid>,
+
+ /// The outlives constraints computed by the type-check.
+ constraints: Frozen<OutlivesConstraintSet<'tcx>>,
+
+ /// 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: Frozen<NormalConstraintGraph>,
+
+ /// 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<Sccs<RegionVid, ConstraintSccIndex>>,
+
+ /// Reverse of the SCC constraint graph -- i.e., an edge `A -> B` exists if
+ /// `B: A`. This is used to compute the universal regions that are required
+ /// to outlive a given SCC. Computed lazily.
+ rev_scc_graph: Option<Rc<ReverseSccGraph>>,
+
+ /// The "R0 member of [R1..Rn]" constraints, indexed by SCC.
+ member_constraints: Rc<MemberConstraintSet<'tcx, ConstraintSccIndex>>,
+
+ /// 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<AppliedMemberConstraint>,
+
+ /// Map closure bounds to a `Span` that should be used for error reporting.
+ closure_bounds_mapping:
+ FxHashMap<Location, FxHashMap<(RegionVid, RegionVid), (ConstraintCategory<'tcx>, Span)>>,
+
+ /// Map universe indexes to information on why we created it.
+ universe_causes: FxHashMap<ty::UniverseIndex, UniverseInfo<'tcx>>,
+
+ /// 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<ConstraintSccIndex, ty::UniverseIndex>,
+
+ /// Contains a "representative" from each SCC. This will be the
+ /// minimal RegionVid belonging to that universe. It is used as a
+ /// kind of hacky way to manage checking outlives relationships,
+ /// since we can 'canonicalize' each region to the representative
+ /// 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<ConstraintSccIndex, ty::RegionVid>,
+
+ /// 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<ConstraintSccIndex>,
+
+ /// Type constraints that we check after solving.
+ type_tests: Vec<TypeTest<'tcx>>,
+
+ /// Information about the universally quantified regions in scope
+ /// on this function.
+ universal_regions: Rc<UniversalRegions<'tcx>>,
+
+ /// Information about how the universally quantified regions in
+ /// scope on this function relate to one another.
+ universal_region_relations: Frozen<UniversalRegionRelations<'tcx>>,
+}
+
+/// Each time that `apply_member_constraint` is successful, it appends
+/// one of these structs to the `member_constraints_applied` field.
+/// This is used in error reporting to trace out what happened.
+///
+/// The way that `apply_member_constraint` works is that it effectively
+/// adds a new lower bound to the SCC it is analyzing: so you wind up
+/// with `'R: 'O` where `'R` is the pick-region and `'O` is the
+/// minimal viable option.
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
+pub(crate) struct AppliedMemberConstraint {
+ /// The SCC that was affected. (The "member region".)
+ ///
+ /// The vector if `AppliedMemberConstraint` elements is kept sorted
+ /// by this field.
+ pub(crate) 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`.
+ pub(crate) min_choice: ty::RegionVid,
+
+ /// The "member constraint index" -- we can find out details about
+ /// the constraint from
+ /// `set.member_constraints[member_constraint_index]`.
+ pub(crate) member_constraint_index: NllMemberConstraintIndex,
+}
+
+pub(crate) struct RegionDefinition<'tcx> {
+ /// What kind of variable is this -- a free region? existential
+ /// variable? etc. (See the `NllRegionVariableOrigin` for more
+ /// info.)
+ pub(crate) 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.
+ pub(crate) 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.
+ pub(crate) external_name: Option<ty::Region<'tcx>>,
+}
+
+/// N.B., the variants in `Cause` are intentionally ordered. Lower
+/// values are preferred when it comes to error messages. Do not
+/// reorder willy nilly.
+#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
+pub(crate) enum Cause {
+ /// point inserted because Local was live at the given Location
+ LiveVar(Local, Location),
+
+ /// point inserted because Local was dropped at the given Location
+ DropVar(Local, Location),
+}
+
+/// A "type test" corresponds to an outlives constraint between a type
+/// and a lifetime, like `T: 'x` or `<T as Foo>::Bar: 'x`. They are
+/// translated from the `Verify` region constraints in the ordinary
+/// inference context.
+///
+/// These sorts of constraints are handled differently than ordinary
+/// constraints, at least at present. During type checking, the
+/// `InferCtxt::process_registered_region_obligations` method will
+/// attempt to convert a type test like `T: 'x` into an ordinary
+/// outlives constraint when possible (for example, `&'a T: 'b` will
+/// be converted into `'a: 'b` and registered as a `Constraint`).
+///
+/// In some cases, however, there are outlives relationships that are
+/// not converted into a region constraint, but rather into one of
+/// these "type tests". The distinction is that a type test does not
+/// influence the inference result, but instead just examines the
+/// values that we ultimately inferred for each region variable and
+/// checks that they meet certain extra criteria. If not, an error
+/// can be issued.
+///
+/// One reason for this is that these type tests typically boil down
+/// to a check like `'a: 'x` where `'a` is a universally quantified
+/// region -- and therefore not one whose value is really meant to be
+/// *inferred*, precisely (this is not always the case: one can have a
+/// type test like `<Foo as Trait<'?0>>::Bar: 'x`, where `'?0` is an
+/// inference variable). Another reason is that these type tests can
+/// involve *disjunction* -- that is, they can be satisfied in more
+/// than one way.
+///
+/// For more information about this translation, see
+/// `InferCtxt::process_registered_region_obligations` and
+/// `InferCtxt::type_must_outlive` in `rustc_infer::infer::InferCtxt`.
+#[derive(Clone, Debug)]
+pub struct TypeTest<'tcx> {
+ /// The type `T` that must outlive the region.
+ pub generic_kind: GenericKind<'tcx>,
+
+ /// The region `'x` that the type must outlive.
+ pub lower_bound: RegionVid,
+
+ /// Where did this constraint arise and why?
+ pub locations: Locations,
+
+ /// A test which, if met by the region `'x`, proves that this type
+ /// constraint is satisfied.
+ pub verify_bound: VerifyBound<'tcx>,
+}
+
+/// When we have an unmet lifetime constraint, we try to propagate it outward (e.g. to a closure
+/// environment). If we can't, it is an error.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+enum RegionRelationCheckResult {
+ Ok,
+ Propagated,
+ Error,
+}
+
+#[derive(Clone, PartialEq, Eq, Debug)]
+enum Trace<'tcx> {
+ StartRegion,
+ FromOutlivesConstraint(OutlivesConstraint<'tcx>),
+ NotVisited,
+}
+
+impl<'tcx> RegionInferenceContext<'tcx> {
+ /// Creates a new region inference context with a total of
+ /// `num_region_variables` valid inference variables; the first N
+ /// of those will be constant regions representing the free
+ /// regions defined in `universal_regions`.
+ ///
+ /// The `outlives_constraints` and `type_tests` are an initial set
+ /// of constraints produced by the MIR type check.
+ pub(crate) fn new(
+ var_infos: VarInfos,
+ universal_regions: Rc<UniversalRegions<'tcx>>,
+ placeholder_indices: Rc<PlaceholderIndices>,
+ universal_region_relations: Frozen<UniversalRegionRelations<'tcx>>,
+ outlives_constraints: OutlivesConstraintSet<'tcx>,
+ member_constraints_in: MemberConstraintSet<'tcx, RegionVid>,
+ closure_bounds_mapping: FxHashMap<
+ Location,
+ FxHashMap<(RegionVid, RegionVid), (ConstraintCategory<'tcx>, Span)>,
+ >,
+ universe_causes: FxHashMap<ty::UniverseIndex, UniverseInfo<'tcx>>,
+ type_tests: Vec<TypeTest<'tcx>>,
+ liveness_constraints: LivenessValues<RegionVid>,
+ elements: &Rc<RegionValueElements>,
+ ) -> Self {
+ // Create a RegionDefinition for each inference variable.
+ let definitions: IndexVec<_, _> = var_infos
+ .iter()
+ .map(|info| RegionDefinition::new(info.universe, info.origin))
+ .collect();
+
+ let constraints = Frozen::freeze(outlives_constraints);
+ let constraint_graph = Frozen::freeze(constraints.graph(definitions.len()));
+ let fr_static = universal_regions.fr_static;
+ let constraint_sccs = Rc::new(constraints.compute_sccs(&constraint_graph, fr_static));
+
+ let mut scc_values =
+ RegionValues::new(elements, universal_regions.len(), &placeholder_indices);
+
+ for region in liveness_constraints.rows() {
+ let scc = constraint_sccs.scc(region);
+ scc_values.merge_liveness(scc, region, &liveness_constraints);
+ }
+
+ let scc_universes = Self::compute_scc_universes(&constraint_sccs, &definitions);
+
+ let scc_representatives = Self::compute_scc_representatives(&constraint_sccs, &definitions);
+
+ let member_constraints =
+ Rc::new(member_constraints_in.into_mapped(|r| constraint_sccs.scc(r)));
+
+ let mut result = Self {
+ var_infos,
+ definitions,
+ liveness_constraints,
+ constraints,
+ constraint_graph,
+ constraint_sccs,
+ rev_scc_graph: None,
+ member_constraints,
+ member_constraints_applied: Vec::new(),
+ closure_bounds_mapping,
+ universe_causes,
+ scc_universes,
+ scc_representatives,
+ scc_values,
+ type_tests,
+ universal_regions,
+ universal_region_relations,
+ };
+
+ result.init_free_and_bound_regions();
+
+ result
+ }
+
+ /// Each SCC is the combination of many region variables which
+ /// have been equated. Therefore, we can associate a universe with
+ /// each SCC which is minimum of all the universes of its
+ /// constituent regions -- this is because whatever value the SCC
+ /// takes on must be a value that each of the regions within the
+ /// SCC could have as well. This implies that the SCC must have
+ /// the minimum, or narrowest, universe.
+ fn compute_scc_universes(
+ constraint_sccs: &Sccs<RegionVid, ConstraintSccIndex>,
+ definitions: &IndexVec<RegionVid, RegionDefinition<'tcx>>,
+ ) -> IndexVec<ConstraintSccIndex, ty::UniverseIndex> {
+ let num_sccs = constraint_sccs.num_sccs();
+ let mut scc_universes = IndexVec::from_elem_n(ty::UniverseIndex::MAX, num_sccs);
+
+ debug!("compute_scc_universes()");
+
+ // For each region R in universe U, ensure that the universe for the SCC
+ // that contains R is "no bigger" than U. This effectively sets the universe
+ // for each SCC to be the minimum of the regions within.
+ for (region_vid, region_definition) in definitions.iter_enumerated() {
+ let scc = constraint_sccs.scc(region_vid);
+ let scc_universe = &mut scc_universes[scc];
+ let scc_min = std::cmp::min(region_definition.universe, *scc_universe);
+ if scc_min != *scc_universe {
+ *scc_universe = scc_min;
+ debug!(
+ "compute_scc_universes: lowered universe of {scc:?} to {scc_min:?} \
+ because it contains {region_vid:?} in {region_universe:?}",
+ scc = scc,
+ scc_min = scc_min,
+ region_vid = region_vid,
+ region_universe = region_definition.universe,
+ );
+ }
+ }
+
+ // Walk each SCC `A` and `B` such that `A: B`
+ // and ensure that universe(A) can see universe(B).
+ //
+ // This serves to enforce the 'empty/placeholder' hierarchy
+ // (described in more detail on `RegionKind`):
+ //
+ // ```
+ // static -----+
+ // | |
+ // empty(U0) placeholder(U1)
+ // | /
+ // empty(U1)
+ // ```
+ //
+ // In particular, imagine we have variables R0 in U0 and R1
+ // created in U1, and constraints like this;
+ //
+ // ```
+ // R1: !1 // R1 outlives the placeholder in U1
+ // R1: R0 // R1 outlives R0
+ // ```
+ //
+ // Here, we wish for R1 to be `'static`, because it
+ // cannot outlive `placeholder(U1)` and `empty(U0)` any other way.
+ //
+ // Thanks to this loop, what happens is that the `R1: R0`
+ // constraint lowers the universe of `R1` to `U0`, which in turn
+ // means that the `R1: !1` constraint will (later) cause
+ // `R1` to become `'static`.
+ for scc_a in constraint_sccs.all_sccs() {
+ for &scc_b in constraint_sccs.successors(scc_a) {
+ let scc_universe_a = scc_universes[scc_a];
+ let scc_universe_b = scc_universes[scc_b];
+ let scc_universe_min = std::cmp::min(scc_universe_a, scc_universe_b);
+ if scc_universe_a != scc_universe_min {
+ scc_universes[scc_a] = scc_universe_min;
+
+ debug!(
+ "compute_scc_universes: lowered universe of {scc_a:?} to {scc_universe_min:?} \
+ because {scc_a:?}: {scc_b:?} and {scc_b:?} is in universe {scc_universe_b:?}",
+ scc_a = scc_a,
+ scc_b = scc_b,
+ scc_universe_min = scc_universe_min,
+ scc_universe_b = scc_universe_b
+ );
+ }
+ }
+ }
+
+ debug!("compute_scc_universes: scc_universe = {:#?}", scc_universes);
+
+ scc_universes
+ }
+
+ /// For each SCC, we compute a unique `RegionVid` (in fact, the
+ /// minimal one that belongs to the SCC). See
+ /// `scc_representatives` field of `RegionInferenceContext` for
+ /// more details.
+ fn compute_scc_representatives(
+ constraints_scc: &Sccs<RegionVid, ConstraintSccIndex>,
+ definitions: &IndexVec<RegionVid, RegionDefinition<'tcx>>,
+ ) -> IndexVec<ConstraintSccIndex, ty::RegionVid> {
+ let num_sccs = constraints_scc.num_sccs();
+ let next_region_vid = definitions.next_index();
+ let mut scc_representatives = IndexVec::from_elem_n(next_region_vid, num_sccs);
+
+ for region_vid in definitions.indices() {
+ let scc = constraints_scc.scc(region_vid);
+ let prev_min = scc_representatives[scc];
+ scc_representatives[scc] = region_vid.min(prev_min);
+ }
+
+ scc_representatives
+ }
+
+ /// Initializes the region variables for each universally
+ /// quantified region (lifetime parameter). The first N variables
+ /// always correspond to the regions appearing in the function
+ /// signature (both named and anonymous) and where-clauses. This
+ /// function iterates over those regions and initializes them with
+ /// minimum values.
+ ///
+ /// For example:
+ /// ```
+ /// fn foo<'a, 'b>( /* ... */ ) where 'a: 'b { /* ... */ }
+ /// ```
+ /// would initialize two variables like so:
+ /// ```ignore (illustrative)
+ /// R0 = { CFG, R0 } // 'a
+ /// R1 = { CFG, R0, R1 } // 'b
+ /// ```
+ /// Here, R0 represents `'a`, and it contains (a) the entire CFG
+ /// and (b) any universally quantified regions that it outlives,
+ /// which in this case is just itself. R1 (`'b`) in contrast also
+ /// outlives `'a` and hence contains R0 and R1.
+ fn init_free_and_bound_regions(&mut self) {
+ // Update the names (if any)
+ for (external_name, variable) in self.universal_regions.named_universal_regions() {
+ debug!(
+ "init_universal_regions: region {:?} has external name {:?}",
+ variable, external_name
+ );
+ self.definitions[variable].external_name = Some(external_name);
+ }
+
+ for variable in self.definitions.indices() {
+ let scc = self.constraint_sccs.scc(variable);
+
+ match self.definitions[variable].origin {
+ NllRegionVariableOrigin::FreeRegion => {
+ // For each free, universally quantified region X:
+
+ // Add all nodes in the CFG to liveness constraints
+ self.liveness_constraints.add_all_points(variable);
+ self.scc_values.add_all_points(scc);
+
+ // Add `end(X)` into the set for X.
+ self.scc_values.add_element(scc, variable);
+ }
+
+ NllRegionVariableOrigin::Placeholder(placeholder) => {
+ // Each placeholder region is only visible from
+ // its universe `ui` and its extensions. So we
+ // can't just add it into `scc` unless the
+ // universe of the scc can name this region.
+ let scc_universe = self.scc_universes[scc];
+ if scc_universe.can_name(placeholder.universe) {
+ self.scc_values.add_element(scc, placeholder);
+ } else {
+ debug!(
+ "init_free_and_bound_regions: placeholder {:?} is \
+ not compatible with universe {:?} of its SCC {:?}",
+ placeholder, scc_universe, scc,
+ );
+ self.add_incompatible_universe(scc);
+ }
+ }
+
+ NllRegionVariableOrigin::Existential { .. } => {
+ // For existential, regions, nothing to do.
+ }
+ }
+ }
+ }
+
+ /// Returns an iterator over all the region indices.
+ pub fn regions(&self) -> impl Iterator<Item = RegionVid> + 'tcx {
+ self.definitions.indices()
+ }
+
+ /// Given a universal region in scope on the MIR, returns the
+ /// corresponding index.
+ ///
+ /// (Panics if `r` is not a registered universal region.)
+ pub fn to_region_vid(&self, r: ty::Region<'tcx>) -> RegionVid {
+ self.universal_regions.to_region_vid(r)
+ }
+
+ /// Adds annotations for `#[rustc_regions]`; see `UniversalRegions::annotate`.
+ pub(crate) fn annotate(&self, tcx: TyCtxt<'tcx>, err: &mut Diagnostic) {
+ self.universal_regions.annotate(tcx, err)
+ }
+
+ /// Returns `true` if the region `r` contains the point `p`.
+ ///
+ /// Panics if called before `solve()` executes,
+ pub(crate) fn region_contains(&self, r: impl ToRegionVid, p: impl ToElementIndex) -> bool {
+ let scc = self.constraint_sccs.scc(r.to_region_vid());
+ self.scc_values.contains(scc, p)
+ }
+
+ /// Returns access to the value of `r` for debugging purposes.
+ pub(crate) fn region_value_str(&self, r: RegionVid) -> String {
+ let scc = self.constraint_sccs.scc(r.to_region_vid());
+ self.scc_values.region_value_str(scc)
+ }
+
+ /// Returns access to the value of `r` for debugging purposes.
+ pub(crate) fn region_universe(&self, r: RegionVid) -> ty::UniverseIndex {
+ let scc = self.constraint_sccs.scc(r.to_region_vid());
+ self.scc_universes[scc]
+ }
+
+ /// 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`.
+ pub(crate) 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,
+ |applied| applied.member_region_scc,
+ &scc,
+ )
+ }
+
+ /// Performs region inference and report errors if we see any
+ /// unsatisfiable constraints. If this is a closure, returns the
+ /// region requirements to propagate to our creator, if any.
+ #[instrument(skip(self, infcx, body, polonius_output), level = "debug")]
+ pub(super) fn solve(
+ &mut self,
+ infcx: &InferCtxt<'_, 'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ body: &Body<'tcx>,
+ polonius_output: Option<Rc<PoloniusOutput>>,
+ ) -> (Option<ClosureRegionRequirements<'tcx>>, RegionErrors<'tcx>) {
+ let mir_def_id = body.source.def_id();
+ self.propagate_constraints(body);
+
+ let mut errors_buffer = RegionErrors::new();
+
+ // If this is a closure, we can propagate unsatisfied
+ // `outlives_requirements` to our creator, so create a vector
+ // to store those. Otherwise, we'll pass in `None` to the
+ // functions below, which will trigger them to report errors
+ // eagerly.
+ let mut outlives_requirements = infcx.tcx.is_typeck_child(mir_def_id).then(Vec::new);
+
+ self.check_type_tests(
+ infcx,
+ param_env,
+ body,
+ outlives_requirements.as_mut(),
+ &mut errors_buffer,
+ );
+
+ // In Polonius mode, the errors about missing universal region relations are in the output
+ // and need to be emitted or propagated. Otherwise, we need to check whether the
+ // constraints were too strong, and if so, emit or propagate those errors.
+ if infcx.tcx.sess.opts.unstable_opts.polonius {
+ self.check_polonius_subset_errors(
+ body,
+ outlives_requirements.as_mut(),
+ &mut errors_buffer,
+ polonius_output.expect("Polonius output is unavailable despite `-Z polonius`"),
+ );
+ } else {
+ self.check_universal_regions(body, outlives_requirements.as_mut(), &mut errors_buffer);
+ }
+
+ if errors_buffer.is_empty() {
+ self.check_member_constraints(infcx, &mut errors_buffer);
+ }
+
+ let outlives_requirements = outlives_requirements.unwrap_or_default();
+
+ if outlives_requirements.is_empty() {
+ (None, errors_buffer)
+ } else {
+ let num_external_vids = self.universal_regions.num_global_and_external_regions();
+ (
+ Some(ClosureRegionRequirements { num_external_vids, outlives_requirements }),
+ errors_buffer,
+ )
+ }
+ }
+
+ /// Propagate the region constraints: this will grow the values
+ /// for each region variable until all the constraints are
+ /// satisfied. Note that some values may grow **too** large to be
+ /// feasible, but we check this later.
+ #[instrument(skip(self, _body), level = "debug")]
+ fn propagate_constraints(&mut self, _body: &Body<'tcx>) {
+ debug!("constraints={:#?}", {
+ let mut constraints: Vec<_> = self.constraints.outlives().iter().collect();
+ constraints.sort_by_key(|c| (c.sup, c.sub));
+ constraints
+ .into_iter()
+ .map(|c| (c, self.constraint_sccs.scc(c.sup), self.constraint_sccs.scc(c.sub)))
+ .collect::<Vec<_>>()
+ });
+
+ // To propagate constraints, we walk the DAG induced by the
+ // SCC. For each SCC, we visit its successors and compute
+ // their values, then we union all those values to get our
+ // own.
+ let constraint_sccs = self.constraint_sccs.clone();
+ for scc in constraint_sccs.all_sccs() {
+ self.compute_value_for_scc(scc);
+ }
+
+ // Sort the applied member constraints so we can binary search
+ // through them later.
+ self.member_constraints_applied.sort_by_key(|applied| applied.member_region_scc);
+ }
+
+ /// Computes the value of the SCC `scc_a`, which has not yet been
+ /// computed, by unioning the values of its successors.
+ /// Assumes that all successors have been computed already
+ /// (which is assured by iterating over SCCs in dependency order).
+ #[instrument(skip(self), level = "debug")]
+ fn compute_value_for_scc(&mut self, scc_a: ConstraintSccIndex) {
+ let constraint_sccs = self.constraint_sccs.clone();
+
+ // Walk each SCC `B` such that `A: B`...
+ for &scc_b in constraint_sccs.successors(scc_a) {
+ debug!(?scc_b);
+
+ // ...and add elements from `B` into `A`. One complication
+ // arises because of universes: If `B` contains something
+ // that `A` cannot name, then `A` can only contain `B` if
+ // it outlives static.
+ if self.universe_compatible(scc_b, scc_a) {
+ // `A` can name everything that is in `B`, so just
+ // merge the bits.
+ self.scc_values.add_region(scc_a, scc_b);
+ } else {
+ self.add_incompatible_universe(scc_a);
+ }
+ }
+
+ // Now take member constraints into account.
+ let member_constraints = self.member_constraints.clone();
+ for m_c_i in member_constraints.indices(scc_a) {
+ self.apply_member_constraint(scc_a, m_c_i, member_constraints.choice_regions(m_c_i));
+ }
+
+ debug!(value = ?self.scc_values.region_value_str(scc_a));
+ }
+
+ /// Invoked for each `R0 member of [R1..Rn]` constraint.
+ ///
+ /// `scc` is the SCC containing R0, and `choice_regions` are the
+ /// `R1..Rn` regions -- they are always known to be universal
+ /// regions (and if that's not true, we just don't attempt to
+ /// enforce the constraint).
+ ///
+ /// The current value of `scc` at the time the method is invoked
+ /// is considered a *lower bound*. If possible, we will modify
+ /// the constraint to set it equal to one of the option regions.
+ /// If we make any changes, returns true, else false.
+ #[instrument(skip(self, member_constraint_index), level = "debug")]
+ fn apply_member_constraint(
+ &mut self,
+ scc: ConstraintSccIndex,
+ member_constraint_index: NllMemberConstraintIndex,
+ choice_regions: &[ty::RegionVid],
+ ) -> bool {
+ // Create a mutable vector of the options. We'll try to winnow
+ // them down.
+ let mut choice_regions: Vec<ty::RegionVid> = choice_regions.to_vec();
+
+ // Convert to the SCC representative: sometimes we have inference
+ // variables in the member constraint that wind up equated with
+ // universal regions. The scc representative is the minimal numbered
+ // one from the corresponding scc so it will be the universal region
+ // if one exists.
+ for c_r in &mut choice_regions {
+ let scc = self.constraint_sccs.scc(*c_r);
+ *c_r = self.scc_representatives[scc];
+ }
+
+ // The 'member region' in a member constraint is part of the
+ // hidden type, which must be in the root universe. Therefore,
+ // it cannot have any placeholders in its value.
+ assert!(self.scc_universes[scc] == ty::UniverseIndex::ROOT);
+ debug_assert!(
+ self.scc_values.placeholders_contained_in(scc).next().is_none(),
+ "scc {:?} in a member constraint has placeholder value: {:?}",
+ scc,
+ self.scc_values.region_value_str(scc),
+ );
+
+ // The existing value for `scc` is a lower-bound. This will
+ // consist of some set `{P} + {LB}` of points `{P}` and
+ // lower-bound free regions `{LB}`. As each choice region `O`
+ // is a free region, it will outlive the points. But we can
+ // only consider the option `O` if `O: LB`.
+ choice_regions.retain(|&o_r| {
+ self.scc_values
+ .universal_regions_outlived_by(scc)
+ .all(|lb| self.universal_region_relations.outlives(o_r, lb))
+ });
+ debug!(?choice_regions, "after lb");
+
+ // Now find all the *upper bounds* -- that is, each UB is a
+ // free region that must outlive the member region `R0` (`UB:
+ // R0`). Therefore, we need only keep an option `O` if `UB: O`
+ // for all UB.
+ let rev_scc_graph = self.reverse_scc_graph();
+ let universal_region_relations = &self.universal_region_relations;
+ for ub in rev_scc_graph.upper_bounds(scc) {
+ debug!(?ub);
+ choice_regions.retain(|&o_r| universal_region_relations.outlives(ub, o_r));
+ }
+ debug!(?choice_regions, "after ub");
+
+ // If we ruled everything out, we're done.
+ if choice_regions.is_empty() {
+ return false;
+ }
+
+ // Otherwise, we need to find the minimum remaining choice, if
+ // any, and take that.
+ debug!("choice_regions remaining are {:#?}", choice_regions);
+ let min = |r1: ty::RegionVid, r2: ty::RegionVid| -> Option<ty::RegionVid> {
+ let r1_outlives_r2 = self.universal_region_relations.outlives(r1, r2);
+ let r2_outlives_r1 = self.universal_region_relations.outlives(r2, r1);
+ match (r1_outlives_r2, r2_outlives_r1) {
+ (true, true) => Some(r1.min(r2)),
+ (true, false) => Some(r2),
+ (false, true) => Some(r1),
+ (false, false) => None,
+ }
+ };
+ let mut min_choice = choice_regions[0];
+ for &other_option in &choice_regions[1..] {
+ debug!(?min_choice, ?other_option,);
+ match min(min_choice, other_option) {
+ Some(m) => min_choice = m,
+ None => {
+ debug!(?min_choice, ?other_option, "incomparable; no min choice",);
+ return false;
+ }
+ }
+ }
+
+ let min_choice_scc = self.constraint_sccs.scc(min_choice);
+ debug!(?min_choice, ?min_choice_scc);
+ if self.scc_values.add_region(scc, min_choice_scc) {
+ self.member_constraints_applied.push(AppliedMemberConstraint {
+ member_region_scc: scc,
+ min_choice,
+ member_constraint_index,
+ });
+
+ true
+ } else {
+ false
+ }
+ }
+
+ /// Returns `true` if all the elements in the value of `scc_b` are nameable
+ /// in `scc_a`. Used during constraint propagation, and only once
+ /// the value of `scc_b` has been computed.
+ fn universe_compatible(&self, scc_b: ConstraintSccIndex, scc_a: ConstraintSccIndex) -> bool {
+ let universe_a = self.scc_universes[scc_a];
+
+ // Quick check: if scc_b's declared universe is a subset of
+ // scc_a's declared universe (typically, both are ROOT), then
+ // it cannot contain any problematic universe elements.
+ if universe_a.can_name(self.scc_universes[scc_b]) {
+ return true;
+ }
+
+ // Otherwise, we have to iterate over the universe elements in
+ // B's value, and check whether all of them are nameable
+ // from universe_a
+ self.scc_values.placeholders_contained_in(scc_b).all(|p| universe_a.can_name(p.universe))
+ }
+
+ /// Extend `scc` so that it can outlive some placeholder region
+ /// from a universe it can't name; at present, the only way for
+ /// this to be true is if `scc` outlives `'static`. This is
+ /// actually stricter than necessary: ideally, we'd support bounds
+ /// like `for<'a: 'b`>` that might then allow us to approximate
+ /// `'a` with `'b` and not `'static`. But it will have to do for
+ /// now.
+ fn add_incompatible_universe(&mut self, scc: ConstraintSccIndex) {
+ debug!("add_incompatible_universe(scc={:?})", scc);
+
+ let fr_static = self.universal_regions.fr_static;
+ self.scc_values.add_all_points(scc);
+ self.scc_values.add_element(scc, fr_static);
+ }
+
+ /// Once regions have been propagated, this method is used to see
+ /// whether the "type tests" produced by typeck were satisfied;
+ /// type tests encode type-outlives relationships like `T:
+ /// 'a`. See `TypeTest` for more details.
+ fn check_type_tests(
+ &self,
+ infcx: &InferCtxt<'_, 'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ body: &Body<'tcx>,
+ mut propagated_outlives_requirements: Option<&mut Vec<ClosureOutlivesRequirement<'tcx>>>,
+ errors_buffer: &mut RegionErrors<'tcx>,
+ ) {
+ let tcx = infcx.tcx;
+
+ // Sometimes we register equivalent type-tests that would
+ // result in basically the exact same error being reported to
+ // the user. Avoid that.
+ let mut deduplicate_errors = FxHashSet::default();
+
+ for type_test in &self.type_tests {
+ debug!("check_type_test: {:?}", type_test);
+
+ let generic_ty = type_test.generic_kind.to_ty(tcx);
+ if self.eval_verify_bound(
+ infcx,
+ param_env,
+ body,
+ generic_ty,
+ type_test.lower_bound,
+ &type_test.verify_bound,
+ ) {
+ continue;
+ }
+
+ if let Some(propagated_outlives_requirements) = &mut propagated_outlives_requirements {
+ if self.try_promote_type_test(
+ infcx,
+ param_env,
+ body,
+ type_test,
+ propagated_outlives_requirements,
+ ) {
+ continue;
+ }
+ }
+
+ // Type-test failed. Report the error.
+ let erased_generic_kind = infcx.tcx.erase_regions(type_test.generic_kind);
+
+ // Skip duplicate-ish errors.
+ if deduplicate_errors.insert((
+ erased_generic_kind,
+ type_test.lower_bound,
+ type_test.locations,
+ )) {
+ debug!(
+ "check_type_test: reporting error for erased_generic_kind={:?}, \
+ lower_bound_region={:?}, \
+ type_test.locations={:?}",
+ erased_generic_kind, type_test.lower_bound, type_test.locations,
+ );
+
+ errors_buffer.push(RegionErrorKind::TypeTestError { type_test: type_test.clone() });
+ }
+ }
+ }
+
+ /// Invoked when we have some type-test (e.g., `T: 'X`) that we cannot
+ /// prove to be satisfied. If this is a closure, we will attempt to
+ /// "promote" this type-test into our `ClosureRegionRequirements` and
+ /// hence pass it up the creator. To do this, we have to phrase the
+ /// type-test in terms of external free regions, as local free
+ /// regions are not nameable by the closure's creator.
+ ///
+ /// Promotion works as follows: we first check that the type `T`
+ /// contains only regions that the creator knows about. If this is
+ /// true, then -- as a consequence -- we know that all regions in
+ /// the type `T` are free regions that outlive the closure body. If
+ /// false, then promotion fails.
+ ///
+ /// Once we've promoted T, we have to "promote" `'X` to some region
+ /// that is "external" to the closure. Generally speaking, a region
+ /// may be the union of some points in the closure body as well as
+ /// various free lifetimes. We can ignore the points in the closure
+ /// body: if the type T can be expressed in terms of external regions,
+ /// we know it outlives the points in the closure body. That
+ /// just leaves the free regions.
+ ///
+ /// The idea then is to lower the `T: 'X` constraint into multiple
+ /// bounds -- e.g., if `'X` is the union of two free lifetimes,
+ /// `'1` and `'2`, then we would create `T: '1` and `T: '2`.
+ #[instrument(level = "debug", skip(self, infcx, propagated_outlives_requirements))]
+ fn try_promote_type_test(
+ &self,
+ infcx: &InferCtxt<'_, 'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ body: &Body<'tcx>,
+ type_test: &TypeTest<'tcx>,
+ propagated_outlives_requirements: &mut Vec<ClosureOutlivesRequirement<'tcx>>,
+ ) -> bool {
+ let tcx = infcx.tcx;
+
+ let TypeTest { generic_kind, lower_bound, locations, verify_bound: _ } = type_test;
+
+ let generic_ty = generic_kind.to_ty(tcx);
+ let Some(subject) = self.try_promote_type_test_subject(infcx, generic_ty) else {
+ return false;
+ };
+
+ debug!("subject = {:?}", subject);
+
+ let r_scc = self.constraint_sccs.scc(*lower_bound);
+
+ debug!(
+ "lower_bound = {:?} r_scc={:?} universe={:?}",
+ lower_bound, r_scc, self.scc_universes[r_scc]
+ );
+
+ // If the type test requires that `T: 'a` where `'a` is a
+ // placeholder from another universe, that effectively requires
+ // `T: 'static`, so we have to propagate that requirement.
+ //
+ // It doesn't matter *what* universe because the promoted `T` will
+ // always be in the root universe.
+ if let Some(p) = self.scc_values.placeholders_contained_in(r_scc).next() {
+ debug!("encountered placeholder in higher universe: {:?}, requiring 'static", p);
+ let static_r = self.universal_regions.fr_static;
+ propagated_outlives_requirements.push(ClosureOutlivesRequirement {
+ subject,
+ outlived_free_region: static_r,
+ blame_span: locations.span(body),
+ category: ConstraintCategory::Boring,
+ });
+
+ // we can return here -- the code below might push add'l constraints
+ // but they would all be weaker than this one.
+ return true;
+ }
+
+ // For each region outlived by lower_bound find a non-local,
+ // universal region (it may be the same region) and add it to
+ // `ClosureOutlivesRequirement`.
+ for ur in self.scc_values.universal_regions_outlived_by(r_scc) {
+ debug!("universal_region_outlived_by ur={:?}", ur);
+ // Check whether we can already prove that the "subject" outlives `ur`.
+ // If so, we don't have to propagate this requirement to our caller.
+ //
+ // To continue the example from the function, if we are trying to promote
+ // a requirement that `T: 'X`, and we know that `'X = '1 + '2` (i.e., the union
+ // `'1` and `'2`), then in this loop `ur` will be `'1` (and `'2`). So here
+ // we check whether `T: '1` is something we *can* prove. If so, no need
+ // to propagate that requirement.
+ //
+ // This is needed because -- particularly in the case
+ // where `ur` is a local bound -- we are sometimes in a
+ // position to prove things that our caller cannot. See
+ // #53570 for an example.
+ if self.eval_verify_bound(
+ infcx,
+ param_env,
+ body,
+ generic_ty,
+ ur,
+ &type_test.verify_bound,
+ ) {
+ continue;
+ }
+
+ let non_local_ub = self.universal_region_relations.non_local_upper_bounds(ur);
+ debug!("try_promote_type_test: non_local_ub={:?}", non_local_ub);
+
+ // This is slightly too conservative. To show T: '1, given `'2: '1`
+ // and `'3: '1` we only need to prove that T: '2 *or* T: '3, but to
+ // avoid potential non-determinism we approximate this by requiring
+ // T: '1 and T: '2.
+ for upper_bound in non_local_ub {
+ debug_assert!(self.universal_regions.is_universal_region(upper_bound));
+ debug_assert!(!self.universal_regions.is_local_free_region(upper_bound));
+
+ let requirement = ClosureOutlivesRequirement {
+ subject,
+ outlived_free_region: upper_bound,
+ blame_span: locations.span(body),
+ category: ConstraintCategory::Boring,
+ };
+ debug!("try_promote_type_test: pushing {:#?}", requirement);
+ propagated_outlives_requirements.push(requirement);
+ }
+ }
+ true
+ }
+
+ /// When we promote a type test `T: 'r`, we have to convert the
+ /// type `T` into something we can store in a query result (so
+ /// something allocated for `'tcx`). This is problematic if `ty`
+ /// contains regions. During the course of NLL region checking, we
+ /// will have replaced all of those regions with fresh inference
+ /// variables. To create a test subject, we want to replace those
+ /// inference variables with some region from the closure
+ /// signature -- this is not always possible, so this is a
+ /// fallible process. Presuming we do find a suitable region, we
+ /// will use it's *external name*, which will be a `RegionKind`
+ /// variant that can be used in query responses such as
+ /// `ReEarlyBound`.
+ #[instrument(level = "debug", skip(self, infcx))]
+ fn try_promote_type_test_subject(
+ &self,
+ infcx: &InferCtxt<'_, 'tcx>,
+ ty: Ty<'tcx>,
+ ) -> Option<ClosureOutlivesSubject<'tcx>> {
+ let tcx = infcx.tcx;
+
+ let ty = tcx.fold_regions(ty, |r, _depth| {
+ let region_vid = self.to_region_vid(r);
+
+ // The challenge if this. We have some region variable `r`
+ // whose value is a set of CFG points and universal
+ // regions. We want to find if that set is *equivalent* to
+ // any of the named regions found in the closure.
+ //
+ // To do so, we compute the
+ // `non_local_universal_upper_bound`. This will be a
+ // non-local, universal region that is greater than `r`.
+ // However, it might not be *contained* within `r`, so
+ // then we further check whether this bound is contained
+ // in `r`. If so, we can say that `r` is equivalent to the
+ // bound.
+ //
+ // Let's work through a few examples. For these, imagine
+ // that we have 3 non-local regions (I'll denote them as
+ // `'static`, `'a`, and `'b`, though of course in the code
+ // they would be represented with indices) where:
+ //
+ // - `'static: 'a`
+ // - `'static: 'b`
+ //
+ // First, let's assume that `r` is some existential
+ // variable with an inferred value `{'a, 'static}` (plus
+ // some CFG nodes). In this case, the non-local upper
+ // bound is `'static`, since that outlives `'a`. `'static`
+ // is also a member of `r` and hence we consider `r`
+ // equivalent to `'static` (and replace it with
+ // `'static`).
+ //
+ // Now let's consider the inferred value `{'a, 'b}`. This
+ // means `r` is effectively `'a | 'b`. I'm not sure if
+ // this can come about, actually, but assuming it did, we
+ // would get a non-local upper bound of `'static`. Since
+ // `'static` is not contained in `r`, we would fail to
+ // find an equivalent.
+ let upper_bound = self.non_local_universal_upper_bound(region_vid);
+ if self.region_contains(region_vid, upper_bound) {
+ self.definitions[upper_bound].external_name.unwrap_or(r)
+ } else {
+ // In the case of a failure, use a `ReVar` result. This will
+ // cause the `needs_infer` later on to return `None`.
+ r
+ }
+ });
+
+ debug!("try_promote_type_test_subject: folded ty = {:?}", ty);
+
+ // `needs_infer` will only be true if we failed to promote some region.
+ if ty.needs_infer() {
+ return None;
+ }
+
+ Some(ClosureOutlivesSubject::Ty(ty))
+ }
+
+ /// Given some universal or existential region `r`, finds a
+ /// non-local, universal region `r+` that outlives `r` at entry to (and
+ /// exit from) the closure. In the worst case, this will be
+ /// `'static`.
+ ///
+ /// This is used for two purposes. First, if we are propagated
+ /// some requirement `T: r`, we can use this method to enlarge `r`
+ /// to something we can encode for our creator (which only knows
+ /// about non-local, universal regions). It is also used when
+ /// encoding `T` as part of `try_promote_type_test_subject` (see
+ /// that fn for details).
+ ///
+ /// This is based on the result `'y` of `universal_upper_bound`,
+ /// except that it converts further takes the non-local upper
+ /// bound of `'y`, so that the final result is non-local.
+ fn non_local_universal_upper_bound(&self, r: RegionVid) -> RegionVid {
+ debug!("non_local_universal_upper_bound(r={:?}={})", r, self.region_value_str(r));
+
+ let lub = self.universal_upper_bound(r);
+
+ // Grow further to get smallest universal region known to
+ // creator.
+ let non_local_lub = self.universal_region_relations.non_local_upper_bound(lub);
+
+ debug!("non_local_universal_upper_bound: non_local_lub={:?}", non_local_lub);
+
+ non_local_lub
+ }
+
+ /// Returns a universally quantified region that outlives the
+ /// value of `r` (`r` may be existentially or universally
+ /// quantified).
+ ///
+ /// Since `r` is (potentially) an existential region, it has some
+ /// value which may include (a) any number of points in the CFG
+ /// and (b) any number of `end('x)` elements of universally
+ /// quantified regions. To convert this into a single universal
+ /// region we do as follows:
+ ///
+ /// - Ignore the CFG points in `'r`. All universally quantified regions
+ /// include the CFG anyhow.
+ /// - For each `end('x)` element in `'r`, compute the mutual LUB, yielding
+ /// a result `'y`.
+ #[instrument(skip(self), level = "debug")]
+ pub(crate) fn universal_upper_bound(&self, r: RegionVid) -> RegionVid {
+ debug!(r = %self.region_value_str(r));
+
+ // Find the smallest universal region that contains all other
+ // universal regions within `region`.
+ let mut lub = self.universal_regions.fr_fn_body;
+ let r_scc = self.constraint_sccs.scc(r);
+ for ur in self.scc_values.universal_regions_outlived_by(r_scc) {
+ lub = self.universal_region_relations.postdom_upper_bound(lub, ur);
+ }
+
+ debug!(?lub);
+
+ lub
+ }
+
+ /// Like `universal_upper_bound`, but returns an approximation more suitable
+ /// for diagnostics. If `r` contains multiple disjoint universal regions
+ /// (e.g. 'a and 'b in `fn foo<'a, 'b> { ... }`, we pick the lower-numbered region.
+ /// This corresponds to picking named regions over unnamed regions
+ /// (e.g. picking early-bound regions over a closure late-bound region).
+ ///
+ /// This means that the returned value may not be a true upper bound, since
+ /// only 'static is known to outlive disjoint universal regions.
+ /// Therefore, this method should only be used in diagnostic code,
+ /// where displaying *some* named universal region is better than
+ /// falling back to 'static.
+ pub(crate) fn approx_universal_upper_bound(&self, r: RegionVid) -> RegionVid {
+ debug!("approx_universal_upper_bound(r={:?}={})", r, self.region_value_str(r));
+
+ // Find the smallest universal region that contains all other
+ // universal regions within `region`.
+ let mut lub = self.universal_regions.fr_fn_body;
+ let r_scc = self.constraint_sccs.scc(r);
+ let static_r = self.universal_regions.fr_static;
+ for ur in self.scc_values.universal_regions_outlived_by(r_scc) {
+ let new_lub = self.universal_region_relations.postdom_upper_bound(lub, ur);
+ debug!("approx_universal_upper_bound: ur={:?} lub={:?} new_lub={:?}", ur, lub, new_lub);
+ // The upper bound of two non-static regions is static: this
+ // means we know nothing about the relationship between these
+ // two regions. Pick a 'better' one to use when constructing
+ // a diagnostic
+ if ur != static_r && lub != static_r && new_lub == static_r {
+ // Prefer the region with an `external_name` - this
+ // indicates that the region is early-bound, so working with
+ // it can produce a nicer error.
+ if self.region_definition(ur).external_name.is_some() {
+ lub = ur;
+ } else if self.region_definition(lub).external_name.is_some() {
+ // Leave lub unchanged
+ } else {
+ // If we get here, we don't have any reason to prefer
+ // one region over the other. Just pick the
+ // one with the lower index for now.
+ lub = std::cmp::min(ur, lub);
+ }
+ } else {
+ lub = new_lub;
+ }
+ }
+
+ debug!("approx_universal_upper_bound: r={:?} lub={:?}", r, lub);
+
+ lub
+ }
+
+ /// Tests if `test` is true when applied to `lower_bound` at
+ /// `point`.
+ fn eval_verify_bound(
+ &self,
+ infcx: &InferCtxt<'_, 'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ body: &Body<'tcx>,
+ generic_ty: Ty<'tcx>,
+ lower_bound: RegionVid,
+ verify_bound: &VerifyBound<'tcx>,
+ ) -> bool {
+ debug!("eval_verify_bound(lower_bound={:?}, verify_bound={:?})", lower_bound, verify_bound);
+
+ match verify_bound {
+ VerifyBound::IfEq(verify_if_eq_b) => {
+ self.eval_if_eq(infcx, param_env, generic_ty, lower_bound, *verify_if_eq_b)
+ }
+
+ VerifyBound::IsEmpty => {
+ let lower_bound_scc = self.constraint_sccs.scc(lower_bound);
+ self.scc_values.elements_contained_in(lower_bound_scc).next().is_none()
+ }
+
+ VerifyBound::OutlivedBy(r) => {
+ let r_vid = self.to_region_vid(*r);
+ self.eval_outlives(r_vid, lower_bound)
+ }
+
+ VerifyBound::AnyBound(verify_bounds) => verify_bounds.iter().any(|verify_bound| {
+ self.eval_verify_bound(
+ infcx,
+ param_env,
+ body,
+ generic_ty,
+ lower_bound,
+ verify_bound,
+ )
+ }),
+
+ VerifyBound::AllBounds(verify_bounds) => verify_bounds.iter().all(|verify_bound| {
+ self.eval_verify_bound(
+ infcx,
+ param_env,
+ body,
+ generic_ty,
+ lower_bound,
+ verify_bound,
+ )
+ }),
+ }
+ }
+
+ fn eval_if_eq(
+ &self,
+ infcx: &InferCtxt<'_, 'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ generic_ty: Ty<'tcx>,
+ lower_bound: RegionVid,
+ verify_if_eq_b: ty::Binder<'tcx, VerifyIfEq<'tcx>>,
+ ) -> bool {
+ let generic_ty = self.normalize_to_scc_representatives(infcx.tcx, generic_ty);
+ let verify_if_eq_b = self.normalize_to_scc_representatives(infcx.tcx, verify_if_eq_b);
+ match test_type_match::extract_verify_if_eq(
+ infcx.tcx,
+ param_env,
+ &verify_if_eq_b,
+ generic_ty,
+ ) {
+ Some(r) => {
+ let r_vid = self.to_region_vid(r);
+ self.eval_outlives(r_vid, lower_bound)
+ }
+ None => false,
+ }
+ }
+
+ /// This is a conservative normalization procedure. It takes every
+ /// free region in `value` and replaces it with the
+ /// "representative" of its SCC (see `scc_representatives` field).
+ /// We are guaranteed that if two values normalize to the same
+ /// thing, then they are equal; this is a conservative check in
+ /// that they could still be equal even if they normalize to
+ /// different results. (For example, there might be two regions
+ /// with the same value that are not in the same SCC).
+ ///
+ /// N.B., this is not an ideal approach and I would like to revisit
+ /// it. However, it works pretty well in practice. In particular,
+ /// this is needed to deal with projection outlives bounds like
+ ///
+ /// ```text
+ /// <T as Foo<'0>>::Item: '1
+ /// ```
+ ///
+ /// In particular, this routine winds up being important when
+ /// there are bounds like `where <T as Foo<'a>>::Item: 'b` in the
+ /// environment. In this case, if we can show that `'0 == 'a`,
+ /// and that `'b: '1`, then we know that the clause is
+ /// satisfied. In such cases, particularly due to limitations of
+ /// the trait solver =), we usually wind up with a where-clause like
+ /// `T: Foo<'a>` in scope, which thus forces `'0 == 'a` to be added as
+ /// a constraint, and thus ensures that they are in the same SCC.
+ ///
+ /// So why can't we do a more correct routine? Well, we could
+ /// *almost* use the `relate_tys` code, but the way it is
+ /// currently setup it creates inference variables to deal with
+ /// higher-ranked things and so forth, and right now the inference
+ /// context is not permitted to make more inference variables. So
+ /// we use this kind of hacky solution.
+ fn normalize_to_scc_representatives<T>(&self, tcx: TyCtxt<'tcx>, value: T) -> T
+ where
+ T: TypeFoldable<'tcx>,
+ {
+ tcx.fold_regions(value, |r, _db| {
+ let vid = self.to_region_vid(r);
+ let scc = self.constraint_sccs.scc(vid);
+ let repr = self.scc_representatives[scc];
+ tcx.mk_region(ty::ReVar(repr))
+ })
+ }
+
+ // Evaluate whether `sup_region == sub_region`.
+ fn eval_equal(&self, r1: RegionVid, r2: RegionVid) -> bool {
+ self.eval_outlives(r1, r2) && self.eval_outlives(r2, r1)
+ }
+
+ // Evaluate whether `sup_region: sub_region`.
+ #[instrument(skip(self), level = "debug")]
+ fn eval_outlives(&self, sup_region: RegionVid, sub_region: RegionVid) -> bool {
+ debug!(
+ "eval_outlives: sup_region's value = {:?} universal={:?}",
+ self.region_value_str(sup_region),
+ self.universal_regions.is_universal_region(sup_region),
+ );
+ debug!(
+ "eval_outlives: sub_region's value = {:?} universal={:?}",
+ self.region_value_str(sub_region),
+ self.universal_regions.is_universal_region(sub_region),
+ );
+
+ let sub_region_scc = self.constraint_sccs.scc(sub_region);
+ let sup_region_scc = self.constraint_sccs.scc(sup_region);
+
+ // If we are checking that `'sup: 'sub`, and `'sub` contains
+ // some placeholder that `'sup` cannot name, then this is only
+ // true if `'sup` outlives static.
+ if !self.universe_compatible(sub_region_scc, sup_region_scc) {
+ debug!(
+ "eval_outlives: sub universe `{sub_region_scc:?}` is not nameable \
+ by super `{sup_region_scc:?}`, promoting to static",
+ );
+
+ return self.eval_outlives(sup_region, self.universal_regions.fr_static);
+ }
+
+ // Both the `sub_region` and `sup_region` consist of the union
+ // of some number of universal regions (along with the union
+ // of various points in the CFG; ignore those points for
+ // now). Therefore, the sup-region outlives the sub-region if,
+ // for each universal region R1 in the sub-region, there
+ // exists some region R2 in the sup-region that outlives R1.
+ let universal_outlives =
+ self.scc_values.universal_regions_outlived_by(sub_region_scc).all(|r1| {
+ self.scc_values
+ .universal_regions_outlived_by(sup_region_scc)
+ .any(|r2| self.universal_region_relations.outlives(r2, r1))
+ });
+
+ if !universal_outlives {
+ debug!(
+ "eval_outlives: returning false because sub region contains a universal region not present in super"
+ );
+ return false;
+ }
+
+ // Now we have to compare all the points in the sub region and make
+ // sure they exist in the sup region.
+
+ if self.universal_regions.is_universal_region(sup_region) {
+ // Micro-opt: universal regions contain all points.
+ debug!(
+ "eval_outlives: returning true because super is universal and hence contains all points"
+ );
+ return true;
+ }
+
+ let result = self.scc_values.contains_points(sup_region_scc, sub_region_scc);
+ debug!("returning {} because of comparison between points in sup/sub", result);
+ result
+ }
+
+ /// Once regions have been propagated, this method is used to see
+ /// whether any of the constraints were too strong. In particular,
+ /// we want to check for a case where a universally quantified
+ /// region exceeded its bounds. Consider:
+ /// ```compile_fail,E0312
+ /// fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x }
+ /// ```
+ /// In this case, returning `x` requires `&'a u32 <: &'b u32`
+ /// and hence we establish (transitively) a constraint that
+ /// `'a: 'b`. The `propagate_constraints` code above will
+ /// therefore add `end('a)` into the region for `'b` -- but we
+ /// have no evidence that `'b` outlives `'a`, so we want to report
+ /// an error.
+ ///
+ /// If `propagated_outlives_requirements` is `Some`, then we will
+ /// push unsatisfied obligations into there. Otherwise, we'll
+ /// report them as errors.
+ fn check_universal_regions(
+ &self,
+ body: &Body<'tcx>,
+ mut propagated_outlives_requirements: Option<&mut Vec<ClosureOutlivesRequirement<'tcx>>>,
+ errors_buffer: &mut RegionErrors<'tcx>,
+ ) {
+ for (fr, fr_definition) in self.definitions.iter_enumerated() {
+ match fr_definition.origin {
+ NllRegionVariableOrigin::FreeRegion => {
+ // Go through each of the universal regions `fr` and check that
+ // they did not grow too large, accumulating any requirements
+ // for our caller into the `outlives_requirements` vector.
+ self.check_universal_region(
+ body,
+ fr,
+ &mut propagated_outlives_requirements,
+ errors_buffer,
+ );
+ }
+
+ NllRegionVariableOrigin::Placeholder(placeholder) => {
+ self.check_bound_universal_region(fr, placeholder, errors_buffer);
+ }
+
+ NllRegionVariableOrigin::Existential { .. } => {
+ // nothing to check here
+ }
+ }
+ }
+ }
+
+ /// Checks if Polonius has found any unexpected free region relations.
+ ///
+ /// In Polonius terms, a "subset error" (or "illegal subset relation error") is the equivalent
+ /// of NLL's "checking if any region constraints were too strong": a placeholder origin `'a`
+ /// was unexpectedly found to be a subset of another placeholder origin `'b`, and means in NLL
+ /// terms that the "longer free region" `'a` outlived the "shorter free region" `'b`.
+ ///
+ /// More details can be found in this blog post by Niko:
+ /// <https://smallcultfollowing.com/babysteps/blog/2019/01/17/polonius-and-region-errors/>
+ ///
+ /// In the canonical example
+ /// ```compile_fail,E0312
+ /// fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x }
+ /// ```
+ /// returning `x` requires `&'a u32 <: &'b u32` and hence we establish (transitively) a
+ /// constraint that `'a: 'b`. It is an error that we have no evidence that this
+ /// constraint holds.
+ ///
+ /// If `propagated_outlives_requirements` is `Some`, then we will
+ /// push unsatisfied obligations into there. Otherwise, we'll
+ /// report them as errors.
+ fn check_polonius_subset_errors(
+ &self,
+ body: &Body<'tcx>,
+ mut propagated_outlives_requirements: Option<&mut Vec<ClosureOutlivesRequirement<'tcx>>>,
+ errors_buffer: &mut RegionErrors<'tcx>,
+ polonius_output: Rc<PoloniusOutput>,
+ ) {
+ debug!(
+ "check_polonius_subset_errors: {} subset_errors",
+ polonius_output.subset_errors.len()
+ );
+
+ // Similarly to `check_universal_regions`: a free region relation, which was not explicitly
+ // declared ("known") was found by Polonius, so emit an error, or propagate the
+ // requirements for our caller into the `propagated_outlives_requirements` vector.
+ //
+ // Polonius doesn't model regions ("origins") as CFG-subsets or durations, but the
+ // `longer_fr` and `shorter_fr` terminology will still be used here, for consistency with
+ // the rest of the NLL infrastructure. The "subset origin" is the "longer free region",
+ // and the "superset origin" is the outlived "shorter free region".
+ //
+ // Note: Polonius will produce a subset error at every point where the unexpected
+ // `longer_fr`'s "placeholder loan" is contained in the `shorter_fr`. This can be helpful
+ // for diagnostics in the future, e.g. to point more precisely at the key locations
+ // requiring this constraint to hold. However, the error and diagnostics code downstream
+ // expects that these errors are not duplicated (and that they are in a certain order).
+ // Otherwise, diagnostics messages such as the ones giving names like `'1` to elided or
+ // anonymous lifetimes for example, could give these names differently, while others like
+ // the outlives suggestions or the debug output from `#[rustc_regions]` would be
+ // duplicated. The polonius subset errors are deduplicated here, while keeping the
+ // CFG-location ordering.
+ let mut subset_errors: Vec<_> = polonius_output
+ .subset_errors
+ .iter()
+ .flat_map(|(_location, subset_errors)| subset_errors.iter())
+ .collect();
+ subset_errors.sort();
+ subset_errors.dedup();
+
+ for (longer_fr, shorter_fr) in subset_errors.into_iter() {
+ debug!(
+ "check_polonius_subset_errors: subset_error longer_fr={:?},\
+ shorter_fr={:?}",
+ longer_fr, shorter_fr
+ );
+
+ let propagated = self.try_propagate_universal_region_error(
+ *longer_fr,
+ *shorter_fr,
+ body,
+ &mut propagated_outlives_requirements,
+ );
+ if propagated == RegionRelationCheckResult::Error {
+ errors_buffer.push(RegionErrorKind::RegionError {
+ longer_fr: *longer_fr,
+ shorter_fr: *shorter_fr,
+ fr_origin: NllRegionVariableOrigin::FreeRegion,
+ is_reported: true,
+ });
+ }
+ }
+
+ // Handle the placeholder errors as usual, until the chalk-rustc-polonius triumvirate has
+ // a more complete picture on how to separate this responsibility.
+ for (fr, fr_definition) in self.definitions.iter_enumerated() {
+ match fr_definition.origin {
+ NllRegionVariableOrigin::FreeRegion => {
+ // handled by polonius above
+ }
+
+ NllRegionVariableOrigin::Placeholder(placeholder) => {
+ self.check_bound_universal_region(fr, placeholder, errors_buffer);
+ }
+
+ NllRegionVariableOrigin::Existential { .. } => {
+ // nothing to check here
+ }
+ }
+ }
+ }
+
+ /// Checks the final value for the free region `fr` to see if it
+ /// grew too large. In particular, examine what `end(X)` points
+ /// wound up in `fr`'s final value; for each `end(X)` where `X !=
+ /// fr`, we want to check that `fr: X`. If not, that's either an
+ /// error, or something we have to propagate to our creator.
+ ///
+ /// Things that are to be propagated are accumulated into the
+ /// `outlives_requirements` vector.
+ #[instrument(
+ skip(self, body, propagated_outlives_requirements, errors_buffer),
+ level = "debug"
+ )]
+ fn check_universal_region(
+ &self,
+ body: &Body<'tcx>,
+ longer_fr: RegionVid,
+ propagated_outlives_requirements: &mut Option<&mut Vec<ClosureOutlivesRequirement<'tcx>>>,
+ errors_buffer: &mut RegionErrors<'tcx>,
+ ) {
+ let longer_fr_scc = self.constraint_sccs.scc(longer_fr);
+
+ // Because this free region must be in the ROOT universe, we
+ // know it cannot contain any bound universes.
+ assert!(self.scc_universes[longer_fr_scc] == ty::UniverseIndex::ROOT);
+ debug_assert!(self.scc_values.placeholders_contained_in(longer_fr_scc).next().is_none());
+
+ // Only check all of the relations for the main representative of each
+ // SCC, otherwise just check that we outlive said representative. This
+ // reduces the number of redundant relations propagated out of
+ // closures.
+ // Note that the representative will be a universal region if there is
+ // one in this SCC, so we will always check the representative here.
+ let representative = self.scc_representatives[longer_fr_scc];
+ if representative != longer_fr {
+ if let RegionRelationCheckResult::Error = self.check_universal_region_relation(
+ longer_fr,
+ representative,
+ body,
+ propagated_outlives_requirements,
+ ) {
+ errors_buffer.push(RegionErrorKind::RegionError {
+ longer_fr,
+ shorter_fr: representative,
+ fr_origin: NllRegionVariableOrigin::FreeRegion,
+ is_reported: true,
+ });
+ }
+ return;
+ }
+
+ // Find every region `o` such that `fr: o`
+ // (because `fr` includes `end(o)`).
+ let mut error_reported = false;
+ for shorter_fr in self.scc_values.universal_regions_outlived_by(longer_fr_scc) {
+ if let RegionRelationCheckResult::Error = self.check_universal_region_relation(
+ longer_fr,
+ shorter_fr,
+ body,
+ propagated_outlives_requirements,
+ ) {
+ // We only report the first region error. Subsequent errors are hidden so as
+ // not to overwhelm the user, but we do record them so as to potentially print
+ // better diagnostics elsewhere...
+ errors_buffer.push(RegionErrorKind::RegionError {
+ longer_fr,
+ shorter_fr,
+ fr_origin: NllRegionVariableOrigin::FreeRegion,
+ is_reported: !error_reported,
+ });
+
+ error_reported = true;
+ }
+ }
+ }
+
+ /// Checks that we can prove that `longer_fr: shorter_fr`. If we can't we attempt to propagate
+ /// the constraint outward (e.g. to a closure environment), but if that fails, there is an
+ /// error.
+ fn check_universal_region_relation(
+ &self,
+ longer_fr: RegionVid,
+ shorter_fr: RegionVid,
+ body: &Body<'tcx>,
+ propagated_outlives_requirements: &mut Option<&mut Vec<ClosureOutlivesRequirement<'tcx>>>,
+ ) -> RegionRelationCheckResult {
+ // If it is known that `fr: o`, carry on.
+ if self.universal_region_relations.outlives(longer_fr, shorter_fr) {
+ RegionRelationCheckResult::Ok
+ } else {
+ // If we are not in a context where we can't propagate errors, or we
+ // could not shrink `fr` to something smaller, then just report an
+ // error.
+ //
+ // Note: in this case, we use the unapproximated regions to report the
+ // error. This gives better error messages in some cases.
+ self.try_propagate_universal_region_error(
+ longer_fr,
+ shorter_fr,
+ body,
+ propagated_outlives_requirements,
+ )
+ }
+ }
+
+ /// Attempt to propagate a region error (e.g. `'a: 'b`) that is not met to a closure's
+ /// creator. If we cannot, then the caller should report an error to the user.
+ fn try_propagate_universal_region_error(
+ &self,
+ longer_fr: RegionVid,
+ shorter_fr: RegionVid,
+ body: &Body<'tcx>,
+ propagated_outlives_requirements: &mut Option<&mut Vec<ClosureOutlivesRequirement<'tcx>>>,
+ ) -> RegionRelationCheckResult {
+ if let Some(propagated_outlives_requirements) = propagated_outlives_requirements {
+ // Shrink `longer_fr` until we find a non-local region (if we do).
+ // We'll call it `fr-` -- it's ever so slightly smaller than
+ // `longer_fr`.
+ if let Some(fr_minus) = self.universal_region_relations.non_local_lower_bound(longer_fr)
+ {
+ debug!("try_propagate_universal_region_error: fr_minus={:?}", fr_minus);
+
+ let blame_span_category = self.find_outlives_blame_span(
+ body,
+ longer_fr,
+ NllRegionVariableOrigin::FreeRegion,
+ shorter_fr,
+ );
+
+ // Grow `shorter_fr` until we find some non-local regions. (We
+ // always will.) We'll call them `shorter_fr+` -- they're ever
+ // so slightly larger than `shorter_fr`.
+ let shorter_fr_plus =
+ self.universal_region_relations.non_local_upper_bounds(shorter_fr);
+ debug!(
+ "try_propagate_universal_region_error: shorter_fr_plus={:?}",
+ shorter_fr_plus
+ );
+ for fr in shorter_fr_plus {
+ // Push the constraint `fr-: shorter_fr+`
+ propagated_outlives_requirements.push(ClosureOutlivesRequirement {
+ subject: ClosureOutlivesSubject::Region(fr_minus),
+ outlived_free_region: fr,
+ blame_span: blame_span_category.1.span,
+ category: blame_span_category.0,
+ });
+ }
+ return RegionRelationCheckResult::Propagated;
+ }
+ }
+
+ RegionRelationCheckResult::Error
+ }
+
+ fn check_bound_universal_region(
+ &self,
+ longer_fr: RegionVid,
+ placeholder: ty::PlaceholderRegion,
+ errors_buffer: &mut RegionErrors<'tcx>,
+ ) {
+ debug!("check_bound_universal_region(fr={:?}, placeholder={:?})", longer_fr, placeholder,);
+
+ let longer_fr_scc = self.constraint_sccs.scc(longer_fr);
+ debug!("check_bound_universal_region: longer_fr_scc={:?}", longer_fr_scc,);
+
+ // If we have some bound universal region `'a`, then the only
+ // elements it can contain is itself -- we don't know anything
+ // else about it!
+ let Some(error_element) = ({
+ self.scc_values.elements_contained_in(longer_fr_scc).find(|element| match element {
+ RegionElement::Location(_) => true,
+ RegionElement::RootUniversalRegion(_) => true,
+ RegionElement::PlaceholderRegion(placeholder1) => placeholder != *placeholder1,
+ })
+ }) else {
+ return;
+ };
+ debug!("check_bound_universal_region: error_element = {:?}", error_element);
+
+ // Find the region that introduced this `error_element`.
+ errors_buffer.push(RegionErrorKind::BoundUniversalRegionError {
+ longer_fr,
+ error_element,
+ placeholder,
+ });
+ }
+
+ fn check_member_constraints(
+ &self,
+ infcx: &InferCtxt<'_, 'tcx>,
+ errors_buffer: &mut RegionErrors<'tcx>,
+ ) {
+ let member_constraints = self.member_constraints.clone();
+ for m_c_i in member_constraints.all_indices() {
+ debug!("check_member_constraint(m_c_i={:?})", m_c_i);
+ let m_c = &member_constraints[m_c_i];
+ let member_region_vid = m_c.member_region_vid;
+ debug!(
+ "check_member_constraint: member_region_vid={:?} with value {}",
+ member_region_vid,
+ self.region_value_str(member_region_vid),
+ );
+ let choice_regions = member_constraints.choice_regions(m_c_i);
+ debug!("check_member_constraint: choice_regions={:?}", choice_regions);
+
+ // Did the member region wind up equal to any of the option regions?
+ if let Some(o) =
+ choice_regions.iter().find(|&&o_r| self.eval_equal(o_r, m_c.member_region_vid))
+ {
+ debug!("check_member_constraint: evaluated as equal to {:?}", o);
+ continue;
+ }
+
+ // If not, report an error.
+ let member_region = infcx.tcx.mk_region(ty::ReVar(member_region_vid));
+ errors_buffer.push(RegionErrorKind::UnexpectedHiddenRegion {
+ span: m_c.definition_span,
+ hidden_ty: m_c.hidden_ty,
+ key: m_c.key,
+ member_region,
+ });
+ }
+ }
+
+ /// 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`.)
+ pub(crate) 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
+ }
+
+ /// If `r2` represents a placeholder region, then this returns
+ /// `true` if `r1` cannot name that placeholder in its
+ /// value; otherwise, returns `false`.
+ pub(crate) 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
+ }
+ }
+ }
+
+ pub(crate) fn retrieve_closure_constraint_info(
+ &self,
+ _body: &Body<'tcx>,
+ constraint: &OutlivesConstraint<'tcx>,
+ ) -> BlameConstraint<'tcx> {
+ let loc = match constraint.locations {
+ Locations::All(span) => {
+ return BlameConstraint {
+ category: constraint.category,
+ from_closure: false,
+ cause: ObligationCause::dummy_with_span(span),
+ variance_info: constraint.variance_info,
+ };
+ }
+ Locations::Single(loc) => loc,
+ };
+
+ let opt_span_category =
+ self.closure_bounds_mapping[&loc].get(&(constraint.sup, constraint.sub));
+ opt_span_category
+ .map(|&(category, span)| BlameConstraint {
+ category,
+ from_closure: true,
+ cause: ObligationCause::dummy_with_span(span),
+ variance_info: constraint.variance_info,
+ })
+ .unwrap_or(BlameConstraint {
+ category: constraint.category,
+ from_closure: false,
+ cause: ObligationCause::dummy_with_span(constraint.span),
+ variance_info: constraint.variance_info,
+ })
+ }
+
+ /// Finds a good `ObligationCause` to blame for the fact that `fr1` outlives `fr2`.
+ pub(crate) fn find_outlives_blame_span(
+ &self,
+ body: &Body<'tcx>,
+ fr1: RegionVid,
+ fr1_origin: NllRegionVariableOrigin,
+ fr2: RegionVid,
+ ) -> (ConstraintCategory<'tcx>, ObligationCause<'tcx>) {
+ let BlameConstraint { category, cause, .. } =
+ self.best_blame_constraint(body, fr1, fr1_origin, |r| {
+ self.provides_universal_region(r, fr1, fr2)
+ });
+ (category, cause)
+ }
+
+ /// 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.
+ pub(crate) fn find_constraint_paths_between_regions(
+ &self,
+ from_region: RegionVid,
+ target_test: impl Fn(RegionVid) -> bool,
+ ) -> Option<(Vec<OutlivesConstraint<'tcx>>, 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].clone() {
+ Trace::NotVisited => {
+ bug!("found unvisited region {:?} on path to {:?}", p, r)
+ }
+
+ Trace::FromOutlivesConstraint(c) => {
+ p = c.sup;
+ result.push(c);
+ }
+
+ 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<'tcx>| {
+ 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),
+ span: p_c.definition_span,
+ category: ConstraintCategory::OpaqueType,
+ variance_info: ty::VarianceDiagInfo::default(),
+ };
+ handle_constraint(constraint);
+ }
+ }
+
+ None
+ }
+
+ /// Finds some region R such that `fr1: R` and `R` is live at `elem`.
+ #[instrument(skip(self), level = "trace")]
+ pub(crate) fn find_sub_region_live_at(&self, fr1: RegionVid, elem: Location) -> RegionVid {
+ trace!(scc = ?self.constraint_sccs.scc(fr1));
+ trace!(universe = ?self.scc_universes[self.constraint_sccs.scc(fr1)]);
+ self.find_constraint_paths_between_regions(fr1, |r| {
+ // First look for some `r` such that `fr1: r` and `r` is live at `elem`
+ trace!(?r, liveness_constraints=?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` and `fr1` is a
+ // placeholder that `r` cannot name. We can blame that
+ // edge.
+ //
+ // Remember that if `R1: R2`, then the universe of R1
+ // must be able to name the universe of R2, because R2 will
+ // be at least `'empty(Universe(R2))`, and `R1` must be at
+ // larger than that.
+ self.find_constraint_paths_between_regions(fr1, |r| {
+ self.cannot_name_placeholder(r, fr1)
+ })
+ })
+ .map(|(_path, r)| r)
+ .unwrap()
+ }
+
+ /// Get the region outlived by `longer_fr` and live at `element`.
+ pub(crate) fn region_from_element(
+ &self,
+ longer_fr: RegionVid,
+ element: &RegionElement,
+ ) -> RegionVid {
+ match *element {
+ RegionElement::Location(l) => self.find_sub_region_live_at(longer_fr, l),
+ RegionElement::RootUniversalRegion(r) => r,
+ RegionElement::PlaceholderRegion(error_placeholder) => self
+ .definitions
+ .iter_enumerated()
+ .find_map(|(r, definition)| match definition.origin {
+ NllRegionVariableOrigin::Placeholder(p) if p == error_placeholder => Some(r),
+ _ => None,
+ })
+ .unwrap(),
+ }
+ }
+
+ /// Get the region definition of `r`.
+ pub(crate) fn region_definition(&self, r: RegionVid) -> &RegionDefinition<'tcx> {
+ &self.definitions[r]
+ }
+
+ /// Check if the SCC of `r` contains `upper`.
+ pub(crate) fn upper_bound_in_region_scc(&self, r: RegionVid, upper: RegionVid) -> bool {
+ let r_scc = self.constraint_sccs.scc(r);
+ self.scc_values.contains(r_scc, upper)
+ }
+
+ pub(crate) fn universal_regions(&self) -> &UniversalRegions<'tcx> {
+ self.universal_regions.as_ref()
+ }
+
+ /// 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.
+ pub(crate) fn best_blame_constraint(
+ &self,
+ body: &Body<'tcx>,
+ from_region: RegionVid,
+ from_region_origin: NllRegionVariableOrigin,
+ target_test: impl Fn(RegionVid) -> bool,
+ ) -> BlameConstraint<'tcx> {
+ 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::<Vec<_>>()
+ );
+
+ // We try to avoid reporting a `ConstraintCategory::Predicate` as our best constraint.
+ // Instead, we use it to produce an improved `ObligationCauseCode`.
+ // FIXME - determine what we should do if we encounter multiple `ConstraintCategory::Predicate`
+ // constraints. Currently, we just pick the first one.
+ let cause_code = path
+ .iter()
+ .find_map(|constraint| {
+ if let ConstraintCategory::Predicate(predicate_span) = constraint.category {
+ // We currently do not store the `DefId` in the `ConstraintCategory`
+ // for performances reasons. The error reporting code used by NLL only
+ // uses the span, so this doesn't cause any problems at the moment.
+ Some(ObligationCauseCode::BindingObligation(
+ CRATE_DEF_ID.to_def_id(),
+ predicate_span,
+ ))
+ } else {
+ None
+ }
+ })
+ .unwrap_or_else(|| ObligationCauseCode::MiscObligation);
+
+ // Classify each of the constraints along the path.
+ let mut categorized_path: Vec<BlameConstraint<'tcx>> = path
+ .iter()
+ .map(|constraint| {
+ if constraint.category == ConstraintCategory::ClosureBounds {
+ self.retrieve_closure_constraint_info(body, &constraint)
+ } else {
+ BlameConstraint {
+ category: constraint.category,
+ from_closure: false,
+ cause: ObligationCause::new(
+ constraint.span,
+ CRATE_HIR_ID,
+ cause_code.clone(),
+ ),
+ variance_info: constraint.variance_info,
+ }
+ }
+ })
+ .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].category {
+ ConstraintCategory::OpaqueType
+ | ConstraintCategory::Boring
+ | ConstraintCategory::BoringNoLocation
+ | ConstraintCategory::Internal
+ | ConstraintCategory::Predicate(_) => false,
+ ConstraintCategory::TypeAnnotation
+ | ConstraintCategory::Return(_)
+ | ConstraintCategory::Yield => true,
+ _ => constraint_sup_scc != target_scc,
+ }
+ } else {
+ !matches!(
+ categorized_path[*i].category,
+ ConstraintCategory::OpaqueType
+ | ConstraintCategory::Boring
+ | ConstraintCategory::BoringNoLocation
+ | ConstraintCategory::Internal
+ | ConstraintCategory::Predicate(_)
+ )
+ }
+ };
+
+ 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 matches!(categorized_path[i].category, ConstraintCategory::Return(_))
+ && next.category == 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.clone();
+ }
+ }
+
+ if categorized_path[i].category == ConstraintCategory::Return(ReturnConstraint::Normal)
+ {
+ let field = categorized_path.iter().find_map(|p| {
+ if let ConstraintCategory::ClosureUpvar(f) = p.category {
+ Some(f)
+ } else {
+ None
+ }
+ });
+
+ if let Some(field) = field {
+ categorized_path[i].category =
+ ConstraintCategory::Return(ReturnConstraint::ClosureUpvar(field));
+ }
+ }
+
+ return categorized_path[i].clone();
+ }
+
+ // 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.category.cmp(&p1.category));
+ debug!("best_blame_constraint: sorted_path={:#?}", categorized_path);
+
+ categorized_path.remove(0)
+ }
+
+ pub(crate) fn universe_info(&self, universe: ty::UniverseIndex) -> UniverseInfo<'tcx> {
+ self.universe_causes[&universe].clone()
+ }
+}
+
+impl<'tcx> RegionDefinition<'tcx> {
+ fn new(universe: ty::UniverseIndex, rv_origin: RegionVariableOrigin) -> Self {
+ // Create a new region definition. Note that, for free
+ // regions, the `external_name` field gets updated later in
+ // `init_universal_regions`.
+
+ let origin = match rv_origin {
+ RegionVariableOrigin::Nll(origin) => origin,
+ _ => NllRegionVariableOrigin::Existential { from_forall: false },
+ };
+
+ Self { origin, universe, external_name: None }
+ }
+}
+
+pub trait ClosureRegionRequirementsExt<'tcx> {
+ fn apply_requirements(
+ &self,
+ tcx: TyCtxt<'tcx>,
+ closure_def_id: DefId,
+ closure_substs: SubstsRef<'tcx>,
+ ) -> Vec<QueryOutlivesConstraint<'tcx>>;
+}
+
+impl<'tcx> ClosureRegionRequirementsExt<'tcx> for ClosureRegionRequirements<'tcx> {
+ /// Given an instance T of the closure type, this method
+ /// instantiates the "extra" requirements that we computed for the
+ /// closure into the inference context. This has the effect of
+ /// adding new outlives obligations to existing variables.
+ ///
+ /// As described on `ClosureRegionRequirements`, the extra
+ /// requirements are expressed in terms of regionvids that index
+ /// into the free regions that appear on the closure type. So, to
+ /// do this, we first copy those regions out from the type T into
+ /// a vector. Then we can just index into that vector to extract
+ /// out the corresponding region from T and apply the
+ /// requirements.
+ fn apply_requirements(
+ &self,
+ tcx: TyCtxt<'tcx>,
+ closure_def_id: DefId,
+ closure_substs: SubstsRef<'tcx>,
+ ) -> Vec<QueryOutlivesConstraint<'tcx>> {
+ debug!(
+ "apply_requirements(closure_def_id={:?}, closure_substs={:?})",
+ closure_def_id, closure_substs
+ );
+
+ // Extract the values of the free regions in `closure_substs`
+ // into a vector. These are the regions that we will be
+ // relating to one another.
+ let closure_mapping = &UniversalRegions::closure_mapping(
+ tcx,
+ closure_substs,
+ self.num_external_vids,
+ tcx.typeck_root_def_id(closure_def_id),
+ );
+ debug!("apply_requirements: closure_mapping={:?}", closure_mapping);
+
+ // Create the predicates.
+ self.outlives_requirements
+ .iter()
+ .map(|outlives_requirement| {
+ let outlived_region = closure_mapping[outlives_requirement.outlived_free_region];
+
+ match outlives_requirement.subject {
+ ClosureOutlivesSubject::Region(region) => {
+ let region = closure_mapping[region];
+ debug!(
+ "apply_requirements: region={:?} \
+ outlived_region={:?} \
+ outlives_requirement={:?}",
+ region, outlived_region, outlives_requirement,
+ );
+ ty::Binder::dummy(ty::OutlivesPredicate(region.into(), outlived_region))
+ }
+
+ ClosureOutlivesSubject::Ty(ty) => {
+ debug!(
+ "apply_requirements: ty={:?} \
+ outlived_region={:?} \
+ outlives_requirement={:?}",
+ ty, outlived_region, outlives_requirement,
+ );
+ ty::Binder::dummy(ty::OutlivesPredicate(ty.into(), outlived_region))
+ }
+ }
+ })
+ .collect()
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct BlameConstraint<'tcx> {
+ pub category: ConstraintCategory<'tcx>,
+ pub from_closure: bool,
+ pub cause: ObligationCause<'tcx>,
+ pub variance_info: ty::VarianceDiagInfo<'tcx>,
+}
diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs
new file mode 100644
index 000000000..d6712b6a4
--- /dev/null
+++ b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs
@@ -0,0 +1,662 @@
+use rustc_data_structures::fx::FxHashMap;
+use rustc_data_structures::vec_map::VecMap;
+use rustc_hir::def_id::LocalDefId;
+use rustc_hir::OpaqueTyOrigin;
+use rustc_infer::infer::error_reporting::unexpected_hidden_region_diagnostic;
+use rustc_infer::infer::TyCtxtInferExt as _;
+use rustc_infer::infer::{DefiningAnchor, InferCtxt};
+use rustc_infer::traits::{Obligation, ObligationCause, TraitEngine};
+use rustc_middle::ty::fold::{TypeFolder, TypeSuperFoldable};
+use rustc_middle::ty::subst::{GenericArg, GenericArgKind, InternalSubsts};
+use rustc_middle::ty::visit::TypeVisitable;
+use rustc_middle::ty::{
+ self, OpaqueHiddenType, OpaqueTypeKey, ToPredicate, Ty, TyCtxt, TypeFoldable,
+};
+use rustc_span::Span;
+use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _;
+use rustc_trait_selection::traits::TraitEngineExt as _;
+
+use super::RegionInferenceContext;
+
+impl<'tcx> RegionInferenceContext<'tcx> {
+ /// Resolve any opaque types that were encountered while borrow checking
+ /// this item. This is then used to get the type in the `type_of` query.
+ ///
+ /// For example consider `fn f<'a>(x: &'a i32) -> impl Sized + 'a { x }`.
+ /// This is lowered to give HIR something like
+ ///
+ /// type f<'a>::_Return<'_a> = impl Sized + '_a;
+ /// fn f<'a>(x: &'a i32) -> f<'static>::_Return<'a> { x }
+ ///
+ /// When checking the return type record the type from the return and the
+ /// type used in the return value. In this case they might be `_Return<'1>`
+ /// and `&'2 i32` respectively.
+ ///
+ /// Once we to this method, we have completed region inference and want to
+ /// call `infer_opaque_definition_from_instantiation` to get the inferred
+ /// type of `_Return<'_a>`. `infer_opaque_definition_from_instantiation`
+ /// compares lifetimes directly, so we need to map the inference variables
+ /// back to concrete lifetimes: `'static`, `ReEarlyBound` or `ReFree`.
+ ///
+ /// First we map all the lifetimes in the concrete type to an equal
+ /// universal region that occurs in the concrete type's substs, in this case
+ /// this would result in `&'1 i32`. We only consider regions in the substs
+ /// in case there is an equal region that does not. For example, this should
+ /// be allowed:
+ /// `fn f<'a: 'b, 'b: 'a>(x: *mut &'b i32) -> impl Sized + 'a { x }`
+ ///
+ /// Then we map the regions in both the type and the subst to their
+ /// `external_name` giving `concrete_type = &'a i32`,
+ /// `substs = ['static, 'a]`. This will then allow
+ /// `infer_opaque_definition_from_instantiation` to determine that
+ /// `_Return<'_a> = &'_a i32`.
+ ///
+ /// There's a slight complication around closures. Given
+ /// `fn f<'a: 'a>() { || {} }` the closure's type is something like
+ /// `f::<'a>::{{closure}}`. The region parameter from f is essentially
+ /// ignored by type checking so ends up being inferred to an empty region.
+ /// Calling `universal_upper_bound` for such a region gives `fr_fn_body`,
+ /// which has no `external_name` in which case we use `'empty` as the
+ /// region to pass to `infer_opaque_definition_from_instantiation`.
+ #[instrument(level = "debug", skip(self, infcx))]
+ pub(crate) fn infer_opaque_types(
+ &self,
+ infcx: &InferCtxt<'_, 'tcx>,
+ opaque_ty_decls: VecMap<OpaqueTypeKey<'tcx>, (OpaqueHiddenType<'tcx>, OpaqueTyOrigin)>,
+ ) -> VecMap<LocalDefId, OpaqueHiddenType<'tcx>> {
+ let mut result: VecMap<LocalDefId, OpaqueHiddenType<'tcx>> = VecMap::new();
+ for (opaque_type_key, (concrete_type, origin)) in opaque_ty_decls {
+ let substs = opaque_type_key.substs;
+ debug!(?concrete_type, ?substs);
+
+ let mut subst_regions = vec![self.universal_regions.fr_static];
+ let universal_substs = infcx.tcx.fold_regions(substs, |region, _| {
+ if let ty::RePlaceholder(..) = region.kind() {
+ // Higher kinded regions don't need remapping, they don't refer to anything outside of this the substs.
+ return region;
+ }
+ let vid = self.to_region_vid(region);
+ trace!(?vid);
+ let scc = self.constraint_sccs.scc(vid);
+ trace!(?scc);
+ match self.scc_values.universal_regions_outlived_by(scc).find_map(|lb| {
+ self.eval_equal(vid, lb).then_some(self.definitions[lb].external_name?)
+ }) {
+ Some(region) => {
+ let vid = self.universal_regions.to_region_vid(region);
+ subst_regions.push(vid);
+ region
+ }
+ None => {
+ subst_regions.push(vid);
+ infcx.tcx.sess.delay_span_bug(
+ concrete_type.span,
+ "opaque type with non-universal region substs",
+ );
+ infcx.tcx.lifetimes.re_static
+ }
+ }
+ });
+
+ subst_regions.sort();
+ subst_regions.dedup();
+
+ let universal_concrete_type =
+ infcx.tcx.fold_regions(concrete_type, |region, _| match *region {
+ ty::ReVar(vid) => subst_regions
+ .iter()
+ .find(|ur_vid| self.eval_equal(vid, **ur_vid))
+ .and_then(|ur_vid| self.definitions[*ur_vid].external_name)
+ .unwrap_or(infcx.tcx.lifetimes.re_root_empty),
+ _ => region,
+ });
+
+ debug!(?universal_concrete_type, ?universal_substs);
+
+ let opaque_type_key =
+ OpaqueTypeKey { def_id: opaque_type_key.def_id, substs: universal_substs };
+ let ty = infcx.infer_opaque_definition_from_instantiation(
+ opaque_type_key,
+ universal_concrete_type,
+ origin,
+ );
+ // Sometimes two opaque types are the same only after we remap the generic parameters
+ // back to the opaque type definition. E.g. we may have `OpaqueType<X, Y>` mapped to `(X, Y)`
+ // and `OpaqueType<Y, X>` mapped to `(Y, X)`, and those are the same, but we only know that
+ // once we convert the generic parameters to those of the opaque type.
+ if let Some(prev) = result.get_mut(&opaque_type_key.def_id) {
+ if prev.ty != ty {
+ if !ty.references_error() {
+ prev.report_mismatch(
+ &OpaqueHiddenType { ty, span: concrete_type.span },
+ infcx.tcx,
+ );
+ }
+ prev.ty = infcx.tcx.ty_error();
+ }
+ // Pick a better span if there is one.
+ // FIXME(oli-obk): collect multiple spans for better diagnostics down the road.
+ prev.span = prev.span.substitute_dummy(concrete_type.span);
+ } else {
+ result.insert(
+ opaque_type_key.def_id,
+ OpaqueHiddenType { ty, span: concrete_type.span },
+ );
+ }
+ }
+ result
+ }
+
+ /// Map the regions in the type to named regions. This is similar to what
+ /// `infer_opaque_types` does, but can infer any universal region, not only
+ /// ones from the substs for the opaque type. It also doesn't double check
+ /// that the regions produced are in fact equal to the named region they are
+ /// replaced with. This is fine because this function is only to improve the
+ /// region names in error messages.
+ pub(crate) fn name_regions<T>(&self, tcx: TyCtxt<'tcx>, ty: T) -> T
+ where
+ T: TypeFoldable<'tcx>,
+ {
+ tcx.fold_regions(ty, |region, _| match *region {
+ ty::ReVar(vid) => {
+ // Find something that we can name
+ let upper_bound = self.approx_universal_upper_bound(vid);
+ let upper_bound = &self.definitions[upper_bound];
+ match upper_bound.external_name {
+ Some(reg) => reg,
+ None => {
+ // Nothing exact found, so we pick the first one that we find.
+ let scc = self.constraint_sccs.scc(vid);
+ for vid in self.rev_scc_graph.as_ref().unwrap().upper_bounds(scc) {
+ match self.definitions[vid].external_name {
+ None => {}
+ Some(region) if region.is_static() => {}
+ Some(region) => return region,
+ }
+ }
+ region
+ }
+ }
+ }
+ _ => region,
+ })
+ }
+}
+
+pub trait InferCtxtExt<'tcx> {
+ fn infer_opaque_definition_from_instantiation(
+ &self,
+ opaque_type_key: OpaqueTypeKey<'tcx>,
+ instantiated_ty: OpaqueHiddenType<'tcx>,
+ origin: OpaqueTyOrigin,
+ ) -> Ty<'tcx>;
+}
+
+impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
+ /// Given the fully resolved, instantiated type for an opaque
+ /// type, i.e., the value of an inference variable like C1 or C2
+ /// (*), computes the "definition type" for an opaque type
+ /// definition -- that is, the inferred value of `Foo1<'x>` or
+ /// `Foo2<'x>` that we would conceptually use in its definition:
+ /// ```ignore (illustrative)
+ /// type Foo1<'x> = impl Bar<'x> = AAA; // <-- this type AAA
+ /// type Foo2<'x> = impl Bar<'x> = BBB; // <-- or this type BBB
+ /// fn foo<'a, 'b>(..) -> (Foo1<'a>, Foo2<'b>) { .. }
+ /// ```
+ /// Note that these values are defined in terms of a distinct set of
+ /// generic parameters (`'x` instead of `'a`) from C1 or C2. The main
+ /// purpose of this function is to do that translation.
+ ///
+ /// (*) C1 and C2 were introduced in the comments on
+ /// `register_member_constraints`. Read that comment for more context.
+ ///
+ /// # Parameters
+ ///
+ /// - `def_id`, the `impl Trait` type
+ /// - `substs`, the substs used to instantiate this opaque type
+ /// - `instantiated_ty`, the inferred type C1 -- fully resolved, lifted version of
+ /// `opaque_defn.concrete_ty`
+ #[instrument(level = "debug", skip(self))]
+ fn infer_opaque_definition_from_instantiation(
+ &self,
+ opaque_type_key: OpaqueTypeKey<'tcx>,
+ instantiated_ty: OpaqueHiddenType<'tcx>,
+ origin: OpaqueTyOrigin,
+ ) -> Ty<'tcx> {
+ if self.is_tainted_by_errors() {
+ return self.tcx.ty_error();
+ }
+
+ let OpaqueTypeKey { def_id, substs } = opaque_type_key;
+
+ // Use substs to build up a reverse map from regions to their
+ // identity mappings. This is necessary because of `impl
+ // Trait` lifetimes are computed by replacing existing
+ // lifetimes with 'static and remapping only those used in the
+ // `impl Trait` return type, resulting in the parameters
+ // shifting.
+ let id_substs = InternalSubsts::identity_for_item(self.tcx, def_id.to_def_id());
+ debug!(?id_substs);
+ let map: FxHashMap<GenericArg<'tcx>, GenericArg<'tcx>> =
+ substs.iter().enumerate().map(|(index, subst)| (subst, id_substs[index])).collect();
+ debug!("map = {:#?}", map);
+
+ // Convert the type from the function into a type valid outside
+ // the function, by replacing invalid regions with 'static,
+ // after producing an error for each of them.
+ let definition_ty = instantiated_ty.ty.fold_with(&mut ReverseMapper::new(
+ self.tcx,
+ opaque_type_key,
+ map,
+ instantiated_ty.ty,
+ instantiated_ty.span,
+ ));
+ debug!(?definition_ty);
+
+ if !check_opaque_type_parameter_valid(
+ self.tcx,
+ opaque_type_key,
+ origin,
+ instantiated_ty.span,
+ ) {
+ return self.tcx.ty_error();
+ }
+
+ // Only check this for TAIT. RPIT already supports `src/test/ui/impl-trait/nested-return-type2.rs`
+ // on stable and we'd break that.
+ if let OpaqueTyOrigin::TyAlias = origin {
+ // This logic duplicates most of `check_opaque_meets_bounds`.
+ // FIXME(oli-obk): Also do region checks here and then consider removing `check_opaque_meets_bounds` entirely.
+ let param_env = self.tcx.param_env(def_id);
+ let body_id = self.tcx.local_def_id_to_hir_id(def_id);
+ // HACK This bubble is required for this tests to pass:
+ // type-alias-impl-trait/issue-67844-nested-opaque.rs
+ self.tcx.infer_ctxt().with_opaque_type_inference(DefiningAnchor::Bubble).enter(
+ move |infcx| {
+ // Require the hidden type to be well-formed with only the generics of the opaque type.
+ // Defining use functions may have more bounds than the opaque type, which is ok, as long as the
+ // hidden type is well formed even without those bounds.
+ let predicate =
+ ty::Binder::dummy(ty::PredicateKind::WellFormed(definition_ty.into()))
+ .to_predicate(infcx.tcx);
+ let mut fulfillment_cx = <dyn TraitEngine<'tcx>>::new(infcx.tcx);
+
+ // Require that the hidden type actually fulfills all the bounds of the opaque type, even without
+ // the bounds that the function supplies.
+ match infcx.register_hidden_type(
+ OpaqueTypeKey { def_id, substs: id_substs },
+ ObligationCause::misc(instantiated_ty.span, body_id),
+ param_env,
+ definition_ty,
+ origin,
+ ) {
+ Ok(infer_ok) => {
+ for obligation in infer_ok.obligations {
+ fulfillment_cx.register_predicate_obligation(&infcx, obligation);
+ }
+ }
+ Err(err) => {
+ infcx
+ .report_mismatched_types(
+ &ObligationCause::misc(instantiated_ty.span, body_id),
+ self.tcx.mk_opaque(def_id.to_def_id(), id_substs),
+ definition_ty,
+ err,
+ )
+ .emit();
+ }
+ }
+
+ fulfillment_cx.register_predicate_obligation(
+ &infcx,
+ Obligation::misc(instantiated_ty.span, body_id, param_env, predicate),
+ );
+
+ // Check that all obligations are satisfied by the implementation's
+ // version.
+ let errors = fulfillment_cx.select_all_or_error(&infcx);
+
+ // This is still required for many(half of the tests in ui/type-alias-impl-trait)
+ // tests to pass
+ let _ = infcx.inner.borrow_mut().opaque_type_storage.take_opaque_types();
+
+ if errors.is_empty() {
+ definition_ty
+ } else {
+ infcx.report_fulfillment_errors(&errors, None, false);
+ self.tcx.ty_error()
+ }
+ },
+ )
+ } else {
+ definition_ty
+ }
+ }
+}
+
+fn check_opaque_type_parameter_valid(
+ tcx: TyCtxt<'_>,
+ opaque_type_key: OpaqueTypeKey<'_>,
+ origin: OpaqueTyOrigin,
+ span: Span,
+) -> bool {
+ match origin {
+ // No need to check return position impl trait (RPIT)
+ // because for type and const parameters they are correct
+ // by construction: we convert
+ //
+ // fn foo<P0..Pn>() -> impl Trait
+ //
+ // into
+ //
+ // type Foo<P0...Pn>
+ // fn foo<P0..Pn>() -> Foo<P0...Pn>.
+ //
+ // For lifetime parameters we convert
+ //
+ // fn foo<'l0..'ln>() -> impl Trait<'l0..'lm>
+ //
+ // into
+ //
+ // type foo::<'p0..'pn>::Foo<'q0..'qm>
+ // fn foo<l0..'ln>() -> foo::<'static..'static>::Foo<'l0..'lm>.
+ //
+ // which would error here on all of the `'static` args.
+ OpaqueTyOrigin::FnReturn(..) | OpaqueTyOrigin::AsyncFn(..) => return true,
+ // Check these
+ OpaqueTyOrigin::TyAlias => {}
+ }
+ let opaque_generics = tcx.generics_of(opaque_type_key.def_id);
+ let mut seen_params: FxHashMap<_, Vec<_>> = FxHashMap::default();
+ for (i, arg) in opaque_type_key.substs.iter().enumerate() {
+ let arg_is_param = match arg.unpack() {
+ GenericArgKind::Type(ty) => matches!(ty.kind(), ty::Param(_)),
+ GenericArgKind::Lifetime(lt) if lt.is_static() => {
+ tcx.sess
+ .struct_span_err(span, "non-defining opaque type use in defining scope")
+ .span_label(
+ tcx.def_span(opaque_generics.param_at(i, tcx).def_id),
+ "cannot use static lifetime; use a bound lifetime \
+ instead or remove the lifetime parameter from the \
+ opaque type",
+ )
+ .emit();
+ return false;
+ }
+ GenericArgKind::Lifetime(lt) => {
+ matches!(*lt, ty::ReEarlyBound(_) | ty::ReFree(_))
+ }
+ GenericArgKind::Const(ct) => matches!(ct.kind(), ty::ConstKind::Param(_)),
+ };
+
+ if arg_is_param {
+ seen_params.entry(arg).or_default().push(i);
+ } else {
+ // Prevent `fn foo() -> Foo<u32>` from being defining.
+ let opaque_param = opaque_generics.param_at(i, tcx);
+ tcx.sess
+ .struct_span_err(span, "non-defining opaque type use in defining scope")
+ .span_note(
+ tcx.def_span(opaque_param.def_id),
+ &format!(
+ "used non-generic {} `{}` for generic parameter",
+ opaque_param.kind.descr(),
+ arg,
+ ),
+ )
+ .emit();
+ return false;
+ }
+ }
+
+ for (_, indices) in seen_params {
+ if indices.len() > 1 {
+ let descr = opaque_generics.param_at(indices[0], tcx).kind.descr();
+ let spans: Vec<_> = indices
+ .into_iter()
+ .map(|i| tcx.def_span(opaque_generics.param_at(i, tcx).def_id))
+ .collect();
+ tcx.sess
+ .struct_span_err(span, "non-defining opaque type use in defining scope")
+ .span_note(spans, &format!("{} used multiple times", descr))
+ .emit();
+ return false;
+ }
+ }
+ true
+}
+
+struct ReverseMapper<'tcx> {
+ tcx: TyCtxt<'tcx>,
+
+ key: ty::OpaqueTypeKey<'tcx>,
+ map: FxHashMap<GenericArg<'tcx>, GenericArg<'tcx>>,
+ map_missing_regions_to_empty: bool,
+
+ /// initially `Some`, set to `None` once error has been reported
+ hidden_ty: Option<Ty<'tcx>>,
+
+ /// Span of function being checked.
+ span: Span,
+}
+
+impl<'tcx> ReverseMapper<'tcx> {
+ fn new(
+ tcx: TyCtxt<'tcx>,
+ key: ty::OpaqueTypeKey<'tcx>,
+ map: FxHashMap<GenericArg<'tcx>, GenericArg<'tcx>>,
+ hidden_ty: Ty<'tcx>,
+ span: Span,
+ ) -> Self {
+ Self {
+ tcx,
+ key,
+ map,
+ map_missing_regions_to_empty: false,
+ hidden_ty: Some(hidden_ty),
+ span,
+ }
+ }
+
+ fn fold_kind_mapping_missing_regions_to_empty(
+ &mut self,
+ kind: GenericArg<'tcx>,
+ ) -> GenericArg<'tcx> {
+ assert!(!self.map_missing_regions_to_empty);
+ self.map_missing_regions_to_empty = true;
+ let kind = kind.fold_with(self);
+ self.map_missing_regions_to_empty = false;
+ kind
+ }
+
+ fn fold_kind_normally(&mut self, kind: GenericArg<'tcx>) -> GenericArg<'tcx> {
+ assert!(!self.map_missing_regions_to_empty);
+ kind.fold_with(self)
+ }
+}
+
+impl<'tcx> TypeFolder<'tcx> for ReverseMapper<'tcx> {
+ fn tcx(&self) -> TyCtxt<'tcx> {
+ self.tcx
+ }
+
+ #[instrument(skip(self), level = "debug")]
+ fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> {
+ match *r {
+ // Ignore bound regions and `'static` regions that appear in the
+ // type, we only need to remap regions that reference lifetimes
+ // from the function declaration.
+ // This would ignore `'r` in a type like `for<'r> fn(&'r u32)`.
+ ty::ReLateBound(..) | ty::ReStatic => return r,
+
+ // If regions have been erased (by writeback), don't try to unerase
+ // them.
+ ty::ReErased => return r,
+
+ // The regions that we expect from borrow checking.
+ ty::ReEarlyBound(_) | ty::ReFree(_) | ty::ReEmpty(ty::UniverseIndex::ROOT) => {}
+
+ ty::ReEmpty(_) | ty::RePlaceholder(_) | ty::ReVar(_) => {
+ // All of the regions in the type should either have been
+ // erased by writeback, or mapped back to named regions by
+ // borrow checking.
+ bug!("unexpected region kind in opaque type: {:?}", r);
+ }
+ }
+
+ let generics = self.tcx().generics_of(self.key.def_id);
+ match self.map.get(&r.into()).map(|k| k.unpack()) {
+ Some(GenericArgKind::Lifetime(r1)) => r1,
+ Some(u) => panic!("region mapped to unexpected kind: {:?}", u),
+ None if self.map_missing_regions_to_empty => self.tcx.lifetimes.re_root_empty,
+ None if generics.parent.is_some() => {
+ if let Some(hidden_ty) = self.hidden_ty.take() {
+ unexpected_hidden_region_diagnostic(
+ self.tcx,
+ self.tcx.def_span(self.key.def_id),
+ hidden_ty,
+ r,
+ self.key,
+ )
+ .emit();
+ }
+ self.tcx.lifetimes.re_root_empty
+ }
+ None => {
+ self.tcx
+ .sess
+ .struct_span_err(self.span, "non-defining opaque type use in defining scope")
+ .span_label(
+ self.span,
+ format!(
+ "lifetime `{}` is part of concrete type but not used in \
+ parameter list of the `impl Trait` type alias",
+ r
+ ),
+ )
+ .emit();
+
+ self.tcx().lifetimes.re_static
+ }
+ }
+ }
+
+ fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> {
+ match *ty.kind() {
+ ty::Closure(def_id, substs) => {
+ // I am a horrible monster and I pray for death. When
+ // we encounter a closure here, it is always a closure
+ // from within the function that we are currently
+ // type-checking -- one that is now being encapsulated
+ // in an opaque type. Ideally, we would
+ // go through the types/lifetimes that it references
+ // and treat them just like we would any other type,
+ // which means we would error out if we find any
+ // reference to a type/region that is not in the
+ // "reverse map".
+ //
+ // **However,** in the case of closures, there is a
+ // somewhat subtle (read: hacky) consideration. The
+ // problem is that our closure types currently include
+ // all the lifetime parameters declared on the
+ // enclosing function, even if they are unused by the
+ // closure itself. We can't readily filter them out,
+ // so here we replace those values with `'empty`. This
+ // can't really make a difference to the rest of the
+ // compiler; those regions are ignored for the
+ // outlives relation, and hence don't affect trait
+ // selection or auto traits, and they are erased
+ // during codegen.
+
+ let generics = self.tcx.generics_of(def_id);
+ let substs = self.tcx.mk_substs(substs.iter().enumerate().map(|(index, kind)| {
+ if index < generics.parent_count {
+ // Accommodate missing regions in the parent kinds...
+ self.fold_kind_mapping_missing_regions_to_empty(kind)
+ } else {
+ // ...but not elsewhere.
+ self.fold_kind_normally(kind)
+ }
+ }));
+
+ self.tcx.mk_closure(def_id, substs)
+ }
+
+ ty::Generator(def_id, substs, movability) => {
+ let generics = self.tcx.generics_of(def_id);
+ let substs = self.tcx.mk_substs(substs.iter().enumerate().map(|(index, kind)| {
+ if index < generics.parent_count {
+ // Accommodate missing regions in the parent kinds...
+ self.fold_kind_mapping_missing_regions_to_empty(kind)
+ } else {
+ // ...but not elsewhere.
+ self.fold_kind_normally(kind)
+ }
+ }));
+
+ self.tcx.mk_generator(def_id, substs, movability)
+ }
+
+ ty::Param(param) => {
+ // Look it up in the substitution list.
+ match self.map.get(&ty.into()).map(|k| k.unpack()) {
+ // Found it in the substitution list; replace with the parameter from the
+ // opaque type.
+ Some(GenericArgKind::Type(t1)) => t1,
+ Some(u) => panic!("type mapped to unexpected kind: {:?}", u),
+ None => {
+ debug!(?param, ?self.map);
+ self.tcx
+ .sess
+ .struct_span_err(
+ self.span,
+ &format!(
+ "type parameter `{}` is part of concrete type but not \
+ used in parameter list for the `impl Trait` type alias",
+ ty
+ ),
+ )
+ .emit();
+
+ self.tcx().ty_error()
+ }
+ }
+ }
+
+ _ => ty.super_fold_with(self),
+ }
+ }
+
+ fn fold_const(&mut self, ct: ty::Const<'tcx>) -> ty::Const<'tcx> {
+ trace!("checking const {:?}", ct);
+ // Find a const parameter
+ match ct.kind() {
+ ty::ConstKind::Param(..) => {
+ // Look it up in the substitution list.
+ match self.map.get(&ct.into()).map(|k| k.unpack()) {
+ // Found it in the substitution list, replace with the parameter from the
+ // opaque type.
+ Some(GenericArgKind::Const(c1)) => c1,
+ Some(u) => panic!("const mapped to unexpected kind: {:?}", u),
+ None => {
+ self.tcx
+ .sess
+ .struct_span_err(
+ self.span,
+ &format!(
+ "const parameter `{}` is part of concrete type but not \
+ used in parameter list for the `impl Trait` type alias",
+ ct
+ ),
+ )
+ .emit();
+
+ self.tcx().const_error(ct.ty())
+ }
+ }
+ }
+
+ _ => ct,
+ }
+ }
+}
diff --git a/compiler/rustc_borrowck/src/region_infer/reverse_sccs.rs b/compiler/rustc_borrowck/src/region_infer/reverse_sccs.rs
new file mode 100644
index 000000000..1e6798eee
--- /dev/null
+++ b/compiler/rustc_borrowck/src/region_infer/reverse_sccs.rs
@@ -0,0 +1,68 @@
+use crate::constraints::ConstraintSccIndex;
+use crate::RegionInferenceContext;
+use itertools::Itertools;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::graph::vec_graph::VecGraph;
+use rustc_data_structures::graph::WithSuccessors;
+use rustc_middle::ty::RegionVid;
+use std::ops::Range;
+use std::rc::Rc;
+
+pub(crate) struct ReverseSccGraph {
+ graph: VecGraph<ConstraintSccIndex>,
+ /// For each SCC, the range of `universal_regions` that use that SCC as
+ /// their value.
+ scc_regions: FxHashMap<ConstraintSccIndex, Range<usize>>,
+ /// All of the universal regions, in grouped so that `scc_regions` can
+ /// index into here.
+ universal_regions: Vec<RegionVid>,
+}
+
+impl ReverseSccGraph {
+ /// Find all universal regions that are required to outlive the given SCC.
+ pub(super) fn upper_bounds<'a>(
+ &'a self,
+ scc0: ConstraintSccIndex,
+ ) -> impl Iterator<Item = RegionVid> + 'a {
+ let mut duplicates = FxHashSet::default();
+ self.graph
+ .depth_first_search(scc0)
+ .flat_map(move |scc1| {
+ self.scc_regions
+ .get(&scc1)
+ .map_or(&[][..], |range| &self.universal_regions[range.clone()])
+ })
+ .copied()
+ .filter(move |r| duplicates.insert(*r))
+ }
+}
+
+impl RegionInferenceContext<'_> {
+ /// Compute and return the reverse SCC-based constraint graph (lazily).
+ pub(super) fn reverse_scc_graph(&mut self) -> Rc<ReverseSccGraph> {
+ if let Some(g) = &self.rev_scc_graph {
+ return g.clone();
+ }
+
+ let graph = self.constraint_sccs.reverse();
+ let mut paired_scc_regions = self
+ .universal_regions
+ .universal_regions()
+ .map(|region| (self.constraint_sccs.scc(region), region))
+ .collect_vec();
+ paired_scc_regions.sort();
+ let universal_regions = paired_scc_regions.iter().map(|&(_, region)| region).collect();
+
+ let mut scc_regions = FxHashMap::default();
+ let mut start = 0;
+ for (scc, group) in &paired_scc_regions.into_iter().group_by(|(scc, _)| *scc) {
+ let group_size = group.count();
+ scc_regions.insert(scc, start..start + group_size);
+ start += group_size;
+ }
+
+ let rev_graph = Rc::new(ReverseSccGraph { graph, scc_regions, universal_regions });
+ self.rev_scc_graph = Some(rev_graph.clone());
+ rev_graph
+ }
+}
diff --git a/compiler/rustc_borrowck/src/region_infer/values.rs b/compiler/rustc_borrowck/src/region_infer/values.rs
new file mode 100644
index 000000000..c81ef10f7
--- /dev/null
+++ b/compiler/rustc_borrowck/src/region_infer/values.rs
@@ -0,0 +1,488 @@
+use rustc_data_structures::fx::FxIndexSet;
+use rustc_index::bit_set::SparseBitMatrix;
+use rustc_index::interval::IntervalSet;
+use rustc_index::interval::SparseIntervalMatrix;
+use rustc_index::vec::Idx;
+use rustc_index::vec::IndexVec;
+use rustc_middle::mir::{BasicBlock, Body, Location};
+use rustc_middle::ty::{self, RegionVid};
+use std::fmt::Debug;
+use std::rc::Rc;
+
+/// Maps between a `Location` and a `PointIndex` (and vice versa).
+pub(crate) struct RegionValueElements {
+ /// For each basic block, how many points are contained within?
+ statements_before_block: IndexVec<BasicBlock, usize>,
+
+ /// Map backward from each point to the basic block that it
+ /// belongs to.
+ basic_blocks: IndexVec<PointIndex, BasicBlock>,
+
+ num_points: usize,
+}
+
+impl RegionValueElements {
+ pub(crate) fn new(body: &Body<'_>) -> Self {
+ let mut num_points = 0;
+ let statements_before_block: IndexVec<BasicBlock, usize> = body
+ .basic_blocks()
+ .iter()
+ .map(|block_data| {
+ let v = num_points;
+ num_points += block_data.statements.len() + 1;
+ v
+ })
+ .collect();
+ debug!("RegionValueElements: statements_before_block={:#?}", statements_before_block);
+ debug!("RegionValueElements: num_points={:#?}", num_points);
+
+ let mut basic_blocks = IndexVec::with_capacity(num_points);
+ for (bb, bb_data) in body.basic_blocks().iter_enumerated() {
+ basic_blocks.extend((0..=bb_data.statements.len()).map(|_| bb));
+ }
+
+ Self { statements_before_block, basic_blocks, num_points }
+ }
+
+ /// Total number of point indices
+ pub(crate) fn num_points(&self) -> usize {
+ self.num_points
+ }
+
+ /// Converts a `Location` into a `PointIndex`. O(1).
+ pub(crate) fn point_from_location(&self, location: Location) -> PointIndex {
+ let Location { block, statement_index } = location;
+ let start_index = self.statements_before_block[block];
+ PointIndex::new(start_index + statement_index)
+ }
+
+ /// Converts a `Location` into a `PointIndex`. O(1).
+ pub(crate) fn entry_point(&self, block: BasicBlock) -> PointIndex {
+ let start_index = self.statements_before_block[block];
+ PointIndex::new(start_index)
+ }
+
+ /// Return the PointIndex for the block start of this index.
+ pub(crate) fn to_block_start(&self, index: PointIndex) -> PointIndex {
+ PointIndex::new(self.statements_before_block[self.basic_blocks[index]])
+ }
+
+ /// Converts a `PointIndex` back to a location. O(1).
+ pub(crate) fn to_location(&self, index: PointIndex) -> Location {
+ assert!(index.index() < self.num_points);
+ let block = self.basic_blocks[index];
+ let start_index = self.statements_before_block[block];
+ let statement_index = index.index() - start_index;
+ Location { block, statement_index }
+ }
+
+ /// Sometimes we get point-indices back from bitsets that may be
+ /// out of range (because they round up to the nearest 2^N number
+ /// of bits). Use this function to filter such points out if you
+ /// like.
+ pub(crate) fn point_in_range(&self, index: PointIndex) -> bool {
+ index.index() < self.num_points
+ }
+}
+
+rustc_index::newtype_index! {
+ /// A single integer representing a `Location` in the MIR control-flow
+ /// graph. Constructed efficiently from `RegionValueElements`.
+ pub struct PointIndex { DEBUG_FORMAT = "PointIndex({})" }
+}
+
+rustc_index::newtype_index! {
+ /// A single integer representing a `ty::Placeholder`.
+ pub struct PlaceholderIndex { DEBUG_FORMAT = "PlaceholderIndex({})" }
+}
+
+/// An individual element in a region value -- the value of a
+/// particular region variable consists of a set of these elements.
+#[derive(Debug, Clone)]
+pub(crate) enum RegionElement {
+ /// A point in the control-flow graph.
+ Location(Location),
+
+ /// A universally quantified region from the root universe (e.g.,
+ /// a lifetime parameter).
+ RootUniversalRegion(RegionVid),
+
+ /// A placeholder (e.g., instantiated from a `for<'a> fn(&'a u32)`
+ /// type).
+ PlaceholderRegion(ty::PlaceholderRegion),
+}
+
+/// When we initially compute liveness, we use an interval matrix storing
+/// liveness ranges for each region-vid.
+pub(crate) struct LivenessValues<N: Idx> {
+ elements: Rc<RegionValueElements>,
+ points: SparseIntervalMatrix<N, PointIndex>,
+}
+
+impl<N: Idx> LivenessValues<N> {
+ /// Creates a new set of "region values" that tracks causal information.
+ /// Each of the regions in num_region_variables will be initialized with an
+ /// empty set of points and no causal information.
+ pub(crate) fn new(elements: Rc<RegionValueElements>) -> Self {
+ Self { points: SparseIntervalMatrix::new(elements.num_points), elements }
+ }
+
+ /// Iterate through each region that has a value in this set.
+ pub(crate) fn rows(&self) -> impl Iterator<Item = N> {
+ self.points.rows()
+ }
+
+ /// Adds the given element to the value for the given region. Returns whether
+ /// the element is newly added (i.e., was not already present).
+ pub(crate) fn add_element(&mut self, row: N, location: Location) -> bool {
+ debug!("LivenessValues::add(r={:?}, location={:?})", row, location);
+ let index = self.elements.point_from_location(location);
+ self.points.insert(row, index)
+ }
+
+ /// Adds all the elements in the given bit array into the given
+ /// region. Returns whether any of them are newly added.
+ pub(crate) fn add_elements(&mut self, row: N, locations: &IntervalSet<PointIndex>) -> bool {
+ debug!("LivenessValues::add_elements(row={:?}, locations={:?})", row, locations);
+ self.points.union_row(row, locations)
+ }
+
+ /// Adds all the control-flow points to the values for `r`.
+ pub(crate) fn add_all_points(&mut self, row: N) {
+ self.points.insert_all_into_row(row);
+ }
+
+ /// Returns `true` if the region `r` contains the given element.
+ pub(crate) fn contains(&self, row: N, location: Location) -> bool {
+ let index = self.elements.point_from_location(location);
+ self.points.row(row).map_or(false, |r| r.contains(index))
+ }
+
+ /// Returns an iterator of all the elements contained by the region `r`
+ pub(crate) fn get_elements(&self, row: N) -> impl Iterator<Item = Location> + '_ {
+ self.points
+ .row(row)
+ .into_iter()
+ .flat_map(|set| set.iter())
+ .take_while(move |&p| self.elements.point_in_range(p))
+ .map(move |p| self.elements.to_location(p))
+ }
+
+ /// Returns a "pretty" string value of the region. Meant for debugging.
+ pub(crate) fn region_value_str(&self, r: N) -> String {
+ region_value_str(self.get_elements(r).map(RegionElement::Location))
+ }
+}
+
+/// Maps from `ty::PlaceholderRegion` values that are used in the rest of
+/// rustc to the internal `PlaceholderIndex` values that are used in
+/// NLL.
+#[derive(Default)]
+pub(crate) struct PlaceholderIndices {
+ indices: FxIndexSet<ty::PlaceholderRegion>,
+}
+
+impl PlaceholderIndices {
+ pub(crate) fn insert(&mut self, placeholder: ty::PlaceholderRegion) -> PlaceholderIndex {
+ let (index, _) = self.indices.insert_full(placeholder);
+ index.into()
+ }
+
+ pub(crate) fn lookup_index(&self, placeholder: ty::PlaceholderRegion) -> PlaceholderIndex {
+ self.indices.get_index_of(&placeholder).unwrap().into()
+ }
+
+ pub(crate) fn lookup_placeholder(
+ &self,
+ placeholder: PlaceholderIndex,
+ ) -> ty::PlaceholderRegion {
+ self.indices[placeholder.index()]
+ }
+
+ pub(crate) fn len(&self) -> usize {
+ self.indices.len()
+ }
+}
+
+/// Stores the full values for a set of regions (in contrast to
+/// `LivenessValues`, which only stores those points in the where a
+/// region is live). The full value for a region may contain points in
+/// the CFG, but also free regions as well as bound universe
+/// placeholders.
+///
+/// Example:
+///
+/// ```text
+/// fn foo(x: &'a u32) -> &'a u32 {
+/// let y: &'0 u32 = x; // let's call this `'0`
+/// y
+/// }
+/// ```
+///
+/// Here, the variable `'0` would contain the free region `'a`,
+/// because (since it is returned) it must live for at least `'a`. But
+/// it would also contain various points from within the function.
+#[derive(Clone)]
+pub(crate) struct RegionValues<N: Idx> {
+ elements: Rc<RegionValueElements>,
+ placeholder_indices: Rc<PlaceholderIndices>,
+ points: SparseIntervalMatrix<N, PointIndex>,
+ free_regions: SparseBitMatrix<N, RegionVid>,
+
+ /// Placeholders represent bound regions -- so something like `'a`
+ /// in for<'a> fn(&'a u32)`.
+ placeholders: SparseBitMatrix<N, PlaceholderIndex>,
+}
+
+impl<N: Idx> RegionValues<N> {
+ /// Creates a new set of "region values" that tracks causal information.
+ /// Each of the regions in num_region_variables will be initialized with an
+ /// empty set of points and no causal information.
+ pub(crate) fn new(
+ elements: &Rc<RegionValueElements>,
+ num_universal_regions: usize,
+ placeholder_indices: &Rc<PlaceholderIndices>,
+ ) -> Self {
+ let num_placeholders = placeholder_indices.len();
+ Self {
+ elements: elements.clone(),
+ points: SparseIntervalMatrix::new(elements.num_points),
+ placeholder_indices: placeholder_indices.clone(),
+ free_regions: SparseBitMatrix::new(num_universal_regions),
+ placeholders: SparseBitMatrix::new(num_placeholders),
+ }
+ }
+
+ /// Adds the given element to the value for the given region. Returns whether
+ /// the element is newly added (i.e., was not already present).
+ pub(crate) fn add_element(&mut self, r: N, elem: impl ToElementIndex) -> bool {
+ debug!("add(r={:?}, elem={:?})", r, elem);
+ elem.add_to_row(self, r)
+ }
+
+ /// Adds all the control-flow points to the values for `r`.
+ pub(crate) fn add_all_points(&mut self, r: N) {
+ self.points.insert_all_into_row(r);
+ }
+
+ /// Adds all elements in `r_from` to `r_to` (because e.g., `r_to:
+ /// r_from`).
+ pub(crate) fn add_region(&mut self, r_to: N, r_from: N) -> bool {
+ self.points.union_rows(r_from, r_to)
+ | self.free_regions.union_rows(r_from, r_to)
+ | self.placeholders.union_rows(r_from, r_to)
+ }
+
+ /// Returns `true` if the region `r` contains the given element.
+ pub(crate) fn contains(&self, r: N, elem: impl ToElementIndex) -> bool {
+ elem.contained_in_row(self, r)
+ }
+
+ /// `self[to] |= values[from]`, essentially: that is, take all the
+ /// elements for the region `from` from `values` and add them to
+ /// the region `to` in `self`.
+ pub(crate) fn merge_liveness<M: Idx>(&mut self, to: N, from: M, values: &LivenessValues<M>) {
+ if let Some(set) = values.points.row(from) {
+ self.points.union_row(to, set);
+ }
+ }
+
+ /// Returns `true` if `sup_region` contains all the CFG points that
+ /// `sub_region` contains. Ignores universal regions.
+ pub(crate) fn contains_points(&self, sup_region: N, sub_region: N) -> bool {
+ if let Some(sub_row) = self.points.row(sub_region) {
+ if let Some(sup_row) = self.points.row(sup_region) {
+ sup_row.superset(sub_row)
+ } else {
+ // sup row is empty, so sub row must be empty
+ sub_row.is_empty()
+ }
+ } else {
+ // sub row is empty, always true
+ true
+ }
+ }
+
+ /// Returns the locations contained within a given region `r`.
+ pub(crate) fn locations_outlived_by<'a>(&'a self, r: N) -> impl Iterator<Item = Location> + 'a {
+ self.points.row(r).into_iter().flat_map(move |set| {
+ set.iter()
+ .take_while(move |&p| self.elements.point_in_range(p))
+ .map(move |p| self.elements.to_location(p))
+ })
+ }
+
+ /// Returns just the universal regions that are contained in a given region's value.
+ pub(crate) fn universal_regions_outlived_by<'a>(
+ &'a self,
+ r: N,
+ ) -> impl Iterator<Item = RegionVid> + 'a {
+ self.free_regions.row(r).into_iter().flat_map(|set| set.iter())
+ }
+
+ /// Returns all the elements contained in a given region's value.
+ pub(crate) fn placeholders_contained_in<'a>(
+ &'a self,
+ r: N,
+ ) -> impl Iterator<Item = ty::PlaceholderRegion> + 'a {
+ self.placeholders
+ .row(r)
+ .into_iter()
+ .flat_map(|set| set.iter())
+ .map(move |p| self.placeholder_indices.lookup_placeholder(p))
+ }
+
+ /// Returns all the elements contained in a given region's value.
+ pub(crate) fn elements_contained_in<'a>(
+ &'a self,
+ r: N,
+ ) -> impl Iterator<Item = RegionElement> + 'a {
+ let points_iter = self.locations_outlived_by(r).map(RegionElement::Location);
+
+ let free_regions_iter =
+ self.universal_regions_outlived_by(r).map(RegionElement::RootUniversalRegion);
+
+ let placeholder_universes_iter =
+ self.placeholders_contained_in(r).map(RegionElement::PlaceholderRegion);
+
+ points_iter.chain(free_regions_iter).chain(placeholder_universes_iter)
+ }
+
+ /// Returns a "pretty" string value of the region. Meant for debugging.
+ pub(crate) fn region_value_str(&self, r: N) -> String {
+ region_value_str(self.elements_contained_in(r))
+ }
+}
+
+pub(crate) trait ToElementIndex: Debug + Copy {
+ fn add_to_row<N: Idx>(self, values: &mut RegionValues<N>, row: N) -> bool;
+
+ fn contained_in_row<N: Idx>(self, values: &RegionValues<N>, row: N) -> bool;
+}
+
+impl ToElementIndex for Location {
+ fn add_to_row<N: Idx>(self, values: &mut RegionValues<N>, row: N) -> bool {
+ let index = values.elements.point_from_location(self);
+ values.points.insert(row, index)
+ }
+
+ fn contained_in_row<N: Idx>(self, values: &RegionValues<N>, row: N) -> bool {
+ let index = values.elements.point_from_location(self);
+ values.points.contains(row, index)
+ }
+}
+
+impl ToElementIndex for RegionVid {
+ fn add_to_row<N: Idx>(self, values: &mut RegionValues<N>, row: N) -> bool {
+ values.free_regions.insert(row, self)
+ }
+
+ fn contained_in_row<N: Idx>(self, values: &RegionValues<N>, row: N) -> bool {
+ values.free_regions.contains(row, self)
+ }
+}
+
+impl ToElementIndex for ty::PlaceholderRegion {
+ fn add_to_row<N: Idx>(self, values: &mut RegionValues<N>, row: N) -> bool {
+ let index = values.placeholder_indices.lookup_index(self);
+ values.placeholders.insert(row, index)
+ }
+
+ fn contained_in_row<N: Idx>(self, values: &RegionValues<N>, row: N) -> bool {
+ let index = values.placeholder_indices.lookup_index(self);
+ values.placeholders.contains(row, index)
+ }
+}
+
+pub(crate) fn location_set_str(
+ elements: &RegionValueElements,
+ points: impl IntoIterator<Item = PointIndex>,
+) -> String {
+ region_value_str(
+ points
+ .into_iter()
+ .take_while(|&p| elements.point_in_range(p))
+ .map(|p| elements.to_location(p))
+ .map(RegionElement::Location),
+ )
+}
+
+fn region_value_str(elements: impl IntoIterator<Item = RegionElement>) -> String {
+ let mut result = String::new();
+ result.push('{');
+
+ // Set to Some(l1, l2) when we have observed all the locations
+ // from l1..=l2 (inclusive) but not yet printed them. This
+ // gets extended if we then see l3 where l3 is the successor
+ // to l2.
+ let mut open_location: Option<(Location, Location)> = None;
+
+ let mut sep = "";
+ let mut push_sep = |s: &mut String| {
+ s.push_str(sep);
+ sep = ", ";
+ };
+
+ for element in elements {
+ match element {
+ RegionElement::Location(l) => {
+ if let Some((location1, location2)) = open_location {
+ if location2.block == l.block
+ && location2.statement_index == l.statement_index - 1
+ {
+ open_location = Some((location1, l));
+ continue;
+ }
+
+ push_sep(&mut result);
+ push_location_range(&mut result, location1, location2);
+ }
+
+ open_location = Some((l, l));
+ }
+
+ RegionElement::RootUniversalRegion(fr) => {
+ if let Some((location1, location2)) = open_location {
+ push_sep(&mut result);
+ push_location_range(&mut result, location1, location2);
+ open_location = None;
+ }
+
+ push_sep(&mut result);
+ result.push_str(&format!("{:?}", fr));
+ }
+
+ RegionElement::PlaceholderRegion(placeholder) => {
+ if let Some((location1, location2)) = open_location {
+ push_sep(&mut result);
+ push_location_range(&mut result, location1, location2);
+ open_location = None;
+ }
+
+ push_sep(&mut result);
+ result.push_str(&format!("{:?}", placeholder));
+ }
+ }
+ }
+
+ if let Some((location1, location2)) = open_location {
+ push_sep(&mut result);
+ push_location_range(&mut result, location1, location2);
+ }
+
+ result.push('}');
+
+ return result;
+
+ fn push_location_range(str: &mut String, location1: Location, location2: Location) {
+ if location1 == location2 {
+ str.push_str(&format!("{:?}", location1));
+ } else {
+ assert_eq!(location1.block, location2.block);
+ str.push_str(&format!(
+ "{:?}[{}..={}]",
+ location1.block, location1.statement_index, location2.statement_index
+ ));
+ }
+ }
+}
diff --git a/compiler/rustc_borrowck/src/renumber.rs b/compiler/rustc_borrowck/src/renumber.rs
new file mode 100644
index 000000000..7a8ce621c
--- /dev/null
+++ b/compiler/rustc_borrowck/src/renumber.rs
@@ -0,0 +1,83 @@
+use rustc_index::vec::IndexVec;
+use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin};
+use rustc_middle::mir::visit::{MutVisitor, TyContext};
+use rustc_middle::mir::{Body, Location, Promoted};
+use rustc_middle::ty::subst::SubstsRef;
+use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable};
+
+/// Replaces all free regions appearing in the MIR with fresh
+/// inference variables, returning the number of variables created.
+#[instrument(skip(infcx, body, promoted), level = "debug")]
+pub fn renumber_mir<'tcx>(
+ infcx: &InferCtxt<'_, 'tcx>,
+ body: &mut Body<'tcx>,
+ promoted: &mut IndexVec<Promoted, Body<'tcx>>,
+) {
+ debug!(?body.arg_count);
+
+ let mut visitor = NllVisitor { infcx };
+
+ for body in promoted.iter_mut() {
+ visitor.visit_body(body);
+ }
+
+ visitor.visit_body(body);
+}
+
+/// Replaces all regions appearing in `value` with fresh inference
+/// variables.
+#[instrument(skip(infcx), level = "debug")]
+pub fn renumber_regions<'tcx, T>(infcx: &InferCtxt<'_, 'tcx>, value: T) -> T
+where
+ T: TypeFoldable<'tcx>,
+{
+ infcx.tcx.fold_regions(value, |_region, _depth| {
+ let origin = NllRegionVariableOrigin::Existential { from_forall: false };
+ infcx.next_nll_region_var(origin)
+ })
+}
+
+struct NllVisitor<'a, 'tcx> {
+ infcx: &'a InferCtxt<'a, 'tcx>,
+}
+
+impl<'a, 'tcx> NllVisitor<'a, 'tcx> {
+ fn renumber_regions<T>(&mut self, value: T) -> T
+ where
+ T: TypeFoldable<'tcx>,
+ {
+ renumber_regions(self.infcx, value)
+ }
+}
+
+impl<'a, 'tcx> MutVisitor<'tcx> for NllVisitor<'a, 'tcx> {
+ fn tcx(&self) -> TyCtxt<'tcx> {
+ self.infcx.tcx
+ }
+
+ #[instrument(skip(self), level = "debug")]
+ fn visit_ty(&mut self, ty: &mut Ty<'tcx>, ty_context: TyContext) {
+ *ty = self.renumber_regions(*ty);
+
+ debug!(?ty);
+ }
+
+ #[instrument(skip(self), level = "debug")]
+ fn visit_substs(&mut self, substs: &mut SubstsRef<'tcx>, location: Location) {
+ *substs = self.renumber_regions(*substs);
+
+ debug!(?substs);
+ }
+
+ #[instrument(skip(self), level = "debug")]
+ fn visit_region(&mut self, region: &mut ty::Region<'tcx>, location: Location) {
+ let old_region = *region;
+ *region = self.renumber_regions(old_region);
+
+ debug!(?region);
+ }
+
+ fn visit_const(&mut self, constant: &mut ty::Const<'tcx>, _location: Location) {
+ *constant = self.renumber_regions(*constant);
+ }
+}
diff --git a/compiler/rustc_borrowck/src/session_diagnostics.rs b/compiler/rustc_borrowck/src/session_diagnostics.rs
new file mode 100644
index 000000000..895723d44
--- /dev/null
+++ b/compiler/rustc_borrowck/src/session_diagnostics.rs
@@ -0,0 +1,44 @@
+use rustc_macros::{SessionDiagnostic, SessionSubdiagnostic};
+use rustc_middle::ty::Ty;
+use rustc_span::Span;
+
+#[derive(SessionDiagnostic)]
+#[error(borrowck::move_unsized, code = "E0161")]
+pub(crate) struct MoveUnsized<'tcx> {
+ pub ty: Ty<'tcx>,
+ #[primary_span]
+ #[label]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(borrowck::higher_ranked_lifetime_error)]
+pub(crate) struct HigherRankedLifetimeError {
+ #[subdiagnostic]
+ pub cause: Option<HigherRankedErrorCause>,
+ #[primary_span]
+ pub span: Span,
+}
+
+#[derive(SessionSubdiagnostic)]
+pub(crate) enum HigherRankedErrorCause {
+ #[note(borrowck::could_not_prove)]
+ CouldNotProve { predicate: String },
+ #[note(borrowck::could_not_normalize)]
+ CouldNotNormalize { value: String },
+}
+
+#[derive(SessionDiagnostic)]
+#[error(borrowck::higher_ranked_subtype_error)]
+pub(crate) struct HigherRankedSubtypeError {
+ #[primary_span]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(borrowck::generic_does_not_live_long_enough)]
+pub(crate) struct GenericDoesNotLiveLongEnough {
+ pub kind: String,
+ #[primary_span]
+ pub span: Span,
+}
diff --git a/compiler/rustc_borrowck/src/type_check/canonical.rs b/compiler/rustc_borrowck/src/type_check/canonical.rs
new file mode 100644
index 000000000..6cfe5efb6
--- /dev/null
+++ b/compiler/rustc_borrowck/src/type_check/canonical.rs
@@ -0,0 +1,171 @@
+use std::fmt;
+
+use rustc_infer::infer::canonical::Canonical;
+use rustc_infer::traits::query::NoSolution;
+use rustc_middle::mir::ConstraintCategory;
+use rustc_middle::ty::{self, ToPredicate, TypeFoldable};
+use rustc_span::def_id::DefId;
+use rustc_span::Span;
+use rustc_trait_selection::traits::query::type_op::{self, TypeOpOutput};
+use rustc_trait_selection::traits::query::Fallible;
+
+use crate::diagnostics::{ToUniverseInfo, UniverseInfo};
+
+use super::{Locations, NormalizeLocation, TypeChecker};
+
+impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
+ /// Given some operation `op` that manipulates types, proves
+ /// predicates, or otherwise uses the inference context, executes
+ /// `op` and then executes all the further obligations that `op`
+ /// returns. This will yield a set of outlives constraints amongst
+ /// regions which are extracted and stored as having occurred at
+ /// `locations`.
+ ///
+ /// **Any `rustc_infer::infer` operations that might generate region
+ /// constraints should occur within this method so that those
+ /// constraints can be properly localized!**
+ #[instrument(skip(self, category, op), level = "trace")]
+ pub(super) fn fully_perform_op<R, Op>(
+ &mut self,
+ locations: Locations,
+ category: ConstraintCategory<'tcx>,
+ op: Op,
+ ) -> Fallible<R>
+ where
+ Op: type_op::TypeOp<'tcx, Output = R>,
+ Op::ErrorInfo: ToUniverseInfo<'tcx>,
+ {
+ let old_universe = self.infcx.universe();
+
+ let TypeOpOutput { output, constraints, error_info } = op.fully_perform(self.infcx)?;
+
+ if let Some(data) = constraints {
+ self.push_region_constraints(locations, category, data);
+ }
+
+ let universe = self.infcx.universe();
+
+ if old_universe != universe {
+ let universe_info = match error_info {
+ Some(error_info) => error_info.to_universe_info(old_universe),
+ None => UniverseInfo::other(),
+ };
+ for u in old_universe..universe {
+ self.borrowck_context
+ .constraints
+ .universe_causes
+ .insert(u + 1, universe_info.clone());
+ }
+ }
+
+ Ok(output)
+ }
+
+ pub(super) fn instantiate_canonical_with_fresh_inference_vars<T>(
+ &mut self,
+ span: Span,
+ canonical: &Canonical<'tcx, T>,
+ ) -> T
+ where
+ T: TypeFoldable<'tcx>,
+ {
+ let (instantiated, _) =
+ self.infcx.instantiate_canonical_with_fresh_inference_vars(span, canonical);
+
+ for u in 0..canonical.max_universe.as_u32() {
+ let info = UniverseInfo::other();
+ self.borrowck_context
+ .constraints
+ .universe_causes
+ .insert(ty::UniverseIndex::from_u32(u), info);
+ }
+
+ instantiated
+ }
+
+ #[instrument(skip(self), level = "debug")]
+ pub(super) fn prove_trait_ref(
+ &mut self,
+ trait_ref: ty::TraitRef<'tcx>,
+ locations: Locations,
+ category: ConstraintCategory<'tcx>,
+ ) {
+ self.prove_predicates(
+ Some(ty::Binder::dummy(ty::PredicateKind::Trait(ty::TraitPredicate {
+ trait_ref,
+ constness: ty::BoundConstness::NotConst,
+ polarity: ty::ImplPolarity::Positive,
+ }))),
+ locations,
+ category,
+ );
+ }
+
+ pub(super) fn normalize_and_prove_instantiated_predicates(
+ &mut self,
+ // Keep this parameter for now, in case we start using
+ // it in `ConstraintCategory` at some point.
+ _def_id: DefId,
+ instantiated_predicates: ty::InstantiatedPredicates<'tcx>,
+ locations: Locations,
+ ) {
+ for (predicate, span) in instantiated_predicates
+ .predicates
+ .into_iter()
+ .zip(instantiated_predicates.spans.into_iter())
+ {
+ debug!(?predicate);
+ let predicate = self.normalize(predicate, locations);
+ self.prove_predicate(predicate, locations, ConstraintCategory::Predicate(span));
+ }
+ }
+
+ pub(super) fn prove_predicates(
+ &mut self,
+ predicates: impl IntoIterator<Item = impl ToPredicate<'tcx>>,
+ locations: Locations,
+ category: ConstraintCategory<'tcx>,
+ ) {
+ for predicate in predicates {
+ let predicate = predicate.to_predicate(self.tcx());
+ debug!("prove_predicates(predicate={:?}, locations={:?})", predicate, locations,);
+
+ self.prove_predicate(predicate, locations, category);
+ }
+ }
+
+ #[instrument(skip(self), level = "debug")]
+ pub(super) fn prove_predicate(
+ &mut self,
+ predicate: ty::Predicate<'tcx>,
+ locations: Locations,
+ category: ConstraintCategory<'tcx>,
+ ) {
+ let param_env = self.param_env;
+ self.fully_perform_op(
+ locations,
+ category,
+ param_env.and(type_op::prove_predicate::ProvePredicate::new(predicate)),
+ )
+ .unwrap_or_else(|NoSolution| {
+ span_mirbug!(self, NoSolution, "could not prove {:?}", predicate);
+ })
+ }
+
+ #[instrument(skip(self), level = "debug")]
+ pub(super) fn normalize<T>(&mut self, value: T, location: impl NormalizeLocation) -> T
+ where
+ T: type_op::normalize::Normalizable<'tcx> + fmt::Display + Copy + 'tcx,
+ {
+ let param_env = self.param_env;
+ self.fully_perform_op(
+ location.to_locations(),
+ ConstraintCategory::Boring,
+ param_env.and(type_op::normalize::Normalize::new(value)),
+ )
+ .unwrap_or_else(|NoSolution| {
+ span_mirbug!(self, NoSolution, "failed to normalize `{:?}`", value);
+ value
+ })
+ }
+}
diff --git a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs
new file mode 100644
index 000000000..167960918
--- /dev/null
+++ b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs
@@ -0,0 +1,204 @@
+use rustc_infer::infer::canonical::QueryOutlivesConstraint;
+use rustc_infer::infer::canonical::QueryRegionConstraints;
+use rustc_infer::infer::outlives::env::RegionBoundPairs;
+use rustc_infer::infer::outlives::obligations::{TypeOutlives, TypeOutlivesDelegate};
+use rustc_infer::infer::region_constraints::{GenericKind, VerifyBound};
+use rustc_infer::infer::{self, InferCtxt, SubregionOrigin};
+use rustc_middle::mir::ConstraintCategory;
+use rustc_middle::ty::subst::GenericArgKind;
+use rustc_middle::ty::TypeVisitable;
+use rustc_middle::ty::{self, TyCtxt};
+use rustc_span::{Span, DUMMY_SP};
+
+use crate::{
+ constraints::OutlivesConstraint,
+ nll::ToRegionVid,
+ region_infer::TypeTest,
+ type_check::{Locations, MirTypeckRegionConstraints},
+ universal_regions::UniversalRegions,
+};
+
+pub(crate) struct ConstraintConversion<'a, 'tcx> {
+ infcx: &'a InferCtxt<'a, 'tcx>,
+ tcx: TyCtxt<'tcx>,
+ universal_regions: &'a UniversalRegions<'tcx>,
+ /// Each RBP `GK: 'a` is assumed to be true. These encode
+ /// relationships like `T: 'a` that are added via implicit bounds
+ /// or the `param_env`.
+ ///
+ /// Each region here is guaranteed to be a key in the `indices`
+ /// map. We use the "original" regions (i.e., the keys from the
+ /// map, and not the values) because the code in
+ /// `process_registered_region_obligations` has some special-cased
+ /// logic expecting to see (e.g.) `ReStatic`, and if we supplied
+ /// our special inference variable there, we would mess that up.
+ region_bound_pairs: &'a RegionBoundPairs<'tcx>,
+ implicit_region_bound: ty::Region<'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ locations: Locations,
+ span: Span,
+ category: ConstraintCategory<'tcx>,
+ constraints: &'a mut MirTypeckRegionConstraints<'tcx>,
+}
+
+impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
+ pub(crate) fn new(
+ infcx: &'a InferCtxt<'a, 'tcx>,
+ universal_regions: &'a UniversalRegions<'tcx>,
+ region_bound_pairs: &'a RegionBoundPairs<'tcx>,
+ implicit_region_bound: ty::Region<'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ locations: Locations,
+ span: Span,
+ category: ConstraintCategory<'tcx>,
+ constraints: &'a mut MirTypeckRegionConstraints<'tcx>,
+ ) -> Self {
+ Self {
+ infcx,
+ tcx: infcx.tcx,
+ universal_regions,
+ region_bound_pairs,
+ implicit_region_bound,
+ param_env,
+ locations,
+ span,
+ category,
+ constraints,
+ }
+ }
+
+ #[instrument(skip(self), level = "debug")]
+ pub(super) fn convert_all(&mut self, query_constraints: &QueryRegionConstraints<'tcx>) {
+ let QueryRegionConstraints { outlives, member_constraints } = query_constraints;
+
+ // Annoying: to invoke `self.to_region_vid`, we need access to
+ // `self.constraints`, but we also want to be mutating
+ // `self.member_constraints`. For now, just swap out the value
+ // we want and replace at the end.
+ let mut tmp = std::mem::take(&mut self.constraints.member_constraints);
+ for member_constraint in member_constraints {
+ tmp.push_constraint(member_constraint, |r| self.to_region_vid(r));
+ }
+ self.constraints.member_constraints = tmp;
+
+ for query_constraint in outlives {
+ self.convert(query_constraint);
+ }
+ }
+
+ pub(super) fn convert(&mut self, query_constraint: &QueryOutlivesConstraint<'tcx>) {
+ debug!("generate: constraints at: {:#?}", self.locations);
+
+ // Extract out various useful fields we'll need below.
+ let ConstraintConversion {
+ tcx, region_bound_pairs, implicit_region_bound, param_env, ..
+ } = *self;
+
+ // At the moment, we never generate any "higher-ranked"
+ // region constraints like `for<'a> 'a: 'b`. At some point
+ // when we move to universes, we will, and this assertion
+ // will start to fail.
+ let ty::OutlivesPredicate(k1, r2) = query_constraint.no_bound_vars().unwrap_or_else(|| {
+ bug!("query_constraint {:?} contained bound vars", query_constraint,);
+ });
+
+ match k1.unpack() {
+ GenericArgKind::Lifetime(r1) => {
+ let r1_vid = self.to_region_vid(r1);
+ let r2_vid = self.to_region_vid(r2);
+ self.add_outlives(r1_vid, r2_vid);
+ }
+
+ GenericArgKind::Type(mut t1) => {
+ // we don't actually use this for anything, but
+ // the `TypeOutlives` code needs an origin.
+ let origin = infer::RelateParamBound(DUMMY_SP, t1, None);
+
+ // Placeholder regions need to be converted now because it may
+ // create new region variables, which can't be done later when
+ // verifying these bounds.
+ if t1.has_placeholders() {
+ t1 = tcx.fold_regions(t1, |r, _| match *r {
+ ty::RePlaceholder(placeholder) => {
+ self.constraints.placeholder_region(self.infcx, placeholder)
+ }
+ _ => r,
+ });
+ }
+
+ TypeOutlives::new(
+ &mut *self,
+ tcx,
+ region_bound_pairs,
+ Some(implicit_region_bound),
+ param_env,
+ )
+ .type_must_outlive(origin, t1, r2);
+ }
+
+ GenericArgKind::Const(_) => {
+ // Consts cannot outlive one another, so we
+ // don't need to handle any relations here.
+ }
+ }
+ }
+
+ fn verify_to_type_test(
+ &mut self,
+ generic_kind: GenericKind<'tcx>,
+ region: ty::Region<'tcx>,
+ verify_bound: VerifyBound<'tcx>,
+ ) -> TypeTest<'tcx> {
+ let lower_bound = self.to_region_vid(region);
+
+ TypeTest { generic_kind, lower_bound, locations: self.locations, verify_bound }
+ }
+
+ fn to_region_vid(&mut self, r: ty::Region<'tcx>) -> ty::RegionVid {
+ if let ty::RePlaceholder(placeholder) = *r {
+ self.constraints.placeholder_region(self.infcx, placeholder).to_region_vid()
+ } else {
+ self.universal_regions.to_region_vid(r)
+ }
+ }
+
+ fn add_outlives(&mut self, sup: ty::RegionVid, sub: ty::RegionVid) {
+ self.constraints.outlives_constraints.push(OutlivesConstraint {
+ locations: self.locations,
+ category: self.category,
+ span: self.span,
+ sub,
+ sup,
+ variance_info: ty::VarianceDiagInfo::default(),
+ });
+ }
+
+ fn add_type_test(&mut self, type_test: TypeTest<'tcx>) {
+ debug!("add_type_test(type_test={:?})", type_test);
+ self.constraints.type_tests.push(type_test);
+ }
+}
+
+impl<'a, 'b, 'tcx> TypeOutlivesDelegate<'tcx> for &'a mut ConstraintConversion<'b, 'tcx> {
+ fn push_sub_region_constraint(
+ &mut self,
+ _origin: SubregionOrigin<'tcx>,
+ a: ty::Region<'tcx>,
+ b: ty::Region<'tcx>,
+ ) {
+ let b = self.to_region_vid(b);
+ let a = self.to_region_vid(a);
+ self.add_outlives(b, a);
+ }
+
+ fn push_verify(
+ &mut self,
+ _origin: SubregionOrigin<'tcx>,
+ kind: GenericKind<'tcx>,
+ a: ty::Region<'tcx>,
+ bound: VerifyBound<'tcx>,
+ ) {
+ let type_test = self.verify_to_type_test(kind, a, bound);
+ self.add_type_test(type_test);
+ }
+}
diff --git a/compiler/rustc_borrowck/src/type_check/free_region_relations.rs b/compiler/rustc_borrowck/src/type_check/free_region_relations.rs
new file mode 100644
index 000000000..cc0318ede
--- /dev/null
+++ b/compiler/rustc_borrowck/src/type_check/free_region_relations.rs
@@ -0,0 +1,374 @@
+use rustc_data_structures::frozen::Frozen;
+use rustc_data_structures::transitive_relation::TransitiveRelation;
+use rustc_infer::infer::canonical::QueryRegionConstraints;
+use rustc_infer::infer::outlives;
+use rustc_infer::infer::outlives::env::RegionBoundPairs;
+use rustc_infer::infer::region_constraints::GenericKind;
+use rustc_infer::infer::InferCtxt;
+use rustc_middle::mir::ConstraintCategory;
+use rustc_middle::traits::query::OutlivesBound;
+use rustc_middle::ty::{self, RegionVid, Ty};
+use rustc_span::DUMMY_SP;
+use rustc_trait_selection::traits::query::type_op::{self, TypeOp};
+use std::rc::Rc;
+use type_op::TypeOpOutput;
+
+use crate::{
+ type_check::constraint_conversion,
+ type_check::{Locations, MirTypeckRegionConstraints},
+ universal_regions::UniversalRegions,
+};
+
+#[derive(Debug)]
+pub(crate) struct UniversalRegionRelations<'tcx> {
+ universal_regions: Rc<UniversalRegions<'tcx>>,
+
+ /// Stores the outlives relations that are known to hold from the
+ /// implied bounds, in-scope where-clauses, and that sort of
+ /// thing.
+ outlives: TransitiveRelation<RegionVid>,
+
+ /// This is the `<=` relation; that is, if `a: b`, then `b <= a`,
+ /// and we store that here. This is useful when figuring out how
+ /// to express some local region in terms of external regions our
+ /// caller will understand.
+ inverse_outlives: TransitiveRelation<RegionVid>,
+}
+
+/// As part of computing the free region relations, we also have to
+/// normalize the input-output types, which we then need later. So we
+/// return those. This vector consists of first the input types and
+/// then the output type as the last element.
+type NormalizedInputsAndOutput<'tcx> = Vec<Ty<'tcx>>;
+
+pub(crate) struct CreateResult<'tcx> {
+ pub(crate) universal_region_relations: Frozen<UniversalRegionRelations<'tcx>>,
+ pub(crate) region_bound_pairs: RegionBoundPairs<'tcx>,
+ pub(crate) normalized_inputs_and_output: NormalizedInputsAndOutput<'tcx>,
+}
+
+pub(crate) fn create<'tcx>(
+ infcx: &InferCtxt<'_, 'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ implicit_region_bound: ty::Region<'tcx>,
+ universal_regions: &Rc<UniversalRegions<'tcx>>,
+ constraints: &mut MirTypeckRegionConstraints<'tcx>,
+) -> CreateResult<'tcx> {
+ UniversalRegionRelationsBuilder {
+ infcx,
+ param_env,
+ implicit_region_bound,
+ constraints,
+ universal_regions: universal_regions.clone(),
+ region_bound_pairs: Default::default(),
+ relations: UniversalRegionRelations {
+ universal_regions: universal_regions.clone(),
+ outlives: Default::default(),
+ inverse_outlives: Default::default(),
+ },
+ }
+ .create()
+}
+
+impl UniversalRegionRelations<'_> {
+ /// Records in the `outlives_relation` (and
+ /// `inverse_outlives_relation`) that `fr_a: fr_b`. Invoked by the
+ /// builder below.
+ fn relate_universal_regions(&mut self, fr_a: RegionVid, fr_b: RegionVid) {
+ debug!("relate_universal_regions: fr_a={:?} outlives fr_b={:?}", fr_a, fr_b);
+ self.outlives.add(fr_a, fr_b);
+ self.inverse_outlives.add(fr_b, fr_a);
+ }
+
+ /// Given two universal regions, returns the postdominating
+ /// upper-bound (effectively the least upper bound).
+ ///
+ /// (See `TransitiveRelation::postdom_upper_bound` for details on
+ /// the postdominating upper bound in general.)
+ pub(crate) fn postdom_upper_bound(&self, fr1: RegionVid, fr2: RegionVid) -> RegionVid {
+ assert!(self.universal_regions.is_universal_region(fr1));
+ assert!(self.universal_regions.is_universal_region(fr2));
+ self.inverse_outlives
+ .postdom_upper_bound(fr1, fr2)
+ .unwrap_or(self.universal_regions.fr_static)
+ }
+
+ /// Finds an "upper bound" for `fr` that is not local. In other
+ /// words, returns the smallest (*) known region `fr1` that (a)
+ /// outlives `fr` and (b) is not local.
+ ///
+ /// (*) If there are multiple competing choices, we return all of them.
+ pub(crate) fn non_local_upper_bounds<'a>(&'a self, fr: RegionVid) -> Vec<RegionVid> {
+ debug!("non_local_upper_bound(fr={:?})", fr);
+ let res = self.non_local_bounds(&self.inverse_outlives, fr);
+ assert!(!res.is_empty(), "can't find an upper bound!?");
+ res
+ }
+
+ /// Returns the "postdominating" bound of the set of
+ /// `non_local_upper_bounds` for the given region.
+ pub(crate) fn non_local_upper_bound(&self, fr: RegionVid) -> RegionVid {
+ let upper_bounds = self.non_local_upper_bounds(fr);
+
+ // In case we find more than one, reduce to one for
+ // convenience. This is to prevent us from generating more
+ // complex constraints, but it will cause spurious errors.
+ let post_dom = self.inverse_outlives.mutual_immediate_postdominator(upper_bounds);
+
+ debug!("non_local_bound: post_dom={:?}", post_dom);
+
+ post_dom
+ .and_then(|post_dom| {
+ // If the mutual immediate postdom is not local, then
+ // there is no non-local result we can return.
+ if !self.universal_regions.is_local_free_region(post_dom) {
+ Some(post_dom)
+ } else {
+ None
+ }
+ })
+ .unwrap_or(self.universal_regions.fr_static)
+ }
+
+ /// Finds a "lower bound" for `fr` that is not local. In other
+ /// words, returns the largest (*) known region `fr1` that (a) is
+ /// outlived by `fr` and (b) is not local.
+ ///
+ /// (*) If there are multiple competing choices, we pick the "postdominating"
+ /// one. See `TransitiveRelation::postdom_upper_bound` for details.
+ pub(crate) fn non_local_lower_bound(&self, fr: RegionVid) -> Option<RegionVid> {
+ debug!("non_local_lower_bound(fr={:?})", fr);
+ let lower_bounds = self.non_local_bounds(&self.outlives, fr);
+
+ // In case we find more than one, reduce to one for
+ // convenience. This is to prevent us from generating more
+ // complex constraints, but it will cause spurious errors.
+ let post_dom = self.outlives.mutual_immediate_postdominator(lower_bounds);
+
+ debug!("non_local_bound: post_dom={:?}", post_dom);
+
+ post_dom.and_then(|post_dom| {
+ // If the mutual immediate postdom is not local, then
+ // there is no non-local result we can return.
+ if !self.universal_regions.is_local_free_region(post_dom) {
+ Some(post_dom)
+ } else {
+ None
+ }
+ })
+ }
+
+ /// Helper for `non_local_upper_bounds` and `non_local_lower_bounds`.
+ /// Repeatedly invokes `postdom_parent` until we find something that is not
+ /// local. Returns `None` if we never do so.
+ fn non_local_bounds<'a>(
+ &self,
+ relation: &'a TransitiveRelation<RegionVid>,
+ fr0: RegionVid,
+ ) -> Vec<RegionVid> {
+ // This method assumes that `fr0` is one of the universally
+ // quantified region variables.
+ assert!(self.universal_regions.is_universal_region(fr0));
+
+ let mut external_parents = vec![];
+ let mut queue = vec![fr0];
+
+ // Keep expanding `fr` into its parents until we reach
+ // non-local regions.
+ while let Some(fr) = queue.pop() {
+ if !self.universal_regions.is_local_free_region(fr) {
+ external_parents.push(fr);
+ continue;
+ }
+
+ queue.extend(relation.parents(fr));
+ }
+
+ debug!("non_local_bound: external_parents={:?}", external_parents);
+
+ external_parents
+ }
+
+ /// Returns `true` if fr1 is known to outlive fr2.
+ ///
+ /// This will only ever be true for universally quantified regions.
+ pub(crate) fn outlives(&self, fr1: RegionVid, fr2: RegionVid) -> bool {
+ self.outlives.contains(fr1, fr2)
+ }
+
+ /// Returns a vector of free regions `x` such that `fr1: x` is
+ /// known to hold.
+ pub(crate) fn regions_outlived_by(&self, fr1: RegionVid) -> Vec<RegionVid> {
+ self.outlives.reachable_from(fr1)
+ }
+
+ /// Returns the _non-transitive_ set of known `outlives` constraints between free regions.
+ pub(crate) fn known_outlives(&self) -> impl Iterator<Item = (RegionVid, RegionVid)> + '_ {
+ self.outlives.base_edges()
+ }
+}
+
+struct UniversalRegionRelationsBuilder<'this, 'tcx> {
+ infcx: &'this InferCtxt<'this, 'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ universal_regions: Rc<UniversalRegions<'tcx>>,
+ implicit_region_bound: ty::Region<'tcx>,
+ constraints: &'this mut MirTypeckRegionConstraints<'tcx>,
+
+ // outputs:
+ relations: UniversalRegionRelations<'tcx>,
+ region_bound_pairs: RegionBoundPairs<'tcx>,
+}
+
+impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> {
+ pub(crate) fn create(mut self) -> CreateResult<'tcx> {
+ let unnormalized_input_output_tys = self
+ .universal_regions
+ .unnormalized_input_tys
+ .iter()
+ .cloned()
+ .chain(Some(self.universal_regions.unnormalized_output_ty));
+
+ // For each of the input/output types:
+ // - Normalize the type. This will create some region
+ // constraints, which we buffer up because we are
+ // not ready to process them yet.
+ // - Then compute the implied bounds. This will adjust
+ // the `region_bound_pairs` and so forth.
+ // - After this is done, we'll process the constraints, once
+ // the `relations` is built.
+ let mut normalized_inputs_and_output =
+ Vec::with_capacity(self.universal_regions.unnormalized_input_tys.len() + 1);
+ let constraint_sets: Vec<_> = unnormalized_input_output_tys
+ .flat_map(|ty| {
+ debug!("build: input_or_output={:?}", ty);
+ // We only add implied bounds for the normalized type as the unnormalized
+ // type may not actually get checked by the caller.
+ //
+ // Can otherwise be unsound, see #91068.
+ let TypeOpOutput { output: norm_ty, constraints: constraints1, .. } = self
+ .param_env
+ .and(type_op::normalize::Normalize::new(ty))
+ .fully_perform(self.infcx)
+ .unwrap_or_else(|_| {
+ self.infcx
+ .tcx
+ .sess
+ .delay_span_bug(DUMMY_SP, &format!("failed to normalize {:?}", ty));
+ TypeOpOutput {
+ output: self.infcx.tcx.ty_error(),
+ constraints: None,
+ error_info: None,
+ }
+ });
+ // Note: we need this in examples like
+ // ```
+ // trait Foo {
+ // type Bar;
+ // fn foo(&self) -> &Self::Bar;
+ // }
+ // impl Foo for () {
+ // type Bar = ();
+ // fn foo(&self) ->&() {}
+ // }
+ // ```
+ // Both &Self::Bar and &() are WF
+ let constraints_implied = self.add_implied_bounds(norm_ty);
+ normalized_inputs_and_output.push(norm_ty);
+ constraints1.into_iter().chain(constraints_implied)
+ })
+ .collect();
+
+ // Insert the facts we know from the predicates. Why? Why not.
+ let param_env = self.param_env;
+ self.add_outlives_bounds(outlives::explicit_outlives_bounds(param_env));
+
+ // Finally:
+ // - outlives is reflexive, so `'r: 'r` for every region `'r`
+ // - `'static: 'r` for every region `'r`
+ // - `'r: 'fn_body` for every (other) universally quantified
+ // region `'r`, all of which are provided by our caller
+ let fr_static = self.universal_regions.fr_static;
+ let fr_fn_body = self.universal_regions.fr_fn_body;
+ for fr in self.universal_regions.universal_regions() {
+ debug!("build: relating free region {:?} to itself and to 'static", fr);
+ self.relations.relate_universal_regions(fr, fr);
+ self.relations.relate_universal_regions(fr_static, fr);
+ self.relations.relate_universal_regions(fr, fr_fn_body);
+ }
+
+ for data in &constraint_sets {
+ constraint_conversion::ConstraintConversion::new(
+ self.infcx,
+ &self.universal_regions,
+ &self.region_bound_pairs,
+ self.implicit_region_bound,
+ self.param_env,
+ Locations::All(DUMMY_SP),
+ DUMMY_SP,
+ ConstraintCategory::Internal,
+ &mut self.constraints,
+ )
+ .convert_all(data);
+ }
+
+ CreateResult {
+ universal_region_relations: Frozen::freeze(self.relations),
+ region_bound_pairs: self.region_bound_pairs,
+ normalized_inputs_and_output,
+ }
+ }
+
+ /// Update the type of a single local, which should represent
+ /// either the return type of the MIR or one of its arguments. At
+ /// the same time, compute and add any implied bounds that come
+ /// from this local.
+ #[instrument(level = "debug", skip(self))]
+ fn add_implied_bounds(&mut self, ty: Ty<'tcx>) -> Option<&'tcx QueryRegionConstraints<'tcx>> {
+ let TypeOpOutput { output: bounds, constraints, .. } = self
+ .param_env
+ .and(type_op::implied_outlives_bounds::ImpliedOutlivesBounds { ty })
+ .fully_perform(self.infcx)
+ .unwrap_or_else(|_| bug!("failed to compute implied bounds {:?}", ty));
+ self.add_outlives_bounds(bounds);
+ constraints
+ }
+
+ /// Registers the `OutlivesBound` items from `outlives_bounds` in
+ /// the outlives relation as well as the region-bound pairs
+ /// listing.
+ fn add_outlives_bounds<I>(&mut self, outlives_bounds: I)
+ where
+ I: IntoIterator<Item = OutlivesBound<'tcx>>,
+ {
+ for outlives_bound in outlives_bounds {
+ debug!("add_outlives_bounds(bound={:?})", outlives_bound);
+
+ match outlives_bound {
+ OutlivesBound::RegionSubRegion(r1, r2) => {
+ // `where Type:` is lowered to `where Type: 'empty` so that
+ // we check `Type` is well formed, but there's no use for
+ // this bound here.
+ if r1.is_empty() {
+ return;
+ }
+
+ // The bound says that `r1 <= r2`; we store `r2: r1`.
+ let r1 = self.universal_regions.to_region_vid(r1);
+ let r2 = self.universal_regions.to_region_vid(r2);
+ self.relations.relate_universal_regions(r2, r1);
+ }
+
+ OutlivesBound::RegionSubParam(r_a, param_b) => {
+ self.region_bound_pairs
+ .insert(ty::OutlivesPredicate(GenericKind::Param(param_b), r_a));
+ }
+
+ OutlivesBound::RegionSubProjection(r_a, projection_b) => {
+ self.region_bound_pairs
+ .insert(ty::OutlivesPredicate(GenericKind::Projection(projection_b), r_a));
+ }
+ }
+ }
+ }
+}
diff --git a/compiler/rustc_borrowck/src/type_check/input_output.rs b/compiler/rustc_borrowck/src/type_check/input_output.rs
new file mode 100644
index 000000000..4431a2e8e
--- /dev/null
+++ b/compiler/rustc_borrowck/src/type_check/input_output.rs
@@ -0,0 +1,245 @@
+//! This module contains code to equate the input/output types appearing
+//! in the MIR with the expected input/output types from the function
+//! signature. This requires a bit of processing, as the expected types
+//! are supplied to us before normalization and may contain opaque
+//! `impl Trait` instances. In contrast, the input/output types found in
+//! the MIR (specifically, in the special local variables for the
+//! `RETURN_PLACE` the MIR arguments) are always fully normalized (and
+//! contain revealed `impl Trait` values).
+
+use crate::type_check::constraint_conversion::ConstraintConversion;
+use rustc_index::vec::Idx;
+use rustc_infer::infer::LateBoundRegionConversionTime;
+use rustc_middle::mir::*;
+use rustc_middle::ty::Ty;
+use rustc_span::Span;
+use rustc_span::DUMMY_SP;
+use rustc_trait_selection::traits::query::type_op::{self, TypeOp};
+use rustc_trait_selection::traits::query::Fallible;
+use type_op::TypeOpOutput;
+
+use crate::universal_regions::UniversalRegions;
+
+use super::{Locations, TypeChecker};
+
+impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
+ #[instrument(skip(self, body, universal_regions), level = "debug")]
+ pub(super) fn equate_inputs_and_outputs(
+ &mut self,
+ body: &Body<'tcx>,
+ universal_regions: &UniversalRegions<'tcx>,
+ normalized_inputs_and_output: &[Ty<'tcx>],
+ ) {
+ let (&normalized_output_ty, normalized_input_tys) =
+ normalized_inputs_and_output.split_last().unwrap();
+
+ debug!(?normalized_output_ty);
+ debug!(?normalized_input_tys);
+
+ let mir_def_id = body.source.def_id().expect_local();
+
+ // If the user explicitly annotated the input types, extract
+ // those.
+ //
+ // e.g., `|x: FxHashMap<_, &'static u32>| ...`
+ let user_provided_sig;
+ if !self.tcx().is_closure(mir_def_id.to_def_id()) {
+ user_provided_sig = None;
+ } else {
+ let typeck_results = self.tcx().typeck(mir_def_id);
+ user_provided_sig = typeck_results.user_provided_sigs.get(&mir_def_id.to_def_id()).map(
+ |user_provided_poly_sig| {
+ // Instantiate the canonicalized variables from
+ // user-provided signature (e.g., the `_` in the code
+ // above) with fresh variables.
+ let poly_sig = self.instantiate_canonical_with_fresh_inference_vars(
+ body.span,
+ &user_provided_poly_sig,
+ );
+
+ // Replace the bound items in the fn sig with fresh
+ // variables, so that they represent the view from
+ // "inside" the closure.
+ self.infcx.replace_bound_vars_with_fresh_vars(
+ body.span,
+ LateBoundRegionConversionTime::FnCall,
+ poly_sig,
+ )
+ },
+ );
+ }
+
+ debug!(?normalized_input_tys, ?body.local_decls);
+
+ // Equate expected input tys with those in the MIR.
+ for (argument_index, &normalized_input_ty) in normalized_input_tys.iter().enumerate() {
+ if argument_index + 1 >= body.local_decls.len() {
+ self.tcx()
+ .sess
+ .delay_span_bug(body.span, "found more normalized_input_ty than local_decls");
+ break;
+ }
+
+ // In MIR, argument N is stored in local N+1.
+ let local = Local::new(argument_index + 1);
+
+ let mir_input_ty = body.local_decls[local].ty;
+
+ let mir_input_span = body.local_decls[local].source_info.span;
+ self.equate_normalized_input_or_output(
+ normalized_input_ty,
+ mir_input_ty,
+ mir_input_span,
+ );
+ }
+
+ if let Some(user_provided_sig) = user_provided_sig {
+ for (argument_index, &user_provided_input_ty) in
+ user_provided_sig.inputs().iter().enumerate()
+ {
+ // In MIR, closures begin an implicit `self`, so
+ // argument N is stored in local N+2.
+ let local = Local::new(argument_index + 2);
+ let mir_input_ty = body.local_decls[local].ty;
+ let mir_input_span = body.local_decls[local].source_info.span;
+
+ // If the user explicitly annotated the input types, enforce those.
+ let user_provided_input_ty =
+ self.normalize(user_provided_input_ty, Locations::All(mir_input_span));
+
+ self.equate_normalized_input_or_output(
+ user_provided_input_ty,
+ mir_input_ty,
+ mir_input_span,
+ );
+ }
+ }
+
+ debug!(
+ "equate_inputs_and_outputs: body.yield_ty {:?}, universal_regions.yield_ty {:?}",
+ body.yield_ty(),
+ universal_regions.yield_ty
+ );
+
+ // We will not have a universal_regions.yield_ty if we yield (by accident)
+ // outside of a generator and return an `impl Trait`, so emit a delay_span_bug
+ // because we don't want to panic in an assert here if we've already got errors.
+ if body.yield_ty().is_some() != universal_regions.yield_ty.is_some() {
+ self.tcx().sess.delay_span_bug(
+ body.span,
+ &format!(
+ "Expected body to have yield_ty ({:?}) iff we have a UR yield_ty ({:?})",
+ body.yield_ty(),
+ universal_regions.yield_ty,
+ ),
+ );
+ }
+
+ if let (Some(mir_yield_ty), Some(ur_yield_ty)) =
+ (body.yield_ty(), universal_regions.yield_ty)
+ {
+ let yield_span = body.local_decls[RETURN_PLACE].source_info.span;
+ self.equate_normalized_input_or_output(ur_yield_ty, mir_yield_ty, yield_span);
+ }
+
+ // Return types are a bit more complex. They may contain opaque `impl Trait` types.
+ let mir_output_ty = body.local_decls[RETURN_PLACE].ty;
+ let output_span = body.local_decls[RETURN_PLACE].source_info.span;
+ if let Err(terr) = self.eq_types(
+ normalized_output_ty,
+ mir_output_ty,
+ Locations::All(output_span),
+ ConstraintCategory::BoringNoLocation,
+ ) {
+ span_mirbug!(
+ self,
+ Location::START,
+ "equate_inputs_and_outputs: `{:?}=={:?}` failed with `{:?}`",
+ normalized_output_ty,
+ mir_output_ty,
+ terr
+ );
+ };
+
+ // If the user explicitly annotated the output types, enforce those.
+ // Note that this only happens for closures.
+ if let Some(user_provided_sig) = user_provided_sig {
+ let user_provided_output_ty = user_provided_sig.output();
+ let user_provided_output_ty =
+ self.normalize(user_provided_output_ty, Locations::All(output_span));
+ if let Err(err) = self.eq_types(
+ user_provided_output_ty,
+ mir_output_ty,
+ Locations::All(output_span),
+ ConstraintCategory::BoringNoLocation,
+ ) {
+ span_mirbug!(
+ self,
+ Location::START,
+ "equate_inputs_and_outputs: `{:?}=={:?}` failed with `{:?}`",
+ mir_output_ty,
+ user_provided_output_ty,
+ err
+ );
+ }
+ }
+ }
+
+ #[instrument(skip(self, span), level = "debug")]
+ fn equate_normalized_input_or_output(&mut self, a: Ty<'tcx>, b: Ty<'tcx>, span: Span) {
+ if let Err(_) =
+ self.eq_types(a, b, Locations::All(span), ConstraintCategory::BoringNoLocation)
+ {
+ // FIXME(jackh726): This is a hack. It's somewhat like
+ // `rustc_traits::normalize_after_erasing_regions`. Ideally, we'd
+ // like to normalize *before* inserting into `local_decls`, but
+ // doing so ends up causing some other trouble.
+ let b = match self.normalize_and_add_constraints(b) {
+ Ok(n) => n,
+ Err(_) => {
+ debug!("equate_inputs_and_outputs: NoSolution");
+ b
+ }
+ };
+
+ // Note: if we have to introduce new placeholders during normalization above, then we won't have
+ // added those universes to the universe info, which we would want in `relate_tys`.
+ if let Err(terr) =
+ self.eq_types(a, b, Locations::All(span), ConstraintCategory::BoringNoLocation)
+ {
+ span_mirbug!(
+ self,
+ Location::START,
+ "equate_normalized_input_or_output: `{:?}=={:?}` failed with `{:?}`",
+ a,
+ b,
+ terr
+ );
+ }
+ }
+ }
+
+ pub(crate) fn normalize_and_add_constraints(&mut self, t: Ty<'tcx>) -> Fallible<Ty<'tcx>> {
+ let TypeOpOutput { output: norm_ty, constraints, .. } =
+ self.param_env.and(type_op::normalize::Normalize::new(t)).fully_perform(self.infcx)?;
+
+ debug!("{:?} normalized to {:?}", t, norm_ty);
+
+ for data in constraints {
+ ConstraintConversion::new(
+ self.infcx,
+ &self.borrowck_context.universal_regions,
+ &self.region_bound_pairs,
+ self.implicit_region_bound,
+ self.param_env,
+ Locations::All(DUMMY_SP),
+ DUMMY_SP,
+ ConstraintCategory::Internal,
+ &mut self.borrowck_context.constraints,
+ )
+ .convert_all(&*data);
+ }
+
+ Ok(norm_ty)
+ }
+}
diff --git a/compiler/rustc_borrowck/src/type_check/liveness/local_use_map.rs b/compiler/rustc_borrowck/src/type_check/liveness/local_use_map.rs
new file mode 100644
index 000000000..fda2cee43
--- /dev/null
+++ b/compiler/rustc_borrowck/src/type_check/liveness/local_use_map.rs
@@ -0,0 +1,170 @@
+use rustc_data_structures::vec_linked_list as vll;
+use rustc_index::vec::IndexVec;
+use rustc_middle::mir::visit::{PlaceContext, Visitor};
+use rustc_middle::mir::{Body, Local, Location};
+
+use crate::def_use::{self, DefUse};
+use crate::region_infer::values::{PointIndex, RegionValueElements};
+
+/// A map that cross references each local with the locations where it
+/// is defined (assigned), used, or dropped. Used during liveness
+/// computation.
+///
+/// We keep track only of `Local`s we'll do the liveness analysis later,
+/// this means that our internal `IndexVec`s will only be sparsely populated.
+/// In the time-memory trade-off between keeping compact vectors with new
+/// indexes (and needing to continuously map the `Local` index to its compact
+/// counterpart) and having `IndexVec`s that we only use a fraction of, time
+/// (and code simplicity) was favored. The rationale is that we only keep
+/// a small number of `IndexVec`s throughout the entire analysis while, in
+/// contrast, we're accessing each `Local` *many* times.
+pub(crate) struct LocalUseMap {
+ /// Head of a linked list of **definitions** of each variable --
+ /// definition in this context means assignment, e.g., `x` is
+ /// defined in `x = y` but not `y`; that first def is the head of
+ /// a linked list that lets you enumerate all places the variable
+ /// is assigned.
+ first_def_at: IndexVec<Local, Option<AppearanceIndex>>,
+
+ /// Head of a linked list of **uses** of each variable -- use in
+ /// this context means that the existing value of the variable is
+ /// read or modified. e.g., `y` is used in `x = y` but not `x`.
+ /// Note that `DROP(x)` terminators are excluded from this list.
+ first_use_at: IndexVec<Local, Option<AppearanceIndex>>,
+
+ /// Head of a linked list of **drops** of each variable -- these
+ /// are a special category of uses corresponding to the drop that
+ /// we add for each local variable.
+ first_drop_at: IndexVec<Local, Option<AppearanceIndex>>,
+
+ appearances: IndexVec<AppearanceIndex, Appearance>,
+}
+
+struct Appearance {
+ point_index: PointIndex,
+ next: Option<AppearanceIndex>,
+}
+
+rustc_index::newtype_index! {
+ pub struct AppearanceIndex { .. }
+}
+
+impl vll::LinkElem for Appearance {
+ type LinkIndex = AppearanceIndex;
+
+ fn next(elem: &Self) -> Option<AppearanceIndex> {
+ elem.next
+ }
+}
+
+impl LocalUseMap {
+ pub(crate) fn build(
+ live_locals: &[Local],
+ elements: &RegionValueElements,
+ body: &Body<'_>,
+ ) -> Self {
+ let nones = IndexVec::from_elem_n(None, body.local_decls.len());
+ let mut local_use_map = LocalUseMap {
+ first_def_at: nones.clone(),
+ first_use_at: nones.clone(),
+ first_drop_at: nones,
+ appearances: IndexVec::new(),
+ };
+
+ if live_locals.is_empty() {
+ return local_use_map;
+ }
+
+ let mut locals_with_use_data: IndexVec<Local, bool> =
+ IndexVec::from_elem_n(false, body.local_decls.len());
+ live_locals.iter().for_each(|&local| locals_with_use_data[local] = true);
+
+ LocalUseMapBuild { local_use_map: &mut local_use_map, elements, locals_with_use_data }
+ .visit_body(&body);
+
+ local_use_map
+ }
+
+ pub(crate) fn defs(&self, local: Local) -> impl Iterator<Item = PointIndex> + '_ {
+ vll::iter(self.first_def_at[local], &self.appearances)
+ .map(move |aa| self.appearances[aa].point_index)
+ }
+
+ pub(crate) fn uses(&self, local: Local) -> impl Iterator<Item = PointIndex> + '_ {
+ vll::iter(self.first_use_at[local], &self.appearances)
+ .map(move |aa| self.appearances[aa].point_index)
+ }
+
+ pub(crate) fn drops(&self, local: Local) -> impl Iterator<Item = PointIndex> + '_ {
+ vll::iter(self.first_drop_at[local], &self.appearances)
+ .map(move |aa| self.appearances[aa].point_index)
+ }
+}
+
+struct LocalUseMapBuild<'me> {
+ local_use_map: &'me mut LocalUseMap,
+ elements: &'me RegionValueElements,
+
+ // Vector used in `visit_local` to signal which `Local`s do we need
+ // def/use/drop information on, constructed from `live_locals` (that
+ // contains the variables we'll do the liveness analysis for).
+ // This vector serves optimization purposes only: we could have
+ // obtained the same information from `live_locals` but we want to
+ // avoid repeatedly calling `Vec::contains()` (see `LocalUseMap` for
+ // the rationale on the time-memory trade-off we're favoring here).
+ locals_with_use_data: IndexVec<Local, bool>,
+}
+
+impl LocalUseMapBuild<'_> {
+ fn insert_def(&mut self, local: Local, location: Location) {
+ Self::insert(
+ self.elements,
+ &mut self.local_use_map.first_def_at[local],
+ &mut self.local_use_map.appearances,
+ location,
+ );
+ }
+
+ fn insert_use(&mut self, local: Local, location: Location) {
+ Self::insert(
+ self.elements,
+ &mut self.local_use_map.first_use_at[local],
+ &mut self.local_use_map.appearances,
+ location,
+ );
+ }
+
+ fn insert_drop(&mut self, local: Local, location: Location) {
+ Self::insert(
+ self.elements,
+ &mut self.local_use_map.first_drop_at[local],
+ &mut self.local_use_map.appearances,
+ location,
+ );
+ }
+
+ fn insert(
+ elements: &RegionValueElements,
+ first_appearance: &mut Option<AppearanceIndex>,
+ appearances: &mut IndexVec<AppearanceIndex, Appearance>,
+ location: Location,
+ ) {
+ let point_index = elements.point_from_location(location);
+ let appearance_index =
+ appearances.push(Appearance { point_index, next: *first_appearance });
+ *first_appearance = Some(appearance_index);
+ }
+}
+
+impl Visitor<'_> for LocalUseMapBuild<'_> {
+ fn visit_local(&mut self, local: Local, context: PlaceContext, location: Location) {
+ if self.locals_with_use_data[local] {
+ match def_use::categorize(context) {
+ Some(DefUse::Def) => self.insert_def(local, location),
+ Some(DefUse::Use) => self.insert_use(local, location),
+ Some(DefUse::Drop) => self.insert_drop(local, location),
+ _ => (),
+ }
+ }
+ }
+}
diff --git a/compiler/rustc_borrowck/src/type_check/liveness/mod.rs b/compiler/rustc_borrowck/src/type_check/liveness/mod.rs
new file mode 100644
index 000000000..d5c401ae1
--- /dev/null
+++ b/compiler/rustc_borrowck/src/type_check/liveness/mod.rs
@@ -0,0 +1,139 @@
+use itertools::{Either, Itertools};
+use rustc_data_structures::fx::FxHashSet;
+use rustc_middle::mir::{Body, Local};
+use rustc_middle::ty::{RegionVid, TyCtxt};
+use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
+use rustc_mir_dataflow::move_paths::MoveData;
+use rustc_mir_dataflow::ResultsCursor;
+use std::rc::Rc;
+
+use crate::{
+ constraints::OutlivesConstraintSet,
+ facts::{AllFacts, AllFactsExt},
+ location::LocationTable,
+ nll::ToRegionVid,
+ region_infer::values::RegionValueElements,
+ universal_regions::UniversalRegions,
+};
+
+use super::TypeChecker;
+
+mod local_use_map;
+mod polonius;
+mod trace;
+
+/// Combines liveness analysis with initialization analysis to
+/// determine which variables are live at which points, both due to
+/// ordinary uses and drops. Returns a set of (ty, location) pairs
+/// that indicate which types must be live at which point in the CFG.
+/// This vector is consumed by `constraint_generation`.
+///
+/// N.B., this computation requires normalization; therefore, it must be
+/// performed before
+pub(super) fn generate<'mir, 'tcx>(
+ typeck: &mut TypeChecker<'_, 'tcx>,
+ body: &Body<'tcx>,
+ elements: &Rc<RegionValueElements>,
+ flow_inits: &mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>,
+ move_data: &MoveData<'tcx>,
+ location_table: &LocationTable,
+ use_polonius: bool,
+) {
+ debug!("liveness::generate");
+
+ let free_regions = regions_that_outlive_free_regions(
+ typeck.infcx.num_region_vars(),
+ &typeck.borrowck_context.universal_regions,
+ &typeck.borrowck_context.constraints.outlives_constraints,
+ );
+ let (relevant_live_locals, boring_locals) =
+ compute_relevant_live_locals(typeck.tcx(), &free_regions, &body);
+ let facts_enabled = use_polonius || AllFacts::enabled(typeck.tcx());
+
+ let polonius_drop_used = if facts_enabled {
+ let mut drop_used = Vec::new();
+ polonius::populate_access_facts(typeck, body, location_table, move_data, &mut drop_used);
+ Some(drop_used)
+ } else {
+ None
+ };
+
+ trace::trace(
+ typeck,
+ body,
+ elements,
+ flow_inits,
+ move_data,
+ relevant_live_locals,
+ boring_locals,
+ polonius_drop_used,
+ );
+}
+
+// The purpose of `compute_relevant_live_locals` is to define the subset of `Local`
+// variables for which we need to do a liveness computation. We only need
+// to compute whether a variable `X` is live if that variable contains
+// some region `R` in its type where `R` is not known to outlive a free
+// region (i.e., where `R` may be valid for just a subset of the fn body).
+fn compute_relevant_live_locals<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ free_regions: &FxHashSet<RegionVid>,
+ body: &Body<'tcx>,
+) -> (Vec<Local>, Vec<Local>) {
+ let (boring_locals, relevant_live_locals): (Vec<_>, Vec<_>) =
+ body.local_decls.iter_enumerated().partition_map(|(local, local_decl)| {
+ if tcx.all_free_regions_meet(&local_decl.ty, |r| {
+ free_regions.contains(&r.to_region_vid())
+ }) {
+ Either::Left(local)
+ } else {
+ Either::Right(local)
+ }
+ });
+
+ debug!("{} total variables", body.local_decls.len());
+ debug!("{} variables need liveness", relevant_live_locals.len());
+ debug!("{} regions outlive free regions", free_regions.len());
+
+ (relevant_live_locals, boring_locals)
+}
+
+/// Computes all regions that are (currently) known to outlive free
+/// regions. For these regions, we do not need to compute
+/// liveness, since the outlives constraints will ensure that they
+/// are live over the whole fn body anyhow.
+fn regions_that_outlive_free_regions<'tcx>(
+ num_region_vars: usize,
+ universal_regions: &UniversalRegions<'tcx>,
+ constraint_set: &OutlivesConstraintSet<'tcx>,
+) -> FxHashSet<RegionVid> {
+ // Build a graph of the outlives constraints thus far. This is
+ // a reverse graph, so for each constraint `R1: R2` we have an
+ // edge `R2 -> R1`. Therefore, if we find all regions
+ // reachable from each free region, we will have all the
+ // regions that are forced to outlive some free region.
+ let rev_constraint_graph = constraint_set.reverse_graph(num_region_vars);
+ let fr_static = universal_regions.fr_static;
+ let rev_region_graph = rev_constraint_graph.region_graph(constraint_set, fr_static);
+
+ // Stack for the depth-first search. Start out with all the free regions.
+ let mut stack: Vec<_> = universal_regions.universal_regions().collect();
+
+ // Set of all free regions, plus anything that outlives them. Initially
+ // just contains the free regions.
+ let mut outlives_free_region: FxHashSet<_> = stack.iter().cloned().collect();
+
+ // Do the DFS -- for each thing in the stack, find all things
+ // that outlive it and add them to the set. If they are not,
+ // push them onto the stack for later.
+ while let Some(sub_region) = stack.pop() {
+ stack.extend(
+ rev_region_graph
+ .outgoing_regions(sub_region)
+ .filter(|&r| outlives_free_region.insert(r)),
+ );
+ }
+
+ // Return the final set of things we visited.
+ outlives_free_region
+}
diff --git a/compiler/rustc_borrowck/src/type_check/liveness/polonius.rs b/compiler/rustc_borrowck/src/type_check/liveness/polonius.rs
new file mode 100644
index 000000000..bc76a465e
--- /dev/null
+++ b/compiler/rustc_borrowck/src/type_check/liveness/polonius.rs
@@ -0,0 +1,140 @@
+use crate::def_use::{self, DefUse};
+use crate::location::{LocationIndex, LocationTable};
+use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor};
+use rustc_middle::mir::{Body, Local, Location, Place};
+use rustc_middle::ty::subst::GenericArg;
+use rustc_mir_dataflow::move_paths::{LookupResult, MoveData, MovePathIndex};
+
+use super::TypeChecker;
+
+type VarPointRelation = Vec<(Local, LocationIndex)>;
+type PathPointRelation = Vec<(MovePathIndex, LocationIndex)>;
+
+struct UseFactsExtractor<'me, 'tcx> {
+ var_defined_at: &'me mut VarPointRelation,
+ var_used_at: &'me mut VarPointRelation,
+ location_table: &'me LocationTable,
+ var_dropped_at: &'me mut VarPointRelation,
+ move_data: &'me MoveData<'tcx>,
+ path_accessed_at_base: &'me mut PathPointRelation,
+}
+
+// A Visitor to walk through the MIR and extract point-wise facts
+impl UseFactsExtractor<'_, '_> {
+ fn location_to_index(&self, location: Location) -> LocationIndex {
+ self.location_table.mid_index(location)
+ }
+
+ fn insert_def(&mut self, local: Local, location: Location) {
+ debug!("UseFactsExtractor::insert_def()");
+ self.var_defined_at.push((local, self.location_to_index(location)));
+ }
+
+ fn insert_use(&mut self, local: Local, location: Location) {
+ debug!("UseFactsExtractor::insert_use()");
+ self.var_used_at.push((local, self.location_to_index(location)));
+ }
+
+ fn insert_drop_use(&mut self, local: Local, location: Location) {
+ debug!("UseFactsExtractor::insert_drop_use()");
+ self.var_dropped_at.push((local, self.location_to_index(location)));
+ }
+
+ fn insert_path_access(&mut self, path: MovePathIndex, location: Location) {
+ debug!("UseFactsExtractor::insert_path_access({:?}, {:?})", path, location);
+ self.path_accessed_at_base.push((path, self.location_to_index(location)));
+ }
+
+ fn place_to_mpi(&self, place: &Place<'_>) -> Option<MovePathIndex> {
+ match self.move_data.rev_lookup.find(place.as_ref()) {
+ LookupResult::Exact(mpi) => Some(mpi),
+ LookupResult::Parent(mmpi) => mmpi,
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for UseFactsExtractor<'a, 'tcx> {
+ fn visit_local(&mut self, local: Local, context: PlaceContext, location: Location) {
+ match def_use::categorize(context) {
+ Some(DefUse::Def) => self.insert_def(local, location),
+ Some(DefUse::Use) => self.insert_use(local, location),
+ Some(DefUse::Drop) => self.insert_drop_use(local, location),
+ _ => (),
+ }
+ }
+
+ fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {
+ self.super_place(place, context, location);
+ match context {
+ PlaceContext::NonMutatingUse(_) => {
+ if let Some(mpi) = self.place_to_mpi(place) {
+ self.insert_path_access(mpi, location);
+ }
+ }
+
+ PlaceContext::MutatingUse(MutatingUseContext::Borrow) => {
+ if let Some(mpi) = self.place_to_mpi(place) {
+ self.insert_path_access(mpi, location);
+ }
+ }
+ _ => (),
+ }
+ }
+}
+
+pub(super) fn populate_access_facts<'a, 'tcx>(
+ typeck: &mut TypeChecker<'a, 'tcx>,
+ body: &Body<'tcx>,
+ location_table: &LocationTable,
+ move_data: &MoveData<'tcx>,
+ dropped_at: &mut Vec<(Local, Location)>,
+) {
+ debug!("populate_access_facts()");
+
+ if let Some(facts) = typeck.borrowck_context.all_facts.as_mut() {
+ let mut extractor = UseFactsExtractor {
+ var_defined_at: &mut facts.var_defined_at,
+ var_used_at: &mut facts.var_used_at,
+ var_dropped_at: &mut facts.var_dropped_at,
+ path_accessed_at_base: &mut facts.path_accessed_at_base,
+ location_table,
+ move_data,
+ };
+ extractor.visit_body(&body);
+
+ facts.var_dropped_at.extend(
+ dropped_at.iter().map(|&(local, location)| (local, location_table.mid_index(location))),
+ );
+
+ for (local, local_decl) in body.local_decls.iter_enumerated() {
+ debug!(
+ "add use_of_var_derefs_origin facts - local={:?}, type={:?}",
+ local, local_decl.ty
+ );
+ let _prof_timer = typeck.infcx.tcx.prof.generic_activity("polonius_fact_generation");
+ let universal_regions = &typeck.borrowck_context.universal_regions;
+ typeck.infcx.tcx.for_each_free_region(&local_decl.ty, |region| {
+ let region_vid = universal_regions.to_region_vid(region);
+ facts.use_of_var_derefs_origin.push((local, region_vid));
+ });
+ }
+ }
+}
+
+// For every potentially drop()-touched region `region` in `local`'s type
+// (`kind`), emit a Polonius `use_of_var_derefs_origin(local, origin)` fact.
+pub(super) fn add_drop_of_var_derefs_origin<'tcx>(
+ typeck: &mut TypeChecker<'_, 'tcx>,
+ local: Local,
+ kind: &GenericArg<'tcx>,
+) {
+ debug!("add_drop_of_var_derefs_origin(local={:?}, kind={:?}", local, kind);
+ if let Some(facts) = typeck.borrowck_context.all_facts.as_mut() {
+ let _prof_timer = typeck.infcx.tcx.prof.generic_activity("polonius_fact_generation");
+ let universal_regions = &typeck.borrowck_context.universal_regions;
+ typeck.infcx.tcx.for_each_free_region(kind, |drop_live_region| {
+ let region_vid = universal_regions.to_region_vid(drop_live_region);
+ facts.drop_of_var_derefs_origin.push((local, region_vid));
+ });
+ }
+}
diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs
new file mode 100644
index 000000000..42b577175
--- /dev/null
+++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs
@@ -0,0 +1,578 @@
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_index::bit_set::HybridBitSet;
+use rustc_index::interval::IntervalSet;
+use rustc_infer::infer::canonical::QueryRegionConstraints;
+use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, Local, Location};
+use rustc_middle::ty::{Ty, TypeVisitable};
+use rustc_trait_selection::traits::query::dropck_outlives::DropckOutlivesResult;
+use rustc_trait_selection::traits::query::type_op::outlives::DropckOutlives;
+use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput};
+use std::rc::Rc;
+
+use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
+use rustc_mir_dataflow::move_paths::{HasMoveData, MoveData, MovePathIndex};
+use rustc_mir_dataflow::ResultsCursor;
+
+use crate::{
+ region_infer::values::{self, PointIndex, RegionValueElements},
+ type_check::liveness::local_use_map::LocalUseMap,
+ type_check::liveness::polonius,
+ type_check::NormalizeLocation,
+ type_check::TypeChecker,
+};
+
+/// This is the heart of the liveness computation. For each variable X
+/// that requires a liveness computation, it walks over all the uses
+/// of X and does a reverse depth-first search ("trace") through the
+/// MIR. This search stops when we find a definition of that variable.
+/// The points visited in this search is the USE-LIVE set for the variable;
+/// of those points is added to all the regions that appear in the variable's
+/// type.
+///
+/// We then also walks through each *drop* of those variables and does
+/// another search, stopping when we reach a use or definition. This
+/// is the DROP-LIVE set of points. Each of the points in the
+/// DROP-LIVE set are to the liveness sets for regions found in the
+/// `dropck_outlives` result of the variable's type (in particular,
+/// this respects `#[may_dangle]` annotations).
+pub(super) fn trace<'mir, 'tcx>(
+ typeck: &mut TypeChecker<'_, 'tcx>,
+ body: &Body<'tcx>,
+ elements: &Rc<RegionValueElements>,
+ flow_inits: &mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>,
+ move_data: &MoveData<'tcx>,
+ relevant_live_locals: Vec<Local>,
+ boring_locals: Vec<Local>,
+ polonius_drop_used: Option<Vec<(Local, Location)>>,
+) {
+ debug!("trace()");
+
+ let local_use_map = &LocalUseMap::build(&relevant_live_locals, elements, body);
+
+ let cx = LivenessContext {
+ typeck,
+ body,
+ flow_inits,
+ elements,
+ local_use_map,
+ move_data,
+ drop_data: FxHashMap::default(),
+ };
+
+ let mut results = LivenessResults::new(cx);
+
+ if let Some(drop_used) = polonius_drop_used {
+ results.add_extra_drop_facts(drop_used, relevant_live_locals.iter().copied().collect())
+ }
+
+ results.compute_for_all_locals(relevant_live_locals);
+
+ results.dropck_boring_locals(boring_locals);
+}
+
+/// Contextual state for the type-liveness generator.
+struct LivenessContext<'me, 'typeck, 'flow, 'tcx> {
+ /// Current type-checker, giving us our inference context etc.
+ typeck: &'me mut TypeChecker<'typeck, 'tcx>,
+
+ /// Defines the `PointIndex` mapping
+ elements: &'me RegionValueElements,
+
+ /// MIR we are analyzing.
+ body: &'me Body<'tcx>,
+
+ /// Mapping to/from the various indices used for initialization tracking.
+ move_data: &'me MoveData<'tcx>,
+
+ /// Cache for the results of `dropck_outlives` query.
+ drop_data: FxHashMap<Ty<'tcx>, DropData<'tcx>>,
+
+ /// Results of dataflow tracking which variables (and paths) have been
+ /// initialized.
+ flow_inits: &'me mut ResultsCursor<'flow, 'tcx, MaybeInitializedPlaces<'flow, 'tcx>>,
+
+ /// Index indicating where each variable is assigned, used, or
+ /// dropped.
+ local_use_map: &'me LocalUseMap,
+}
+
+struct DropData<'tcx> {
+ dropck_result: DropckOutlivesResult<'tcx>,
+ region_constraint_data: Option<&'tcx QueryRegionConstraints<'tcx>>,
+}
+
+struct LivenessResults<'me, 'typeck, 'flow, 'tcx> {
+ cx: LivenessContext<'me, 'typeck, 'flow, 'tcx>,
+
+ /// Set of points that define the current local.
+ defs: HybridBitSet<PointIndex>,
+
+ /// Points where the current variable is "use live" -- meaning
+ /// that there is a future "full use" that may use its value.
+ use_live_at: IntervalSet<PointIndex>,
+
+ /// Points where the current variable is "drop live" -- meaning
+ /// that there is no future "full use" that may use its value, but
+ /// there is a future drop.
+ drop_live_at: IntervalSet<PointIndex>,
+
+ /// Locations where drops may occur.
+ drop_locations: Vec<Location>,
+
+ /// Stack used when doing (reverse) DFS.
+ stack: Vec<PointIndex>,
+}
+
+impl<'me, 'typeck, 'flow, 'tcx> LivenessResults<'me, 'typeck, 'flow, 'tcx> {
+ fn new(cx: LivenessContext<'me, 'typeck, 'flow, 'tcx>) -> Self {
+ let num_points = cx.elements.num_points();
+ LivenessResults {
+ cx,
+ defs: HybridBitSet::new_empty(num_points),
+ use_live_at: IntervalSet::new(num_points),
+ drop_live_at: IntervalSet::new(num_points),
+ drop_locations: vec![],
+ stack: vec![],
+ }
+ }
+
+ fn compute_for_all_locals(&mut self, relevant_live_locals: Vec<Local>) {
+ for local in relevant_live_locals {
+ self.reset_local_state();
+ self.add_defs_for(local);
+ self.compute_use_live_points_for(local);
+ self.compute_drop_live_points_for(local);
+
+ let local_ty = self.cx.body.local_decls[local].ty;
+
+ if !self.use_live_at.is_empty() {
+ self.cx.add_use_live_facts_for(local_ty, &self.use_live_at);
+ }
+
+ if !self.drop_live_at.is_empty() {
+ self.cx.add_drop_live_facts_for(
+ local,
+ local_ty,
+ &self.drop_locations,
+ &self.drop_live_at,
+ );
+ }
+ }
+ }
+
+ // Runs dropck for locals whose liveness isn't relevant. This is
+ // necessary to eagerly detect unbound recursion during drop glue computation.
+ fn dropck_boring_locals(&mut self, boring_locals: Vec<Local>) {
+ for local in boring_locals {
+ let local_ty = self.cx.body.local_decls[local].ty;
+ let drop_data = self.cx.drop_data.entry(local_ty).or_insert_with({
+ let typeck = &mut self.cx.typeck;
+ move || LivenessContext::compute_drop_data(typeck, local_ty)
+ });
+
+ drop_data.dropck_result.report_overflows(
+ self.cx.typeck.infcx.tcx,
+ self.cx.body.local_decls[local].source_info.span,
+ local_ty,
+ );
+ }
+ }
+
+ /// Add extra drop facts needed for Polonius.
+ ///
+ /// Add facts for all locals with free regions, since regions may outlive
+ /// the function body only at certain nodes in the CFG.
+ fn add_extra_drop_facts(
+ &mut self,
+ drop_used: Vec<(Local, Location)>,
+ relevant_live_locals: FxHashSet<Local>,
+ ) {
+ let locations = IntervalSet::new(self.cx.elements.num_points());
+
+ for (local, location) in drop_used {
+ if !relevant_live_locals.contains(&local) {
+ let local_ty = self.cx.body.local_decls[local].ty;
+ if local_ty.has_free_regions() {
+ self.cx.add_drop_live_facts_for(local, local_ty, &[location], &locations);
+ }
+ }
+ }
+ }
+
+ /// Clear the value of fields that are "per local variable".
+ fn reset_local_state(&mut self) {
+ self.defs.clear();
+ self.use_live_at.clear();
+ self.drop_live_at.clear();
+ self.drop_locations.clear();
+ assert!(self.stack.is_empty());
+ }
+
+ /// Adds the definitions of `local` into `self.defs`.
+ fn add_defs_for(&mut self, local: Local) {
+ for def in self.cx.local_use_map.defs(local) {
+ debug!("- defined at {:?}", def);
+ self.defs.insert(def);
+ }
+ }
+
+ /// Computes all points where local is "use live" -- meaning its
+ /// current value may be used later (except by a drop). This is
+ /// done by walking backwards from each use of `local` until we
+ /// find a `def` of local.
+ ///
+ /// Requires `add_defs_for(local)` to have been executed.
+ fn compute_use_live_points_for(&mut self, local: Local) {
+ debug!("compute_use_live_points_for(local={:?})", local);
+
+ self.stack.extend(self.cx.local_use_map.uses(local));
+ while let Some(p) = self.stack.pop() {
+ // We are live in this block from the closest to us of:
+ //
+ // * Inclusively, the block start
+ // * Exclusively, the previous definition (if it's in this block)
+ // * Exclusively, the previous live_at setting (an optimization)
+ let block_start = self.cx.elements.to_block_start(p);
+ let previous_defs = self.defs.last_set_in(block_start..=p);
+ let previous_live_at = self.use_live_at.last_set_in(block_start..=p);
+
+ let exclusive_start = match (previous_defs, previous_live_at) {
+ (Some(a), Some(b)) => Some(std::cmp::max(a, b)),
+ (Some(a), None) | (None, Some(a)) => Some(a),
+ (None, None) => None,
+ };
+
+ if let Some(exclusive) = exclusive_start {
+ self.use_live_at.insert_range(exclusive + 1..=p);
+
+ // If we have a bound after the start of the block, we should
+ // not add the predecessors for this block.
+ continue;
+ } else {
+ // Add all the elements of this block.
+ self.use_live_at.insert_range(block_start..=p);
+
+ // Then add the predecessors for this block, which are the
+ // terminators of predecessor basic blocks. Push those onto the
+ // stack so that the next iteration(s) will process them.
+
+ let block = self.cx.elements.to_location(block_start).block;
+ self.stack.extend(
+ self.cx.body.basic_blocks.predecessors()[block]
+ .iter()
+ .map(|&pred_bb| self.cx.body.terminator_loc(pred_bb))
+ .map(|pred_loc| self.cx.elements.point_from_location(pred_loc)),
+ );
+ }
+ }
+ }
+
+ /// Computes all points where local is "drop live" -- meaning its
+ /// current value may be dropped later (but not used). This is
+ /// done by iterating over the drops of `local` where `local` (or
+ /// some subpart of `local`) is initialized. For each such drop,
+ /// we walk backwards until we find a point where `local` is
+ /// either defined or use-live.
+ ///
+ /// Requires `compute_use_live_points_for` and `add_defs_for` to
+ /// have been executed.
+ fn compute_drop_live_points_for(&mut self, local: Local) {
+ debug!("compute_drop_live_points_for(local={:?})", local);
+
+ let mpi = self.cx.move_data.rev_lookup.find_local(local);
+ debug!("compute_drop_live_points_for: mpi = {:?}", mpi);
+
+ // Find the drops where `local` is initialized.
+ for drop_point in self.cx.local_use_map.drops(local) {
+ let location = self.cx.elements.to_location(drop_point);
+ debug_assert_eq!(self.cx.body.terminator_loc(location.block), location,);
+
+ if self.cx.initialized_at_terminator(location.block, mpi) {
+ if self.drop_live_at.insert(drop_point) {
+ self.drop_locations.push(location);
+ self.stack.push(drop_point);
+ }
+ }
+ }
+
+ debug!("compute_drop_live_points_for: drop_locations={:?}", self.drop_locations);
+
+ // Reverse DFS. But for drops, we do it a bit differently.
+ // The stack only ever stores *terminators of blocks*. Within
+ // a block, we walk back the statements in an inner loop.
+ while let Some(term_point) = self.stack.pop() {
+ self.compute_drop_live_points_for_block(mpi, term_point);
+ }
+ }
+
+ /// Executes one iteration of the drop-live analysis loop.
+ ///
+ /// The parameter `mpi` is the `MovePathIndex` of the local variable
+ /// we are currently analyzing.
+ ///
+ /// The point `term_point` represents some terminator in the MIR,
+ /// where the local `mpi` is drop-live on entry to that terminator.
+ ///
+ /// This method adds all drop-live points within the block and --
+ /// where applicable -- pushes the terminators of preceding blocks
+ /// onto `self.stack`.
+ fn compute_drop_live_points_for_block(&mut self, mpi: MovePathIndex, term_point: PointIndex) {
+ debug!(
+ "compute_drop_live_points_for_block(mpi={:?}, term_point={:?})",
+ self.cx.move_data.move_paths[mpi].place,
+ self.cx.elements.to_location(term_point),
+ );
+
+ // We are only invoked with terminators where `mpi` is
+ // drop-live on entry.
+ debug_assert!(self.drop_live_at.contains(term_point));
+
+ // Otherwise, scan backwards through the statements in the
+ // block. One of them may be either a definition or use
+ // live point.
+ let term_location = self.cx.elements.to_location(term_point);
+ debug_assert_eq!(self.cx.body.terminator_loc(term_location.block), term_location,);
+ let block = term_location.block;
+ let entry_point = self.cx.elements.entry_point(term_location.block);
+ for p in (entry_point..term_point).rev() {
+ debug!("compute_drop_live_points_for_block: p = {:?}", self.cx.elements.to_location(p));
+
+ if self.defs.contains(p) {
+ debug!("compute_drop_live_points_for_block: def site");
+ return;
+ }
+
+ if self.use_live_at.contains(p) {
+ debug!("compute_drop_live_points_for_block: use-live at {:?}", p);
+ return;
+ }
+
+ if !self.drop_live_at.insert(p) {
+ debug!("compute_drop_live_points_for_block: already drop-live");
+ return;
+ }
+ }
+
+ let body = self.cx.body;
+ for &pred_block in body.basic_blocks.predecessors()[block].iter() {
+ debug!("compute_drop_live_points_for_block: pred_block = {:?}", pred_block,);
+
+ // Check whether the variable is (at least partially)
+ // initialized at the exit of this predecessor. If so, we
+ // want to enqueue it on our list. If not, go check the
+ // next block.
+ //
+ // Note that we only need to check whether `live_local`
+ // became de-initialized at basic block boundaries. If it
+ // were to become de-initialized within the block, that
+ // would have been a "use-live" transition in the earlier
+ // loop, and we'd have returned already.
+ //
+ // NB. It's possible that the pred-block ends in a call
+ // which stores to the variable; in that case, the
+ // variable may be uninitialized "at exit" because this
+ // call only considers the *unconditional effects* of the
+ // terminator. *But*, in that case, the terminator is also
+ // a *definition* of the variable, in which case we want
+ // to stop the search anyhow. (But see Note 1 below.)
+ if !self.cx.initialized_at_exit(pred_block, mpi) {
+ debug!("compute_drop_live_points_for_block: not initialized");
+ continue;
+ }
+
+ let pred_term_loc = self.cx.body.terminator_loc(pred_block);
+ let pred_term_point = self.cx.elements.point_from_location(pred_term_loc);
+
+ // If the terminator of this predecessor either *assigns*
+ // our value or is a "normal use", then stop.
+ if self.defs.contains(pred_term_point) {
+ debug!("compute_drop_live_points_for_block: defined at {:?}", pred_term_loc);
+ continue;
+ }
+
+ if self.use_live_at.contains(pred_term_point) {
+ debug!("compute_drop_live_points_for_block: use-live at {:?}", pred_term_loc);
+ continue;
+ }
+
+ // Otherwise, we are drop-live on entry to the terminator,
+ // so walk it.
+ if self.drop_live_at.insert(pred_term_point) {
+ debug!("compute_drop_live_points_for_block: pushed to stack");
+ self.stack.push(pred_term_point);
+ }
+ }
+
+ // Note 1. There is a weird scenario that you might imagine
+ // being problematic here, but which actually cannot happen.
+ // The problem would be if we had a variable that *is* initialized
+ // (but dead) on entry to the terminator, and where the current value
+ // will be dropped in the case of unwind. In that case, we ought to
+ // consider `X` to be drop-live in between the last use and call.
+ // Here is the example:
+ //
+ // ```
+ // BB0 {
+ // X = ...
+ // use(X); // last use
+ // ... // <-- X ought to be drop-live here
+ // X = call() goto BB1 unwind BB2
+ // }
+ //
+ // BB1 {
+ // DROP(X)
+ // }
+ //
+ // BB2 {
+ // DROP(X)
+ // }
+ // ```
+ //
+ // However, the current code would, when walking back from BB2,
+ // simply stop and never explore BB0. This seems bad! But it turns
+ // out this code is flawed anyway -- note that the existing value of
+ // `X` would leak in the case where unwinding did *not* occur.
+ //
+ // What we *actually* generate is a store to a temporary
+ // for the call (`TMP = call()...`) and then a
+ // `DropAndReplace` to swap that with `X`
+ // (`DropAndReplace` has very particular semantics).
+ }
+}
+
+impl<'tcx> LivenessContext<'_, '_, '_, 'tcx> {
+ /// Returns `true` if the local variable (or some part of it) is initialized at the current
+ /// cursor position. Callers should call one of the `seek` methods immediately before to point
+ /// the cursor to the desired location.
+ fn initialized_at_curr_loc(&self, mpi: MovePathIndex) -> bool {
+ let state = self.flow_inits.get();
+ if state.contains(mpi) {
+ return true;
+ }
+
+ let move_paths = &self.flow_inits.analysis().move_data().move_paths;
+ move_paths[mpi].find_descendant(&move_paths, |mpi| state.contains(mpi)).is_some()
+ }
+
+ /// Returns `true` if the local variable (or some part of it) is initialized in
+ /// the terminator of `block`. We need to check this to determine if a
+ /// DROP of some local variable will have an effect -- note that
+ /// drops, as they may unwind, are always terminators.
+ fn initialized_at_terminator(&mut self, block: BasicBlock, mpi: MovePathIndex) -> bool {
+ self.flow_inits.seek_before_primary_effect(self.body.terminator_loc(block));
+ self.initialized_at_curr_loc(mpi)
+ }
+
+ /// Returns `true` if the path `mpi` (or some part of it) is initialized at
+ /// the exit of `block`.
+ ///
+ /// **Warning:** Does not account for the result of `Call`
+ /// instructions.
+ fn initialized_at_exit(&mut self, block: BasicBlock, mpi: MovePathIndex) -> bool {
+ self.flow_inits.seek_after_primary_effect(self.body.terminator_loc(block));
+ self.initialized_at_curr_loc(mpi)
+ }
+
+ /// Stores the result that all regions in `value` are live for the
+ /// points `live_at`.
+ fn add_use_live_facts_for(
+ &mut self,
+ value: impl TypeVisitable<'tcx>,
+ live_at: &IntervalSet<PointIndex>,
+ ) {
+ debug!("add_use_live_facts_for(value={:?})", value);
+
+ Self::make_all_regions_live(self.elements, &mut self.typeck, value, live_at)
+ }
+
+ /// Some variable with type `live_ty` is "drop live" at `location`
+ /// -- i.e., it may be dropped later. This means that *some* of
+ /// the regions in its type must be live at `location`. The
+ /// precise set will depend on the dropck constraints, and in
+ /// particular this takes `#[may_dangle]` into account.
+ fn add_drop_live_facts_for(
+ &mut self,
+ dropped_local: Local,
+ dropped_ty: Ty<'tcx>,
+ drop_locations: &[Location],
+ live_at: &IntervalSet<PointIndex>,
+ ) {
+ debug!(
+ "add_drop_live_constraint(\
+ dropped_local={:?}, \
+ dropped_ty={:?}, \
+ drop_locations={:?}, \
+ live_at={:?})",
+ dropped_local,
+ dropped_ty,
+ drop_locations,
+ values::location_set_str(self.elements, live_at.iter()),
+ );
+
+ let drop_data = self.drop_data.entry(dropped_ty).or_insert_with({
+ let typeck = &mut self.typeck;
+ move || Self::compute_drop_data(typeck, dropped_ty)
+ });
+
+ if let Some(data) = &drop_data.region_constraint_data {
+ for &drop_location in drop_locations {
+ self.typeck.push_region_constraints(
+ drop_location.to_locations(),
+ ConstraintCategory::Boring,
+ data,
+ );
+ }
+ }
+
+ drop_data.dropck_result.report_overflows(
+ self.typeck.infcx.tcx,
+ self.body.source_info(*drop_locations.first().unwrap()).span,
+ dropped_ty,
+ );
+
+ // All things in the `outlives` array may be touched by
+ // the destructor and must be live at this point.
+ for &kind in &drop_data.dropck_result.kinds {
+ Self::make_all_regions_live(self.elements, &mut self.typeck, kind, live_at);
+
+ polonius::add_drop_of_var_derefs_origin(&mut self.typeck, dropped_local, &kind);
+ }
+ }
+
+ fn make_all_regions_live(
+ elements: &RegionValueElements,
+ typeck: &mut TypeChecker<'_, 'tcx>,
+ value: impl TypeVisitable<'tcx>,
+ live_at: &IntervalSet<PointIndex>,
+ ) {
+ debug!("make_all_regions_live(value={:?})", value);
+ debug!(
+ "make_all_regions_live: live_at={}",
+ values::location_set_str(elements, live_at.iter()),
+ );
+
+ let tcx = typeck.tcx();
+ tcx.for_each_free_region(&value, |live_region| {
+ let live_region_vid =
+ typeck.borrowck_context.universal_regions.to_region_vid(live_region);
+ typeck
+ .borrowck_context
+ .constraints
+ .liveness_constraints
+ .add_elements(live_region_vid, live_at);
+ });
+ }
+
+ fn compute_drop_data(
+ typeck: &mut TypeChecker<'_, 'tcx>,
+ dropped_ty: Ty<'tcx>,
+ ) -> DropData<'tcx> {
+ debug!("compute_drop_data(dropped_ty={:?})", dropped_ty,);
+
+ let param_env = typeck.param_env;
+ let TypeOpOutput { output, constraints, .. } =
+ param_env.and(DropckOutlives::new(dropped_ty)).fully_perform(typeck.infcx).unwrap();
+
+ DropData { dropck_result: output, region_constraint_data: constraints }
+ }
+}
diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs
new file mode 100644
index 000000000..d32b1edcd
--- /dev/null
+++ b/compiler/rustc_borrowck/src/type_check/mod.rs
@@ -0,0 +1,2721 @@
+//! This pass type-checks the MIR to ensure it is not broken.
+
+use std::rc::Rc;
+use std::{fmt, iter, mem};
+
+use either::Either;
+
+use hir::OpaqueTyOrigin;
+use rustc_data_structures::frozen::Frozen;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::vec_map::VecMap;
+use rustc_hir as hir;
+use rustc_hir::def::DefKind;
+use rustc_hir::def_id::LocalDefId;
+use rustc_hir::lang_items::LangItem;
+use rustc_index::vec::{Idx, IndexVec};
+use rustc_infer::infer::canonical::QueryRegionConstraints;
+use rustc_infer::infer::outlives::env::RegionBoundPairs;
+use rustc_infer::infer::region_constraints::RegionConstraintData;
+use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
+use rustc_infer::infer::{
+ InferCtxt, InferOk, LateBoundRegion, LateBoundRegionConversionTime, NllRegionVariableOrigin,
+};
+use rustc_middle::mir::tcx::PlaceTy;
+use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor};
+use rustc_middle::mir::AssertKind;
+use rustc_middle::mir::*;
+use rustc_middle::ty::adjustment::PointerCast;
+use rustc_middle::ty::cast::CastTy;
+use rustc_middle::ty::subst::{GenericArgKind, SubstsRef, UserSubsts};
+use rustc_middle::ty::visit::TypeVisitable;
+use rustc_middle::ty::{
+ self, CanonicalUserTypeAnnotation, CanonicalUserTypeAnnotations, OpaqueHiddenType,
+ OpaqueTypeKey, RegionVid, ToPredicate, Ty, TyCtxt, UserType, UserTypeAnnotationIndex,
+};
+use rustc_span::def_id::CRATE_DEF_ID;
+use rustc_span::{Span, DUMMY_SP};
+use rustc_target::abi::VariantIdx;
+use rustc_trait_selection::traits::query::type_op;
+use rustc_trait_selection::traits::query::type_op::custom::scrape_region_constraints;
+use rustc_trait_selection::traits::query::type_op::custom::CustomTypeOp;
+use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput};
+use rustc_trait_selection::traits::query::Fallible;
+use rustc_trait_selection::traits::PredicateObligation;
+
+use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
+use rustc_mir_dataflow::move_paths::MoveData;
+use rustc_mir_dataflow::ResultsCursor;
+
+use crate::session_diagnostics::MoveUnsized;
+use crate::{
+ borrow_set::BorrowSet,
+ constraints::{OutlivesConstraint, OutlivesConstraintSet},
+ diagnostics::UniverseInfo,
+ facts::AllFacts,
+ location::LocationTable,
+ member_constraints::MemberConstraintSet,
+ nll::ToRegionVid,
+ path_utils,
+ region_infer::values::{
+ LivenessValues, PlaceholderIndex, PlaceholderIndices, RegionValueElements,
+ },
+ region_infer::{ClosureRegionRequirementsExt, TypeTest},
+ type_check::free_region_relations::{CreateResult, UniversalRegionRelations},
+ universal_regions::{DefiningTy, UniversalRegions},
+ Upvar,
+};
+
+macro_rules! span_mirbug {
+ ($context:expr, $elem:expr, $($message:tt)*) => ({
+ $crate::type_check::mirbug(
+ $context.tcx(),
+ $context.last_span,
+ &format!(
+ "broken MIR in {:?} ({:?}): {}",
+ $context.body().source.def_id(),
+ $elem,
+ format_args!($($message)*),
+ ),
+ )
+ })
+}
+
+macro_rules! span_mirbug_and_err {
+ ($context:expr, $elem:expr, $($message:tt)*) => ({
+ {
+ span_mirbug!($context, $elem, $($message)*);
+ $context.error()
+ }
+ })
+}
+
+mod canonical;
+mod constraint_conversion;
+pub mod free_region_relations;
+mod input_output;
+pub(crate) mod liveness;
+mod relate_tys;
+
+/// Type checks the given `mir` in the context of the inference
+/// context `infcx`. Returns any region constraints that have yet to
+/// be proven. This result includes liveness constraints that
+/// ensure that regions appearing in the types of all local variables
+/// are live at all points where that local variable may later be
+/// used.
+///
+/// This phase of type-check ought to be infallible -- this is because
+/// the original, HIR-based type-check succeeded. So if any errors
+/// occur here, we will get a `bug!` reported.
+///
+/// # Parameters
+///
+/// - `infcx` -- inference context to use
+/// - `param_env` -- parameter environment to use for trait solving
+/// - `body` -- MIR body to type-check
+/// - `promoted` -- map of promoted constants within `body`
+/// - `universal_regions` -- the universal regions from `body`s function signature
+/// - `location_table` -- MIR location map of `body`
+/// - `borrow_set` -- information about borrows occurring in `body`
+/// - `all_facts` -- when using Polonius, this is the generated set of Polonius facts
+/// - `flow_inits` -- results of a maybe-init dataflow analysis
+/// - `move_data` -- move-data constructed when performing the maybe-init dataflow analysis
+/// - `elements` -- MIR region map
+pub(crate) fn type_check<'mir, 'tcx>(
+ infcx: &InferCtxt<'_, 'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ body: &Body<'tcx>,
+ promoted: &IndexVec<Promoted, Body<'tcx>>,
+ universal_regions: &Rc<UniversalRegions<'tcx>>,
+ location_table: &LocationTable,
+ borrow_set: &BorrowSet<'tcx>,
+ all_facts: &mut Option<AllFacts>,
+ flow_inits: &mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>,
+ move_data: &MoveData<'tcx>,
+ elements: &Rc<RegionValueElements>,
+ upvars: &[Upvar<'tcx>],
+ use_polonius: bool,
+) -> MirTypeckResults<'tcx> {
+ let implicit_region_bound = infcx.tcx.mk_region(ty::ReVar(universal_regions.fr_fn_body));
+ let mut universe_causes = FxHashMap::default();
+ universe_causes.insert(ty::UniverseIndex::from_u32(0), UniverseInfo::other());
+ let mut constraints = MirTypeckRegionConstraints {
+ placeholder_indices: PlaceholderIndices::default(),
+ placeholder_index_to_region: IndexVec::default(),
+ liveness_constraints: LivenessValues::new(elements.clone()),
+ outlives_constraints: OutlivesConstraintSet::default(),
+ member_constraints: MemberConstraintSet::default(),
+ closure_bounds_mapping: Default::default(),
+ type_tests: Vec::default(),
+ universe_causes,
+ };
+
+ let CreateResult {
+ universal_region_relations,
+ region_bound_pairs,
+ normalized_inputs_and_output,
+ } = free_region_relations::create(
+ infcx,
+ param_env,
+ implicit_region_bound,
+ universal_regions,
+ &mut constraints,
+ );
+
+ debug!(?normalized_inputs_and_output);
+
+ for u in ty::UniverseIndex::ROOT..infcx.universe() {
+ let info = UniverseInfo::other();
+ constraints.universe_causes.insert(u, info);
+ }
+
+ let mut borrowck_context = BorrowCheckContext {
+ universal_regions,
+ location_table,
+ borrow_set,
+ all_facts,
+ constraints: &mut constraints,
+ upvars,
+ };
+
+ let opaque_type_values = type_check_internal(
+ infcx,
+ param_env,
+ body,
+ promoted,
+ &region_bound_pairs,
+ implicit_region_bound,
+ &mut borrowck_context,
+ |mut cx| {
+ debug!("inside extra closure of type_check_internal");
+ cx.equate_inputs_and_outputs(&body, universal_regions, &normalized_inputs_and_output);
+ liveness::generate(
+ &mut cx,
+ body,
+ elements,
+ flow_inits,
+ move_data,
+ location_table,
+ use_polonius,
+ );
+
+ translate_outlives_facts(&mut cx);
+ let opaque_type_values =
+ infcx.inner.borrow_mut().opaque_type_storage.take_opaque_types();
+
+ opaque_type_values
+ .into_iter()
+ .map(|(opaque_type_key, decl)| {
+ cx.fully_perform_op(
+ Locations::All(body.span),
+ ConstraintCategory::OpaqueType,
+ CustomTypeOp::new(
+ |infcx| {
+ infcx.register_member_constraints(
+ param_env,
+ opaque_type_key,
+ decl.hidden_type.ty,
+ decl.hidden_type.span,
+ );
+ Ok(InferOk { value: (), obligations: vec![] })
+ },
+ || "opaque_type_map".to_string(),
+ ),
+ )
+ .unwrap();
+ let mut hidden_type = infcx.resolve_vars_if_possible(decl.hidden_type);
+ trace!(
+ "finalized opaque type {:?} to {:#?}",
+ opaque_type_key,
+ hidden_type.ty.kind()
+ );
+ if hidden_type.has_infer_types_or_consts() {
+ infcx.tcx.sess.delay_span_bug(
+ decl.hidden_type.span,
+ &format!("could not resolve {:#?}", hidden_type.ty.kind()),
+ );
+ hidden_type.ty = infcx.tcx.ty_error();
+ }
+
+ (opaque_type_key, (hidden_type, decl.origin))
+ })
+ .collect()
+ },
+ );
+
+ MirTypeckResults { constraints, universal_region_relations, opaque_type_values }
+}
+
+#[instrument(
+ skip(infcx, body, promoted, region_bound_pairs, borrowck_context, extra),
+ level = "debug"
+)]
+fn type_check_internal<'a, 'tcx, R>(
+ infcx: &'a InferCtxt<'a, 'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ body: &'a Body<'tcx>,
+ promoted: &'a IndexVec<Promoted, Body<'tcx>>,
+ region_bound_pairs: &'a RegionBoundPairs<'tcx>,
+ implicit_region_bound: ty::Region<'tcx>,
+ borrowck_context: &'a mut BorrowCheckContext<'a, 'tcx>,
+ extra: impl FnOnce(TypeChecker<'a, 'tcx>) -> R,
+) -> R {
+ debug!("body: {:#?}", body);
+ let mut checker = TypeChecker::new(
+ infcx,
+ body,
+ param_env,
+ region_bound_pairs,
+ implicit_region_bound,
+ borrowck_context,
+ );
+ let errors_reported = {
+ let mut verifier = TypeVerifier::new(&mut checker, promoted);
+ verifier.visit_body(&body);
+ verifier.errors_reported
+ };
+
+ if !errors_reported {
+ // if verifier failed, don't do further checks to avoid ICEs
+ checker.typeck_mir(body);
+ }
+
+ extra(checker)
+}
+
+fn translate_outlives_facts(typeck: &mut TypeChecker<'_, '_>) {
+ let cx = &mut typeck.borrowck_context;
+ if let Some(facts) = cx.all_facts {
+ let _prof_timer = typeck.infcx.tcx.prof.generic_activity("polonius_fact_generation");
+ let location_table = cx.location_table;
+ facts.subset_base.extend(cx.constraints.outlives_constraints.outlives().iter().flat_map(
+ |constraint: &OutlivesConstraint<'_>| {
+ if let Some(from_location) = constraint.locations.from_location() {
+ Either::Left(iter::once((
+ constraint.sup,
+ constraint.sub,
+ location_table.mid_index(from_location),
+ )))
+ } else {
+ Either::Right(
+ location_table
+ .all_points()
+ .map(move |location| (constraint.sup, constraint.sub, location)),
+ )
+ }
+ },
+ ));
+ }
+}
+
+#[track_caller]
+fn mirbug(tcx: TyCtxt<'_>, span: Span, msg: &str) {
+ // We sometimes see MIR failures (notably predicate failures) due to
+ // the fact that we check rvalue sized predicates here. So use `delay_span_bug`
+ // to avoid reporting bugs in those cases.
+ tcx.sess.diagnostic().delay_span_bug(span, msg);
+}
+
+enum FieldAccessError {
+ OutOfRange { field_count: usize },
+}
+
+/// Verifies that MIR types are sane to not crash further checks.
+///
+/// The sanitize_XYZ methods here take an MIR object and compute its
+/// type, calling `span_mirbug` and returning an error type if there
+/// is a problem.
+struct TypeVerifier<'a, 'b, 'tcx> {
+ cx: &'a mut TypeChecker<'b, 'tcx>,
+ promoted: &'b IndexVec<Promoted, Body<'tcx>>,
+ last_span: Span,
+ errors_reported: bool,
+}
+
+impl<'a, 'b, 'tcx> Visitor<'tcx> for TypeVerifier<'a, 'b, 'tcx> {
+ fn visit_span(&mut self, span: Span) {
+ if !span.is_dummy() {
+ self.last_span = span;
+ }
+ }
+
+ fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {
+ self.sanitize_place(place, location, context);
+ }
+
+ fn visit_constant(&mut self, constant: &Constant<'tcx>, location: Location) {
+ self.super_constant(constant, location);
+ let ty = self.sanitize_type(constant, constant.literal.ty());
+
+ self.cx.infcx.tcx.for_each_free_region(&ty, |live_region| {
+ let live_region_vid =
+ self.cx.borrowck_context.universal_regions.to_region_vid(live_region);
+ self.cx
+ .borrowck_context
+ .constraints
+ .liveness_constraints
+ .add_element(live_region_vid, location);
+ });
+
+ // HACK(compiler-errors): Constants that are gathered into Body.required_consts
+ // have their locations erased...
+ let locations = if location != Location::START {
+ location.to_locations()
+ } else {
+ Locations::All(constant.span)
+ };
+
+ if let Some(annotation_index) = constant.user_ty {
+ if let Err(terr) = self.cx.relate_type_and_user_type(
+ constant.literal.ty(),
+ ty::Variance::Invariant,
+ &UserTypeProjection { base: annotation_index, projs: vec![] },
+ locations,
+ ConstraintCategory::Boring,
+ ) {
+ let annotation = &self.cx.user_type_annotations[annotation_index];
+ span_mirbug!(
+ self,
+ constant,
+ "bad constant user type {:?} vs {:?}: {:?}",
+ annotation,
+ constant.literal.ty(),
+ terr,
+ );
+ }
+ } else {
+ let tcx = self.tcx();
+ let maybe_uneval = match constant.literal {
+ ConstantKind::Ty(ct) => match ct.kind() {
+ ty::ConstKind::Unevaluated(uv) => Some(uv),
+ _ => None,
+ },
+ _ => None,
+ };
+ if let Some(uv) = maybe_uneval {
+ if let Some(promoted) = uv.promoted {
+ let check_err = |verifier: &mut TypeVerifier<'a, 'b, 'tcx>,
+ promoted: &Body<'tcx>,
+ ty,
+ san_ty| {
+ if let Err(terr) =
+ verifier.cx.eq_types(ty, san_ty, locations, ConstraintCategory::Boring)
+ {
+ span_mirbug!(
+ verifier,
+ promoted,
+ "bad promoted type ({:?}: {:?}): {:?}",
+ ty,
+ san_ty,
+ terr
+ );
+ };
+ };
+
+ if !self.errors_reported {
+ let promoted_body = &self.promoted[promoted];
+ self.sanitize_promoted(promoted_body, location);
+
+ let promoted_ty = promoted_body.return_ty();
+ check_err(self, promoted_body, ty, promoted_ty);
+ }
+ } else {
+ if let Err(terr) = self.cx.fully_perform_op(
+ locations,
+ ConstraintCategory::Boring,
+ self.cx.param_env.and(type_op::ascribe_user_type::AscribeUserType::new(
+ constant.literal.ty(),
+ uv.def.did,
+ UserSubsts { substs: uv.substs, user_self_ty: None },
+ )),
+ ) {
+ span_mirbug!(
+ self,
+ constant,
+ "bad constant type {:?} ({:?})",
+ constant,
+ terr
+ );
+ }
+ }
+ } else if let Some(static_def_id) = constant.check_static_ptr(tcx) {
+ let unnormalized_ty = tcx.type_of(static_def_id);
+ let normalized_ty = self.cx.normalize(unnormalized_ty, locations);
+ let literal_ty = constant.literal.ty().builtin_deref(true).unwrap().ty;
+
+ if let Err(terr) = self.cx.eq_types(
+ literal_ty,
+ normalized_ty,
+ locations,
+ ConstraintCategory::Boring,
+ ) {
+ span_mirbug!(self, constant, "bad static type {:?} ({:?})", constant, terr);
+ }
+ }
+
+ if let ty::FnDef(def_id, substs) = *constant.literal.ty().kind() {
+ let instantiated_predicates = tcx.predicates_of(def_id).instantiate(tcx, substs);
+ self.cx.normalize_and_prove_instantiated_predicates(
+ def_id,
+ instantiated_predicates,
+ locations,
+ );
+ }
+ }
+ }
+
+ fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
+ self.super_rvalue(rvalue, location);
+ let rval_ty = rvalue.ty(self.body(), self.tcx());
+ self.sanitize_type(rvalue, rval_ty);
+ }
+
+ fn visit_local_decl(&mut self, local: Local, local_decl: &LocalDecl<'tcx>) {
+ self.super_local_decl(local, local_decl);
+ self.sanitize_type(local_decl, local_decl.ty);
+
+ if let Some(user_ty) = &local_decl.user_ty {
+ for (user_ty, span) in user_ty.projections_and_spans() {
+ let ty = if !local_decl.is_nonref_binding() {
+ // If we have a binding of the form `let ref x: T = ..`
+ // then remove the outermost reference so we can check the
+ // type annotation for the remaining type.
+ if let ty::Ref(_, rty, _) = local_decl.ty.kind() {
+ *rty
+ } else {
+ bug!("{:?} with ref binding has wrong type {}", local, local_decl.ty);
+ }
+ } else {
+ local_decl.ty
+ };
+
+ if let Err(terr) = self.cx.relate_type_and_user_type(
+ ty,
+ ty::Variance::Invariant,
+ user_ty,
+ Locations::All(*span),
+ ConstraintCategory::TypeAnnotation,
+ ) {
+ span_mirbug!(
+ self,
+ local,
+ "bad user type on variable {:?}: {:?} != {:?} ({:?})",
+ local,
+ local_decl.ty,
+ local_decl.user_ty,
+ terr,
+ );
+ }
+ }
+ }
+ }
+
+ fn visit_body(&mut self, body: &Body<'tcx>) {
+ self.sanitize_type(&"return type", body.return_ty());
+ for local_decl in &body.local_decls {
+ self.sanitize_type(local_decl, local_decl.ty);
+ }
+ if self.errors_reported {
+ return;
+ }
+ self.super_body(body);
+ }
+}
+
+impl<'a, 'b, 'tcx> TypeVerifier<'a, 'b, 'tcx> {
+ fn new(
+ cx: &'a mut TypeChecker<'b, 'tcx>,
+ promoted: &'b IndexVec<Promoted, Body<'tcx>>,
+ ) -> Self {
+ TypeVerifier { promoted, last_span: cx.body.span, cx, errors_reported: false }
+ }
+
+ fn body(&self) -> &Body<'tcx> {
+ self.cx.body
+ }
+
+ fn tcx(&self) -> TyCtxt<'tcx> {
+ self.cx.infcx.tcx
+ }
+
+ fn sanitize_type(&mut self, parent: &dyn fmt::Debug, ty: Ty<'tcx>) -> Ty<'tcx> {
+ if ty.has_escaping_bound_vars() || ty.references_error() {
+ span_mirbug_and_err!(self, parent, "bad type {:?}", ty)
+ } else {
+ ty
+ }
+ }
+
+ /// Checks that the types internal to the `place` match up with
+ /// what would be expected.
+ fn sanitize_place(
+ &mut self,
+ place: &Place<'tcx>,
+ location: Location,
+ context: PlaceContext,
+ ) -> PlaceTy<'tcx> {
+ debug!("sanitize_place: {:?}", place);
+
+ let mut place_ty = PlaceTy::from_ty(self.body().local_decls[place.local].ty);
+
+ for elem in place.projection.iter() {
+ if place_ty.variant_index.is_none() {
+ if place_ty.ty.references_error() {
+ assert!(self.errors_reported);
+ return PlaceTy::from_ty(self.tcx().ty_error());
+ }
+ }
+ place_ty = self.sanitize_projection(place_ty, elem, place, location);
+ }
+
+ if let PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy) = context {
+ let tcx = self.tcx();
+ let trait_ref = ty::TraitRef {
+ def_id: tcx.require_lang_item(LangItem::Copy, Some(self.last_span)),
+ substs: tcx.mk_substs_trait(place_ty.ty, &[]),
+ };
+
+ // To have a `Copy` operand, the type `T` of the
+ // value must be `Copy`. Note that we prove that `T: Copy`,
+ // rather than using the `is_copy_modulo_regions`
+ // test. This is important because
+ // `is_copy_modulo_regions` ignores the resulting region
+ // obligations and assumes they pass. This can result in
+ // bounds from `Copy` impls being unsoundly ignored (e.g.,
+ // #29149). Note that we decide to use `Copy` before knowing
+ // whether the bounds fully apply: in effect, the rule is
+ // that if a value of some type could implement `Copy`, then
+ // it must.
+ self.cx.prove_trait_ref(
+ trait_ref,
+ location.to_locations(),
+ ConstraintCategory::CopyBound,
+ );
+ }
+
+ place_ty
+ }
+
+ fn sanitize_promoted(&mut self, promoted_body: &'b Body<'tcx>, location: Location) {
+ // Determine the constraints from the promoted MIR by running the type
+ // checker on the promoted MIR, then transfer the constraints back to
+ // the main MIR, changing the locations to the provided location.
+
+ let parent_body = mem::replace(&mut self.cx.body, promoted_body);
+
+ // Use new sets of constraints and closure bounds so that we can
+ // modify their locations.
+ let all_facts = &mut None;
+ let mut constraints = Default::default();
+ let mut closure_bounds = Default::default();
+ let mut liveness_constraints =
+ LivenessValues::new(Rc::new(RegionValueElements::new(&promoted_body)));
+ // Don't try to add borrow_region facts for the promoted MIR
+
+ let mut swap_constraints = |this: &mut Self| {
+ mem::swap(this.cx.borrowck_context.all_facts, all_facts);
+ mem::swap(
+ &mut this.cx.borrowck_context.constraints.outlives_constraints,
+ &mut constraints,
+ );
+ mem::swap(
+ &mut this.cx.borrowck_context.constraints.closure_bounds_mapping,
+ &mut closure_bounds,
+ );
+ mem::swap(
+ &mut this.cx.borrowck_context.constraints.liveness_constraints,
+ &mut liveness_constraints,
+ );
+ };
+
+ swap_constraints(self);
+
+ self.visit_body(&promoted_body);
+
+ if !self.errors_reported {
+ // if verifier failed, don't do further checks to avoid ICEs
+ self.cx.typeck_mir(promoted_body);
+ }
+
+ self.cx.body = parent_body;
+ // Merge the outlives constraints back in, at the given location.
+ swap_constraints(self);
+
+ let locations = location.to_locations();
+ for constraint in constraints.outlives().iter() {
+ let mut constraint = constraint.clone();
+ constraint.locations = locations;
+ if let ConstraintCategory::Return(_)
+ | ConstraintCategory::UseAsConst
+ | ConstraintCategory::UseAsStatic = constraint.category
+ {
+ // "Returning" from a promoted is an assignment to a
+ // temporary from the user's point of view.
+ constraint.category = ConstraintCategory::Boring;
+ }
+ self.cx.borrowck_context.constraints.outlives_constraints.push(constraint)
+ }
+ for region in liveness_constraints.rows() {
+ // If the region is live at at least one location in the promoted MIR,
+ // then add a liveness constraint to the main MIR for this region
+ // at the location provided as an argument to this method
+ if liveness_constraints.get_elements(region).next().is_some() {
+ self.cx
+ .borrowck_context
+ .constraints
+ .liveness_constraints
+ .add_element(region, location);
+ }
+ }
+
+ if !closure_bounds.is_empty() {
+ let combined_bounds_mapping =
+ closure_bounds.into_iter().flat_map(|(_, value)| value).collect();
+ let existing = self
+ .cx
+ .borrowck_context
+ .constraints
+ .closure_bounds_mapping
+ .insert(location, combined_bounds_mapping);
+ assert!(existing.is_none(), "Multiple promoteds/closures at the same location.");
+ }
+ }
+
+ fn sanitize_projection(
+ &mut self,
+ base: PlaceTy<'tcx>,
+ pi: PlaceElem<'tcx>,
+ place: &Place<'tcx>,
+ location: Location,
+ ) -> PlaceTy<'tcx> {
+ debug!("sanitize_projection: {:?} {:?} {:?}", base, pi, place);
+ let tcx = self.tcx();
+ let base_ty = base.ty;
+ match pi {
+ ProjectionElem::Deref => {
+ let deref_ty = base_ty.builtin_deref(true);
+ PlaceTy::from_ty(deref_ty.map(|t| t.ty).unwrap_or_else(|| {
+ span_mirbug_and_err!(self, place, "deref of non-pointer {:?}", base_ty)
+ }))
+ }
+ ProjectionElem::Index(i) => {
+ let index_ty = Place::from(i).ty(self.body(), tcx).ty;
+ if index_ty != tcx.types.usize {
+ PlaceTy::from_ty(span_mirbug_and_err!(self, i, "index by non-usize {:?}", i))
+ } else {
+ PlaceTy::from_ty(base_ty.builtin_index().unwrap_or_else(|| {
+ span_mirbug_and_err!(self, place, "index of non-array {:?}", base_ty)
+ }))
+ }
+ }
+ ProjectionElem::ConstantIndex { .. } => {
+ // consider verifying in-bounds
+ PlaceTy::from_ty(base_ty.builtin_index().unwrap_or_else(|| {
+ span_mirbug_and_err!(self, place, "index of non-array {:?}", base_ty)
+ }))
+ }
+ ProjectionElem::Subslice { from, to, from_end } => {
+ PlaceTy::from_ty(match base_ty.kind() {
+ ty::Array(inner, _) => {
+ assert!(!from_end, "array subslices should not use from_end");
+ tcx.mk_array(*inner, to - from)
+ }
+ ty::Slice(..) => {
+ assert!(from_end, "slice subslices should use from_end");
+ base_ty
+ }
+ _ => span_mirbug_and_err!(self, place, "slice of non-array {:?}", base_ty),
+ })
+ }
+ ProjectionElem::Downcast(maybe_name, index) => match base_ty.kind() {
+ ty::Adt(adt_def, _substs) if adt_def.is_enum() => {
+ if index.as_usize() >= adt_def.variants().len() {
+ PlaceTy::from_ty(span_mirbug_and_err!(
+ self,
+ place,
+ "cast to variant #{:?} but enum only has {:?}",
+ index,
+ adt_def.variants().len()
+ ))
+ } else {
+ PlaceTy { ty: base_ty, variant_index: Some(index) }
+ }
+ }
+ // We do not need to handle generators here, because this runs
+ // before the generator transform stage.
+ _ => {
+ let ty = if let Some(name) = maybe_name {
+ span_mirbug_and_err!(
+ self,
+ place,
+ "can't downcast {:?} as {:?}",
+ base_ty,
+ name
+ )
+ } else {
+ span_mirbug_and_err!(self, place, "can't downcast {:?}", base_ty)
+ };
+ PlaceTy::from_ty(ty)
+ }
+ },
+ ProjectionElem::Field(field, fty) => {
+ let fty = self.sanitize_type(place, fty);
+ let fty = self.cx.normalize(fty, location);
+ match self.field_ty(place, base, field, location) {
+ Ok(ty) => {
+ let ty = self.cx.normalize(ty, location);
+ if let Err(terr) = self.cx.eq_types(
+ ty,
+ fty,
+ location.to_locations(),
+ ConstraintCategory::Boring,
+ ) {
+ span_mirbug!(
+ self,
+ place,
+ "bad field access ({:?}: {:?}): {:?}",
+ ty,
+ fty,
+ terr
+ );
+ }
+ }
+ Err(FieldAccessError::OutOfRange { field_count }) => span_mirbug!(
+ self,
+ place,
+ "accessed field #{} but variant only has {}",
+ field.index(),
+ field_count
+ ),
+ }
+ PlaceTy::from_ty(fty)
+ }
+ }
+ }
+
+ fn error(&mut self) -> Ty<'tcx> {
+ self.errors_reported = true;
+ self.tcx().ty_error()
+ }
+
+ fn field_ty(
+ &mut self,
+ parent: &dyn fmt::Debug,
+ base_ty: PlaceTy<'tcx>,
+ field: Field,
+ location: Location,
+ ) -> Result<Ty<'tcx>, FieldAccessError> {
+ let tcx = self.tcx();
+
+ let (variant, substs) = match base_ty {
+ PlaceTy { ty, variant_index: Some(variant_index) } => match *ty.kind() {
+ ty::Adt(adt_def, substs) => (adt_def.variant(variant_index), substs),
+ ty::Generator(def_id, substs, _) => {
+ let mut variants = substs.as_generator().state_tys(def_id, tcx);
+ let Some(mut variant) = variants.nth(variant_index.into()) else {
+ bug!(
+ "variant_index of generator out of range: {:?}/{:?}",
+ variant_index,
+ substs.as_generator().state_tys(def_id, tcx).count()
+ );
+ };
+ return match variant.nth(field.index()) {
+ Some(ty) => Ok(ty),
+ None => Err(FieldAccessError::OutOfRange { field_count: variant.count() }),
+ };
+ }
+ _ => bug!("can't have downcast of non-adt non-generator type"),
+ },
+ PlaceTy { ty, variant_index: None } => match *ty.kind() {
+ ty::Adt(adt_def, substs) if !adt_def.is_enum() => {
+ (adt_def.variant(VariantIdx::new(0)), substs)
+ }
+ ty::Closure(_, substs) => {
+ return match substs
+ .as_closure()
+ .tupled_upvars_ty()
+ .tuple_fields()
+ .get(field.index())
+ {
+ Some(&ty) => Ok(ty),
+ None => Err(FieldAccessError::OutOfRange {
+ field_count: substs.as_closure().upvar_tys().count(),
+ }),
+ };
+ }
+ ty::Generator(_, substs, _) => {
+ // Only prefix fields (upvars and current state) are
+ // accessible without a variant index.
+ return match substs.as_generator().prefix_tys().nth(field.index()) {
+ Some(ty) => Ok(ty),
+ None => Err(FieldAccessError::OutOfRange {
+ field_count: substs.as_generator().prefix_tys().count(),
+ }),
+ };
+ }
+ ty::Tuple(tys) => {
+ return match tys.get(field.index()) {
+ Some(&ty) => Ok(ty),
+ None => Err(FieldAccessError::OutOfRange { field_count: tys.len() }),
+ };
+ }
+ _ => {
+ return Ok(span_mirbug_and_err!(
+ self,
+ parent,
+ "can't project out of {:?}",
+ base_ty
+ ));
+ }
+ },
+ };
+
+ if let Some(field) = variant.fields.get(field.index()) {
+ Ok(self.cx.normalize(field.ty(tcx, substs), location))
+ } else {
+ Err(FieldAccessError::OutOfRange { field_count: variant.fields.len() })
+ }
+ }
+}
+
+/// The MIR type checker. Visits the MIR and enforces all the
+/// constraints needed for it to be valid and well-typed. Along the
+/// way, it accrues region constraints -- these can later be used by
+/// NLL region checking.
+struct TypeChecker<'a, 'tcx> {
+ infcx: &'a InferCtxt<'a, 'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ last_span: Span,
+ body: &'a Body<'tcx>,
+ /// User type annotations are shared between the main MIR and the MIR of
+ /// all of the promoted items.
+ user_type_annotations: &'a CanonicalUserTypeAnnotations<'tcx>,
+ region_bound_pairs: &'a RegionBoundPairs<'tcx>,
+ implicit_region_bound: ty::Region<'tcx>,
+ reported_errors: FxHashSet<(Ty<'tcx>, Span)>,
+ borrowck_context: &'a mut BorrowCheckContext<'a, 'tcx>,
+}
+
+struct BorrowCheckContext<'a, 'tcx> {
+ pub(crate) universal_regions: &'a UniversalRegions<'tcx>,
+ location_table: &'a LocationTable,
+ all_facts: &'a mut Option<AllFacts>,
+ borrow_set: &'a BorrowSet<'tcx>,
+ pub(crate) constraints: &'a mut MirTypeckRegionConstraints<'tcx>,
+ upvars: &'a [Upvar<'tcx>],
+}
+
+pub(crate) struct MirTypeckResults<'tcx> {
+ pub(crate) constraints: MirTypeckRegionConstraints<'tcx>,
+ pub(crate) universal_region_relations: Frozen<UniversalRegionRelations<'tcx>>,
+ pub(crate) opaque_type_values:
+ VecMap<OpaqueTypeKey<'tcx>, (OpaqueHiddenType<'tcx>, OpaqueTyOrigin)>,
+}
+
+/// A collection of region constraints that must be satisfied for the
+/// program to be considered well-typed.
+pub(crate) struct MirTypeckRegionConstraints<'tcx> {
+ /// Maps from a `ty::Placeholder` to the corresponding
+ /// `PlaceholderIndex` bit that we will use for it.
+ ///
+ /// To keep everything in sync, do not insert this set
+ /// directly. Instead, use the `placeholder_region` helper.
+ pub(crate) placeholder_indices: PlaceholderIndices,
+
+ /// Each time we add a placeholder to `placeholder_indices`, we
+ /// also create a corresponding "representative" region vid for
+ /// that wraps it. This vector tracks those. This way, when we
+ /// convert the same `ty::RePlaceholder(p)` twice, we can map to
+ /// the same underlying `RegionVid`.
+ pub(crate) placeholder_index_to_region: IndexVec<PlaceholderIndex, ty::Region<'tcx>>,
+
+ /// In general, the type-checker is not responsible for enforcing
+ /// liveness constraints; this job falls to the region inferencer,
+ /// which performs a liveness analysis. However, in some limited
+ /// cases, the MIR type-checker creates temporary regions that do
+ /// not otherwise appear in the MIR -- in particular, the
+ /// late-bound regions that it instantiates at call-sites -- and
+ /// hence it must report on their liveness constraints.
+ pub(crate) liveness_constraints: LivenessValues<RegionVid>,
+
+ pub(crate) outlives_constraints: OutlivesConstraintSet<'tcx>,
+
+ pub(crate) member_constraints: MemberConstraintSet<'tcx, RegionVid>,
+
+ pub(crate) closure_bounds_mapping:
+ FxHashMap<Location, FxHashMap<(RegionVid, RegionVid), (ConstraintCategory<'tcx>, Span)>>,
+
+ pub(crate) universe_causes: FxHashMap<ty::UniverseIndex, UniverseInfo<'tcx>>,
+
+ pub(crate) type_tests: Vec<TypeTest<'tcx>>,
+}
+
+impl<'tcx> MirTypeckRegionConstraints<'tcx> {
+ fn placeholder_region(
+ &mut self,
+ infcx: &InferCtxt<'_, 'tcx>,
+ placeholder: ty::PlaceholderRegion,
+ ) -> ty::Region<'tcx> {
+ let placeholder_index = self.placeholder_indices.insert(placeholder);
+ match self.placeholder_index_to_region.get(placeholder_index) {
+ Some(&v) => v,
+ None => {
+ let origin = NllRegionVariableOrigin::Placeholder(placeholder);
+ let region = infcx.next_nll_region_var_in_universe(origin, placeholder.universe);
+ self.placeholder_index_to_region.push(region);
+ region
+ }
+ }
+ }
+}
+
+/// The `Locations` type summarizes *where* region constraints are
+/// required to hold. Normally, this is at a particular point which
+/// created the obligation, but for constraints that the user gave, we
+/// want the constraint to hold at all points.
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
+pub enum Locations {
+ /// Indicates that a type constraint should always be true. This
+ /// is particularly important in the new borrowck analysis for
+ /// things like the type of the return slot. Consider this
+ /// example:
+ ///
+ /// ```compile_fail,E0515
+ /// fn foo<'a>(x: &'a u32) -> &'a u32 {
+ /// let y = 22;
+ /// return &y; // error
+ /// }
+ /// ```
+ ///
+ /// Here, we wind up with the signature from the return type being
+ /// something like `&'1 u32` where `'1` is a universal region. But
+ /// the type of the return slot `_0` is something like `&'2 u32`
+ /// where `'2` is an existential region variable. The type checker
+ /// requires that `&'2 u32 = &'1 u32` -- but at what point? In the
+ /// older NLL analysis, we required this only at the entry point
+ /// to the function. By the nature of the constraints, this wound
+ /// up propagating to all points reachable from start (because
+ /// `'1` -- as a universal region -- is live everywhere). In the
+ /// newer analysis, though, this doesn't work: `_0` is considered
+ /// dead at the start (it has no usable value) and hence this type
+ /// equality is basically a no-op. Then, later on, when we do `_0
+ /// = &'3 y`, that region `'3` never winds up related to the
+ /// universal region `'1` and hence no error occurs. Therefore, we
+ /// use Locations::All instead, which ensures that the `'1` and
+ /// `'2` are equal everything. We also use this for other
+ /// user-given type annotations; e.g., if the user wrote `let mut
+ /// x: &'static u32 = ...`, we would ensure that all values
+ /// assigned to `x` are of `'static` lifetime.
+ ///
+ /// The span points to the place the constraint arose. For example,
+ /// it points to the type in a user-given type annotation. If
+ /// there's no sensible span then it's DUMMY_SP.
+ All(Span),
+
+ /// An outlives constraint that only has to hold at a single location,
+ /// usually it represents a point where references flow from one spot to
+ /// another (e.g., `x = y`)
+ Single(Location),
+}
+
+impl Locations {
+ pub fn from_location(&self) -> Option<Location> {
+ match self {
+ Locations::All(_) => None,
+ Locations::Single(from_location) => Some(*from_location),
+ }
+ }
+
+ /// Gets a span representing the location.
+ pub fn span(&self, body: &Body<'_>) -> Span {
+ match self {
+ Locations::All(span) => *span,
+ Locations::Single(l) => body.source_info(*l).span,
+ }
+ }
+}
+
+impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
+ fn new(
+ infcx: &'a InferCtxt<'a, 'tcx>,
+ body: &'a Body<'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ region_bound_pairs: &'a RegionBoundPairs<'tcx>,
+ implicit_region_bound: ty::Region<'tcx>,
+ borrowck_context: &'a mut BorrowCheckContext<'a, 'tcx>,
+ ) -> Self {
+ let mut checker = Self {
+ infcx,
+ last_span: DUMMY_SP,
+ body,
+ user_type_annotations: &body.user_type_annotations,
+ param_env,
+ region_bound_pairs,
+ implicit_region_bound,
+ borrowck_context,
+ reported_errors: Default::default(),
+ };
+ checker.check_user_type_annotations();
+ checker
+ }
+
+ fn body(&self) -> &Body<'tcx> {
+ self.body
+ }
+
+ fn unsized_feature_enabled(&self) -> bool {
+ let features = self.tcx().features();
+ features.unsized_locals || features.unsized_fn_params
+ }
+
+ /// Equate the inferred type and the annotated type for user type annotations
+ #[instrument(skip(self), level = "debug")]
+ fn check_user_type_annotations(&mut self) {
+ debug!(?self.user_type_annotations);
+ for user_annotation in self.user_type_annotations {
+ let CanonicalUserTypeAnnotation { span, ref user_ty, inferred_ty } = *user_annotation;
+ let inferred_ty = self.normalize(inferred_ty, Locations::All(span));
+ let annotation = self.instantiate_canonical_with_fresh_inference_vars(span, user_ty);
+ match annotation {
+ UserType::Ty(mut ty) => {
+ ty = self.normalize(ty, Locations::All(span));
+
+ if let Err(terr) = self.eq_types(
+ ty,
+ inferred_ty,
+ Locations::All(span),
+ ConstraintCategory::BoringNoLocation,
+ ) {
+ span_mirbug!(
+ self,
+ user_annotation,
+ "bad user type ({:?} = {:?}): {:?}",
+ ty,
+ inferred_ty,
+ terr
+ );
+ }
+
+ self.prove_predicate(
+ ty::Binder::dummy(ty::PredicateKind::WellFormed(inferred_ty.into()))
+ .to_predicate(self.tcx()),
+ Locations::All(span),
+ ConstraintCategory::TypeAnnotation,
+ );
+ }
+ UserType::TypeOf(def_id, user_substs) => {
+ if let Err(terr) = self.fully_perform_op(
+ Locations::All(span),
+ ConstraintCategory::BoringNoLocation,
+ self.param_env.and(type_op::ascribe_user_type::AscribeUserType::new(
+ inferred_ty,
+ def_id,
+ user_substs,
+ )),
+ ) {
+ span_mirbug!(
+ self,
+ user_annotation,
+ "bad user type AscribeUserType({:?}, {:?} {:?}, type_of={:?}): {:?}",
+ inferred_ty,
+ def_id,
+ user_substs,
+ self.tcx().type_of(def_id),
+ terr,
+ );
+ }
+ }
+ }
+ }
+ }
+
+ #[instrument(skip(self, data), level = "debug")]
+ fn push_region_constraints(
+ &mut self,
+ locations: Locations,
+ category: ConstraintCategory<'tcx>,
+ data: &QueryRegionConstraints<'tcx>,
+ ) {
+ debug!("constraints generated: {:#?}", data);
+
+ constraint_conversion::ConstraintConversion::new(
+ self.infcx,
+ self.borrowck_context.universal_regions,
+ self.region_bound_pairs,
+ self.implicit_region_bound,
+ self.param_env,
+ locations,
+ locations.span(self.body),
+ category,
+ &mut self.borrowck_context.constraints,
+ )
+ .convert_all(data);
+ }
+
+ /// Try to relate `sub <: sup`
+ fn sub_types(
+ &mut self,
+ sub: Ty<'tcx>,
+ sup: Ty<'tcx>,
+ locations: Locations,
+ category: ConstraintCategory<'tcx>,
+ ) -> Fallible<()> {
+ // Use this order of parameters because the sup type is usually the
+ // "expected" type in diagnostics.
+ self.relate_types(sup, ty::Variance::Contravariant, sub, locations, category)
+ }
+
+ #[instrument(skip(self, category), level = "debug")]
+ fn eq_types(
+ &mut self,
+ expected: Ty<'tcx>,
+ found: Ty<'tcx>,
+ locations: Locations,
+ category: ConstraintCategory<'tcx>,
+ ) -> Fallible<()> {
+ self.relate_types(expected, ty::Variance::Invariant, found, locations, category)
+ }
+
+ #[instrument(skip(self), level = "debug")]
+ fn relate_type_and_user_type(
+ &mut self,
+ a: Ty<'tcx>,
+ v: ty::Variance,
+ user_ty: &UserTypeProjection,
+ locations: Locations,
+ category: ConstraintCategory<'tcx>,
+ ) -> Fallible<()> {
+ let annotated_type = self.user_type_annotations[user_ty.base].inferred_ty;
+ let mut curr_projected_ty = PlaceTy::from_ty(annotated_type);
+
+ let tcx = self.infcx.tcx;
+
+ for proj in &user_ty.projs {
+ let projected_ty = curr_projected_ty.projection_ty_core(
+ tcx,
+ self.param_env,
+ proj,
+ |this, field, ()| {
+ let ty = this.field_ty(tcx, field);
+ self.normalize(ty, locations)
+ },
+ );
+ curr_projected_ty = projected_ty;
+ }
+ debug!(
+ "user_ty base: {:?} freshened: {:?} projs: {:?} yields: {:?}",
+ user_ty.base, annotated_type, user_ty.projs, curr_projected_ty
+ );
+
+ let ty = curr_projected_ty.ty;
+ self.relate_types(ty, v.xform(ty::Variance::Contravariant), a, locations, category)?;
+
+ Ok(())
+ }
+
+ fn tcx(&self) -> TyCtxt<'tcx> {
+ self.infcx.tcx
+ }
+
+ #[instrument(skip(self, body, location), level = "debug")]
+ fn check_stmt(&mut self, body: &Body<'tcx>, stmt: &Statement<'tcx>, location: Location) {
+ let tcx = self.tcx();
+ debug!("stmt kind: {:?}", stmt.kind);
+ match stmt.kind {
+ StatementKind::Assign(box (ref place, ref rv)) => {
+ // Assignments to temporaries are not "interesting";
+ // they are not caused by the user, but rather artifacts
+ // of lowering. Assignments to other sorts of places *are* interesting
+ // though.
+ let category = match place.as_local() {
+ Some(RETURN_PLACE) => {
+ let defining_ty = &self.borrowck_context.universal_regions.defining_ty;
+ if defining_ty.is_const() {
+ if tcx.is_static(defining_ty.def_id()) {
+ ConstraintCategory::UseAsStatic
+ } else {
+ ConstraintCategory::UseAsConst
+ }
+ } else {
+ ConstraintCategory::Return(ReturnConstraint::Normal)
+ }
+ }
+ Some(l)
+ if matches!(
+ body.local_decls[l].local_info,
+ Some(box LocalInfo::AggregateTemp)
+ ) =>
+ {
+ ConstraintCategory::Usage
+ }
+ Some(l) if !body.local_decls[l].is_user_variable() => {
+ ConstraintCategory::Boring
+ }
+ _ => ConstraintCategory::Assignment,
+ };
+ debug!(
+ "assignment category: {:?} {:?}",
+ category,
+ place.as_local().map(|l| &body.local_decls[l])
+ );
+
+ let place_ty = place.ty(body, tcx).ty;
+ debug!(?place_ty);
+ let place_ty = self.normalize(place_ty, location);
+ debug!("place_ty normalized: {:?}", place_ty);
+ let rv_ty = rv.ty(body, tcx);
+ debug!(?rv_ty);
+ let rv_ty = self.normalize(rv_ty, location);
+ debug!("normalized rv_ty: {:?}", rv_ty);
+ if let Err(terr) =
+ self.sub_types(rv_ty, place_ty, location.to_locations(), category)
+ {
+ span_mirbug!(
+ self,
+ stmt,
+ "bad assignment ({:?} = {:?}): {:?}",
+ place_ty,
+ rv_ty,
+ terr
+ );
+ }
+
+ if let Some(annotation_index) = self.rvalue_user_ty(rv) {
+ if let Err(terr) = self.relate_type_and_user_type(
+ rv_ty,
+ ty::Variance::Invariant,
+ &UserTypeProjection { base: annotation_index, projs: vec![] },
+ location.to_locations(),
+ ConstraintCategory::Boring,
+ ) {
+ let annotation = &self.user_type_annotations[annotation_index];
+ span_mirbug!(
+ self,
+ stmt,
+ "bad user type on rvalue ({:?} = {:?}): {:?}",
+ annotation,
+ rv_ty,
+ terr
+ );
+ }
+ }
+
+ self.check_rvalue(body, rv, location);
+ if !self.unsized_feature_enabled() {
+ let trait_ref = ty::TraitRef {
+ def_id: tcx.require_lang_item(LangItem::Sized, Some(self.last_span)),
+ substs: tcx.mk_substs_trait(place_ty, &[]),
+ };
+ self.prove_trait_ref(
+ trait_ref,
+ location.to_locations(),
+ ConstraintCategory::SizedBound,
+ );
+ }
+ }
+ StatementKind::AscribeUserType(box (ref place, ref projection), variance) => {
+ let place_ty = place.ty(body, tcx).ty;
+ if let Err(terr) = self.relate_type_and_user_type(
+ place_ty,
+ variance,
+ projection,
+ Locations::All(stmt.source_info.span),
+ ConstraintCategory::TypeAnnotation,
+ ) {
+ let annotation = &self.user_type_annotations[projection.base];
+ span_mirbug!(
+ self,
+ stmt,
+ "bad type assert ({:?} <: {:?} with projections {:?}): {:?}",
+ place_ty,
+ annotation,
+ projection.projs,
+ terr
+ );
+ }
+ }
+ StatementKind::CopyNonOverlapping(box rustc_middle::mir::CopyNonOverlapping {
+ ..
+ }) => span_bug!(
+ stmt.source_info.span,
+ "Unexpected StatementKind::CopyNonOverlapping, should only appear after lowering_intrinsics",
+ ),
+ StatementKind::FakeRead(..)
+ | StatementKind::StorageLive(..)
+ | StatementKind::StorageDead(..)
+ | StatementKind::Retag { .. }
+ | StatementKind::Coverage(..)
+ | StatementKind::Nop => {}
+ StatementKind::Deinit(..) | StatementKind::SetDiscriminant { .. } => {
+ bug!("Statement not allowed in this MIR phase")
+ }
+ }
+ }
+
+ #[instrument(skip(self, body, term_location), level = "debug")]
+ fn check_terminator(
+ &mut self,
+ body: &Body<'tcx>,
+ term: &Terminator<'tcx>,
+ term_location: Location,
+ ) {
+ let tcx = self.tcx();
+ debug!("terminator kind: {:?}", term.kind);
+ match term.kind {
+ TerminatorKind::Goto { .. }
+ | TerminatorKind::Resume
+ | TerminatorKind::Abort
+ | TerminatorKind::Return
+ | TerminatorKind::GeneratorDrop
+ | TerminatorKind::Unreachable
+ | TerminatorKind::Drop { .. }
+ | TerminatorKind::FalseEdge { .. }
+ | TerminatorKind::FalseUnwind { .. }
+ | TerminatorKind::InlineAsm { .. } => {
+ // no checks needed for these
+ }
+
+ TerminatorKind::DropAndReplace { ref place, ref value, target: _, unwind: _ } => {
+ let place_ty = place.ty(body, tcx).ty;
+ let rv_ty = value.ty(body, tcx);
+
+ let locations = term_location.to_locations();
+ if let Err(terr) =
+ self.sub_types(rv_ty, place_ty, locations, ConstraintCategory::Assignment)
+ {
+ span_mirbug!(
+ self,
+ term,
+ "bad DropAndReplace ({:?} = {:?}): {:?}",
+ place_ty,
+ rv_ty,
+ terr
+ );
+ }
+ }
+ TerminatorKind::SwitchInt { ref discr, switch_ty, .. } => {
+ self.check_operand(discr, term_location);
+
+ let discr_ty = discr.ty(body, tcx);
+ if let Err(terr) = self.sub_types(
+ discr_ty,
+ switch_ty,
+ term_location.to_locations(),
+ ConstraintCategory::Assignment,
+ ) {
+ span_mirbug!(
+ self,
+ term,
+ "bad SwitchInt ({:?} on {:?}): {:?}",
+ switch_ty,
+ discr_ty,
+ terr
+ );
+ }
+ if !switch_ty.is_integral() && !switch_ty.is_char() && !switch_ty.is_bool() {
+ span_mirbug!(self, term, "bad SwitchInt discr ty {:?}", switch_ty);
+ }
+ // FIXME: check the values
+ }
+ TerminatorKind::Call {
+ ref func,
+ ref args,
+ ref destination,
+ from_hir_call,
+ target,
+ ..
+ } => {
+ self.check_operand(func, term_location);
+ for arg in args {
+ self.check_operand(arg, term_location);
+ }
+
+ let func_ty = func.ty(body, tcx);
+ debug!("func_ty.kind: {:?}", func_ty.kind());
+
+ let sig = match func_ty.kind() {
+ ty::FnDef(..) | ty::FnPtr(_) => func_ty.fn_sig(tcx),
+ _ => {
+ span_mirbug!(self, term, "call to non-function {:?}", func_ty);
+ return;
+ }
+ };
+ let (sig, map) = tcx.replace_late_bound_regions(sig, |br| {
+ self.infcx.next_region_var(LateBoundRegion(
+ term.source_info.span,
+ br.kind,
+ LateBoundRegionConversionTime::FnCall,
+ ))
+ });
+ debug!(?sig);
+ let sig = self.normalize(sig, term_location);
+ self.check_call_dest(body, term, &sig, *destination, target, term_location);
+
+ self.prove_predicates(
+ sig.inputs_and_output
+ .iter()
+ .map(|ty| ty::Binder::dummy(ty::PredicateKind::WellFormed(ty.into()))),
+ term_location.to_locations(),
+ ConstraintCategory::Boring,
+ );
+
+ // The ordinary liveness rules will ensure that all
+ // regions in the type of the callee are live here. We
+ // then further constrain the late-bound regions that
+ // were instantiated at the call site to be live as
+ // well. The resulting is that all the input (and
+ // output) types in the signature must be live, since
+ // all the inputs that fed into it were live.
+ for &late_bound_region in map.values() {
+ let region_vid =
+ self.borrowck_context.universal_regions.to_region_vid(late_bound_region);
+ self.borrowck_context
+ .constraints
+ .liveness_constraints
+ .add_element(region_vid, term_location);
+ }
+
+ self.check_call_inputs(body, term, &sig, args, term_location, from_hir_call);
+ }
+ TerminatorKind::Assert { ref cond, ref msg, .. } => {
+ self.check_operand(cond, term_location);
+
+ let cond_ty = cond.ty(body, tcx);
+ if cond_ty != tcx.types.bool {
+ span_mirbug!(self, term, "bad Assert ({:?}, not bool", cond_ty);
+ }
+
+ if let AssertKind::BoundsCheck { ref len, ref index } = *msg {
+ if len.ty(body, tcx) != tcx.types.usize {
+ span_mirbug!(self, len, "bounds-check length non-usize {:?}", len)
+ }
+ if index.ty(body, tcx) != tcx.types.usize {
+ span_mirbug!(self, index, "bounds-check index non-usize {:?}", index)
+ }
+ }
+ }
+ TerminatorKind::Yield { ref value, .. } => {
+ self.check_operand(value, term_location);
+
+ let value_ty = value.ty(body, tcx);
+ match body.yield_ty() {
+ None => span_mirbug!(self, term, "yield in non-generator"),
+ Some(ty) => {
+ if let Err(terr) = self.sub_types(
+ value_ty,
+ ty,
+ term_location.to_locations(),
+ ConstraintCategory::Yield,
+ ) {
+ span_mirbug!(
+ self,
+ term,
+ "type of yield value is {:?}, but the yield type is {:?}: {:?}",
+ value_ty,
+ ty,
+ terr
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fn check_call_dest(
+ &mut self,
+ body: &Body<'tcx>,
+ term: &Terminator<'tcx>,
+ sig: &ty::FnSig<'tcx>,
+ destination: Place<'tcx>,
+ target: Option<BasicBlock>,
+ term_location: Location,
+ ) {
+ let tcx = self.tcx();
+ match target {
+ Some(_) => {
+ let dest_ty = destination.ty(body, tcx).ty;
+ let dest_ty = self.normalize(dest_ty, term_location);
+ let category = match destination.as_local() {
+ Some(RETURN_PLACE) => {
+ if let BorrowCheckContext {
+ universal_regions:
+ UniversalRegions {
+ defining_ty:
+ DefiningTy::Const(def_id, _)
+ | DefiningTy::InlineConst(def_id, _),
+ ..
+ },
+ ..
+ } = self.borrowck_context
+ {
+ if tcx.is_static(*def_id) {
+ ConstraintCategory::UseAsStatic
+ } else {
+ ConstraintCategory::UseAsConst
+ }
+ } else {
+ ConstraintCategory::Return(ReturnConstraint::Normal)
+ }
+ }
+ Some(l) if !body.local_decls[l].is_user_variable() => {
+ ConstraintCategory::Boring
+ }
+ _ => ConstraintCategory::Assignment,
+ };
+
+ let locations = term_location.to_locations();
+
+ if let Err(terr) = self.sub_types(sig.output(), dest_ty, locations, category) {
+ span_mirbug!(
+ self,
+ term,
+ "call dest mismatch ({:?} <- {:?}): {:?}",
+ dest_ty,
+ sig.output(),
+ terr
+ );
+ }
+
+ // When `unsized_fn_params` and `unsized_locals` are both not enabled,
+ // this check is done at `check_local`.
+ if self.unsized_feature_enabled() {
+ let span = term.source_info.span;
+ self.ensure_place_sized(dest_ty, span);
+ }
+ }
+ None => {
+ if !self
+ .tcx()
+ .conservative_is_privately_uninhabited(self.param_env.and(sig.output()))
+ {
+ span_mirbug!(self, term, "call to converging function {:?} w/o dest", sig);
+ }
+ }
+ }
+ }
+
+ fn check_call_inputs(
+ &mut self,
+ body: &Body<'tcx>,
+ term: &Terminator<'tcx>,
+ sig: &ty::FnSig<'tcx>,
+ args: &[Operand<'tcx>],
+ term_location: Location,
+ from_hir_call: bool,
+ ) {
+ debug!("check_call_inputs({:?}, {:?})", sig, args);
+ if args.len() < sig.inputs().len() || (args.len() > sig.inputs().len() && !sig.c_variadic) {
+ span_mirbug!(self, term, "call to {:?} with wrong # of args", sig);
+ }
+
+ let func_ty = if let TerminatorKind::Call { func, .. } = &term.kind {
+ Some(func.ty(body, self.infcx.tcx))
+ } else {
+ None
+ };
+ debug!(?func_ty);
+
+ for (n, (fn_arg, op_arg)) in iter::zip(sig.inputs(), args).enumerate() {
+ let op_arg_ty = op_arg.ty(body, self.tcx());
+
+ let op_arg_ty = self.normalize(op_arg_ty, term_location);
+ let category = if from_hir_call {
+ ConstraintCategory::CallArgument(func_ty)
+ } else {
+ ConstraintCategory::Boring
+ };
+ if let Err(terr) =
+ self.sub_types(op_arg_ty, *fn_arg, term_location.to_locations(), category)
+ {
+ span_mirbug!(
+ self,
+ term,
+ "bad arg #{:?} ({:?} <- {:?}): {:?}",
+ n,
+ fn_arg,
+ op_arg_ty,
+ terr
+ );
+ }
+ }
+ }
+
+ fn check_iscleanup(&mut self, body: &Body<'tcx>, block_data: &BasicBlockData<'tcx>) {
+ let is_cleanup = block_data.is_cleanup;
+ self.last_span = block_data.terminator().source_info.span;
+ match block_data.terminator().kind {
+ TerminatorKind::Goto { target } => {
+ self.assert_iscleanup(body, block_data, target, is_cleanup)
+ }
+ TerminatorKind::SwitchInt { ref targets, .. } => {
+ for target in targets.all_targets() {
+ self.assert_iscleanup(body, block_data, *target, is_cleanup);
+ }
+ }
+ TerminatorKind::Resume => {
+ if !is_cleanup {
+ span_mirbug!(self, block_data, "resume on non-cleanup block!")
+ }
+ }
+ TerminatorKind::Abort => {
+ if !is_cleanup {
+ span_mirbug!(self, block_data, "abort on non-cleanup block!")
+ }
+ }
+ TerminatorKind::Return => {
+ if is_cleanup {
+ span_mirbug!(self, block_data, "return on cleanup block")
+ }
+ }
+ TerminatorKind::GeneratorDrop { .. } => {
+ if is_cleanup {
+ span_mirbug!(self, block_data, "generator_drop in cleanup block")
+ }
+ }
+ TerminatorKind::Yield { resume, drop, .. } => {
+ if is_cleanup {
+ span_mirbug!(self, block_data, "yield in cleanup block")
+ }
+ self.assert_iscleanup(body, block_data, resume, is_cleanup);
+ if let Some(drop) = drop {
+ self.assert_iscleanup(body, block_data, drop, is_cleanup);
+ }
+ }
+ TerminatorKind::Unreachable => {}
+ TerminatorKind::Drop { target, unwind, .. }
+ | TerminatorKind::DropAndReplace { target, unwind, .. }
+ | TerminatorKind::Assert { target, cleanup: unwind, .. } => {
+ self.assert_iscleanup(body, block_data, target, is_cleanup);
+ if let Some(unwind) = unwind {
+ if is_cleanup {
+ span_mirbug!(self, block_data, "unwind on cleanup block")
+ }
+ self.assert_iscleanup(body, block_data, unwind, true);
+ }
+ }
+ TerminatorKind::Call { ref target, cleanup, .. } => {
+ if let &Some(target) = target {
+ self.assert_iscleanup(body, block_data, target, is_cleanup);
+ }
+ if let Some(cleanup) = cleanup {
+ if is_cleanup {
+ span_mirbug!(self, block_data, "cleanup on cleanup block")
+ }
+ self.assert_iscleanup(body, block_data, cleanup, true);
+ }
+ }
+ TerminatorKind::FalseEdge { real_target, imaginary_target } => {
+ self.assert_iscleanup(body, block_data, real_target, is_cleanup);
+ self.assert_iscleanup(body, block_data, imaginary_target, is_cleanup);
+ }
+ TerminatorKind::FalseUnwind { real_target, unwind } => {
+ self.assert_iscleanup(body, block_data, real_target, is_cleanup);
+ if let Some(unwind) = unwind {
+ if is_cleanup {
+ span_mirbug!(self, block_data, "cleanup in cleanup block via false unwind");
+ }
+ self.assert_iscleanup(body, block_data, unwind, true);
+ }
+ }
+ TerminatorKind::InlineAsm { destination, cleanup, .. } => {
+ if let Some(target) = destination {
+ self.assert_iscleanup(body, block_data, target, is_cleanup);
+ }
+ if let Some(cleanup) = cleanup {
+ if is_cleanup {
+ span_mirbug!(self, block_data, "cleanup on cleanup block")
+ }
+ self.assert_iscleanup(body, block_data, cleanup, true);
+ }
+ }
+ }
+ }
+
+ fn assert_iscleanup(
+ &mut self,
+ body: &Body<'tcx>,
+ ctxt: &dyn fmt::Debug,
+ bb: BasicBlock,
+ iscleanuppad: bool,
+ ) {
+ if body[bb].is_cleanup != iscleanuppad {
+ span_mirbug!(self, ctxt, "cleanuppad mismatch: {:?} should be {:?}", bb, iscleanuppad);
+ }
+ }
+
+ fn check_local(&mut self, body: &Body<'tcx>, local: Local, local_decl: &LocalDecl<'tcx>) {
+ match body.local_kind(local) {
+ LocalKind::ReturnPointer | LocalKind::Arg => {
+ // return values of normal functions are required to be
+ // sized by typeck, but return values of ADT constructors are
+ // not because we don't include a `Self: Sized` bounds on them.
+ //
+ // Unbound parts of arguments were never required to be Sized
+ // - maybe we should make that a warning.
+ return;
+ }
+ LocalKind::Var | LocalKind::Temp => {}
+ }
+
+ // When `unsized_fn_params` or `unsized_locals` is enabled, only function calls
+ // and nullary ops are checked in `check_call_dest`.
+ if !self.unsized_feature_enabled() {
+ let span = local_decl.source_info.span;
+ let ty = local_decl.ty;
+ self.ensure_place_sized(ty, span);
+ }
+ }
+
+ fn ensure_place_sized(&mut self, ty: Ty<'tcx>, span: Span) {
+ let tcx = self.tcx();
+
+ // Erase the regions from `ty` to get a global type. The
+ // `Sized` bound in no way depends on precise regions, so this
+ // shouldn't affect `is_sized`.
+ let erased_ty = tcx.erase_regions(ty);
+ if !erased_ty.is_sized(tcx.at(span), self.param_env) {
+ // in current MIR construction, all non-control-flow rvalue
+ // expressions evaluate through `as_temp` or `into` a return
+ // slot or local, so to find all unsized rvalues it is enough
+ // to check all temps, return slots and locals.
+ if self.reported_errors.replace((ty, span)).is_none() {
+ // While this is located in `nll::typeck` this error is not
+ // an NLL error, it's a required check to prevent creation
+ // of unsized rvalues in a call expression.
+ self.tcx().sess.emit_err(MoveUnsized { ty, span });
+ }
+ }
+ }
+
+ fn aggregate_field_ty(
+ &mut self,
+ ak: &AggregateKind<'tcx>,
+ field_index: usize,
+ location: Location,
+ ) -> Result<Ty<'tcx>, FieldAccessError> {
+ let tcx = self.tcx();
+
+ match *ak {
+ AggregateKind::Adt(adt_did, variant_index, substs, _, active_field_index) => {
+ let def = tcx.adt_def(adt_did);
+ let variant = &def.variant(variant_index);
+ let adj_field_index = active_field_index.unwrap_or(field_index);
+ if let Some(field) = variant.fields.get(adj_field_index) {
+ Ok(self.normalize(field.ty(tcx, substs), location))
+ } else {
+ Err(FieldAccessError::OutOfRange { field_count: variant.fields.len() })
+ }
+ }
+ AggregateKind::Closure(_, substs) => {
+ match substs.as_closure().upvar_tys().nth(field_index) {
+ Some(ty) => Ok(ty),
+ None => Err(FieldAccessError::OutOfRange {
+ field_count: substs.as_closure().upvar_tys().count(),
+ }),
+ }
+ }
+ AggregateKind::Generator(_, substs, _) => {
+ // It doesn't make sense to look at a field beyond the prefix;
+ // these require a variant index, and are not initialized in
+ // aggregate rvalues.
+ match substs.as_generator().prefix_tys().nth(field_index) {
+ Some(ty) => Ok(ty),
+ None => Err(FieldAccessError::OutOfRange {
+ field_count: substs.as_generator().prefix_tys().count(),
+ }),
+ }
+ }
+ AggregateKind::Array(ty) => Ok(ty),
+ AggregateKind::Tuple => {
+ unreachable!("This should have been covered in check_rvalues");
+ }
+ }
+ }
+
+ fn check_operand(&mut self, op: &Operand<'tcx>, location: Location) {
+ if let Operand::Constant(constant) = op {
+ let maybe_uneval = match constant.literal {
+ ConstantKind::Ty(ct) => match ct.kind() {
+ ty::ConstKind::Unevaluated(uv) => Some(uv),
+ _ => None,
+ },
+ _ => None,
+ };
+ if let Some(uv) = maybe_uneval {
+ if uv.promoted.is_none() {
+ let tcx = self.tcx();
+ let def_id = uv.def.def_id_for_type_of();
+ if tcx.def_kind(def_id) == DefKind::InlineConst {
+ let def_id = def_id.expect_local();
+ let predicates =
+ self.prove_closure_bounds(tcx, def_id, uv.substs, location);
+ self.normalize_and_prove_instantiated_predicates(
+ def_id.to_def_id(),
+ predicates,
+ location.to_locations(),
+ );
+ }
+ }
+ }
+ }
+ }
+
+ #[instrument(skip(self, body), level = "debug")]
+ fn check_rvalue(&mut self, body: &Body<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) {
+ let tcx = self.tcx();
+
+ match rvalue {
+ Rvalue::Aggregate(ak, ops) => {
+ for op in ops {
+ self.check_operand(op, location);
+ }
+ self.check_aggregate_rvalue(&body, rvalue, ak, ops, location)
+ }
+
+ Rvalue::Repeat(operand, len) => {
+ self.check_operand(operand, location);
+
+ // If the length cannot be evaluated we must assume that the length can be larger
+ // than 1.
+ // If the length is larger than 1, the repeat expression will need to copy the
+ // element, so we require the `Copy` trait.
+ if len.try_eval_usize(tcx, self.param_env).map_or(true, |len| len > 1) {
+ match operand {
+ Operand::Copy(..) | Operand::Constant(..) => {
+ // These are always okay: direct use of a const, or a value that can evidently be copied.
+ }
+ Operand::Move(place) => {
+ // Make sure that repeated elements implement `Copy`.
+ let span = body.source_info(location).span;
+ let ty = place.ty(body, tcx).ty;
+ let trait_ref = ty::TraitRef::new(
+ tcx.require_lang_item(LangItem::Copy, Some(span)),
+ tcx.mk_substs_trait(ty, &[]),
+ );
+
+ self.prove_trait_ref(
+ trait_ref,
+ Locations::Single(location),
+ ConstraintCategory::CopyBound,
+ );
+ }
+ }
+ }
+ }
+
+ &Rvalue::NullaryOp(_, ty) => {
+ let trait_ref = ty::TraitRef {
+ def_id: tcx.require_lang_item(LangItem::Sized, Some(self.last_span)),
+ substs: tcx.mk_substs_trait(ty, &[]),
+ };
+
+ self.prove_trait_ref(
+ trait_ref,
+ location.to_locations(),
+ ConstraintCategory::SizedBound,
+ );
+ }
+
+ Rvalue::ShallowInitBox(operand, ty) => {
+ self.check_operand(operand, location);
+
+ let trait_ref = ty::TraitRef {
+ def_id: tcx.require_lang_item(LangItem::Sized, Some(self.last_span)),
+ substs: tcx.mk_substs_trait(*ty, &[]),
+ };
+
+ self.prove_trait_ref(
+ trait_ref,
+ location.to_locations(),
+ ConstraintCategory::SizedBound,
+ );
+ }
+
+ Rvalue::Cast(cast_kind, op, ty) => {
+ self.check_operand(op, location);
+
+ match cast_kind {
+ CastKind::Pointer(PointerCast::ReifyFnPointer) => {
+ let fn_sig = op.ty(body, tcx).fn_sig(tcx);
+
+ // The type that we see in the fcx is like
+ // `foo::<'a, 'b>`, where `foo` is the path to a
+ // function definition. When we extract the
+ // signature, it comes from the `fn_sig` query,
+ // and hence may contain unnormalized results.
+ let fn_sig = self.normalize(fn_sig, location);
+
+ let ty_fn_ptr_from = tcx.mk_fn_ptr(fn_sig);
+
+ if let Err(terr) = self.eq_types(
+ *ty,
+ ty_fn_ptr_from,
+ location.to_locations(),
+ ConstraintCategory::Cast,
+ ) {
+ span_mirbug!(
+ self,
+ rvalue,
+ "equating {:?} with {:?} yields {:?}",
+ ty_fn_ptr_from,
+ ty,
+ terr
+ );
+ }
+ }
+
+ CastKind::Pointer(PointerCast::ClosureFnPointer(unsafety)) => {
+ let sig = match op.ty(body, tcx).kind() {
+ ty::Closure(_, substs) => substs.as_closure().sig(),
+ _ => bug!(),
+ };
+ let ty_fn_ptr_from = tcx.mk_fn_ptr(tcx.signature_unclosure(sig, *unsafety));
+
+ if let Err(terr) = self.eq_types(
+ *ty,
+ ty_fn_ptr_from,
+ location.to_locations(),
+ ConstraintCategory::Cast,
+ ) {
+ span_mirbug!(
+ self,
+ rvalue,
+ "equating {:?} with {:?} yields {:?}",
+ ty_fn_ptr_from,
+ ty,
+ terr
+ );
+ }
+ }
+
+ CastKind::Pointer(PointerCast::UnsafeFnPointer) => {
+ let fn_sig = op.ty(body, tcx).fn_sig(tcx);
+
+ // The type that we see in the fcx is like
+ // `foo::<'a, 'b>`, where `foo` is the path to a
+ // function definition. When we extract the
+ // signature, it comes from the `fn_sig` query,
+ // and hence may contain unnormalized results.
+ let fn_sig = self.normalize(fn_sig, location);
+
+ let ty_fn_ptr_from = tcx.safe_to_unsafe_fn_ty(fn_sig);
+
+ if let Err(terr) = self.eq_types(
+ *ty,
+ ty_fn_ptr_from,
+ location.to_locations(),
+ ConstraintCategory::Cast,
+ ) {
+ span_mirbug!(
+ self,
+ rvalue,
+ "equating {:?} with {:?} yields {:?}",
+ ty_fn_ptr_from,
+ ty,
+ terr
+ );
+ }
+ }
+
+ CastKind::Pointer(PointerCast::Unsize) => {
+ let &ty = ty;
+ let trait_ref = ty::TraitRef {
+ def_id: tcx
+ .require_lang_item(LangItem::CoerceUnsized, Some(self.last_span)),
+ substs: tcx.mk_substs_trait(op.ty(body, tcx), &[ty.into()]),
+ };
+
+ self.prove_trait_ref(
+ trait_ref,
+ location.to_locations(),
+ ConstraintCategory::Cast,
+ );
+ }
+
+ CastKind::Pointer(PointerCast::MutToConstPointer) => {
+ let ty::RawPtr(ty::TypeAndMut {
+ ty: ty_from,
+ mutbl: hir::Mutability::Mut,
+ }) = op.ty(body, tcx).kind() else {
+ span_mirbug!(
+ self,
+ rvalue,
+ "unexpected base type for cast {:?}",
+ ty,
+ );
+ return;
+ };
+ let ty::RawPtr(ty::TypeAndMut {
+ ty: ty_to,
+ mutbl: hir::Mutability::Not,
+ }) = ty.kind() else {
+ span_mirbug!(
+ self,
+ rvalue,
+ "unexpected target type for cast {:?}",
+ ty,
+ );
+ return;
+ };
+ if let Err(terr) = self.sub_types(
+ *ty_from,
+ *ty_to,
+ location.to_locations(),
+ ConstraintCategory::Cast,
+ ) {
+ span_mirbug!(
+ self,
+ rvalue,
+ "relating {:?} with {:?} yields {:?}",
+ ty_from,
+ ty_to,
+ terr
+ );
+ }
+ }
+
+ CastKind::Pointer(PointerCast::ArrayToPointer) => {
+ let ty_from = op.ty(body, tcx);
+
+ let opt_ty_elem_mut = match ty_from.kind() {
+ ty::RawPtr(ty::TypeAndMut { mutbl: array_mut, ty: array_ty }) => {
+ match array_ty.kind() {
+ ty::Array(ty_elem, _) => Some((ty_elem, *array_mut)),
+ _ => None,
+ }
+ }
+ _ => None,
+ };
+
+ let Some((ty_elem, ty_mut)) = opt_ty_elem_mut else {
+ span_mirbug!(
+ self,
+ rvalue,
+ "ArrayToPointer cast from unexpected type {:?}",
+ ty_from,
+ );
+ return;
+ };
+
+ let (ty_to, ty_to_mut) = match ty.kind() {
+ ty::RawPtr(ty::TypeAndMut { mutbl: ty_to_mut, ty: ty_to }) => {
+ (ty_to, *ty_to_mut)
+ }
+ _ => {
+ span_mirbug!(
+ self,
+ rvalue,
+ "ArrayToPointer cast to unexpected type {:?}",
+ ty,
+ );
+ return;
+ }
+ };
+
+ if ty_to_mut == Mutability::Mut && ty_mut == Mutability::Not {
+ span_mirbug!(
+ self,
+ rvalue,
+ "ArrayToPointer cast from const {:?} to mut {:?}",
+ ty,
+ ty_to
+ );
+ return;
+ }
+
+ if let Err(terr) = self.sub_types(
+ *ty_elem,
+ *ty_to,
+ location.to_locations(),
+ ConstraintCategory::Cast,
+ ) {
+ span_mirbug!(
+ self,
+ rvalue,
+ "relating {:?} with {:?} yields {:?}",
+ ty_elem,
+ ty_to,
+ terr
+ )
+ }
+ }
+
+ CastKind::PointerExposeAddress => {
+ let ty_from = op.ty(body, tcx);
+ let cast_ty_from = CastTy::from_ty(ty_from);
+ let cast_ty_to = CastTy::from_ty(*ty);
+ match (cast_ty_from, cast_ty_to) {
+ (Some(CastTy::Ptr(_) | CastTy::FnPtr), Some(CastTy::Int(_))) => (),
+ _ => {
+ span_mirbug!(
+ self,
+ rvalue,
+ "Invalid PointerExposeAddress cast {:?} -> {:?}",
+ ty_from,
+ ty
+ )
+ }
+ }
+ }
+
+ CastKind::PointerFromExposedAddress => {
+ let ty_from = op.ty(body, tcx);
+ let cast_ty_from = CastTy::from_ty(ty_from);
+ let cast_ty_to = CastTy::from_ty(*ty);
+ match (cast_ty_from, cast_ty_to) {
+ (Some(CastTy::Int(_)), Some(CastTy::Ptr(_))) => (),
+ _ => {
+ span_mirbug!(
+ self,
+ rvalue,
+ "Invalid PointerFromExposedAddress cast {:?} -> {:?}",
+ ty_from,
+ ty
+ )
+ }
+ }
+ }
+
+ CastKind::Misc => {
+ let ty_from = op.ty(body, tcx);
+ let cast_ty_from = CastTy::from_ty(ty_from);
+ let cast_ty_to = CastTy::from_ty(*ty);
+ // Misc casts are either between floats and ints, or one ptr type to another.
+ match (cast_ty_from, cast_ty_to) {
+ (
+ Some(CastTy::Int(_) | CastTy::Float),
+ Some(CastTy::Int(_) | CastTy::Float),
+ )
+ | (Some(CastTy::Ptr(_) | CastTy::FnPtr), Some(CastTy::Ptr(_))) => (),
+ _ => {
+ span_mirbug!(
+ self,
+ rvalue,
+ "Invalid Misc cast {:?} -> {:?}",
+ ty_from,
+ ty,
+ )
+ }
+ }
+ }
+ }
+ }
+
+ Rvalue::Ref(region, _borrow_kind, borrowed_place) => {
+ self.add_reborrow_constraint(&body, location, *region, borrowed_place);
+ }
+
+ Rvalue::BinaryOp(
+ BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge,
+ box (left, right),
+ ) => {
+ self.check_operand(left, location);
+ self.check_operand(right, location);
+
+ let ty_left = left.ty(body, tcx);
+ match ty_left.kind() {
+ // Types with regions are comparable if they have a common super-type.
+ ty::RawPtr(_) | ty::FnPtr(_) => {
+ let ty_right = right.ty(body, tcx);
+ let common_ty = self.infcx.next_ty_var(TypeVariableOrigin {
+ kind: TypeVariableOriginKind::MiscVariable,
+ span: body.source_info(location).span,
+ });
+ self.sub_types(
+ ty_left,
+ common_ty,
+ location.to_locations(),
+ ConstraintCategory::Boring,
+ )
+ .unwrap_or_else(|err| {
+ bug!("Could not equate type variable with {:?}: {:?}", ty_left, err)
+ });
+ if let Err(terr) = self.sub_types(
+ ty_right,
+ common_ty,
+ location.to_locations(),
+ ConstraintCategory::Boring,
+ ) {
+ span_mirbug!(
+ self,
+ rvalue,
+ "unexpected comparison types {:?} and {:?} yields {:?}",
+ ty_left,
+ ty_right,
+ terr
+ )
+ }
+ }
+ // For types with no regions we can just check that the
+ // both operands have the same type.
+ ty::Int(_) | ty::Uint(_) | ty::Bool | ty::Char | ty::Float(_)
+ if ty_left == right.ty(body, tcx) => {}
+ // Other types are compared by trait methods, not by
+ // `Rvalue::BinaryOp`.
+ _ => span_mirbug!(
+ self,
+ rvalue,
+ "unexpected comparison types {:?} and {:?}",
+ ty_left,
+ right.ty(body, tcx)
+ ),
+ }
+ }
+
+ Rvalue::Use(operand) | Rvalue::UnaryOp(_, operand) => {
+ self.check_operand(operand, location);
+ }
+ Rvalue::CopyForDeref(place) => {
+ let op = &Operand::Copy(*place);
+ self.check_operand(op, location);
+ }
+
+ Rvalue::BinaryOp(_, box (left, right))
+ | Rvalue::CheckedBinaryOp(_, box (left, right)) => {
+ self.check_operand(left, location);
+ self.check_operand(right, location);
+ }
+
+ Rvalue::AddressOf(..)
+ | Rvalue::ThreadLocalRef(..)
+ | Rvalue::Len(..)
+ | Rvalue::Discriminant(..) => {}
+ }
+ }
+
+ /// If this rvalue supports a user-given type annotation, then
+ /// extract and return it. This represents the final type of the
+ /// rvalue and will be unified with the inferred type.
+ fn rvalue_user_ty(&self, rvalue: &Rvalue<'tcx>) -> Option<UserTypeAnnotationIndex> {
+ match rvalue {
+ Rvalue::Use(_)
+ | Rvalue::ThreadLocalRef(_)
+ | Rvalue::Repeat(..)
+ | Rvalue::Ref(..)
+ | Rvalue::AddressOf(..)
+ | Rvalue::Len(..)
+ | Rvalue::Cast(..)
+ | Rvalue::ShallowInitBox(..)
+ | Rvalue::BinaryOp(..)
+ | Rvalue::CheckedBinaryOp(..)
+ | Rvalue::NullaryOp(..)
+ | Rvalue::CopyForDeref(..)
+ | Rvalue::UnaryOp(..)
+ | Rvalue::Discriminant(..) => None,
+
+ Rvalue::Aggregate(aggregate, _) => match **aggregate {
+ AggregateKind::Adt(_, _, _, user_ty, _) => user_ty,
+ AggregateKind::Array(_) => None,
+ AggregateKind::Tuple => None,
+ AggregateKind::Closure(_, _) => None,
+ AggregateKind::Generator(_, _, _) => None,
+ },
+ }
+ }
+
+ fn check_aggregate_rvalue(
+ &mut self,
+ body: &Body<'tcx>,
+ rvalue: &Rvalue<'tcx>,
+ aggregate_kind: &AggregateKind<'tcx>,
+ operands: &[Operand<'tcx>],
+ location: Location,
+ ) {
+ let tcx = self.tcx();
+
+ self.prove_aggregate_predicates(aggregate_kind, location);
+
+ if *aggregate_kind == AggregateKind::Tuple {
+ // tuple rvalue field type is always the type of the op. Nothing to check here.
+ return;
+ }
+
+ for (i, operand) in operands.iter().enumerate() {
+ let field_ty = match self.aggregate_field_ty(aggregate_kind, i, location) {
+ Ok(field_ty) => field_ty,
+ Err(FieldAccessError::OutOfRange { field_count }) => {
+ span_mirbug!(
+ self,
+ rvalue,
+ "accessed field #{} but variant only has {}",
+ i,
+ field_count
+ );
+ continue;
+ }
+ };
+ let operand_ty = operand.ty(body, tcx);
+ let operand_ty = self.normalize(operand_ty, location);
+
+ if let Err(terr) = self.sub_types(
+ operand_ty,
+ field_ty,
+ location.to_locations(),
+ ConstraintCategory::Boring,
+ ) {
+ span_mirbug!(
+ self,
+ rvalue,
+ "{:?} is not a subtype of {:?}: {:?}",
+ operand_ty,
+ field_ty,
+ terr
+ );
+ }
+ }
+ }
+
+ /// Adds the constraints that arise from a borrow expression `&'a P` at the location `L`.
+ ///
+ /// # Parameters
+ ///
+ /// - `location`: the location `L` where the borrow expression occurs
+ /// - `borrow_region`: the region `'a` associated with the borrow
+ /// - `borrowed_place`: the place `P` being borrowed
+ fn add_reborrow_constraint(
+ &mut self,
+ body: &Body<'tcx>,
+ location: Location,
+ borrow_region: ty::Region<'tcx>,
+ borrowed_place: &Place<'tcx>,
+ ) {
+ // These constraints are only meaningful during borrowck:
+ let BorrowCheckContext { borrow_set, location_table, all_facts, constraints, .. } =
+ self.borrowck_context;
+
+ // In Polonius mode, we also push a `loan_issued_at` fact
+ // linking the loan to the region (in some cases, though,
+ // there is no loan associated with this borrow expression --
+ // that occurs when we are borrowing an unsafe place, for
+ // example).
+ if let Some(all_facts) = all_facts {
+ let _prof_timer = self.infcx.tcx.prof.generic_activity("polonius_fact_generation");
+ if let Some(borrow_index) = borrow_set.get_index_of(&location) {
+ let region_vid = borrow_region.to_region_vid();
+ all_facts.loan_issued_at.push((
+ region_vid,
+ borrow_index,
+ location_table.mid_index(location),
+ ));
+ }
+ }
+
+ // If we are reborrowing the referent of another reference, we
+ // need to add outlives relationships. In a case like `&mut
+ // *p`, where the `p` has type `&'b mut Foo`, for example, we
+ // need to ensure that `'b: 'a`.
+
+ debug!(
+ "add_reborrow_constraint({:?}, {:?}, {:?})",
+ location, borrow_region, borrowed_place
+ );
+
+ let mut cursor = borrowed_place.projection.as_ref();
+ let tcx = self.infcx.tcx;
+ let field = path_utils::is_upvar_field_projection(
+ tcx,
+ &self.borrowck_context.upvars,
+ borrowed_place.as_ref(),
+ body,
+ );
+ let category = if let Some(field) = field {
+ ConstraintCategory::ClosureUpvar(field)
+ } else {
+ ConstraintCategory::Boring
+ };
+
+ while let [proj_base @ .., elem] = cursor {
+ cursor = proj_base;
+
+ debug!("add_reborrow_constraint - iteration {:?}", elem);
+
+ match elem {
+ ProjectionElem::Deref => {
+ let base_ty = Place::ty_from(borrowed_place.local, proj_base, body, tcx).ty;
+
+ debug!("add_reborrow_constraint - base_ty = {:?}", base_ty);
+ match base_ty.kind() {
+ ty::Ref(ref_region, _, mutbl) => {
+ constraints.outlives_constraints.push(OutlivesConstraint {
+ sup: ref_region.to_region_vid(),
+ sub: borrow_region.to_region_vid(),
+ locations: location.to_locations(),
+ span: location.to_locations().span(body),
+ category,
+ variance_info: ty::VarianceDiagInfo::default(),
+ });
+
+ match mutbl {
+ hir::Mutability::Not => {
+ // Immutable reference. We don't need the base
+ // to be valid for the entire lifetime of
+ // the borrow.
+ break;
+ }
+ hir::Mutability::Mut => {
+ // Mutable reference. We *do* need the base
+ // to be valid, because after the base becomes
+ // invalid, someone else can use our mutable deref.
+
+ // This is in order to make the following function
+ // illegal:
+ // ```
+ // fn unsafe_deref<'a, 'b>(x: &'a &'b mut T) -> &'b mut T {
+ // &mut *x
+ // }
+ // ```
+ //
+ // As otherwise you could clone `&mut T` using the
+ // following function:
+ // ```
+ // fn bad(x: &mut T) -> (&mut T, &mut T) {
+ // let my_clone = unsafe_deref(&'a x);
+ // ENDREGION 'a;
+ // (my_clone, x)
+ // }
+ // ```
+ }
+ }
+ }
+ ty::RawPtr(..) => {
+ // deref of raw pointer, guaranteed to be valid
+ break;
+ }
+ ty::Adt(def, _) if def.is_box() => {
+ // deref of `Box`, need the base to be valid - propagate
+ }
+ _ => bug!("unexpected deref ty {:?} in {:?}", base_ty, borrowed_place),
+ }
+ }
+ ProjectionElem::Field(..)
+ | ProjectionElem::Downcast(..)
+ | ProjectionElem::Index(..)
+ | ProjectionElem::ConstantIndex { .. }
+ | ProjectionElem::Subslice { .. } => {
+ // other field access
+ }
+ }
+ }
+ }
+
+ fn prove_aggregate_predicates(
+ &mut self,
+ aggregate_kind: &AggregateKind<'tcx>,
+ location: Location,
+ ) {
+ let tcx = self.tcx();
+
+ debug!(
+ "prove_aggregate_predicates(aggregate_kind={:?}, location={:?})",
+ aggregate_kind, location
+ );
+
+ let (def_id, instantiated_predicates) = match *aggregate_kind {
+ AggregateKind::Adt(adt_did, _, substs, _, _) => {
+ (adt_did, tcx.predicates_of(adt_did).instantiate(tcx, substs))
+ }
+
+ // For closures, we have some **extra requirements** we
+ //
+ // have to check. In particular, in their upvars and
+ // signatures, closures often reference various regions
+ // from the surrounding function -- we call those the
+ // closure's free regions. When we borrow-check (and hence
+ // region-check) closures, we may find that the closure
+ // requires certain relationships between those free
+ // regions. However, because those free regions refer to
+ // portions of the CFG of their caller, the closure is not
+ // in a position to verify those relationships. In that
+ // case, the requirements get "propagated" to us, and so
+ // we have to solve them here where we instantiate the
+ // closure.
+ //
+ // Despite the opacity of the previous paragraph, this is
+ // actually relatively easy to understand in terms of the
+ // desugaring. A closure gets desugared to a struct, and
+ // these extra requirements are basically like where
+ // clauses on the struct.
+ AggregateKind::Closure(def_id, substs)
+ | AggregateKind::Generator(def_id, substs, _) => {
+ (def_id.to_def_id(), self.prove_closure_bounds(tcx, def_id, substs, location))
+ }
+
+ AggregateKind::Array(_) | AggregateKind::Tuple => {
+ (CRATE_DEF_ID.to_def_id(), ty::InstantiatedPredicates::empty())
+ }
+ };
+
+ self.normalize_and_prove_instantiated_predicates(
+ def_id,
+ instantiated_predicates,
+ location.to_locations(),
+ );
+ }
+
+ fn prove_closure_bounds(
+ &mut self,
+ tcx: TyCtxt<'tcx>,
+ def_id: LocalDefId,
+ substs: SubstsRef<'tcx>,
+ location: Location,
+ ) -> ty::InstantiatedPredicates<'tcx> {
+ if let Some(ref closure_region_requirements) = tcx.mir_borrowck(def_id).closure_requirements
+ {
+ let closure_constraints = QueryRegionConstraints {
+ outlives: closure_region_requirements.apply_requirements(
+ tcx,
+ def_id.to_def_id(),
+ substs,
+ ),
+
+ // Presently, closures never propagate member
+ // constraints to their parents -- they are enforced
+ // locally. This is largely a non-issue as member
+ // constraints only come from `-> impl Trait` and
+ // friends which don't appear (thus far...) in
+ // closures.
+ member_constraints: vec![],
+ };
+
+ let bounds_mapping = closure_constraints
+ .outlives
+ .iter()
+ .enumerate()
+ .filter_map(|(idx, constraint)| {
+ let ty::OutlivesPredicate(k1, r2) =
+ constraint.no_bound_vars().unwrap_or_else(|| {
+ bug!("query_constraint {:?} contained bound vars", constraint,);
+ });
+
+ match k1.unpack() {
+ GenericArgKind::Lifetime(r1) => {
+ // constraint is r1: r2
+ let r1_vid = self.borrowck_context.universal_regions.to_region_vid(r1);
+ let r2_vid = self.borrowck_context.universal_regions.to_region_vid(r2);
+ let outlives_requirements =
+ &closure_region_requirements.outlives_requirements[idx];
+ Some((
+ (r1_vid, r2_vid),
+ (outlives_requirements.category, outlives_requirements.blame_span),
+ ))
+ }
+ GenericArgKind::Type(_) | GenericArgKind::Const(_) => None,
+ }
+ })
+ .collect();
+
+ let existing = self
+ .borrowck_context
+ .constraints
+ .closure_bounds_mapping
+ .insert(location, bounds_mapping);
+ assert!(existing.is_none(), "Multiple closures at the same location.");
+
+ self.push_region_constraints(
+ location.to_locations(),
+ ConstraintCategory::ClosureBounds,
+ &closure_constraints,
+ );
+ }
+
+ // Now equate closure substs to regions inherited from `typeck_root_def_id`. Fixes #98589.
+ let typeck_root_def_id = tcx.typeck_root_def_id(self.body.source.def_id());
+ let typeck_root_substs = ty::InternalSubsts::identity_for_item(tcx, typeck_root_def_id);
+
+ let parent_substs = match tcx.def_kind(def_id) {
+ DefKind::Closure => substs.as_closure().parent_substs(),
+ DefKind::Generator => substs.as_generator().parent_substs(),
+ DefKind::InlineConst => substs.as_inline_const().parent_substs(),
+ other => bug!("unexpected item {:?}", other),
+ };
+ let parent_substs = tcx.mk_substs(parent_substs.iter());
+
+ assert_eq!(typeck_root_substs.len(), parent_substs.len());
+ if let Err(_) = self.eq_substs(
+ typeck_root_substs,
+ parent_substs,
+ location.to_locations(),
+ ConstraintCategory::BoringNoLocation,
+ ) {
+ span_mirbug!(
+ self,
+ def_id,
+ "could not relate closure to parent {:?} != {:?}",
+ typeck_root_substs,
+ parent_substs
+ );
+ }
+
+ tcx.predicates_of(def_id).instantiate(tcx, substs)
+ }
+
+ #[instrument(skip(self, body), level = "debug")]
+ fn typeck_mir(&mut self, body: &Body<'tcx>) {
+ self.last_span = body.span;
+ debug!(?body.span);
+
+ for (local, local_decl) in body.local_decls.iter_enumerated() {
+ self.check_local(&body, local, local_decl);
+ }
+
+ for (block, block_data) in body.basic_blocks().iter_enumerated() {
+ let mut location = Location { block, statement_index: 0 };
+ for stmt in &block_data.statements {
+ if !stmt.source_info.span.is_dummy() {
+ self.last_span = stmt.source_info.span;
+ }
+ self.check_stmt(body, stmt, location);
+ location.statement_index += 1;
+ }
+
+ self.check_terminator(&body, block_data.terminator(), location);
+ self.check_iscleanup(&body, block_data);
+ }
+ }
+}
+
+trait NormalizeLocation: fmt::Debug + Copy {
+ fn to_locations(self) -> Locations;
+}
+
+impl NormalizeLocation for Locations {
+ fn to_locations(self) -> Locations {
+ self
+ }
+}
+
+impl NormalizeLocation for Location {
+ fn to_locations(self) -> Locations {
+ Locations::Single(self)
+ }
+}
+
+/// Runs `infcx.instantiate_opaque_types`. Unlike other `TypeOp`s,
+/// this is not canonicalized - it directly affects the main `InferCtxt`
+/// that we use during MIR borrowchecking.
+#[derive(Debug)]
+pub(super) struct InstantiateOpaqueType<'tcx> {
+ pub base_universe: Option<ty::UniverseIndex>,
+ pub region_constraints: Option<RegionConstraintData<'tcx>>,
+ pub obligations: Vec<PredicateObligation<'tcx>>,
+}
+
+impl<'tcx> TypeOp<'tcx> for InstantiateOpaqueType<'tcx> {
+ type Output = ();
+ /// We use this type itself to store the information used
+ /// when reporting errors. Since this is not a query, we don't
+ /// re-run anything during error reporting - we just use the information
+ /// we saved to help extract an error from the already-existing region
+ /// constraints in our `InferCtxt`
+ type ErrorInfo = InstantiateOpaqueType<'tcx>;
+
+ fn fully_perform(mut self, infcx: &InferCtxt<'_, 'tcx>) -> Fallible<TypeOpOutput<'tcx, Self>> {
+ let (mut output, region_constraints) = scrape_region_constraints(infcx, || {
+ Ok(InferOk { value: (), obligations: self.obligations.clone() })
+ })?;
+ self.region_constraints = Some(region_constraints);
+ output.error_info = Some(self);
+ Ok(output)
+ }
+}
diff --git a/compiler/rustc_borrowck/src/type_check/relate_tys.rs b/compiler/rustc_borrowck/src/type_check/relate_tys.rs
new file mode 100644
index 000000000..c97a6a1a6
--- /dev/null
+++ b/compiler/rustc_borrowck/src/type_check/relate_tys.rs
@@ -0,0 +1,187 @@
+use rustc_infer::infer::nll_relate::{NormalizationStrategy, TypeRelating, TypeRelatingDelegate};
+use rustc_infer::infer::NllRegionVariableOrigin;
+use rustc_infer::traits::ObligationCause;
+use rustc_middle::mir::ConstraintCategory;
+use rustc_middle::ty::error::TypeError;
+use rustc_middle::ty::relate::TypeRelation;
+use rustc_middle::ty::{self, Const, Ty};
+use rustc_span::Span;
+use rustc_trait_selection::traits::query::Fallible;
+
+use crate::constraints::OutlivesConstraint;
+use crate::diagnostics::UniverseInfo;
+use crate::type_check::{InstantiateOpaqueType, Locations, TypeChecker};
+
+impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
+ /// Adds sufficient constraints to ensure that `a R b` where `R` depends on `v`:
+ ///
+ /// - "Covariant" `a <: b`
+ /// - "Invariant" `a == b`
+ /// - "Contravariant" `a :> b`
+ ///
+ /// N.B., the type `a` is permitted to have unresolved inference
+ /// variables, but not the type `b`.
+ #[instrument(skip(self), level = "debug")]
+ pub(super) fn relate_types(
+ &mut self,
+ a: Ty<'tcx>,
+ v: ty::Variance,
+ b: Ty<'tcx>,
+ locations: Locations,
+ category: ConstraintCategory<'tcx>,
+ ) -> Fallible<()> {
+ TypeRelating::new(
+ self.infcx,
+ NllTypeRelatingDelegate::new(self, locations, category, UniverseInfo::relate(a, b)),
+ v,
+ )
+ .relate(a, b)?;
+ Ok(())
+ }
+
+ /// Add sufficient constraints to ensure `a == b`. See also [Self::relate_types].
+ pub(super) fn eq_substs(
+ &mut self,
+ a: ty::SubstsRef<'tcx>,
+ b: ty::SubstsRef<'tcx>,
+ locations: Locations,
+ category: ConstraintCategory<'tcx>,
+ ) -> Fallible<()> {
+ TypeRelating::new(
+ self.infcx,
+ NllTypeRelatingDelegate::new(self, locations, category, UniverseInfo::other()),
+ ty::Variance::Invariant,
+ )
+ .relate(a, b)?;
+ Ok(())
+ }
+}
+
+struct NllTypeRelatingDelegate<'me, 'bccx, 'tcx> {
+ type_checker: &'me mut TypeChecker<'bccx, 'tcx>,
+
+ /// Where (and why) is this relation taking place?
+ locations: Locations,
+
+ /// What category do we assign the resulting `'a: 'b` relationships?
+ category: ConstraintCategory<'tcx>,
+
+ /// Information so that error reporting knows what types we are relating
+ /// when reporting a bound region error.
+ universe_info: UniverseInfo<'tcx>,
+}
+
+impl<'me, 'bccx, 'tcx> NllTypeRelatingDelegate<'me, 'bccx, 'tcx> {
+ fn new(
+ type_checker: &'me mut TypeChecker<'bccx, 'tcx>,
+ locations: Locations,
+ category: ConstraintCategory<'tcx>,
+ universe_info: UniverseInfo<'tcx>,
+ ) -> Self {
+ Self { type_checker, locations, category, universe_info }
+ }
+}
+
+impl<'tcx> TypeRelatingDelegate<'tcx> for NllTypeRelatingDelegate<'_, '_, 'tcx> {
+ fn span(&self) -> Span {
+ self.locations.span(self.type_checker.body)
+ }
+
+ fn param_env(&self) -> ty::ParamEnv<'tcx> {
+ self.type_checker.param_env
+ }
+
+ fn create_next_universe(&mut self) -> ty::UniverseIndex {
+ let universe = self.type_checker.infcx.create_next_universe();
+ self.type_checker
+ .borrowck_context
+ .constraints
+ .universe_causes
+ .insert(universe, self.universe_info.clone());
+ universe
+ }
+
+ fn next_existential_region_var(&mut self, from_forall: bool) -> ty::Region<'tcx> {
+ let origin = NllRegionVariableOrigin::Existential { from_forall };
+ self.type_checker.infcx.next_nll_region_var(origin)
+ }
+
+ fn next_placeholder_region(&mut self, placeholder: ty::PlaceholderRegion) -> ty::Region<'tcx> {
+ self.type_checker
+ .borrowck_context
+ .constraints
+ .placeholder_region(self.type_checker.infcx, placeholder)
+ }
+
+ fn generalize_existential(&mut self, universe: ty::UniverseIndex) -> ty::Region<'tcx> {
+ self.type_checker.infcx.next_nll_region_var_in_universe(
+ NllRegionVariableOrigin::Existential { from_forall: false },
+ universe,
+ )
+ }
+
+ fn push_outlives(
+ &mut self,
+ sup: ty::Region<'tcx>,
+ sub: ty::Region<'tcx>,
+ info: ty::VarianceDiagInfo<'tcx>,
+ ) {
+ let sub = self.type_checker.borrowck_context.universal_regions.to_region_vid(sub);
+ let sup = self.type_checker.borrowck_context.universal_regions.to_region_vid(sup);
+ self.type_checker.borrowck_context.constraints.outlives_constraints.push(
+ OutlivesConstraint {
+ sup,
+ sub,
+ locations: self.locations,
+ span: self.locations.span(self.type_checker.body),
+ category: self.category,
+ variance_info: info,
+ },
+ );
+ }
+
+ // We don't have to worry about the equality of consts during borrow checking
+ // as consts always have a static lifetime.
+ // FIXME(oli-obk): is this really true? We can at least have HKL and with
+ // inline consts we may have further lifetimes that may be unsound to treat as
+ // 'static.
+ fn const_equate(&mut self, _a: Const<'tcx>, _b: Const<'tcx>) {}
+
+ fn normalization() -> NormalizationStrategy {
+ NormalizationStrategy::Eager
+ }
+
+ fn forbid_inference_vars() -> bool {
+ true
+ }
+
+ fn register_opaque_type(
+ &mut self,
+ a: Ty<'tcx>,
+ b: Ty<'tcx>,
+ a_is_expected: bool,
+ ) -> Result<(), TypeError<'tcx>> {
+ let param_env = self.param_env();
+ let span = self.span();
+ let def_id = self.type_checker.body.source.def_id().expect_local();
+ let body_id = self.type_checker.tcx().hir().local_def_id_to_hir_id(def_id);
+ let cause = ObligationCause::misc(span, body_id);
+ self.type_checker
+ .fully_perform_op(
+ self.locations,
+ self.category,
+ InstantiateOpaqueType {
+ obligations: self
+ .type_checker
+ .infcx
+ .handle_opaque_type(a, b, a_is_expected, &cause, param_env)?
+ .obligations,
+ // These fields are filled in during execution of the operation
+ base_universe: None,
+ region_constraints: None,
+ },
+ )
+ .unwrap();
+ Ok(())
+ }
+}
diff --git a/compiler/rustc_borrowck/src/universal_regions.rs b/compiler/rustc_borrowck/src/universal_regions.rs
new file mode 100644
index 000000000..2a7713bc4
--- /dev/null
+++ b/compiler/rustc_borrowck/src/universal_regions.rs
@@ -0,0 +1,841 @@
+//! Code to extract the universally quantified regions declared on a
+//! function and the relationships between them. For example:
+//!
+//! ```
+//! fn foo<'a, 'b, 'c: 'b>() { }
+//! ```
+//!
+//! here we would return a map assigning each of `{'a, 'b, 'c}`
+//! to an index, as well as the `FreeRegionMap` which can compute
+//! relationships between them.
+//!
+//! The code in this file doesn't *do anything* with those results; it
+//! just returns them for other code to use.
+
+use either::Either;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::Diagnostic;
+use rustc_hir as hir;
+use rustc_hir::def_id::{DefId, LocalDefId};
+use rustc_hir::lang_items::LangItem;
+use rustc_hir::{BodyOwnerKind, HirId};
+use rustc_index::vec::{Idx, IndexVec};
+use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin};
+use rustc_middle::ty::fold::TypeFoldable;
+use rustc_middle::ty::subst::{InternalSubsts, Subst, SubstsRef};
+use rustc_middle::ty::{self, InlineConstSubsts, InlineConstSubstsParts, RegionVid, Ty, TyCtxt};
+use std::iter;
+
+use crate::nll::ToRegionVid;
+
+#[derive(Debug)]
+pub struct UniversalRegions<'tcx> {
+ indices: UniversalRegionIndices<'tcx>,
+
+ /// The vid assigned to `'static`
+ pub fr_static: RegionVid,
+
+ /// A special region vid created to represent the current MIR fn
+ /// body. It will outlive the entire CFG but it will not outlive
+ /// any other universal regions.
+ pub fr_fn_body: RegionVid,
+
+ /// We create region variables such that they are ordered by their
+ /// `RegionClassification`. The first block are globals, then
+ /// externals, then locals. So, things from:
+ /// - `FIRST_GLOBAL_INDEX..first_extern_index` are global,
+ /// - `first_extern_index..first_local_index` are external,
+ /// - `first_local_index..num_universals` are local.
+ first_extern_index: usize,
+
+ /// See `first_extern_index`.
+ first_local_index: usize,
+
+ /// The total number of universal region variables instantiated.
+ num_universals: usize,
+
+ /// A special region variable created for the `'empty(U0)` region.
+ /// Note that this is **not** a "universal" region, as it doesn't
+ /// represent a universally bound placeholder or any such thing.
+ /// But we do create it here in this type because it's a useful region
+ /// to have around in a few limited cases.
+ pub root_empty: RegionVid,
+
+ /// The "defining" type for this function, with all universal
+ /// regions instantiated. For a closure or generator, this is the
+ /// closure type, but for a top-level function it's the `FnDef`.
+ pub defining_ty: DefiningTy<'tcx>,
+
+ /// The return type of this function, with all regions replaced by
+ /// their universal `RegionVid` equivalents.
+ ///
+ /// N.B., associated types in this type have not been normalized,
+ /// as the name suggests. =)
+ pub unnormalized_output_ty: Ty<'tcx>,
+
+ /// The fully liberated input types of this function, with all
+ /// regions replaced by their universal `RegionVid` equivalents.
+ ///
+ /// N.B., associated types in these types have not been normalized,
+ /// as the name suggests. =)
+ pub unnormalized_input_tys: &'tcx [Ty<'tcx>],
+
+ pub yield_ty: Option<Ty<'tcx>>,
+}
+
+/// The "defining type" for this MIR. The key feature of the "defining
+/// type" is that it contains the information needed to derive all the
+/// universal regions that are in scope as well as the types of the
+/// inputs/output from the MIR. In general, early-bound universal
+/// regions appear free in the defining type and late-bound regions
+/// appear bound in the signature.
+#[derive(Copy, Clone, Debug)]
+pub enum DefiningTy<'tcx> {
+ /// The MIR is a closure. The signature is found via
+ /// `ClosureSubsts::closure_sig_ty`.
+ Closure(DefId, SubstsRef<'tcx>),
+
+ /// The MIR is a generator. The signature is that generators take
+ /// no parameters and return the result of
+ /// `ClosureSubsts::generator_return_ty`.
+ Generator(DefId, SubstsRef<'tcx>, hir::Movability),
+
+ /// The MIR is a fn item with the given `DefId` and substs. The signature
+ /// of the function can be bound then with the `fn_sig` query.
+ FnDef(DefId, SubstsRef<'tcx>),
+
+ /// The MIR represents some form of constant. The signature then
+ /// is that it has no inputs and a single return value, which is
+ /// the value of the constant.
+ Const(DefId, SubstsRef<'tcx>),
+
+ /// The MIR represents an inline const. The signature has no inputs and a
+ /// single return value found via `InlineConstSubsts::ty`.
+ InlineConst(DefId, SubstsRef<'tcx>),
+}
+
+impl<'tcx> DefiningTy<'tcx> {
+ /// Returns a list of all the upvar types for this MIR. If this is
+ /// not a closure or generator, there are no upvars, and hence it
+ /// will be an empty list. The order of types in this list will
+ /// match up with the upvar order in the HIR, typesystem, and MIR.
+ pub fn upvar_tys(self) -> impl Iterator<Item = Ty<'tcx>> + 'tcx {
+ match self {
+ DefiningTy::Closure(_, substs) => Either::Left(substs.as_closure().upvar_tys()),
+ DefiningTy::Generator(_, substs, _) => {
+ Either::Right(Either::Left(substs.as_generator().upvar_tys()))
+ }
+ DefiningTy::FnDef(..) | DefiningTy::Const(..) | DefiningTy::InlineConst(..) => {
+ Either::Right(Either::Right(iter::empty()))
+ }
+ }
+ }
+
+ /// Number of implicit inputs -- notably the "environment"
+ /// parameter for closures -- that appear in MIR but not in the
+ /// user's code.
+ pub fn implicit_inputs(self) -> usize {
+ match self {
+ DefiningTy::Closure(..) | DefiningTy::Generator(..) => 1,
+ DefiningTy::FnDef(..) | DefiningTy::Const(..) | DefiningTy::InlineConst(..) => 0,
+ }
+ }
+
+ pub fn is_fn_def(&self) -> bool {
+ matches!(*self, DefiningTy::FnDef(..))
+ }
+
+ pub fn is_const(&self) -> bool {
+ matches!(*self, DefiningTy::Const(..) | DefiningTy::InlineConst(..))
+ }
+
+ pub fn def_id(&self) -> DefId {
+ match *self {
+ DefiningTy::Closure(def_id, ..)
+ | DefiningTy::Generator(def_id, ..)
+ | DefiningTy::FnDef(def_id, ..)
+ | DefiningTy::Const(def_id, ..)
+ | DefiningTy::InlineConst(def_id, ..) => def_id,
+ }
+ }
+}
+
+#[derive(Debug)]
+struct UniversalRegionIndices<'tcx> {
+ /// For those regions that may appear in the parameter environment
+ /// ('static and early-bound regions), we maintain a map from the
+ /// `ty::Region` to the internal `RegionVid` we are using. This is
+ /// used because trait matching and type-checking will feed us
+ /// region constraints that reference those regions and we need to
+ /// be able to map them our internal `RegionVid`. This is
+ /// basically equivalent to an `InternalSubsts`, except that it also
+ /// contains an entry for `ReStatic` -- it might be nice to just
+ /// use a substs, and then handle `ReStatic` another way.
+ indices: FxHashMap<ty::Region<'tcx>, RegionVid>,
+}
+
+#[derive(Debug, PartialEq)]
+pub enum RegionClassification {
+ /// A **global** region is one that can be named from
+ /// anywhere. There is only one, `'static`.
+ Global,
+
+ /// An **external** region is only relevant for
+ /// closures, generators, and inline consts. In that
+ /// case, it refers to regions that are free in the type
+ /// -- basically, something bound in the surrounding context.
+ ///
+ /// Consider this example:
+ ///
+ /// ```ignore (pseudo-rust)
+ /// fn foo<'a, 'b>(a: &'a u32, b: &'b u32, c: &'static u32) {
+ /// let closure = for<'x> |x: &'x u32| { .. };
+ /// // ^^^^^^^ pretend this were legal syntax
+ /// // for declaring a late-bound region in
+ /// // a closure signature
+ /// }
+ /// ```
+ ///
+ /// Here, the lifetimes `'a` and `'b` would be **external** to the
+ /// closure.
+ ///
+ /// If we are not analyzing a closure/generator/inline-const,
+ /// there are no external lifetimes.
+ External,
+
+ /// A **local** lifetime is one about which we know the full set
+ /// of relevant constraints (that is, relationships to other named
+ /// regions). For a closure, this includes any region bound in
+ /// the closure's signature. For a fn item, this includes all
+ /// regions other than global ones.
+ ///
+ /// Continuing with the example from `External`, if we were
+ /// analyzing the closure, then `'x` would be local (and `'a` and
+ /// `'b` are external). If we are analyzing the function item
+ /// `foo`, then `'a` and `'b` are local (and `'x` is not in
+ /// scope).
+ Local,
+}
+
+const FIRST_GLOBAL_INDEX: usize = 0;
+
+impl<'tcx> UniversalRegions<'tcx> {
+ /// Creates a new and fully initialized `UniversalRegions` that
+ /// contains indices for all the free regions found in the given
+ /// MIR -- that is, all the regions that appear in the function's
+ /// signature. This will also compute the relationships that are
+ /// known between those regions.
+ pub fn new(
+ infcx: &InferCtxt<'_, 'tcx>,
+ mir_def: ty::WithOptConstParam<LocalDefId>,
+ param_env: ty::ParamEnv<'tcx>,
+ ) -> Self {
+ let tcx = infcx.tcx;
+ let mir_hir_id = tcx.hir().local_def_id_to_hir_id(mir_def.did);
+ UniversalRegionsBuilder { infcx, mir_def, mir_hir_id, param_env }.build()
+ }
+
+ /// Given a reference to a closure type, extracts all the values
+ /// from its free regions and returns a vector with them. This is
+ /// used when the closure's creator checks that the
+ /// `ClosureRegionRequirements` are met. The requirements from
+ /// `ClosureRegionRequirements` are expressed in terms of
+ /// `RegionVid` entries that map into the returned vector `V`: so
+ /// if the `ClosureRegionRequirements` contains something like
+ /// `'1: '2`, then the caller would impose the constraint that
+ /// `V[1]: V[2]`.
+ pub fn closure_mapping(
+ tcx: TyCtxt<'tcx>,
+ closure_substs: SubstsRef<'tcx>,
+ expected_num_vars: usize,
+ typeck_root_def_id: DefId,
+ ) -> IndexVec<RegionVid, ty::Region<'tcx>> {
+ let mut region_mapping = IndexVec::with_capacity(expected_num_vars);
+ region_mapping.push(tcx.lifetimes.re_static);
+ tcx.for_each_free_region(&closure_substs, |fr| {
+ region_mapping.push(fr);
+ });
+
+ for_each_late_bound_region_defined_on(tcx, typeck_root_def_id, |r| {
+ region_mapping.push(r);
+ });
+
+ assert_eq!(
+ region_mapping.len(),
+ expected_num_vars,
+ "index vec had unexpected number of variables"
+ );
+
+ region_mapping
+ }
+
+ /// Returns `true` if `r` is a member of this set of universal regions.
+ pub fn is_universal_region(&self, r: RegionVid) -> bool {
+ (FIRST_GLOBAL_INDEX..self.num_universals).contains(&r.index())
+ }
+
+ /// Classifies `r` as a universal region, returning `None` if this
+ /// is not a member of this set of universal regions.
+ pub fn region_classification(&self, r: RegionVid) -> Option<RegionClassification> {
+ let index = r.index();
+ if (FIRST_GLOBAL_INDEX..self.first_extern_index).contains(&index) {
+ Some(RegionClassification::Global)
+ } else if (self.first_extern_index..self.first_local_index).contains(&index) {
+ Some(RegionClassification::External)
+ } else if (self.first_local_index..self.num_universals).contains(&index) {
+ Some(RegionClassification::Local)
+ } else {
+ None
+ }
+ }
+
+ /// Returns an iterator over all the RegionVids corresponding to
+ /// universally quantified free regions.
+ pub fn universal_regions(&self) -> impl Iterator<Item = RegionVid> {
+ (FIRST_GLOBAL_INDEX..self.num_universals).map(RegionVid::new)
+ }
+
+ /// Returns `true` if `r` is classified as an local region.
+ pub fn is_local_free_region(&self, r: RegionVid) -> bool {
+ self.region_classification(r) == Some(RegionClassification::Local)
+ }
+
+ /// Returns the number of universal regions created in any category.
+ pub fn len(&self) -> usize {
+ self.num_universals
+ }
+
+ /// Returns the number of global plus external universal regions.
+ /// For closures, these are the regions that appear free in the
+ /// closure type (versus those bound in the closure
+ /// signature). They are therefore the regions between which the
+ /// closure may impose constraints that its creator must verify.
+ pub fn num_global_and_external_regions(&self) -> usize {
+ self.first_local_index
+ }
+
+ /// Gets an iterator over all the early-bound regions that have names.
+ pub fn named_universal_regions<'s>(
+ &'s self,
+ ) -> impl Iterator<Item = (ty::Region<'tcx>, ty::RegionVid)> + 's {
+ self.indices.indices.iter().map(|(&r, &v)| (r, v))
+ }
+
+ /// See `UniversalRegionIndices::to_region_vid`.
+ pub fn to_region_vid(&self, r: ty::Region<'tcx>) -> RegionVid {
+ if let ty::ReEmpty(ty::UniverseIndex::ROOT) = *r {
+ self.root_empty
+ } else {
+ self.indices.to_region_vid(r)
+ }
+ }
+
+ /// As part of the NLL unit tests, you can annotate a function with
+ /// `#[rustc_regions]`, and we will emit information about the region
+ /// inference context and -- in particular -- the external constraints
+ /// that this region imposes on others. The methods in this file
+ /// handle the part about dumping the inference context internal
+ /// state.
+ pub(crate) fn annotate(&self, tcx: TyCtxt<'tcx>, err: &mut Diagnostic) {
+ match self.defining_ty {
+ DefiningTy::Closure(def_id, substs) => {
+ err.note(&format!(
+ "defining type: {} with closure substs {:#?}",
+ tcx.def_path_str_with_substs(def_id, substs),
+ &substs[tcx.generics_of(def_id).parent_count..],
+ ));
+
+ // FIXME: It'd be nice to print the late-bound regions
+ // here, but unfortunately these wind up stored into
+ // tests, and the resulting print-outs include def-ids
+ // and other things that are not stable across tests!
+ // So we just include the region-vid. Annoying.
+ let typeck_root_def_id = tcx.typeck_root_def_id(def_id);
+ for_each_late_bound_region_defined_on(tcx, typeck_root_def_id, |r| {
+ err.note(&format!("late-bound region is {:?}", self.to_region_vid(r),));
+ });
+ }
+ DefiningTy::Generator(def_id, substs, _) => {
+ err.note(&format!(
+ "defining type: {} with generator substs {:#?}",
+ tcx.def_path_str_with_substs(def_id, substs),
+ &substs[tcx.generics_of(def_id).parent_count..],
+ ));
+
+ // FIXME: As above, we'd like to print out the region
+ // `r` but doing so is not stable across architectures
+ // and so forth.
+ let typeck_root_def_id = tcx.typeck_root_def_id(def_id);
+ for_each_late_bound_region_defined_on(tcx, typeck_root_def_id, |r| {
+ err.note(&format!("late-bound region is {:?}", self.to_region_vid(r),));
+ });
+ }
+ DefiningTy::FnDef(def_id, substs) => {
+ err.note(&format!(
+ "defining type: {}",
+ tcx.def_path_str_with_substs(def_id, substs),
+ ));
+ }
+ DefiningTy::Const(def_id, substs) => {
+ err.note(&format!(
+ "defining constant type: {}",
+ tcx.def_path_str_with_substs(def_id, substs),
+ ));
+ }
+ DefiningTy::InlineConst(def_id, substs) => {
+ err.note(&format!(
+ "defining inline constant type: {}",
+ tcx.def_path_str_with_substs(def_id, substs),
+ ));
+ }
+ }
+ }
+}
+
+struct UniversalRegionsBuilder<'cx, 'tcx> {
+ infcx: &'cx InferCtxt<'cx, 'tcx>,
+ mir_def: ty::WithOptConstParam<LocalDefId>,
+ mir_hir_id: HirId,
+ param_env: ty::ParamEnv<'tcx>,
+}
+
+const FR: NllRegionVariableOrigin = NllRegionVariableOrigin::FreeRegion;
+
+impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> {
+ fn build(self) -> UniversalRegions<'tcx> {
+ debug!("build(mir_def={:?})", self.mir_def);
+
+ let param_env = self.param_env;
+ debug!("build: param_env={:?}", param_env);
+
+ assert_eq!(FIRST_GLOBAL_INDEX, self.infcx.num_region_vars());
+
+ // Create the "global" region that is always free in all contexts: 'static.
+ let fr_static = self.infcx.next_nll_region_var(FR).to_region_vid();
+
+ // We've now added all the global regions. The next ones we
+ // add will be external.
+ let first_extern_index = self.infcx.num_region_vars();
+
+ let defining_ty = self.defining_ty();
+ debug!("build: defining_ty={:?}", defining_ty);
+
+ let mut indices = self.compute_indices(fr_static, defining_ty);
+ debug!("build: indices={:?}", indices);
+
+ let typeck_root_def_id = self.infcx.tcx.typeck_root_def_id(self.mir_def.did.to_def_id());
+
+ // If this is is a 'root' body (not a closure/generator/inline const), then
+ // there are no extern regions, so the local regions start at the same
+ // position as the (empty) sub-list of extern regions
+ let first_local_index = if self.mir_def.did.to_def_id() == typeck_root_def_id {
+ first_extern_index
+ } else {
+ // If this is a closure, generator, or inline-const, then the late-bound regions from the enclosing
+ // function are actually external regions to us. For example, here, 'a is not local
+ // to the closure c (although it is local to the fn foo):
+ // fn foo<'a>() {
+ // let c = || { let x: &'a u32 = ...; }
+ // }
+ self.infcx
+ .replace_late_bound_regions_with_nll_infer_vars(self.mir_def.did, &mut indices);
+ // Any regions created during the execution of `defining_ty` or during the above
+ // late-bound region replacement are all considered 'extern' regions
+ self.infcx.num_region_vars()
+ };
+
+ // "Liberate" the late-bound regions. These correspond to
+ // "local" free regions.
+
+ let bound_inputs_and_output = self.compute_inputs_and_output(&indices, defining_ty);
+
+ let inputs_and_output = self.infcx.replace_bound_regions_with_nll_infer_vars(
+ FR,
+ self.mir_def.did,
+ bound_inputs_and_output,
+ &mut indices,
+ );
+ // Converse of above, if this is a function then the late-bound regions declared on its
+ // signature are local to the fn.
+ if self.mir_def.did.to_def_id() == typeck_root_def_id {
+ self.infcx
+ .replace_late_bound_regions_with_nll_infer_vars(self.mir_def.did, &mut indices);
+ }
+
+ let (unnormalized_output_ty, mut unnormalized_input_tys) =
+ inputs_and_output.split_last().unwrap();
+
+ // C-variadic fns also have a `VaList` input that's not listed in the signature
+ // (as it's created inside the body itself, not passed in from outside).
+ if let DefiningTy::FnDef(def_id, _) = defining_ty {
+ if self.infcx.tcx.fn_sig(def_id).c_variadic() {
+ let va_list_did = self.infcx.tcx.require_lang_item(
+ LangItem::VaList,
+ Some(self.infcx.tcx.def_span(self.mir_def.did)),
+ );
+ let region = self
+ .infcx
+ .tcx
+ .mk_region(ty::ReVar(self.infcx.next_nll_region_var(FR).to_region_vid()));
+ let va_list_ty = self
+ .infcx
+ .tcx
+ .bound_type_of(va_list_did)
+ .subst(self.infcx.tcx, &[region.into()]);
+
+ unnormalized_input_tys = self.infcx.tcx.mk_type_list(
+ unnormalized_input_tys.iter().copied().chain(iter::once(va_list_ty)),
+ );
+ }
+ }
+
+ let fr_fn_body = self.infcx.next_nll_region_var(FR).to_region_vid();
+ let num_universals = self.infcx.num_region_vars();
+
+ debug!("build: global regions = {}..{}", FIRST_GLOBAL_INDEX, first_extern_index);
+ debug!("build: extern regions = {}..{}", first_extern_index, first_local_index);
+ debug!("build: local regions = {}..{}", first_local_index, num_universals);
+
+ let yield_ty = match defining_ty {
+ DefiningTy::Generator(_, substs, _) => Some(substs.as_generator().yield_ty()),
+ _ => None,
+ };
+
+ let root_empty = self
+ .infcx
+ .next_nll_region_var(NllRegionVariableOrigin::Existential { from_forall: true })
+ .to_region_vid();
+
+ UniversalRegions {
+ indices,
+ fr_static,
+ fr_fn_body,
+ root_empty,
+ first_extern_index,
+ first_local_index,
+ num_universals,
+ defining_ty,
+ unnormalized_output_ty: *unnormalized_output_ty,
+ unnormalized_input_tys,
+ yield_ty,
+ }
+ }
+
+ /// Returns the "defining type" of the current MIR;
+ /// see `DefiningTy` for details.
+ fn defining_ty(&self) -> DefiningTy<'tcx> {
+ let tcx = self.infcx.tcx;
+ let typeck_root_def_id = tcx.typeck_root_def_id(self.mir_def.did.to_def_id());
+
+ match tcx.hir().body_owner_kind(self.mir_def.did) {
+ BodyOwnerKind::Closure | BodyOwnerKind::Fn => {
+ let defining_ty = if self.mir_def.did.to_def_id() == typeck_root_def_id {
+ tcx.type_of(typeck_root_def_id)
+ } else {
+ let tables = tcx.typeck(self.mir_def.did);
+ tables.node_type(self.mir_hir_id)
+ };
+
+ debug!("defining_ty (pre-replacement): {:?}", defining_ty);
+
+ let defining_ty =
+ self.infcx.replace_free_regions_with_nll_infer_vars(FR, defining_ty);
+
+ match *defining_ty.kind() {
+ ty::Closure(def_id, substs) => DefiningTy::Closure(def_id, substs),
+ ty::Generator(def_id, substs, movability) => {
+ DefiningTy::Generator(def_id, substs, movability)
+ }
+ ty::FnDef(def_id, substs) => DefiningTy::FnDef(def_id, substs),
+ _ => span_bug!(
+ tcx.def_span(self.mir_def.did),
+ "expected defining type for `{:?}`: `{:?}`",
+ self.mir_def.did,
+ defining_ty
+ ),
+ }
+ }
+
+ BodyOwnerKind::Const | BodyOwnerKind::Static(..) => {
+ let identity_substs = InternalSubsts::identity_for_item(tcx, typeck_root_def_id);
+ if self.mir_def.did.to_def_id() == typeck_root_def_id {
+ let substs =
+ self.infcx.replace_free_regions_with_nll_infer_vars(FR, identity_substs);
+ DefiningTy::Const(self.mir_def.did.to_def_id(), substs)
+ } else {
+ let ty = tcx.typeck(self.mir_def.did).node_type(self.mir_hir_id);
+ let substs = InlineConstSubsts::new(
+ tcx,
+ InlineConstSubstsParts { parent_substs: identity_substs, ty },
+ )
+ .substs;
+ let substs = self.infcx.replace_free_regions_with_nll_infer_vars(FR, substs);
+ DefiningTy::InlineConst(self.mir_def.did.to_def_id(), substs)
+ }
+ }
+ }
+ }
+
+ /// Builds a hashmap that maps from the universal regions that are
+ /// in scope (as a `ty::Region<'tcx>`) to their indices (as a
+ /// `RegionVid`). The map returned by this function contains only
+ /// the early-bound regions.
+ fn compute_indices(
+ &self,
+ fr_static: RegionVid,
+ defining_ty: DefiningTy<'tcx>,
+ ) -> UniversalRegionIndices<'tcx> {
+ let tcx = self.infcx.tcx;
+ let typeck_root_def_id = tcx.typeck_root_def_id(self.mir_def.did.to_def_id());
+ let identity_substs = InternalSubsts::identity_for_item(tcx, typeck_root_def_id);
+ let fr_substs = match defining_ty {
+ DefiningTy::Closure(_, ref substs)
+ | DefiningTy::Generator(_, ref substs, _)
+ | DefiningTy::InlineConst(_, ref substs) => {
+ // In the case of closures, we rely on the fact that
+ // the first N elements in the ClosureSubsts are
+ // inherited from the `typeck_root_def_id`.
+ // Therefore, when we zip together (below) with
+ // `identity_substs`, we will get only those regions
+ // that correspond to early-bound regions declared on
+ // the `typeck_root_def_id`.
+ assert!(substs.len() >= identity_substs.len());
+ assert_eq!(substs.regions().count(), identity_substs.regions().count());
+ substs
+ }
+
+ DefiningTy::FnDef(_, substs) | DefiningTy::Const(_, substs) => substs,
+ };
+
+ let global_mapping = iter::once((tcx.lifetimes.re_static, fr_static));
+ let subst_mapping =
+ iter::zip(identity_substs.regions(), fr_substs.regions().map(|r| r.to_region_vid()));
+
+ UniversalRegionIndices { indices: global_mapping.chain(subst_mapping).collect() }
+ }
+
+ fn compute_inputs_and_output(
+ &self,
+ indices: &UniversalRegionIndices<'tcx>,
+ defining_ty: DefiningTy<'tcx>,
+ ) -> ty::Binder<'tcx, &'tcx ty::List<Ty<'tcx>>> {
+ let tcx = self.infcx.tcx;
+ match defining_ty {
+ DefiningTy::Closure(def_id, substs) => {
+ assert_eq!(self.mir_def.did.to_def_id(), def_id);
+ let closure_sig = substs.as_closure().sig();
+ let inputs_and_output = closure_sig.inputs_and_output();
+ let bound_vars = tcx.mk_bound_variable_kinds(
+ inputs_and_output
+ .bound_vars()
+ .iter()
+ .chain(iter::once(ty::BoundVariableKind::Region(ty::BrEnv))),
+ );
+ let br = ty::BoundRegion {
+ var: ty::BoundVar::from_usize(bound_vars.len() - 1),
+ kind: ty::BrEnv,
+ };
+ let env_region = ty::ReLateBound(ty::INNERMOST, br);
+ let closure_ty = tcx.closure_env_ty(def_id, substs, env_region).unwrap();
+
+ // The "inputs" of the closure in the
+ // signature appear as a tuple. The MIR side
+ // flattens this tuple.
+ let (&output, tuplized_inputs) =
+ inputs_and_output.skip_binder().split_last().unwrap();
+ assert_eq!(tuplized_inputs.len(), 1, "multiple closure inputs");
+ let &ty::Tuple(inputs) = tuplized_inputs[0].kind() else {
+ bug!("closure inputs not a tuple: {:?}", tuplized_inputs[0]);
+ };
+
+ ty::Binder::bind_with_vars(
+ tcx.mk_type_list(
+ iter::once(closure_ty).chain(inputs).chain(iter::once(output)),
+ ),
+ bound_vars,
+ )
+ }
+
+ DefiningTy::Generator(def_id, substs, movability) => {
+ assert_eq!(self.mir_def.did.to_def_id(), def_id);
+ let resume_ty = substs.as_generator().resume_ty();
+ let output = substs.as_generator().return_ty();
+ let generator_ty = tcx.mk_generator(def_id, substs, movability);
+ let inputs_and_output =
+ self.infcx.tcx.intern_type_list(&[generator_ty, resume_ty, output]);
+ ty::Binder::dummy(inputs_and_output)
+ }
+
+ DefiningTy::FnDef(def_id, _) => {
+ let sig = tcx.fn_sig(def_id);
+ let sig = indices.fold_to_region_vids(tcx, sig);
+ sig.inputs_and_output()
+ }
+
+ DefiningTy::Const(def_id, _) => {
+ // For a constant body, there are no inputs, and one
+ // "output" (the type of the constant).
+ assert_eq!(self.mir_def.did.to_def_id(), def_id);
+ let ty = tcx.type_of(self.mir_def.def_id_for_type_of());
+ let ty = indices.fold_to_region_vids(tcx, ty);
+ ty::Binder::dummy(tcx.intern_type_list(&[ty]))
+ }
+
+ DefiningTy::InlineConst(def_id, substs) => {
+ assert_eq!(self.mir_def.did.to_def_id(), def_id);
+ let ty = substs.as_inline_const().ty();
+ ty::Binder::dummy(tcx.intern_type_list(&[ty]))
+ }
+ }
+ }
+}
+
+trait InferCtxtExt<'tcx> {
+ fn replace_free_regions_with_nll_infer_vars<T>(
+ &self,
+ origin: NllRegionVariableOrigin,
+ value: T,
+ ) -> T
+ where
+ T: TypeFoldable<'tcx>;
+
+ fn replace_bound_regions_with_nll_infer_vars<T>(
+ &self,
+ origin: NllRegionVariableOrigin,
+ all_outlive_scope: LocalDefId,
+ value: ty::Binder<'tcx, T>,
+ indices: &mut UniversalRegionIndices<'tcx>,
+ ) -> T
+ where
+ T: TypeFoldable<'tcx>;
+
+ fn replace_late_bound_regions_with_nll_infer_vars(
+ &self,
+ mir_def_id: LocalDefId,
+ indices: &mut UniversalRegionIndices<'tcx>,
+ );
+}
+
+impl<'cx, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'cx, 'tcx> {
+ fn replace_free_regions_with_nll_infer_vars<T>(
+ &self,
+ origin: NllRegionVariableOrigin,
+ value: T,
+ ) -> T
+ where
+ T: TypeFoldable<'tcx>,
+ {
+ self.tcx.fold_regions(value, |_region, _depth| self.next_nll_region_var(origin))
+ }
+
+ #[instrument(level = "debug", skip(self, indices))]
+ fn replace_bound_regions_with_nll_infer_vars<T>(
+ &self,
+ origin: NllRegionVariableOrigin,
+ all_outlive_scope: LocalDefId,
+ value: ty::Binder<'tcx, T>,
+ indices: &mut UniversalRegionIndices<'tcx>,
+ ) -> T
+ where
+ T: TypeFoldable<'tcx>,
+ {
+ let (value, _map) = self.tcx.replace_late_bound_regions(value, |br| {
+ debug!(?br);
+ let liberated_region = self.tcx.mk_region(ty::ReFree(ty::FreeRegion {
+ scope: all_outlive_scope.to_def_id(),
+ bound_region: br.kind,
+ }));
+ let region_vid = self.next_nll_region_var(origin);
+ indices.insert_late_bound_region(liberated_region, region_vid.to_region_vid());
+ debug!(?liberated_region, ?region_vid);
+ region_vid
+ });
+ value
+ }
+
+ /// Finds late-bound regions that do not appear in the parameter listing and adds them to the
+ /// indices vector. Typically, we identify late-bound regions as we process the inputs and
+ /// outputs of the closure/function. However, sometimes there are late-bound regions which do
+ /// not appear in the fn parameters but which are nonetheless in scope. The simplest case of
+ /// this are unused functions, like fn foo<'a>() { } (see e.g., #51351). Despite not being used,
+ /// users can still reference these regions (e.g., let x: &'a u32 = &22;), so we need to create
+ /// entries for them and store them in the indices map. This code iterates over the complete
+ /// set of late-bound regions and checks for any that we have not yet seen, adding them to the
+ /// inputs vector.
+ #[instrument(skip(self, indices))]
+ fn replace_late_bound_regions_with_nll_infer_vars(
+ &self,
+ mir_def_id: LocalDefId,
+ indices: &mut UniversalRegionIndices<'tcx>,
+ ) {
+ debug!("replace_late_bound_regions_with_nll_infer_vars(mir_def_id={:?})", mir_def_id);
+ let typeck_root_def_id = self.tcx.typeck_root_def_id(mir_def_id.to_def_id());
+ for_each_late_bound_region_defined_on(self.tcx, typeck_root_def_id, |r| {
+ debug!("replace_late_bound_regions_with_nll_infer_vars: r={:?}", r);
+ if !indices.indices.contains_key(&r) {
+ let region_vid = self.next_nll_region_var(FR);
+ debug!(?region_vid);
+ indices.insert_late_bound_region(r, region_vid.to_region_vid());
+ }
+ });
+ }
+}
+
+impl<'tcx> UniversalRegionIndices<'tcx> {
+ /// Initially, the `UniversalRegionIndices` map contains only the
+ /// early-bound regions in scope. Once that is all setup, we come
+ /// in later and instantiate the late-bound regions, and then we
+ /// insert the `ReFree` version of those into the map as
+ /// well. These are used for error reporting.
+ fn insert_late_bound_region(&mut self, r: ty::Region<'tcx>, vid: ty::RegionVid) {
+ debug!("insert_late_bound_region({:?}, {:?})", r, vid);
+ self.indices.insert(r, vid);
+ }
+
+ /// Converts `r` into a local inference variable: `r` can either
+ /// by a `ReVar` (i.e., already a reference to an inference
+ /// variable) or it can be `'static` or some early-bound
+ /// region. This is useful when taking the results from
+ /// type-checking and trait-matching, which may sometimes
+ /// reference those regions from the `ParamEnv`. It is also used
+ /// during initialization. Relies on the `indices` map having been
+ /// fully initialized.
+ pub fn to_region_vid(&self, r: ty::Region<'tcx>) -> RegionVid {
+ if let ty::ReVar(..) = *r {
+ r.to_region_vid()
+ } else {
+ *self
+ .indices
+ .get(&r)
+ .unwrap_or_else(|| bug!("cannot convert `{:?}` to a region vid", r))
+ }
+ }
+
+ /// Replaces all free regions in `value` with region vids, as
+ /// returned by `to_region_vid`.
+ pub fn fold_to_region_vids<T>(&self, tcx: TyCtxt<'tcx>, value: T) -> T
+ where
+ T: TypeFoldable<'tcx>,
+ {
+ tcx.fold_regions(value, |region, _| tcx.mk_region(ty::ReVar(self.to_region_vid(region))))
+ }
+}
+
+/// Iterates over the late-bound regions defined on fn_def_id and
+/// invokes `f` with the liberated form of each one.
+fn for_each_late_bound_region_defined_on<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ fn_def_id: DefId,
+ mut f: impl FnMut(ty::Region<'tcx>),
+) {
+ if let Some(late_bounds) = tcx.is_late_bound_map(fn_def_id.expect_local()) {
+ for &region_def_id in late_bounds.iter() {
+ let name = tcx.item_name(region_def_id.to_def_id());
+ let liberated_region = tcx.mk_region(ty::ReFree(ty::FreeRegion {
+ scope: fn_def_id,
+ bound_region: ty::BoundRegionKind::BrNamed(region_def_id.to_def_id(), name),
+ }));
+ f(liberated_region);
+ }
+ }
+}
diff --git a/compiler/rustc_borrowck/src/used_muts.rs b/compiler/rustc_borrowck/src/used_muts.rs
new file mode 100644
index 000000000..8833753b1
--- /dev/null
+++ b/compiler/rustc_borrowck/src/used_muts.rs
@@ -0,0 +1,110 @@
+use rustc_data_structures::fx::FxHashSet;
+use rustc_middle::mir::visit::{PlaceContext, Visitor};
+use rustc_middle::mir::{
+ Local, Location, Place, Statement, StatementKind, Terminator, TerminatorKind,
+};
+
+use crate::MirBorrowckCtxt;
+
+impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
+ /// Walks the MIR adding to the set of `used_mut` locals that will be ignored for the purposes
+ /// of the `unused_mut` lint.
+ ///
+ /// `temporary_used_locals` should contain locals that were found to be temporary, mutable and
+ /// used from borrow checking. This function looks for assignments into these locals from
+ /// user-declared locals and adds those user-defined locals to the `used_mut` set. This can
+ /// occur due to a rare case involving upvars in closures.
+ ///
+ /// `never_initialized_mut_locals` should contain the set of user-declared mutable locals
+ /// (not arguments) that have not already been marked as being used.
+ /// This function then looks for assignments from statements or the terminator into the locals
+ /// from this set and removes them from the set. This leaves only those locals that have not
+ /// been assigned to - this set is used as a proxy for locals that were not initialized due to
+ /// unreachable code. These locals are then considered "used" to silence the lint for them.
+ /// See #55344 for context.
+ pub(crate) fn gather_used_muts(
+ &mut self,
+ temporary_used_locals: FxHashSet<Local>,
+ mut never_initialized_mut_locals: FxHashSet<Local>,
+ ) {
+ {
+ let mut visitor = GatherUsedMutsVisitor {
+ temporary_used_locals,
+ never_initialized_mut_locals: &mut never_initialized_mut_locals,
+ mbcx: self,
+ };
+ visitor.visit_body(&visitor.mbcx.body);
+ }
+
+ // Take the union of the existed `used_mut` set with those variables we've found were
+ // never initialized.
+ debug!("gather_used_muts: never_initialized_mut_locals={:?}", never_initialized_mut_locals);
+ self.used_mut = self.used_mut.union(&never_initialized_mut_locals).cloned().collect();
+ }
+}
+
+/// MIR visitor for collecting used mutable variables.
+/// The 'visit lifetime represents the duration of the MIR walk.
+struct GatherUsedMutsVisitor<'visit, 'cx, 'tcx> {
+ temporary_used_locals: FxHashSet<Local>,
+ never_initialized_mut_locals: &'visit mut FxHashSet<Local>,
+ mbcx: &'visit mut MirBorrowckCtxt<'cx, 'tcx>,
+}
+
+impl GatherUsedMutsVisitor<'_, '_, '_> {
+ fn remove_never_initialized_mut_locals(&mut self, into: Place<'_>) {
+ // Remove any locals that we found were initialized from the
+ // `never_initialized_mut_locals` set. At the end, the only remaining locals will
+ // be those that were never initialized - we will consider those as being used as
+ // they will either have been removed by unreachable code optimizations; or linted
+ // as unused variables.
+ self.never_initialized_mut_locals.remove(&into.local);
+ }
+}
+
+impl<'visit, 'cx, 'tcx> Visitor<'tcx> for GatherUsedMutsVisitor<'visit, 'cx, 'tcx> {
+ fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
+ debug!("visit_terminator: terminator={:?}", terminator);
+ match &terminator.kind {
+ TerminatorKind::Call { destination, .. } => {
+ self.remove_never_initialized_mut_locals(*destination);
+ }
+ TerminatorKind::DropAndReplace { place, .. } => {
+ self.remove_never_initialized_mut_locals(*place);
+ }
+ _ => {}
+ }
+
+ self.super_terminator(terminator, location);
+ }
+
+ fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
+ if let StatementKind::Assign(box (into, _)) = &statement.kind {
+ debug!(
+ "visit_statement: statement={:?} local={:?} \
+ never_initialized_mut_locals={:?}",
+ statement, into.local, self.never_initialized_mut_locals
+ );
+ self.remove_never_initialized_mut_locals(*into);
+ }
+
+ self.super_statement(statement, location);
+ }
+
+ fn visit_local(&mut self, local: Local, place_context: PlaceContext, location: Location) {
+ if place_context.is_place_assignment() && self.temporary_used_locals.contains(&local) {
+ // Propagate the Local assigned at this Location as a used mutable local variable
+ for moi in &self.mbcx.move_data.loc_map[location] {
+ let mpi = &self.mbcx.move_data.moves[*moi].path;
+ let path = &self.mbcx.move_data.move_paths[*mpi];
+ debug!(
+ "assignment of {:?} to {:?}, adding {:?} to used mutable set",
+ path.place, local, path.place
+ );
+ if let Some(user_local) = path.place.as_local() {
+ self.mbcx.used_mut.insert(user_local);
+ }
+ }
+ }
+ }
+}