From 698f8c2f01ea549d77d7dc3338a12e04c11057b9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:02:58 +0200 Subject: Adding upstream version 1.64.0+dfsg1. Signed-off-by: Daniel Baumann --- compiler/rustc_borrowck/Cargo.toml | 31 + compiler/rustc_borrowck/src/borrow_set.rs | 345 +++ compiler/rustc_borrowck/src/borrowck_errors.rs | 486 ++++ .../rustc_borrowck/src/constraint_generation.rs | 250 ++ compiler/rustc_borrowck/src/constraints/graph.rs | 235 ++ compiler/rustc_borrowck/src/constraints/mod.rs | 124 + compiler/rustc_borrowck/src/consumers.rs | 39 + compiler/rustc_borrowck/src/dataflow.rs | 438 ++++ compiler/rustc_borrowck/src/def_use.rs | 80 + .../src/diagnostics/bound_region_errors.rs | 494 ++++ .../src/diagnostics/conflict_errors.rs | 2773 ++++++++++++++++++++ .../src/diagnostics/explain_borrow.rs | 744 ++++++ .../src/diagnostics/find_all_local_uses.rs | 26 + .../rustc_borrowck/src/diagnostics/find_use.rs | 128 + compiler/rustc_borrowck/src/diagnostics/mod.rs | 1127 ++++++++ .../rustc_borrowck/src/diagnostics/move_errors.rs | 529 ++++ .../src/diagnostics/mutability_errors.rs | 1115 ++++++++ .../src/diagnostics/outlives_suggestion.rs | 261 ++ .../src/diagnostics/region_errors.rs | 904 +++++++ .../rustc_borrowck/src/diagnostics/region_name.rs | 896 +++++++ .../rustc_borrowck/src/diagnostics/var_name.rs | 133 + compiler/rustc_borrowck/src/facts.rs | 212 ++ compiler/rustc_borrowck/src/invalidation.rs | 442 ++++ compiler/rustc_borrowck/src/lib.rs | 2380 +++++++++++++++++ compiler/rustc_borrowck/src/location.rs | 107 + compiler/rustc_borrowck/src/member_constraints.rs | 230 ++ compiler/rustc_borrowck/src/nll.rs | 462 ++++ compiler/rustc_borrowck/src/path_utils.rs | 171 ++ compiler/rustc_borrowck/src/place_ext.rs | 81 + compiler/rustc_borrowck/src/places_conflict.rs | 537 ++++ compiler/rustc_borrowck/src/prefixes.rs | 145 + .../rustc_borrowck/src/region_infer/dump_mir.rs | 93 + .../rustc_borrowck/src/region_infer/graphviz.rs | 140 + compiler/rustc_borrowck/src/region_infer/mod.rs | 2365 +++++++++++++++++ .../src/region_infer/opaque_types.rs | 662 +++++ .../src/region_infer/reverse_sccs.rs | 68 + compiler/rustc_borrowck/src/region_infer/values.rs | 488 ++++ compiler/rustc_borrowck/src/renumber.rs | 83 + compiler/rustc_borrowck/src/session_diagnostics.rs | 44 + .../rustc_borrowck/src/type_check/canonical.rs | 171 ++ .../src/type_check/constraint_conversion.rs | 204 ++ .../src/type_check/free_region_relations.rs | 374 +++ .../rustc_borrowck/src/type_check/input_output.rs | 245 ++ .../src/type_check/liveness/local_use_map.rs | 170 ++ .../rustc_borrowck/src/type_check/liveness/mod.rs | 139 + .../src/type_check/liveness/polonius.rs | 140 + .../src/type_check/liveness/trace.rs | 578 ++++ compiler/rustc_borrowck/src/type_check/mod.rs | 2721 +++++++++++++++++++ .../rustc_borrowck/src/type_check/relate_tys.rs | 187 ++ compiler/rustc_borrowck/src/universal_regions.rs | 841 ++++++ compiler/rustc_borrowck/src/used_muts.rs | 110 + 51 files changed, 25748 insertions(+) create mode 100644 compiler/rustc_borrowck/Cargo.toml create mode 100644 compiler/rustc_borrowck/src/borrow_set.rs create mode 100644 compiler/rustc_borrowck/src/borrowck_errors.rs create mode 100644 compiler/rustc_borrowck/src/constraint_generation.rs create mode 100644 compiler/rustc_borrowck/src/constraints/graph.rs create mode 100644 compiler/rustc_borrowck/src/constraints/mod.rs create mode 100644 compiler/rustc_borrowck/src/consumers.rs create mode 100644 compiler/rustc_borrowck/src/dataflow.rs create mode 100644 compiler/rustc_borrowck/src/def_use.rs create mode 100644 compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs create mode 100644 compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs create mode 100644 compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs create mode 100644 compiler/rustc_borrowck/src/diagnostics/find_all_local_uses.rs create mode 100644 compiler/rustc_borrowck/src/diagnostics/find_use.rs create mode 100644 compiler/rustc_borrowck/src/diagnostics/mod.rs create mode 100644 compiler/rustc_borrowck/src/diagnostics/move_errors.rs create mode 100644 compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs create mode 100644 compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs create mode 100644 compiler/rustc_borrowck/src/diagnostics/region_errors.rs create mode 100644 compiler/rustc_borrowck/src/diagnostics/region_name.rs create mode 100644 compiler/rustc_borrowck/src/diagnostics/var_name.rs create mode 100644 compiler/rustc_borrowck/src/facts.rs create mode 100644 compiler/rustc_borrowck/src/invalidation.rs create mode 100644 compiler/rustc_borrowck/src/lib.rs create mode 100644 compiler/rustc_borrowck/src/location.rs create mode 100644 compiler/rustc_borrowck/src/member_constraints.rs create mode 100644 compiler/rustc_borrowck/src/nll.rs create mode 100644 compiler/rustc_borrowck/src/path_utils.rs create mode 100644 compiler/rustc_borrowck/src/place_ext.rs create mode 100644 compiler/rustc_borrowck/src/places_conflict.rs create mode 100644 compiler/rustc_borrowck/src/prefixes.rs create mode 100644 compiler/rustc_borrowck/src/region_infer/dump_mir.rs create mode 100644 compiler/rustc_borrowck/src/region_infer/graphviz.rs create mode 100644 compiler/rustc_borrowck/src/region_infer/mod.rs create mode 100644 compiler/rustc_borrowck/src/region_infer/opaque_types.rs create mode 100644 compiler/rustc_borrowck/src/region_infer/reverse_sccs.rs create mode 100644 compiler/rustc_borrowck/src/region_infer/values.rs create mode 100644 compiler/rustc_borrowck/src/renumber.rs create mode 100644 compiler/rustc_borrowck/src/session_diagnostics.rs create mode 100644 compiler/rustc_borrowck/src/type_check/canonical.rs create mode 100644 compiler/rustc_borrowck/src/type_check/constraint_conversion.rs create mode 100644 compiler/rustc_borrowck/src/type_check/free_region_relations.rs create mode 100644 compiler/rustc_borrowck/src/type_check/input_output.rs create mode 100644 compiler/rustc_borrowck/src/type_check/liveness/local_use_map.rs create mode 100644 compiler/rustc_borrowck/src/type_check/liveness/mod.rs create mode 100644 compiler/rustc_borrowck/src/type_check/liveness/polonius.rs create mode 100644 compiler/rustc_borrowck/src/type_check/liveness/trace.rs create mode 100644 compiler/rustc_borrowck/src/type_check/mod.rs create mode 100644 compiler/rustc_borrowck/src/type_check/relate_tys.rs create mode 100644 compiler/rustc_borrowck/src/universal_regions.rs create mode 100644 compiler/rustc_borrowck/src/used_muts.rs (limited to 'compiler/rustc_borrowck') 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>, + + /// 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>, + + /// Map from local to all the borrows on that local. + pub local_map: FxHashMap>, + + pub(crate) locals_state_at_exit: LocalsStateAtExit, +} + +impl<'tcx> Index 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 }, +} + +impl LocalsStateAtExit { + fn build<'tcx>( + locals_are_invalidated_at_exit: bool, + body: &Body<'tcx>, + move_data: &MoveData<'tcx>, + ) -> Self { + struct HasStorageDead(BitSet); + + 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 { + BorrowIndex::from_usize(0)..BorrowIndex::from_usize(self.len()) + } + + pub(crate) fn iter_enumerated(&self) -> impl Iterator)> { + self.indices().zip(self.location_map.values()) + } + + pub(crate) fn get_index_of(&self, location: &Location) -> Option { + self.location_map.get_index_of(location).map(BorrowIndex::from) + } +} + +struct GatherBorrows<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + body: &'a Body<'tcx>, + location_map: FxIndexMap>, + activation_map: FxHashMap>, + local_map: FxHashMap>, + + /// 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, + + 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, + ) -> 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, + ) -> 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, + ) -> 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, + 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, + ) -> 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, + ) -> 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, + ) -> 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>( + &self, + sp: S, + msg: impl Into, + 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, + all_facts: &mut Option, + 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, + location_table: &'cg LocationTable, + liveness_constraints: &'cg mut LivenessValues, + 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(&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 { + _direction: D, + first_constraints: IndexVec>, + next_constraints: IndexVec>, +} + +pub(crate) type NormalConstraintGraph = ConstraintGraph; + +pub(crate) type ReverseConstraintGraph = ConstraintGraph; + +/// 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 ConstraintGraph { + /// 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, + constraints: &'s OutlivesConstraintSet<'tcx>, + pointer: Option, + next_static_idx: Option, + static_region: RegionVid, +} + +impl<'s, 'tcx, D: ConstraintGraphDirecton> Iterator for Edges<'s, 'tcx, D> { + type Item = OutlivesConstraint<'tcx>; + + fn next(&mut self) -> Option { + 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, + 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, + 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.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) -> >::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>, +} + +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 { + let region_graph = &constraint_graph.region_graph(self, static_region); + Sccs::new(region_graph) + } + + pub(crate) fn outlives(&self) -> &IndexVec> { + &self.outlives + } +} + +impl<'tcx> Index 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, +) -> 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 { + 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> = + 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>, +} + +struct StackEntry { + bb: mir::BasicBlock, + lo: usize, + hi: usize, +} + +struct OutOfScopePrecomputer<'a, 'tcx> { + visited: BitSet, + visit_stack: Vec, + body: &'a Body<'tcx>, + regioncx: &'a RegionInferenceContext<'tcx>, + borrows_out_of_scope_at_location: FxHashMap>, +} + +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, + 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, 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; + + 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, + _statement: &mir::Statement<'tcx>, + location: Location, + ) { + self.kill_loans_out_of_scope_at_location(trans, location); + } + + fn statement_effect( + &self, + trans: &mut impl GenKill, + 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, + _terminator: &mir::Terminator<'tcx>, + location: Location, + ) { + self.kill_loans_out_of_scope_at_location(trans, location); + } + + fn terminator_effect( + &self, + trans: &mut impl GenKill, + 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, + _block: mir::BasicBlock, + _return_places: CallReturnPlaces<'_, 'tcx>, + ) { + } +} + +impl DebugWithContext> 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 { + 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 + '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>> +{ + 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> { + 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>, + ) -> Option>; + + 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>, + ) -> Option> { + mbcx.infcx.tcx.infer_ctxt().enter_with_canonical( + cause.span, + &self.canonical_query, + |ref infcx, key, _| { + let mut fulfill_cx = >::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>>, + 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>, + ) -> Option> { + mbcx.infcx.tcx.infer_ctxt().enter_with_canonical( + cause.span, + &self.canonical_query, + |ref infcx, key, _| { + let mut fulfill_cx = >::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>, + ) -> Option> { + mbcx.infcx.tcx.infer_ctxt().enter_with_canonical( + cause.span, + &self.canonical_query, + |ref infcx, key, _| { + let mut fulfill_cx = >::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>, + ) -> Option> { + 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 + 'tcx>, + infcx: &InferCtxt<'_, 'tcx>, + placeholder_region: ty::Region<'tcx>, + error_region: Option>, +) -> Option> { + // 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, + ®ion_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>, + region_constraints: &RegionConstraintData<'tcx>, + mut region_var_origin: impl FnMut(RegionVid) -> RegionVariableOrigin, + mut universe_of_region: impl FnMut(RegionVid) -> UniverseIndex, +) -> Option> { + 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::>(); + + 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, ¬e_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, ¬e_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, _> = tcx.infer_ctxt().enter(|infcx| { + let mut fulfill_cx = >::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, + ) { + 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 &'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 ", + ); + + 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, + 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> { + 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::>()", + 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 || "), + }; + 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, + 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, Vec) { + fn predecessor_locations<'tcx, 'a>( + body: &'a mir::Body<'tcx>, + location: Location, + ) -> impl Iterator + 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, 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::>(); + (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, + } + 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> { + // 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> { + 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 ", + ); + + 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 = 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), + UsedLaterInLoop(LaterUseKind, Span, Option), + 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, + }, + 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>, + err: &mut Diagnostic, + borrow_desc: &str, + borrow_span: Option, + 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) { + 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 = ` 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 { + let mut visited_locations = FxHashSet::default(); + let mut pending_locations = VecDeque::new(); + visited_locations.insert(from); + pending_locations.push_back(from); + debug!("reach_through_backedge: from={:?} to={:?}", from, to,); + + let mut outmost_back_edge = None; + while let Some(location) = pending_locations.pop_front() { + debug!( + "reach_through_backedge: location={:?} outmost_back_edge={:?} + pending_locations={:?} visited_locations={:?}", + location, outmost_back_edge, pending_locations, visited_locations + ); + + if location == to && outmost_back_edge.is_some() { + // We've managed to reach the use location + debug!("reach_through_backedge: found!"); + return outmost_back_edge; + } + + let block = &self.body.basic_blocks()[location.block]; + + if location.statement_index < block.statements.len() { + let successor = location.successor_within_block(); + if visited_locations.insert(successor) { + pending_locations.push_back(successor); + } + } else { + pending_locations.extend( + block + .terminator() + .successors() + .map(|bb| Location { statement_index: 0, block: bb }) + .filter(|s| visited_locations.insert(*s)) + .map(|s| { + if self.is_back_edge(location, s) { + match outmost_back_edge { + None => { + outmost_back_edge = Some(location); + } + + Some(back_edge) + if location.dominates(back_edge, &self.dominators) => + { + outmost_back_edge = Some(location); + } + + Some(_) => {} + } + } + + s + }), + ); + } + } + + None + } + + /// true if `from` location can reach `loop_head` location and `loop_head` dominates all the + /// intermediate nodes + fn can_reach_head_of_loop(&self, from: Location, loop_head: Location) -> bool { + self.find_loop_head_dfs(from, loop_head, &mut FxHashSet::default()) + } + + fn find_loop_head_dfs( + &self, + from: Location, + loop_head: Location, + visited_locations: &mut FxHashSet, + ) -> bool { + visited_locations.insert(from); + + if from == loop_head { + return true; + } + + if loop_head.dominates(from, &self.dominators) { + let block = &self.body.basic_blocks()[from.block]; + + if from.statement_index < block.statements.len() { + let successor = from.successor_within_block(); + + if !visited_locations.contains(&successor) + && self.find_loop_head_dfs(successor, loop_head, visited_locations) + { + return true; + } + } else { + for bb in block.terminator().successors() { + let successor = Location { statement_index: 0, block: bb }; + + if !visited_locations.contains(&successor) + && self.find_loop_head_dfs(successor, loop_head, visited_locations) + { + return true; + } + } + } + } + + false + } + + /// True if an edge `source -> target` is a backedge -- in other words, if the target + /// dominates the source. + fn is_back_edge(&self, source: Location, target: Location) -> bool { + target.dominates(source, &self.dominators) + } + + /// Determine how the borrow was later used. + /// 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) { + 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` + _ 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 { + let mut visitor = AllLocalUsesVisitor { for_local: local, uses: BTreeSet::default() }; + visitor.visit_body(body); + visitor.uses +} + +struct AllLocalUsesVisitor { + for_local: Local, + uses: BTreeSet, +} + +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>, + tcx: TyCtxt<'tcx>, + region_vid: RegionVid, + start_point: Location, +) -> Option { + let mut uf = UseFinder { body, regioncx, tcx, region_vid, start_point }; + + uf.find() +} + +struct UseFinder<'cx, 'tcx> { + body: &'cx Body<'tcx>, + regioncx: &'cx Rc>, + tcx: TyCtxt<'tcx>, + region_vid: RegionVid, + start_point: Location, +} + +impl<'cx, 'tcx> UseFinder<'cx, 'tcx> { + fn find(&mut self) -> Option { + let mut queue = VecDeque::new(); + let mut visited = FxHashSet::default(); + + queue.push_back(self.start_point); + while let Some(p) = queue.pop_front() { + if !self.regioncx.region_contains(self.region_vid, p) { + continue; + } + + if !visited.insert(p) { + continue; + } + + let block_data = &self.body[p.block]; + + match self.def_use(p, block_data.visitable(p.statement_index)) { + Some(DefUseResult::Def) => {} + + Some(DefUseResult::UseLive { local }) => { + return Some(Cause::LiveVar(local, p)); + } + + Some(DefUseResult::UseDrop { local }) => { + return Some(Cause::DropVar(local, p)); + } + + None => { + if p.statement_index < block_data.statements.len() { + queue.push_back(p.successor_within_block()); + } else { + queue.extend( + block_data + .terminator() + .successors() + .filter(|&bb| Some(&Some(bb)) != block_data.terminator().unwind()) + .map(|bb| Location { statement_index: 0, block: bb }), + ); + } + } + } + } + + None + } + + fn def_use(&self, location: Location, thing: &dyn MirVisitable<'tcx>) -> Option { + let mut visitor = DefUseVisitor { + body: self.body, + tcx: self.tcx, + region_vid: self.region_vid, + def_use_result: None, + }; + + thing.apply(location, &mut visitor); + + visitor.def_use_result + } +} + +struct DefUseVisitor<'cx, 'tcx> { + body: &'cx Body<'tcx>, + tcx: TyCtxt<'tcx>, + region_vid: RegionVid, + def_use_result: Option, +} + +enum DefUseResult { + Def, + UseLive { local: Local }, + UseDrop { local: Local }, +} + +impl<'cx, 'tcx> Visitor<'tcx> for DefUseVisitor<'cx, 'tcx> { + fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) { + let local_ty = self.body.local_decls[local].ty; + + let mut found_it = false; + self.tcx.for_each_free_region(&local_ty, |r| { + if r.to_region_vid() == self.region_vid { + found_it = true; + } + }); + + if found_it { + self.def_use_result = match 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 { + 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 { + 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 { + 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 { + 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, + including_tuple_field: IncludingTupleField, + ) -> Option { + 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, + 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, + /// 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 { + 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) { + 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) { + 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, + kind_desc: impl Into, + ) { + 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(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 { + 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, 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>, + 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, + }, + // Part of a value expression can't be moved from, + // e.g., match &String::new() { &x => (), } + MovesFromValue { + original_path: Place<'tcx>, + span: Span, + move_from: MovePathIndex, + kind: IllegalMoveOriginKind<'tcx>, + binds_to: Vec, + }, + // Everything that isn't from pattern matching. + OtherIllegalMove { + original_path: Place<'tcx>, + use_spans: UseSpans<'tcx>, + kind: IllegalMoveOriginKind<'tcx>, + }, +} + +impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { + pub(crate) fn report_move_errors(&mut self, move_errors: Vec<(Place<'tcx>, MoveError<'tcx>)>) { + let grouped_errors = self.group_move_errors(move_errors); + for error in grouped_errors { + self.report(error); + } + } + + fn group_move_errors( + &self, + errors: Vec<(Place<'tcx>, MoveError<'tcx>)>, + ) -> Vec> { + let mut grouped_errors = Vec::new(); + for (original_path, error) in errors { + self.append_to_grouped_errors(&mut grouped_errors, original_path, error); + } + grouped_errors + } + + fn append_to_grouped_errors( + &self, + grouped_errors: &mut Vec>, + original_path: Place<'tcx>, + error: MoveError<'tcx>, + ) { + match error { + MoveError::UnionMove { .. } => { + unimplemented!("don't know how to report union move errors yet.") + } + MoveError::IllegalMove { cannot_move_out_of: IllegalMoveOrigin { location, kind } } => { + // Note: that the only time we assign a place isn't a temporary + // to a user variable is when initializing it. + // If that ever stops being the case, then the ever initialized + // flow could be used. + if let Some(StatementKind::Assign(box ( + place, + Rvalue::Use(Operand::Move(move_from)), + ))) = self.body.basic_blocks()[location.block] + .statements + .get(location.statement_index) + .map(|stmt| &stmt.kind) + { + if let Some(local) = place.as_local() { + let local_decl = &self.body.local_decls[local]; + // opt_match_place is the + // match_span is the span of the expression being matched on + // match *x.y { ... } match_place is Some(*x.y) + // ^^^^ match_span is the span of *x.y + // + // opt_match_place is None for let [mut] x = ... statements, + // whether or not the right-hand side is a place expression + if let Some(box LocalInfo::User(ClearCrossCrate::Set(BindingForm::Var( + VarBindingForm { + opt_match_place: Some((opt_match_place, match_span)), + binding_mode: _, + opt_ty_info: _, + pat_span: _, + }, + )))) = local_decl.local_info + { + let stmt_source_info = self.body.source_info(location); + self.append_binding_error( + grouped_errors, + kind, + original_path, + *move_from, + local, + opt_match_place, + match_span, + stmt_source_info.span, + ); + return; + } + } + } + + let move_spans = self.move_spans(original_path.as_ref(), location); + grouped_errors.push(GroupedMoveError::OtherIllegalMove { + use_spans: move_spans, + original_path, + kind, + }); + } + } + } + + fn append_binding_error( + &self, + grouped_errors: &mut Vec>, + kind: IllegalMoveOriginKind<'tcx>, + original_path: Place<'tcx>, + move_from: Place<'tcx>, + bind_to: Local, + match_place: Option>, + match_span: Span, + statement_span: Span, + ) { + debug!("append_binding_error(match_place={:?}, match_span={:?})", match_place, match_span); + + let from_simple_let = match_place.is_none(); + let match_place = match_place.unwrap_or(move_from); + + match self.move_data.rev_lookup.find(match_place.as_ref()) { + // Error with the match place + LookupResult::Parent(_) => { + for ge in &mut *grouped_errors { + if let GroupedMoveError::MovesFromPlace { span, binds_to, .. } = ge + && match_span == *span + { + debug!("appending local({:?}) to list", bind_to); + if !binds_to.is_empty() { + binds_to.push(bind_to); + } + return; + } + } + debug!("found a new move error location"); + + // Don't need to point to x in let x = ... . + let (binds_to, span) = if from_simple_let { + (vec![], statement_span) + } else { + (vec![bind_to], match_span) + }; + grouped_errors.push(GroupedMoveError::MovesFromPlace { + span, + move_from, + original_path, + kind, + binds_to, + }); + } + // Error with the pattern + LookupResult::Exact(_) => { + let LookupResult::Parent(Some(mpi)) = self.move_data.rev_lookup.find(move_from.as_ref()) else { + // move_from should be a projection from match_place. + unreachable!("Probably not unreachable..."); + }; + for ge in &mut *grouped_errors { + if let GroupedMoveError::MovesFromValue { + span, + move_from: other_mpi, + binds_to, + .. + } = ge + { + if match_span == *span && mpi == *other_mpi { + debug!("appending local({:?}) to list", bind_to); + binds_to.push(bind_to); + return; + } + } + } + debug!("found a new move error location"); + grouped_errors.push(GroupedMoveError::MovesFromValue { + span: match_span, + move_from: mpi, + original_path, + kind, + binds_to: vec![bind_to], + }); + } + }; + } + + fn report(&mut self, error: GroupedMoveError<'tcx>) { + let (mut err, err_span) = { + let (span, use_spans, original_path, kind) = match error { + GroupedMoveError::MovesFromPlace { span, original_path, ref kind, .. } + | GroupedMoveError::MovesFromValue { span, original_path, ref kind, .. } => { + (span, None, original_path, kind) + } + GroupedMoveError::OtherIllegalMove { use_spans, original_path, ref kind } => { + (use_spans.args_or_use(), Some(use_spans), original_path, kind) + } + }; + debug!( + "report: original_path={:?} span={:?}, kind={:?} \ + original_path.is_upvar_field_projection={:?}", + original_path, + span, + kind, + self.is_upvar_field_projection(original_path.as_ref()) + ); + ( + match kind { + &IllegalMoveOriginKind::BorrowedContent { target_place } => self + .report_cannot_move_from_borrowed_content( + original_path, + target_place, + span, + use_spans, + ), + &IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => { + self.cannot_move_out_of_interior_of_drop(span, ty) + } + &IllegalMoveOriginKind::InteriorOfSliceOrArray { ty, is_index } => { + self.cannot_move_out_of_interior_noncopy(span, ty, Some(is_index)) + } + }, + span, + ) + }; + + self.add_move_hints(error, &mut err, err_span); + self.buffer_error(err); + } + + fn report_cannot_move_from_static( + &mut self, + place: Place<'tcx>, + span: Span, + ) -> DiagnosticBuilder<'a, ErrorGuaranteed> { + let description = if place.projection.len() == 1 { + format!("static item {}", self.describe_any_place(place.as_ref())) + } else { + let base_static = PlaceRef { local: place.local, projection: &[ProjectionElem::Deref] }; + + format!( + "{} as {} is a static item", + self.describe_any_place(place.as_ref()), + self.describe_any_place(base_static), + ) + }; + + self.cannot_move_out_of(span, &description) + } + + fn report_cannot_move_from_borrowed_content( + &mut self, + move_place: Place<'tcx>, + deref_target_place: Place<'tcx>, + span: Span, + use_spans: Option>, + ) -> DiagnosticBuilder<'a, ErrorGuaranteed> { + // Inspect the type of the content behind the + // borrow to provide feedback about why this + // was a move rather than a copy. + let ty = deref_target_place.ty(self.body, self.infcx.tcx).ty; + let upvar_field = self + .prefixes(move_place.as_ref(), PrefixSet::All) + .find_map(|p| self.is_upvar_field_projection(p)); + + let deref_base = match deref_target_place.projection.as_ref() { + [proj_base @ .., ProjectionElem::Deref] => { + PlaceRef { local: deref_target_place.local, projection: &proj_base } + } + _ => bug!("deref_target_place is not a deref projection"), + }; + + if let PlaceRef { local, projection: [] } = deref_base { + let decl = &self.body.local_decls[local]; + if decl.is_ref_for_guard() { + let mut err = self.cannot_move_out_of( + span, + &format!("`{}` in pattern guard", self.local_names[local].unwrap()), + ); + err.note( + "variables bound in patterns cannot be moved from \ + until after the end of the pattern guard", + ); + return err; + } else if decl.is_ref_to_static() { + return self.report_cannot_move_from_static(move_place, span); + } + } + + debug!("report: ty={:?}", ty); + let mut err = match ty.kind() { + ty::Array(..) | ty::Slice(..) => { + self.cannot_move_out_of_interior_noncopy(span, ty, None) + } + ty::Closure(def_id, closure_substs) + if def_id.as_local() == Some(self.mir_def_id()) && upvar_field.is_some() => + { + let closure_kind_ty = closure_substs.as_closure().kind_ty(); + let closure_kind = match closure_kind_ty.to_opt_closure_kind() { + Some(kind @ (ty::ClosureKind::Fn | ty::ClosureKind::FnMut)) => kind, + Some(ty::ClosureKind::FnOnce) => { + bug!("closure kind does not match first argument type") + } + None => bug!("closure kind not inferred by borrowck"), + }; + let capture_description = + format!("captured variable in an `{closure_kind}` closure"); + + let upvar = &self.upvars[upvar_field.unwrap().index()]; + let upvar_hir_id = upvar.place.get_root_variable(); + let upvar_name = upvar.place.to_string(self.infcx.tcx); + let upvar_span = self.infcx.tcx.hir().span(upvar_hir_id); + + let place_name = self.describe_any_place(move_place.as_ref()); + + let place_description = + if self.is_upvar_field_projection(move_place.as_ref()).is_some() { + format!("{place_name}, a {capture_description}") + } else { + format!("{place_name}, as `{upvar_name}` is a {capture_description}") + }; + + debug!( + "report: closure_kind_ty={:?} closure_kind={:?} place_description={:?}", + closure_kind_ty, closure_kind, place_description, + ); + + let mut diag = self.cannot_move_out_of(span, &place_description); + + diag.span_label(upvar_span, "captured outer variable"); + diag.span_label( + self.body.span, + format!("captured by this `{closure_kind}` closure"), + ); + + diag + } + _ => { + let source = self.borrowed_content_source(deref_base); + let move_place_ref = move_place.as_ref(); + match ( + self.describe_place_with_options( + move_place_ref, + DescribePlaceOpt { + including_downcast: false, + including_tuple_field: false, + }, + ), + self.describe_name(move_place_ref), + source.describe_for_named_place(), + ) { + (Some(place_desc), Some(name), Some(source_desc)) => self.cannot_move_out_of( + span, + &format!("`{place_desc}` as enum variant `{name}` which is behind a {source_desc}"), + ), + (Some(place_desc), Some(name), None) => self.cannot_move_out_of( + span, + &format!("`{place_desc}` as enum variant `{name}`"), + ), + (Some(place_desc), _, Some(source_desc)) => self.cannot_move_out_of( + span, + &format!("`{place_desc}` which is behind a {source_desc}"), + ), + (_, _, _) => self.cannot_move_out_of( + span, + &source.describe_for_unnamed_place(self.infcx.tcx), + ), + } + } + }; + if let Some(use_spans) = use_spans { + self.explain_captures( + &mut err, span, span, use_spans, move_place, None, "", "", "", false, true, + ); + } + err + } + + fn add_move_hints(&self, error: GroupedMoveError<'tcx>, err: &mut Diagnostic, span: Span) { + match error { + GroupedMoveError::MovesFromPlace { mut binds_to, move_from, .. } => { + if let Ok(snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(span) { + err.span_suggestion( + span, + "consider borrowing here", + format!("&{snippet}"), + Applicability::Unspecified, + ); + } + + if binds_to.is_empty() { + let place_ty = move_from.ty(self.body, self.infcx.tcx).ty; + let place_desc = match self.describe_place(move_from.as_ref()) { + Some(desc) => format!("`{desc}`"), + None => "value".to_string(), + }; + + self.note_type_does_not_implement_copy( + err, + &place_desc, + place_ty, + Some(span), + "", + ); + } else { + binds_to.sort(); + binds_to.dedup(); + + self.add_move_error_details(err, &binds_to); + } + } + GroupedMoveError::MovesFromValue { mut binds_to, .. } => { + binds_to.sort(); + binds_to.dedup(); + self.add_move_error_suggestions(err, &binds_to); + self.add_move_error_details(err, &binds_to); + } + // No binding. Nothing to suggest. + GroupedMoveError::OtherIllegalMove { ref original_path, use_spans, .. } => { + let span = use_spans.var_or_use(); + let place_ty = original_path.ty(self.body, self.infcx.tcx).ty; + let place_desc = match self.describe_place(original_path.as_ref()) { + Some(desc) => format!("`{desc}`"), + None => "value".to_string(), + }; + self.note_type_does_not_implement_copy(err, &place_desc, place_ty, Some(span), ""); + + use_spans.args_span_label(err, format!("move out of {place_desc} occurs here")); + } + } + } + + fn add_move_error_suggestions(&self, err: &mut Diagnostic, binds_to: &[Local]) { + let mut suggestions: Vec<(Span, &str, String)> = Vec::new(); + for local in binds_to { + let bind_to = &self.body.local_decls[*local]; + if let Some(box LocalInfo::User(ClearCrossCrate::Set(BindingForm::Var( + VarBindingForm { pat_span, .. }, + )))) = bind_to.local_info + { + if let Ok(pat_snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(pat_span) + { + if let Some(stripped) = pat_snippet.strip_prefix('&') { + let pat_snippet = stripped.trim_start(); + let (suggestion, to_remove) = if pat_snippet.starts_with("mut") + && pat_snippet["mut".len()..].starts_with(rustc_lexer::is_whitespace) + { + (pat_snippet["mut".len()..].trim_start(), "&mut") + } else { + (pat_snippet, "&") + }; + suggestions.push((pat_span, to_remove, suggestion.to_owned())); + } + } + } + } + suggestions.sort_unstable_by_key(|&(span, _, _)| span); + suggestions.dedup_by_key(|&mut (span, _, _)| span); + for (span, to_remove, suggestion) in suggestions { + err.span_suggestion( + span, + &format!("consider removing the `{to_remove}`"), + suggestion, + Applicability::MachineApplicable, + ); + } + } + + fn add_move_error_details(&self, err: &mut Diagnostic, binds_to: &[Local]) { + for (j, local) in binds_to.iter().enumerate() { + let bind_to = &self.body.local_decls[*local]; + let binding_span = bind_to.source_info.span; + + if j == 0 { + err.span_label(binding_span, "data moved here"); + } else { + err.span_label(binding_span, "...and here"); + } + + if binds_to.len() == 1 { + self.note_type_does_not_implement_copy( + err, + &format!("`{}`", self.local_names[*local].unwrap()), + bind_to.ty, + Some(binding_span), + "", + ); + } + } + + if binds_to.len() > 1 { + err.note( + "move occurs because these variables have types that \ + don't implement the `Copy` trait", + ); + } + } +} 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) { + 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::>(); + 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::>(); + 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 { + 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) -> 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, + opt_ty_info: Option, +) -> (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 { + // 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 { + 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>, +} + +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 { + 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::>(); + + // 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::>(); + 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>; + +#[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> { + 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 { + 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, + ) -> 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` 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(¬e); + diag.help("see 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) { + /// | - `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 + '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, + 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 { + 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 { + 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 { + 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 { + 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(items: &[T]) -> Box> { + /// | - 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 { + 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 { + // 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 + // ``` + // + // 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 { + 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 { + 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`, + /// 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 { + // 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 { + 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>, + upvars: &[Upvar<'tcx>], + fr: RegionVid, + ) -> Option<(Option, Span)> { + debug!("get_var_name_and_span_for_region(fr={:?})", fr); + assert!(self.universal_regions().is_universal_region(fr)); + + debug!("get_var_name_and_span_for_region: attempting upvar"); + self.get_upvar_index_for_region(tcx, fr) + .map(|index| { + // 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 { + 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 { + 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>, + argument_index: usize, + ) -> (Option, Span) { + let implicit_inputs = self.universal_regions().defining_ty.implicit_inputs(); + let argument_local = Local::new(implicit_inputs + argument_index + 1); + debug!("get_argument_name_and_span_for_region: argument_local={:?}", argument_local); + + let argument_name = local_names[argument_local]; + let argument_span = body.local_decls[argument_local].source_info.span; + debug!( + "get_argument_name_and_span_for_region: argument_name={:?} argument_span={:?}", + argument_name, argument_span + ); + + (argument_name, argument_span) + } +} diff --git a/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; + +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, + location_table: &LocationTable, + ) -> Result<(), Box>; +} + +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, + location_table: &LocationTable, + ) -> Result<(), Box> { + 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(&self, rows: &[T], file_name: &str) -> Result<(), Box> + 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>; +} + +impl FactRow for RegionVid { + fn write( + &self, + out: &mut dyn Write, + location_table: &LocationTable, + ) -> Result<(), Box> { + write_row(out, location_table, &[self]) + } +} + +impl FactRow for (A, B) +where + A: FactCell, + B: FactCell, +{ + fn write( + &self, + out: &mut dyn Write, + location_table: &LocationTable, + ) -> Result<(), Box> { + write_row(out, location_table, &[&self.0, &self.1]) + } +} + +impl FactRow for (A, B, C) +where + A: FactCell, + B: FactCell, + C: FactCell, +{ + fn write( + &self, + out: &mut dyn Write, + location_table: &LocationTable, + ) -> Result<(), Box> { + write_row(out, location_table, &[&self.0, &self.1, &self.2]) + } +} + +impl 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> { + 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> { + 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 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, + 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, + 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, +) -> &'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>, + return_body_with_facts: bool, +) -> (BorrowCheckResult<'tcx>, Option>>) { + 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, ®ioncx, &opt_closure_req); + + // We also have a `#[rustc_regions]` annotation that causes us to dump + // information. + nll::dump_annotation( + infcx, + &body, + ®ioncx, + &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, ®ioncx, &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(®ioncx), + 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 = 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, + /// 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>, + /// 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, + /// 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>, + /// 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, + /// 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>, + + /// The set of borrows extracted from the MIR + borrow_set: Rc>, + + /// Dominators for MIR + dominators: Dominators, + + /// Information about upvars not necessarily preserved in types or MIR + upvars: Vec>, + + /// Names of local (user) variables (extracted from `var_debug_info`). + local_names: IndexVec>, + + /// 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>, + + /// The counter for generating new region names. + next_region_name: RefCell, + + /// Results of Polonius analysis. + polonius_output: Option>, + + 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), + + /// 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, + 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 { + // 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 { + 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, 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` 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: 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 { + 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, (PlaceRef<'tcx>, DiagnosticBuilder<'tcx, ErrorGuaranteed>)>, + /// Diagnostics to be reported buffer. + buffered: Vec, + /// Set to Some if we emit an error during borrowck + tainted_by_errors: Option, + } + + 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, + 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 { + // 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, +} + +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 { + (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, + + /// 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>, + + /// 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, +} + +/// Represents a `R0 member of [R1..Rn]` constraint +pub(crate) struct NllMemberConstraint<'tcx> { + next_constraint: Option, + + /// 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( + 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 + 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 + Captures<'tcx> + '_ { + let mut next = self.first_constraints.get(&member_region_vid).cloned(); + std::iter::from_fn(move || -> Option { + 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 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>, + 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; + +/// 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>, + pub polonius_input: Option>, + pub polonius_output: Option>, + pub opt_closure_req: Option>, + 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>, +) -> 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>, + 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>, +) { + 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>, + opaque_type_values: &VecMap>, + 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, +{ + 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, + 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 { + 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) { + // 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>, +} + +#[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 { + 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 = + 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> { + 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 = 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>, +} + +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> { + 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 = 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>, + + /// 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, + + /// The outlives constraints computed by the type-check. + constraints: Frozen>, + + /// 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, + + /// 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>, + + /// 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>, + + /// The "R0 member of [R1..Rn]" constraints, indexed by SCC. + member_constraints: Rc>, + + /// Records the member constraints that we applied to each scc. + /// This is useful for error reporting. Once constraint + /// propagation is done, this vector is sorted according to + /// `member_region_scc`. + member_constraints_applied: Vec, + + /// Map closure bounds to a `Span` that should be used for error reporting. + closure_bounds_mapping: + FxHashMap, Span)>>, + + /// Map universe indexes to information on why we created it. + universe_causes: FxHashMap>, + + /// Contains the minimum universe of any variable within the same + /// SCC. We will ensure that no SCC contains values that are not + /// visible from this index. + scc_universes: IndexVec, + + /// 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, + + /// 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, + + /// Type constraints that we check after solving. + type_tests: Vec>, + + /// Information about the universally quantified regions in scope + /// on this function. + universal_regions: Rc>, + + /// Information about how the universally quantified regions in + /// scope on this function relate to one another. + universal_region_relations: Frozen>, +} + +/// 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>, +} + +/// 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 `::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 `>::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>, + placeholder_indices: Rc, + universal_region_relations: Frozen>, + outlives_constraints: OutlivesConstraintSet<'tcx>, + member_constraints_in: MemberConstraintSet<'tcx, RegionVid>, + closure_bounds_mapping: FxHashMap< + Location, + FxHashMap<(RegionVid, RegionVid), (ConstraintCategory<'tcx>, Span)>, + >, + universe_causes: FxHashMap>, + type_tests: Vec>, + liveness_constraints: LivenessValues, + elements: &Rc, + ) -> 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, + definitions: &IndexVec>, + ) -> IndexVec { + 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, + definitions: &IndexVec>, + ) -> IndexVec { + 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 + '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>, + ) -> (Option>, 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::>() + }); + + // 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 = 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 { + 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>>, + 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>, + ) -> 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> { + 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 + /// >::Item: '1 + /// ``` + /// + /// In particular, this routine winds up being important when + /// there are bounds like `where >::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(&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>>, + 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: + /// + /// + /// 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>>, + errors_buffer: &mut RegionErrors<'tcx>, + polonius_output: Rc, + ) { + 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>>, + 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>>, + ) -> 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>>, + ) -> 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>, 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::>() + ); + + // 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> = 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>; +} + +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> { + 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, (OpaqueHiddenType<'tcx>, OpaqueTyOrigin)>, + ) -> VecMap> { + let mut result: VecMap> = 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` mapped to `(X, Y)` + // and `OpaqueType` 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(&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>> = + 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 = >::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() -> impl Trait + // + // into + // + // type Foo + // fn foo() -> Foo. + // + // For lifetime parameters we convert + // + // fn foo<'l0..'ln>() -> impl Trait<'l0..'lm> + // + // into + // + // type foo::<'p0..'pn>::Foo<'q0..'qm> + // fn foo() -> 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` 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>>, + map_missing_regions_to_empty: bool, + + /// initially `Some`, set to `None` once error has been reported + hidden_ty: Option>, + + /// Span of function being checked. + span: Span, +} + +impl<'tcx> ReverseMapper<'tcx> { + fn new( + tcx: TyCtxt<'tcx>, + key: ty::OpaqueTypeKey<'tcx>, + map: FxHashMap, 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, + /// For each SCC, the range of `universal_regions` that use that SCC as + /// their value. + scc_regions: FxHashMap>, + /// All of the universal regions, in grouped so that `scc_regions` can + /// index into here. + universal_regions: Vec, +} + +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 + '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 { + 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, + + /// Map backward from each point to the basic block that it + /// belongs to. + basic_blocks: IndexVec, + + num_points: usize, +} + +impl RegionValueElements { + pub(crate) fn new(body: &Body<'_>) -> Self { + let mut num_points = 0; + let statements_before_block: IndexVec = 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 { + elements: Rc, + points: SparseIntervalMatrix, +} + +impl LivenessValues { + /// 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) -> 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 { + 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) -> 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 + '_ { + 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, +} + +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 { + elements: Rc, + placeholder_indices: Rc, + points: SparseIntervalMatrix, + free_regions: SparseBitMatrix, + + /// Placeholders represent bound regions -- so something like `'a` + /// in for<'a> fn(&'a u32)`. + placeholders: SparseBitMatrix, +} + +impl RegionValues { + /// 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, + num_universal_regions: usize, + placeholder_indices: &Rc, + ) -> 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(&mut self, to: N, from: M, values: &LivenessValues) { + 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 + '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 + '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 + '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 + '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(self, values: &mut RegionValues, row: N) -> bool; + + fn contained_in_row(self, values: &RegionValues, row: N) -> bool; +} + +impl ToElementIndex for Location { + fn add_to_row(self, values: &mut RegionValues, row: N) -> bool { + let index = values.elements.point_from_location(self); + values.points.insert(row, index) + } + + fn contained_in_row(self, values: &RegionValues, row: N) -> bool { + let index = values.elements.point_from_location(self); + values.points.contains(row, index) + } +} + +impl ToElementIndex for RegionVid { + fn add_to_row(self, values: &mut RegionValues, row: N) -> bool { + values.free_regions.insert(row, self) + } + + fn contained_in_row(self, values: &RegionValues, row: N) -> bool { + values.free_regions.contains(row, self) + } +} + +impl ToElementIndex for ty::PlaceholderRegion { + fn add_to_row(self, values: &mut RegionValues, row: N) -> bool { + let index = values.placeholder_indices.lookup_index(self); + values.placeholders.insert(row, index) + } + + fn contained_in_row(self, values: &RegionValues, 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, +) -> 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) -> 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>, +) { + 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(&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, + #[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( + &mut self, + locations: Locations, + category: ConstraintCategory<'tcx>, + op: Op, + ) -> Fallible + 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( + &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>, + 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(&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>, + + /// Stores the outlives relations that are known to hold from the + /// implied bounds, in-scope where-clauses, and that sort of + /// thing. + outlives: TransitiveRelation, + + /// 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, +} + +/// 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>; + +pub(crate) struct CreateResult<'tcx> { + pub(crate) universal_region_relations: Frozen>, + 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>, + 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 { + 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 { + 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, + fr0: RegionVid, + ) -> Vec { + // 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 { + 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 + '_ { + self.outlives.base_edges() + } +} + +struct UniversalRegionRelationsBuilder<'this, 'tcx> { + infcx: &'this InferCtxt<'this, 'tcx>, + param_env: ty::ParamEnv<'tcx>, + universal_regions: Rc>, + 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(&mut self, outlives_bounds: I) + where + I: IntoIterator>, + { + 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> { + 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>, + + /// 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>, + + /// 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>, + + appearances: IndexVec, +} + +struct Appearance { + point_index: PointIndex, + next: Option, +} + +rustc_index::newtype_index! { + pub struct AppearanceIndex { .. } +} + +impl vll::LinkElem for Appearance { + type LinkIndex = AppearanceIndex; + + fn next(elem: &Self) -> Option { + 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 = + 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 + '_ { + 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 + '_ { + 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 + '_ { + 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, +} + +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, + appearances: &mut IndexVec, + 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, + 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, + body: &Body<'tcx>, +) -> (Vec, Vec) { + 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 { + // 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 { + 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, + flow_inits: &mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>, + move_data: &MoveData<'tcx>, + relevant_live_locals: Vec, + boring_locals: Vec, + polonius_drop_used: Option>, +) { + 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, 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, + + /// 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, + + /// 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, + + /// Locations where drops may occur. + drop_locations: Vec, + + /// Stack used when doing (reverse) DFS. + stack: Vec, +} + +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) { + 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) { + 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, + ) { + 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, + ) { + 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, + ) { + 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, + ) { + 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>, + universal_regions: &Rc>, + location_table: &LocationTable, + borrow_set: &BorrowSet<'tcx>, + all_facts: &mut Option, + flow_inits: &mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>, + move_data: &MoveData<'tcx>, + elements: &Rc, + 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, + ®ion_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>, + 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>, + 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>, + ) -> 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, 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, + 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>, + pub(crate) opaque_type_values: + VecMap, (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>, + + /// 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, + + pub(crate) outlives_constraints: OutlivesConstraintSet<'tcx>, + + pub(crate) member_constraints: MemberConstraintSet<'tcx, RegionVid>, + + pub(crate) closure_bounds_mapping: + FxHashMap, Span)>>, + + pub(crate) universe_causes: FxHashMap>, + + pub(crate) type_tests: Vec>, +} + +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 { + 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, + 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, 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 { + 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, + pub region_constraints: Option>, + pub obligations: Vec>, +} + +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> { + 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>, +} + +/// 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> + '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, 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, + 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> { + 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 { + 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 { + (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, 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, + 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>> { + 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( + &self, + origin: NllRegionVariableOrigin, + value: T, + ) -> T + where + T: TypeFoldable<'tcx>; + + fn replace_bound_regions_with_nll_infer_vars( + &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( + &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( + &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(&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 ®ion_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, + mut never_initialized_mut_locals: FxHashSet, + ) { + { + 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, + never_initialized_mut_locals: &'visit mut FxHashSet, + 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); + } + } + } + } +} -- cgit v1.2.3