diff --git a/compiler/rustc_borrowck/src/consumers.rs b/compiler/rustc_borrowck/src/consumers.rs index 548973714105b..a233bb3dd3765 100644 --- a/compiler/rustc_borrowck/src/consumers.rs +++ b/compiler/rustc_borrowck/src/consumers.rs @@ -16,8 +16,8 @@ pub use super::polonius::legacy::{ PoloniusFacts as PoloniusInput, PoloniusLocationTable, PoloniusOutput, PoloniusRegionVid, RichLocation, RustcFacts, }; -pub use super::region_infer::RegionInferenceContext; use crate::BorrowCheckRootCtxt; +use crate::region_infer::InferredRegions; /// Struct used during mir borrowck to collect bodies with facts for a typeck root and all /// its nested bodies. @@ -59,15 +59,15 @@ impl<'tcx> BorrowckConsumer<'tcx> { #[derive(Debug, Copy, Clone)] pub enum ConsumerOptions { /// Retrieve the [`Body`] along with the [`BorrowSet`] - /// and [`RegionInferenceContext`]. If you would like the body only, use + /// and [`InferredRegions`]. If you would like the body only, use /// [`TyCtxt::mir_promoted`]. /// /// These can be used in conjunction with [`calculate_borrows_out_of_scope_at_location`]. - RegionInferenceContext, + InferredRegions, /// The recommended option. Retrieves the maximal amount of information /// without significant slowdowns. /// - /// Implies [`RegionInferenceContext`](ConsumerOptions::RegionInferenceContext), + /// Implies [`InferredRegions`](ConsumerOptions::InferredRegions), /// and additionally retrieve the [`PoloniusLocationTable`] and [`PoloniusInput`] that /// would be given to Polonius. Critically, this does not run Polonius, which /// one may want to avoid due to performance issues on large bodies. @@ -89,9 +89,10 @@ pub struct BodyWithBorrowckFacts<'tcx> { pub promoted: IndexVec>, /// The set of borrows occurring in `body` with data about them. pub borrow_set: BorrowSet<'tcx>, - /// Context generated during borrowck, intended to be passed to + /// The inferred region values. These are included because they + /// are necessary as input to /// [`calculate_borrows_out_of_scope_at_location`]. - pub region_inference_context: RegionInferenceContext<'tcx>, + pub inferred_regions: InferredRegions<'tcx>, /// The table that maps Polonius points to locations in the table. /// Populated when using [`ConsumerOptions::PoloniusInputFacts`] /// or [`ConsumerOptions::PoloniusOutputFacts`]. diff --git a/compiler/rustc_borrowck/src/dataflow.rs b/compiler/rustc_borrowck/src/dataflow.rs index 813ceaeb8da9f..08d92c60e4c28 100644 --- a/compiler/rustc_borrowck/src/dataflow.rs +++ b/compiler/rustc_borrowck/src/dataflow.rs @@ -1,5 +1,6 @@ use std::fmt; +use either::Either; use rustc_data_structures::fx::FxIndexMap; use rustc_index::bit_set::{DenseBitSet, MixedBitSet}; use rustc_middle::mir::{ @@ -11,10 +12,13 @@ use rustc_mir_dataflow::impls::{ EverInitializedPlaces, EverInitializedPlacesDomain, MaybeUninitializedPlaces, MaybeUninitializedPlacesDomain, }; +use rustc_mir_dataflow::points::{DenseLocationMap, PointIndex}; use rustc_mir_dataflow::{Analysis, GenKill, JoinSemiLattice}; use tracing::debug; -use crate::{BorrowSet, PlaceConflictBias, PlaceExt, RegionInferenceContext, places_conflict}; +use crate::polonius::{LiveLoans, PoloniusDiagnosticsContext}; +use crate::region_infer::InferredRegions; +use crate::{BorrowSet, PlaceConflictBias, PlaceExt, places_conflict}; // This analysis is different to most others. Its results aren't computed with // `iterate_to_fixpoint`, but are instead composed from the results of three sub-analyses that are @@ -182,27 +186,25 @@ struct OutOfScopePrecomputer<'a, 'tcx> { visited: DenseBitSet, visit_stack: Vec, body: &'a Body<'tcx>, - regioncx: &'a RegionInferenceContext<'tcx>, borrows_out_of_scope_at_location: FxIndexMap>, } impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> { fn compute( body: &Body<'tcx>, - regioncx: &RegionInferenceContext<'tcx>, + scc_values: &InferredRegions<'_>, borrow_set: &BorrowSet<'tcx>, ) -> FxIndexMap> { let mut prec = OutOfScopePrecomputer { visited: DenseBitSet::new_empty(body.basic_blocks.len()), visit_stack: vec![], body, - regioncx, borrows_out_of_scope_at_location: FxIndexMap::default(), }; for (borrow_index, borrow_data) in borrow_set.iter_enumerated() { let borrow_region = borrow_data.region; let location = borrow_data.reserve_location; - prec.precompute_borrows_out_of_scope(borrow_index, borrow_region, location); + prec.precompute_borrows_out_of_scope(scc_values, borrow_index, borrow_region, location); } prec.borrows_out_of_scope_at_location @@ -210,6 +212,7 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> { fn precompute_borrows_out_of_scope( &mut self, + scc_values: &InferredRegions<'_>, borrow_index: BorrowIndex, borrow_region: RegionVid, first_location: Location, @@ -222,12 +225,9 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> { let first_lo = first_location.statement_index; let first_hi = first_bb_data.statements.len(); - if let Some(kill_stmt) = self.regioncx.first_non_contained_inclusive( - borrow_region, - first_block, - first_lo, - first_hi, - ) { + if let Some(kill_stmt) = + scc_values.first_non_contained_inclusive(borrow_region, first_block, first_lo, first_hi) + { let kill_location = Location { block: first_block, statement_index: kill_stmt }; // If region does not contain a point at the location, then add to list and skip // successor locations. @@ -255,7 +255,7 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> { let bb_data = &self.body[block]; let num_stmts = bb_data.statements.len(); if let Some(kill_stmt) = - self.regioncx.first_non_contained_inclusive(borrow_region, block, 0, num_stmts) + scc_values.first_non_contained_inclusive(borrow_region, block, 0, num_stmts) { let kill_location = Location { block, statement_index: kill_stmt }; // If region does not contain a point at the location, then add to list and skip @@ -285,25 +285,26 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> { // This is `pub` because it's used by unstable external borrowck data users, see `consumers.rs`. pub fn calculate_borrows_out_of_scope_at_location<'tcx>( body: &Body<'tcx>, - regioncx: &RegionInferenceContext<'tcx>, + scc_values: &InferredRegions<'_>, borrow_set: &BorrowSet<'tcx>, ) -> FxIndexMap> { - OutOfScopePrecomputer::compute(body, regioncx, borrow_set) + OutOfScopePrecomputer::compute(body, scc_values, borrow_set) } struct PoloniusOutOfScopePrecomputer<'a, 'tcx> { visited: DenseBitSet, visit_stack: Vec, body: &'a Body<'tcx>, - regioncx: &'a RegionInferenceContext<'tcx>, - + live_loans: &'a LiveLoans, + location_map: &'a DenseLocationMap, loans_out_of_scope_at_location: FxIndexMap>, } impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> { fn compute( body: &Body<'tcx>, - regioncx: &RegionInferenceContext<'tcx>, + polonius_diagnostics: &PoloniusDiagnosticsContext, + location_map: &DenseLocationMap, borrow_set: &BorrowSet<'tcx>, ) -> FxIndexMap> { // The in-tree polonius analysis computes loans going out of scope using the @@ -312,7 +313,8 @@ impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> { visited: DenseBitSet::new_empty(body.basic_blocks.len()), visit_stack: vec![], body, - regioncx, + live_loans: &polonius_diagnostics.live_loans, + location_map, loans_out_of_scope_at_location: FxIndexMap::default(), }; for (loan_idx, loan_data) in borrow_set.iter_enumerated() { @@ -412,7 +414,8 @@ impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> { // Reachability is location-insensitive, and we could take advantage of that, by jumping // to a further point than just the next statement: we can jump to the furthest point // within the block where `r` is live. - if self.regioncx.is_loan_live_at(loan_idx, location) { + let point = self.location_map.point_from_location(location); + if self.is_loan_live_at(loan_idx, point) { continue; } @@ -423,21 +426,31 @@ impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> { None } + + fn is_loan_live_at(&self, loan_idx: BorrowIndex, point: PointIndex) -> bool { + self.live_loans.contains(point, loan_idx) + } } impl<'a, 'tcx> Borrows<'a, 'tcx> { pub fn new( tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, - regioncx: &RegionInferenceContext<'tcx>, + inference_results: Either<&'a InferredRegions<'_>, &'a PoloniusDiagnosticsContext>, + location_map: &DenseLocationMap, borrow_set: &'a BorrowSet<'tcx>, ) -> Self { - let borrows_out_of_scope_at_location = - if !tcx.sess.opts.unstable_opts.polonius.is_next_enabled() { - calculate_borrows_out_of_scope_at_location(body, regioncx, borrow_set) - } else { - PoloniusOutOfScopePrecomputer::compute(body, regioncx, borrow_set) - }; + let borrows_out_of_scope_at_location = match inference_results { + Either::Left(inferred_regions) => { + calculate_borrows_out_of_scope_at_location(body, inferred_regions, borrow_set) + } + Either::Right(polonius_diagnostics) => PoloniusOutOfScopePrecomputer::compute( + body, + polonius_diagnostics, + location_map, + borrow_set, + ), + }; Borrows { tcx, body, borrow_set, borrows_out_of_scope_at_location } } diff --git a/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs index 6ed07cf9b1c8c..5abc81d304dac 100644 --- a/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs @@ -403,8 +403,8 @@ impl<'tcx> TypeOpInfo<'tcx> for crate::type_check::InstantiateOpaqueType<'tcx> { // started MIR borrowchecking with, so the region // constraints have already been taken. Use the data from // our `mbcx` instead. - |vid| RegionVariableOrigin::Nll(mbcx.regioncx.definitions[vid].origin), - |vid| mbcx.regioncx.definitions[vid].universe, + |vid| RegionVariableOrigin::Nll(mbcx.definitions[vid].origin), + |vid| mbcx.definitions[vid].universe, ) } } diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index 1582833bf15d4..25c29da5f719a 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -3454,7 +3454,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { let tcx = self.infcx.tcx; - let return_ty = self.regioncx.universal_regions().unnormalized_output_ty; + let return_ty = self.universal_regions().unnormalized_output_ty; // to avoid panics if let Some(iter_trait) = tcx.get_diagnostic_item(sym::Iterator) diff --git a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs index bbd0a8ae07108..40cd111b7b6d8 100644 --- a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs +++ b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs @@ -21,7 +21,7 @@ use super::{RegionName, UseSpans, find_use}; use crate::borrow_set::BorrowData; use crate::constraints::OutlivesConstraint; use crate::nll::ConstraintDescription; -use crate::region_infer::{BlameConstraint, Cause}; +use crate::region_infer::{BlameConstraint, Cause, ConstraintSearch}; use crate::{MirBorrowckCtxt, WriteKind}; #[derive(Debug)] @@ -572,14 +572,23 @@ fn suggest_rewrite_if_let( } } -impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { +impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> { + pub(crate) fn constraint_search<'mbc>(&'mbc self) -> ConstraintSearch<'mbc, 'tcx> { + ConstraintSearch { + definitions: self.definitions, + fr_static: self.universal_regions().fr_static, + constraint_graph: &self.constraint_graph, + constraints: &self.outlives_constraints, + } + } + fn free_region_constraint_info( &self, borrow_region: RegionVid, outlived_region: RegionVid, ) -> (ConstraintCategory<'tcx>, bool, Span, Option, Vec>) { - let (blame_constraint, path) = self.regioncx.best_blame_constraint( + let (blame_constraint, path) = self.constraint_search().best_blame_constraint( borrow_region, NllRegionVariableOrigin::FreeRegion, outlived_region, @@ -611,14 +620,17 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { borrow: &BorrowData<'tcx>, kind_place: Option<(WriteKind, Place<'tcx>)>, ) -> BorrowExplanation<'tcx> { - let regioncx = &self.regioncx; let body: &Body<'_> = self.body; let tcx = self.infcx.tcx; let borrow_region_vid = borrow.region; debug!(?borrow_region_vid); - let mut region_sub = self.regioncx.find_sub_region_live_at(borrow_region_vid, location); + let mut region_sub = self.constraint_search().find_sub_region_live_at( + &self.liveness_constraints, + borrow_region_vid, + location, + ); debug!(?region_sub); let mut use_location = location; @@ -630,11 +642,13 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { // loop iteration. In this case, try using the loop terminator location in // `find_sub_region_live_at`. if let Some(loop_terminator_location) = - regioncx.find_loop_terminator_location(borrow.region, body) + self.scc_values.find_loop_terminator_location(borrow.region, body) { - region_sub = self - .regioncx - .find_sub_region_live_at(borrow_region_vid, loop_terminator_location); + region_sub = self.constraint_search().find_sub_region_live_at( + &self.liveness_constraints, + borrow_region_vid, + loop_terminator_location, + ); debug!("explain_why_borrow_contains_point: region_sub in loop={:?}", region_sub); use_location = loop_terminator_location; use_in_later_iteration_of_loop = true; @@ -658,7 +672,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { false } }; - match find_use::find(body, regioncx, tcx, region_sub, use_location) { + match find_use::find(body, self.scc_values, tcx, region_sub, use_location) { Some(Cause::LiveVar(local, location)) if !is_local_boring(local) => { let span = body.source_info(location).span; let spans = self diff --git a/compiler/rustc_borrowck/src/diagnostics/find_use.rs b/compiler/rustc_borrowck/src/diagnostics/find_use.rs index 96f48840468e5..f5c31f38fa679 100644 --- a/compiler/rustc_borrowck/src/diagnostics/find_use.rs +++ b/compiler/rustc_borrowck/src/diagnostics/find_use.rs @@ -6,36 +6,36 @@ use rustc_middle::mir::{self, Body, Local, Location}; use rustc_middle::ty::{RegionVid, TyCtxt}; use crate::def_use::{self, DefUse}; -use crate::region_infer::{Cause, RegionInferenceContext}; +use crate::region_infer::{Cause, InferredRegions}; pub(crate) fn find<'tcx>( body: &Body<'tcx>, - regioncx: &RegionInferenceContext<'tcx>, + scc_values: &InferredRegions<'_>, tcx: TyCtxt<'tcx>, region_vid: RegionVid, start_point: Location, ) -> Option { - let mut uf = UseFinder { body, regioncx, tcx, region_vid, start_point }; + let mut uf = UseFinder { body, tcx, region_vid, start_point, scc_values: &scc_values }; uf.find() } -struct UseFinder<'a, 'tcx> { +struct UseFinder<'a, 'b, 'tcx> { body: &'a Body<'tcx>, - regioncx: &'a RegionInferenceContext<'tcx>, + scc_values: &'a InferredRegions<'b>, tcx: TyCtxt<'tcx>, region_vid: RegionVid, start_point: Location, } -impl<'a, 'tcx> UseFinder<'a, 'tcx> { +impl<'a, 'b, 'tcx> UseFinder<'a, 'b, 'tcx> { fn find(&mut self) -> Option { let mut queue = VecDeque::new(); let mut visited = FxIndexSet::default(); queue.push_back(self.start_point); while let Some(p) = queue.pop_front() { - if !self.regioncx.region_contains(self.region_vid, p) { + if !self.scc_values.region_contains(self.region_vid, p) { continue; } diff --git a/compiler/rustc_borrowck/src/diagnostics/mod.rs b/compiler/rustc_borrowck/src/diagnostics/mod.rs index 04bacd049bc97..8376674cb61da 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mod.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mod.rs @@ -668,7 +668,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { let tcx = self.infcx.tcx; let Some((gat_hir_id, generics)) = path.iter().find_map(|constraint| { let outlived = constraint.sub; - if let Some(origin) = self.regioncx.definitions.get(outlived) + if let Some(origin) = self.definitions.get(outlived) && let NllRegionVariableOrigin::Placeholder(placeholder) = origin.origin && let Some(id) = placeholder.bound.kind.get_id() && let Some(placeholder_id) = id.as_local() diff --git a/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs b/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs index 62ba4d172a3f4..c3ff8a81bcfdf 100644 --- a/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs +++ b/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs @@ -16,9 +16,10 @@ use rustc_trait_selection::errors::impl_trait_overcapture_suggestion; use crate::MirBorrowckCtxt; use crate::borrow_set::BorrowData; -use crate::consumers::RegionInferenceContext; +use crate::region_infer::ConstraintSearch; use crate::region_infer::opaque_types::DeferredOpaqueTypeError; use crate::type_check::Locations; +use crate::universal_regions::UniversalRegions; impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { pub(crate) fn report_opaque_type_errors(&mut self, errors: Vec>) { @@ -41,13 +42,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { hidden_type, member_region, } => { - let named_ty = - self.regioncx.name_regions_for_member_constraint(infcx.tcx, hidden_type.ty); - let named_key = self - .regioncx - .name_regions_for_member_constraint(infcx.tcx, opaque_type_key); - let named_region = - self.regioncx.name_regions_for_member_constraint(infcx.tcx, member_region); + let named_ty = self.name_regions_for_member_constraint(hidden_type.ty); + let named_key = self.name_regions_for_member_constraint(opaque_type_key); + let named_region = self.name_regions_for_member_constraint(member_region); let diag = unexpected_hidden_region_diagnostic( infcx, self.mir_def_id(), @@ -107,9 +104,10 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { let tcx = self.infcx.tcx; let ControlFlow::Break((opaque_def_id, offending_region_idx, location)) = ty .visit_with(&mut FindOpaqueRegion { - regioncx: &self.regioncx, tcx, borrow_region: borrow.region, + universal_regions: self.universal_regions(), + constraint_search: self.constraint_search(), }) else { continue; @@ -209,8 +207,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { /// This visitor contains the bulk of the logic for this lint. struct FindOpaqueRegion<'a, 'tcx> { tcx: TyCtxt<'tcx>, - regioncx: &'a RegionInferenceContext<'tcx>, + constraint_search: ConstraintSearch<'a, 'tcx>, borrow_region: ty::RegionVid, + universal_regions: &'a UniversalRegions<'tcx>, } impl<'tcx> TypeVisitor> for FindOpaqueRegion<'_, 'tcx> { @@ -237,11 +236,11 @@ impl<'tcx> TypeVisitor> for FindOpaqueRegion<'_, 'tcx> { if opaque_region.is_bound() { continue; } - let opaque_region_vid = self.regioncx.to_region_vid(opaque_region); + let opaque_region_vid = self.universal_regions.to_region_vid(opaque_region); // Find a path between the borrow region and our opaque capture. if let Some(path) = self - .regioncx + .constraint_search .constraint_path_between_regions(self.borrow_region, opaque_region_vid) { for constraint in path { diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs index ae389d1a6e10c..3393dbf832962 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs @@ -148,20 +148,20 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { /// 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) + self.to_error_region_vid(r).and_then(|r| self.definitions[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) { + if self.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); + let upper_bound = self.scc_values.approx_universal_upper_bound(r, self.definitions); - if self.regioncx.upper_bound_in_region_scc(r, upper_bound) { + if self.scc_values.upper_bound_in_region_scc(r, upper_bound) { self.to_error_region_vid(upper_bound) } else { None @@ -185,7 +185,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { if let Some(r) = self.to_error_region(fr) && let ty::ReLateParam(late_param) = r.kind() && let ty::LateParamRegionKind::ClosureEnv = late_param.kind - && let DefiningTy::Closure(_, args) = self.regioncx.universal_regions().defining_ty + && let DefiningTy::Closure(_, args) = self.universal_regions().defining_ty { return args.as_closure().kind() == ty::ClosureKind::FnMut; } @@ -205,7 +205,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { // find generic associated types in the given region 'lower_bound' let gat_id_and_generics = self - .regioncx + .scc_values .placeholders_contained_in(lower_bound) .map(|placeholder| { if let Some(id) = placeholder.bound.kind.get_id() @@ -238,21 +238,18 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { .rfind(|bgp| tcx.local_def_id_to_hir_id(bgp.def_id) == gat_hir_id) .is_some() { - for bound in *bounds { - hrtb_bounds.push(bound); - } + hrtb_bounds.extend(bounds.iter()); } else { - for bound in *bounds { - if let Trait(trait_bound) = bound { - if trait_bound + for bound in bounds.iter() { + if let Trait(trait_bound) = bound + && trait_bound .bound_generic_params .iter() .rfind(|bgp| tcx.local_def_id_to_hir_id(bgp.def_id) == gat_hir_id) .is_some() - { - hrtb_bounds.push(bound); - return; - } + { + hrtb_bounds.push(bound); + return; } } } @@ -365,11 +362,16 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { placeholder, error_element, } => { - let error_vid = self.regioncx.region_from_element(longer_fr, &error_element); + let error_vid = self.constraint_search().region_from_element( + self.liveness_constraints, + longer_fr, + &error_element, + self.definitions, + ); // Find the code to blame for the fact that `longer_fr` outlives `error_fr`. let cause = self - .regioncx + .constraint_search() .best_blame_constraint( longer_fr, NllRegionVariableOrigin::Placeholder(placeholder), @@ -379,7 +381,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { .cause; let universe = placeholder.universe; - let universe_info = self.regioncx.universe_info(universe); + let universe_info = self.universe_info(universe); universe_info.report_erroneous_element(self, placeholder, error_element, cause); } @@ -430,7 +432,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { debug!("report_region_error(fr={:?}, outlived_fr={:?})", fr, outlived_fr); let (blame_constraint, path) = - self.regioncx.best_blame_constraint(fr, fr_origin, outlived_fr); + self.constraint_search().best_blame_constraint(fr, fr_origin, outlived_fr); let BlameConstraint { category, cause, variance_info, .. } = blame_constraint; debug!("report_region_error: category={:?} {:?} {:?}", category, cause, variance_info); @@ -447,8 +449,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } 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), + self.universal_regions().is_local_free_region(fr), + self.universal_regions().is_local_free_region(outlived_fr), ); debug!( @@ -580,7 +582,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { ) -> Diag<'infcx> { let ErrorConstraintInfo { outlived_fr, span, .. } = errci; - let mut output_ty = self.regioncx.universal_regions().unnormalized_output_ty; + let mut output_ty = self.universal_regions().unnormalized_output_ty; if let ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. }) = *output_ty.kind() { output_ty = self.infcx.tcx.type_of(def_id).instantiate_identity() }; @@ -603,7 +605,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { let mut diag = self.dcx().create_err(err); if let ReturnConstraint::ClosureUpvar(upvar_field) = kind { - let def_id = match self.regioncx.universal_regions().defining_ty { + let def_id = match self.universal_regions().defining_ty { DefiningTy::Closure(def_id, _) => def_id, ty => bug!("unexpected DefiningTy {:?}", ty), }; @@ -649,14 +651,14 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { fn report_escaping_data_error(&self, errci: &ErrorConstraintInfo<'tcx>) -> Diag<'infcx> { let ErrorConstraintInfo { span, category, .. } = errci; - let fr_name_and_span = self.regioncx.get_var_name_and_span_for_region( + let fr_name_and_span = self.universal_regions().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( + let outlived_fr_name_and_span = self.universal_regions().get_var_name_and_span_for_region( self.infcx.tcx, self.body, &self.local_names(), @@ -664,15 +666,14 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { errci.outlived_fr, ); - let escapes_from = - self.infcx.tcx.def_descr(self.regioncx.universal_regions().defining_ty.def_id()); + let escapes_from = self.infcx.tcx.def_descr(self.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() + && self.universal_regions().defining_ty.is_fn_def()) + || self.universal_regions().defining_ty.is_const() { return self.report_general_error(errci); } @@ -773,7 +774,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { outlived_fr_name.highlight_region_name(&mut diag); let err_category = if matches!(category, ConstraintCategory::Return(_)) - && self.regioncx.universal_regions().is_local_free_region(*outlived_fr) + && self.universal_regions().is_local_free_region(*outlived_fr) { LifetimeReturnCategoryErr::WrongReturn { span: *span, diff --git a/compiler/rustc_borrowck/src/diagnostics/region_name.rs b/compiler/rustc_borrowck/src/diagnostics/region_name.rs index fba0879e81337..9e4d1154d4fa0 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_name.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_name.rs @@ -248,7 +248,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { self.next_region_name.try_borrow().unwrap() ); - assert!(self.regioncx.universal_regions().is_universal_region(fr)); + //FIXME! assert!(self.regioncx.universal_regions().is_universal_region(fr)); match self.region_names.borrow_mut().entry(fr) { IndexEntry::Occupied(precomputed_name) => Some(*precomputed_name.get()), @@ -324,7 +324,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { } ty::LateParamRegionKind::ClosureEnv => { - let def_ty = self.regioncx.universal_regions().defining_ty; + let def_ty = self.universal_regions().defining_ty; let closure_kind = match def_ty { DefiningTy::Closure(_, args) => args.as_closure().kind(), @@ -385,12 +385,13 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { &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 implicit_inputs = self.universal_regions().defining_ty.implicit_inputs(); + let argument_index = + self.universal_regions().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( + let arg_ty = + self.universal_regions().unnormalized_input_tys[implicit_inputs + argument_index]; + let (_, span) = self.universal_regions().get_argument_name_and_span_for_region( self.body, self.local_names(), argument_index, @@ -643,8 +644,9 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { /// ``` #[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( + let upvar_index = + self.universal_regions().get_upvar_index_for_region(self.infcx.tcx, fr)?; + let (upvar_name, upvar_span) = self.universal_regions().get_upvar_name_and_span_for_region( self.infcx.tcx, self.upvars, upvar_index, @@ -665,7 +667,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { fn give_name_if_anonymous_region_appears_in_output(&self, fr: RegionVid) -> Option { let tcx = self.infcx.tcx; - let return_ty = self.regioncx.universal_regions().unnormalized_output_ty; + let return_ty = self.universal_regions().unnormalized_output_ty; debug!("give_name_if_anonymous_region_appears_in_output: return_ty = {:?}", return_ty); if !tcx.any_free_region_meets(&return_ty, |r| r.as_var() == fr) { return None; @@ -848,7 +850,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { ) -> Option { // Note: coroutines from `async fn` yield `()`, so we don't have to // worry about them here. - let yield_ty = self.regioncx.universal_regions().yield_ty?; + let yield_ty = self.universal_regions().yield_ty?; debug!("give_name_if_anonymous_region_appears_in_yield_ty: yield_ty = {:?}", yield_ty); let tcx = self.infcx.tcx; @@ -939,18 +941,15 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { .predicates; if let Some(upvar_index) = self - .regioncx .universal_regions() .defining_ty .upvar_tys() .iter() .position(|ty| self.any_param_predicate_mentions(&predicates, ty, region)) { - let (upvar_name, upvar_span) = self.regioncx.get_upvar_name_and_span_for_region( - self.infcx.tcx, - self.upvars, - upvar_index, - ); + let (upvar_name, upvar_span) = self + .universal_regions() + .get_upvar_name_and_span_for_region(self.infcx.tcx, self.upvars, upvar_index); let region_name = self.synthesize_region_name(); Some(RegionName { @@ -958,17 +957,14 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { source: RegionNameSource::AnonRegionFromUpvar(upvar_span, upvar_name), }) } else if let Some(arg_index) = self - .regioncx .universal_regions() .unnormalized_input_tys .iter() .position(|ty| self.any_param_predicate_mentions(&predicates, *ty, region)) { - let (arg_name, arg_span) = self.regioncx.get_argument_name_and_span_for_region( - self.body, - self.local_names(), - arg_index, - ); + let (arg_name, arg_span) = self + .universal_regions() + .get_argument_name_and_span_for_region(self.body, self.local_names(), arg_index); let region_name = self.synthesize_region_name(); Some(RegionName { diff --git a/compiler/rustc_borrowck/src/diagnostics/var_name.rs b/compiler/rustc_borrowck/src/diagnostics/var_name.rs index 14ed6a27a7a15..7acfa2740b700 100644 --- a/compiler/rustc_borrowck/src/diagnostics/var_name.rs +++ b/compiler/rustc_borrowck/src/diagnostics/var_name.rs @@ -4,9 +4,9 @@ use rustc_middle::ty::{self, RegionVid, TyCtxt}; use rustc_span::{Span, Symbol}; use tracing::debug; -use crate::region_infer::RegionInferenceContext; +use crate::universal_regions::UniversalRegions; -impl<'tcx> RegionInferenceContext<'tcx> { +impl<'tcx> UniversalRegions<'tcx> { pub(crate) fn get_var_name_and_span_for_region( &self, tcx: TyCtxt<'tcx>, @@ -16,7 +16,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { fr: RegionVid, ) -> Option<(Option, Span)> { debug!("get_var_name_and_span_for_region(fr={fr:?})"); - assert!(self.universal_regions().is_universal_region(fr)); + assert!(self.is_universal_region(fr)); debug!("get_var_name_and_span_for_region: attempting upvar"); self.get_upvar_index_for_region(tcx, fr) @@ -39,17 +39,16 @@ impl<'tcx> RegionInferenceContext<'tcx> { tcx: TyCtxt<'tcx>, fr: RegionVid, ) -> Option { - let upvar_index = - self.universal_regions().defining_ty.upvar_tys().iter().position(|upvar_ty| { - debug!("get_upvar_index_for_region: upvar_ty={upvar_ty:?}"); - tcx.any_free_region_meets(&upvar_ty, |r| { - let r = r.as_var(); - debug!("get_upvar_index_for_region: r={r:?} fr={fr:?}"); - r == fr - }) - })?; + let upvar_index = self.defining_ty.upvar_tys().iter().position(|upvar_ty| { + debug!("get_upvar_index_for_region: upvar_ty={upvar_ty:?}"); + tcx.any_free_region_meets(&upvar_ty, |r| { + let r = r.as_var(); + debug!("get_upvar_index_for_region: r={r:?} fr={fr:?}"); + r == fr + }) + })?; - let upvar_ty = self.universal_regions().defining_ty.upvar_tys().get(upvar_index); + let upvar_ty = self.defining_ty.upvar_tys().get(upvar_index); debug!( "get_upvar_index_for_region: found {fr:?} in upvar {upvar_index} which has type {upvar_ty:?}", @@ -88,18 +87,16 @@ impl<'tcx> RegionInferenceContext<'tcx> { tcx: TyCtxt<'tcx>, fr: RegionVid, ) -> Option { - let implicit_inputs = self.universal_regions().defining_ty.implicit_inputs(); + let implicit_inputs = self.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.as_var() == fr) - }, - )?; + self.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.as_var() == fr) + })?; debug!( "get_argument_index_for_region: found {fr:?} in argument {argument_index} which has type {:?}", - self.universal_regions().unnormalized_input_tys[argument_index], + self.unnormalized_input_tys[argument_index], ); Some(argument_index) @@ -113,7 +110,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { local_names: &IndexSlice>, argument_index: usize, ) -> (Option, Span) { - let implicit_inputs = self.universal_regions().defining_ty.implicit_inputs(); + let implicit_inputs = self.defining_ty.implicit_inputs(); let argument_local = Local::from_usize(implicit_inputs + argument_index + 1); debug!("get_argument_name_and_span_for_region: argument_local={argument_local:?}"); diff --git a/compiler/rustc_borrowck/src/handle_placeholders.rs b/compiler/rustc_borrowck/src/handle_placeholders.rs index 2a7dc8ba10162..778b81f3e40e2 100644 --- a/compiler/rustc_borrowck/src/handle_placeholders.rs +++ b/compiler/rustc_borrowck/src/handle_placeholders.rs @@ -2,7 +2,6 @@ //! (with placeholders and universes) and turn them into regular //! outlives constraints. use rustc_data_structures::frozen::Frozen; -use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::graph::scc; use rustc_data_structures::graph::scc::Sccs; use rustc_index::IndexVec; @@ -13,12 +12,11 @@ use tracing::{debug, trace}; use crate::constraints::{ConstraintSccIndex, OutlivesConstraintSet}; use crate::consumers::OutlivesConstraint; -use crate::diagnostics::UniverseInfo; -use crate::region_infer::values::{LivenessValues, PlaceholderIndices}; -use crate::region_infer::{ConstraintSccs, RegionDefinition, Representative, TypeTest}; +use crate::region_infer::values::LivenessValues; +use crate::region_infer::{ConstraintSccs, RegionDefinition, Representative}; use crate::ty::VarianceDiagInfo; +use crate::type_check::Locations; use crate::type_check::free_region_relations::UniversalRegionRelations; -use crate::type_check::{Locations, MirTypeckRegionConstraints}; use crate::universal_regions::UniversalRegions; use crate::{BorrowckInferCtxt, NllRegionVariableOrigin}; @@ -28,11 +26,6 @@ pub(crate) struct LoweredConstraints<'tcx> { pub(crate) constraint_sccs: Sccs, pub(crate) definitions: Frozen>>, pub(crate) scc_annotations: IndexVec, - pub(crate) outlives_constraints: Frozen>, - pub(crate) type_tests: Vec>, - pub(crate) liveness_constraints: LivenessValues, - pub(crate) universe_causes: FxIndexMap>, - pub(crate) placeholder_indices: PlaceholderIndices<'tcx>, } impl<'d, 'tcx, A: scc::Annotation> SccAnnotations<'d, 'tcx, A> { @@ -41,6 +34,8 @@ impl<'d, 'tcx, A: scc::Annotation> SccAnnotations<'d, 'tcx, A> { } } +pub(crate) type RegionDefinitions<'tcx> = IndexVec>; + /// A Visitor for SCC annotation construction. pub(crate) struct SccAnnotations<'d, 'tcx, A: scc::Annotation> { pub(crate) scc_to_annotation: IndexVec, @@ -174,7 +169,8 @@ impl scc::Annotation for RegionTracker { pub(super) fn region_definitions<'tcx>( infcx: &BorrowckInferCtxt<'tcx>, universal_regions: &UniversalRegions<'tcx>, -) -> (Frozen>>, bool) { + liveness_constraints: &mut LivenessValues, +) -> (Frozen>, bool) { let var_infos = infcx.get_region_var_infos(); // Create a RegionDefinition for each inference variable. This happens here because // it allows us to sneak in a cheap check for placeholders. Otherwise, its proper home @@ -182,18 +178,22 @@ pub(super) fn region_definitions<'tcx>( let mut definitions = IndexVec::with_capacity(var_infos.len()); let mut has_placeholders = false; - for info in var_infos.iter() { + for (rvid, info) in var_infos.iter_enumerated() { let origin = match info.origin { RegionVariableOrigin::Nll(origin) => origin, _ => NllRegionVariableOrigin::Existential { name: None }, }; + if let NllRegionVariableOrigin::FreeRegion = origin { + // Add all nodes in the CFG to liveness constraints for free regions + liveness_constraints.add_all_points(rvid); + } + let definition = RegionDefinition { origin, universe: info.universe, external_name: None }; has_placeholders |= matches!(origin, NllRegionVariableOrigin::Placeholder(_)); definitions.push(definition); } - // Add external names from universal regions in fun function definitions. // FIXME: this two-step method is annoying, but I don't know how to avoid it. for (external_name, variable) in universal_regions.named_universal_regions_iter() { @@ -232,21 +232,15 @@ pub(super) fn region_definitions<'tcx>( /// /// Every constraint added by this method is an internal `IllegalUniverse` constraint. pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>( - constraints: MirTypeckRegionConstraints<'tcx>, + liveness_constraints: &mut LivenessValues, + outlives_constraints: &mut OutlivesConstraintSet<'tcx>, universal_region_relations: &Frozen>, infcx: &BorrowckInferCtxt<'tcx>, ) -> LoweredConstraints<'tcx> { let universal_regions = &universal_region_relations.universal_regions; - let (definitions, has_placeholders) = region_definitions(infcx, universal_regions); - let MirTypeckRegionConstraints { - placeholder_indices, - placeholder_index_to_region: _, - liveness_constraints, - mut outlives_constraints, - universe_causes, - type_tests, - } = constraints; + let (definitions, has_placeholders) = + region_definitions(infcx, universal_regions, liveness_constraints); let fr_static = universal_regions.fr_static; let compute_sccs = @@ -268,14 +262,9 @@ pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>( debug!("No placeholder regions found; skipping rewriting logic!"); return LoweredConstraints { - type_tests, constraint_sccs, scc_annotations: scc_annotations.scc_to_annotation, definitions, - outlives_constraints: Frozen::freeze(outlives_constraints), - liveness_constraints, - universe_causes, - placeholder_indices, }; } debug!("Placeholders present; activating placeholder handling logic!"); @@ -284,7 +273,7 @@ pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>( &constraint_sccs, &scc_annotations, fr_static, - &mut outlives_constraints, + outlives_constraints, ); let (constraint_sccs, scc_annotations) = if added_constraints { @@ -301,16 +290,7 @@ pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>( (constraint_sccs, scc_annotations.scc_to_annotation) }; - LoweredConstraints { - constraint_sccs, - definitions, - scc_annotations, - outlives_constraints: Frozen::freeze(outlives_constraints), - type_tests, - liveness_constraints, - universe_causes, - placeholder_indices, - } + LoweredConstraints { constraint_sccs, definitions, scc_annotations } } pub(crate) fn rewrite_placeholder_outlives<'tcx>( diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 9186d82974d77..c2a95266e785c 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -21,6 +21,7 @@ use std::ops::{ControlFlow, Deref}; use std::rc::Rc; use borrow_set::LocalsStateAtExit; +use either::Either; use polonius_engine::AllFacts; use root_cx::BorrowCheckRootCtxt; use rustc_abi::FieldIdx; @@ -55,10 +56,13 @@ use smallvec::SmallVec; use tracing::{debug, instrument}; use crate::borrow_set::{BorrowData, BorrowSet}; +use crate::constraints::OutlivesConstraintSet; +use crate::constraints::graph::NormalConstraintGraph; use crate::consumers::{BodyWithBorrowckFacts, RustcFacts}; use crate::dataflow::{BorrowIndex, Borrowck, BorrowckDomain, Borrows}; use crate::diagnostics::{ AccessKind, BorrowckDiagnosticsBuffer, IllegalMoveOriginKind, MoveError, RegionName, + UniverseInfo, }; use crate::path_utils::*; use crate::place_ext::PlaceExt; @@ -68,12 +72,14 @@ use crate::polonius::legacy::{ }; use crate::polonius::{PoloniusContext, PoloniusDiagnosticsContext}; use crate::prefixes::PrefixSet; -use crate::region_infer::RegionInferenceContext; use crate::region_infer::opaque_types::DeferredOpaqueTypeError; +use crate::region_infer::values::LivenessValues; +use crate::region_infer::{InferredRegions, RegionDefinition}; use crate::renumber::RegionCtxt; use crate::session_diagnostics::VarNeedNotMut; use crate::type_check::free_region_relations::UniversalRegionRelations; use crate::type_check::{Locations, MirTypeckRegionConstraints, MirTypeckResults}; +use crate::universal_regions::UniversalRegions; mod borrow_set; mod borrowck_errors; @@ -292,7 +298,7 @@ struct CollectRegionConstraintsResult<'tcx> { borrow_set: BorrowSet<'tcx>, location_table: PoloniusLocationTable, location_map: Rc, - universal_region_relations: Frozen>, + universal_region_relations: Rc>>, region_bound_pairs: Frozen>, known_type_outlives_obligations: Frozen>>, constraints: MirTypeckRegionConstraints<'tcx>, @@ -371,7 +377,7 @@ fn borrowck_collect_region_constraints<'tcx>( borrow_set, location_table, location_map, - universal_region_relations, + universal_region_relations: Rc::new(universal_region_relations), region_bound_pairs, known_type_outlives_obligations, constraints, @@ -411,15 +417,21 @@ fn borrowck_check_region_constraints<'tcx>( let body = &body_owned; let def = body.source.def_id().expect_local(); + let universal_region_relations = Rc::new(universal_region_relations); + // Compute non-lexical lifetimes using the constraints computed // by typechecking the MIR body. let nll::NllOutput { - regioncx, + scc_values, polonius_input, polonius_output, opt_closure_req, nll_errors, polonius_diagnostics, + definitions, + liveness_constraints, + outlives_constraints, + universe_causes, } = nll::compute_regions( root_cx, &infcx, @@ -427,8 +439,8 @@ fn borrowck_check_region_constraints<'tcx>( &location_table, &move_data, &borrow_set, - location_map, - universal_region_relations, + location_map.clone(), + Rc::clone(&universal_region_relations), constraints, polonius_facts, polonius_context, @@ -436,19 +448,38 @@ fn borrowck_check_region_constraints<'tcx>( // Dump MIR results into a file, if that is enabled. This lets us // write unit-tests, as well as helping with debugging. - nll::dump_nll_mir(&infcx, body, ®ioncx, &opt_closure_req, &borrow_set); + nll::dump_nll_mir( + &infcx, + body, + &scc_values, + &opt_closure_req, + &borrow_set, + &definitions, + &universal_region_relations, + &outlives_constraints, + &liveness_constraints, + ); polonius::dump_polonius_mir( &infcx, body, - ®ioncx, + &scc_values, &opt_closure_req, &borrow_set, + &definitions, + &liveness_constraints, + &outlives_constraints, + &universal_region_relations, polonius_diagnostics.as_ref(), ); // We also have a `#[rustc_regions]` annotation that causes us to dump // information. - nll::dump_annotation(&infcx, body, ®ioncx, &opt_closure_req); + nll::dump_annotation( + &infcx, + body, + &opt_closure_req, + &universal_region_relations.universal_regions, + ); let movable_coroutine = body.coroutine.is_some() && tcx.coroutine_movability(def.to_def_id()) == hir::Movability::Movable; @@ -474,7 +505,7 @@ fn borrowck_check_region_constraints<'tcx>( access_place_error_reported: Default::default(), reservation_error_reported: Default::default(), uninitialized_error_reported: Default::default(), - regioncx: ®ioncx, + scc_values: &scc_values, used_mut: Default::default(), used_mut_upvars: SmallVec::new(), borrow_set: &borrow_set, @@ -486,6 +517,12 @@ fn borrowck_check_region_constraints<'tcx>( move_errors: Vec::new(), diags_buffer, polonius_diagnostics: polonius_diagnostics.as_ref(), + definitions: &definitions, + universal_region_relations: &universal_region_relations, + outlives_constraints: &outlives_constraints, + constraint_graph: outlives_constraints.graph(definitions.len()), + liveness_constraints: &liveness_constraints, + universe_causes: &universe_causes, }; struct MoveVisitor<'a, 'b, 'infcx, 'tcx> { ctxt: &'a mut MirBorrowckCtxt<'b, 'infcx, 'tcx>, @@ -513,7 +550,7 @@ fn borrowck_check_region_constraints<'tcx>( access_place_error_reported: Default::default(), reservation_error_reported: Default::default(), uninitialized_error_reported: Default::default(), - regioncx: ®ioncx, + scc_values: &scc_values, used_mut: Default::default(), used_mut_upvars: SmallVec::new(), borrow_set: &borrow_set, @@ -525,6 +562,12 @@ fn borrowck_check_region_constraints<'tcx>( diags_buffer, polonius_output: polonius_output.as_deref(), polonius_diagnostics: polonius_diagnostics.as_ref(), + definitions: &definitions, + universal_region_relations: &universal_region_relations, + outlives_constraints: &outlives_constraints, + constraint_graph: outlives_constraints.graph(definitions.len()), + liveness_constraints: &liveness_constraints, + universe_causes: &universe_causes, }; // Compute and report region errors, if any. @@ -534,7 +577,18 @@ fn borrowck_check_region_constraints<'tcx>( mbcx.report_region_errors(nll_errors); } - let flow_results = get_flow_results(tcx, body, &move_data, &borrow_set, ®ioncx); + let flow_results = get_flow_results( + tcx, + body, + &move_data, + &borrow_set, + if let Some(polonius_results) = polonius_diagnostics.as_ref() { + either::Right(polonius_results) + } else { + either::Left(&scc_values) + }, + &location_map, + ); visit_results( body, traversal::reverse_postorder(body).map(|(bb, _)| bb), @@ -580,10 +634,10 @@ fn borrowck_check_region_constraints<'tcx>( body: body_owned, promoted, borrow_set, - region_inference_context: regioncx, location_table: polonius_input.as_ref().map(|_| location_table), input_facts: polonius_input, output_facts: polonius_output, + inferred_regions: scc_values, }, ); } @@ -598,15 +652,14 @@ fn get_flow_results<'a, 'tcx>( body: &'a Body<'tcx>, move_data: &'a MoveData<'tcx>, borrow_set: &'a BorrowSet<'tcx>, - regioncx: &RegionInferenceContext<'tcx>, + inference_results: Either<&'a InferredRegions<'tcx>, &'a PoloniusDiagnosticsContext>, + location_map: &'a DenseLocationMap, ) -> Results<'tcx, Borrowck<'a, 'tcx>> { + let _ = inference_results; // We compute these three analyses individually, but them combine them into // a single results so that `mbcx` can visit them all together. - let borrows = Borrows::new(tcx, body, regioncx, borrow_set).iterate_to_fixpoint( - tcx, - body, - Some("borrowck"), - ); + let borrows = Borrows::new(tcx, body, inference_results, location_map, borrow_set) + .iterate_to_fixpoint(tcx, body, Some("borrowck")); let uninits = MaybeUninitializedPlaces::new(tcx, body, move_data).iterate_to_fixpoint( tcx, body, @@ -748,9 +801,10 @@ struct MirBorrowckCtxt<'a, 'infcx, 'tcx> { /// 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<[FieldIdx; 8]>, - /// Region inference context. This contains the results from region inference and lets us e.g. + + /// This contains the results from region inference and lets us e.g. /// find out which CFG points are contained in each borrow region. - regioncx: &'a RegionInferenceContext<'tcx>, + scc_values: &'a InferredRegions<'tcx>, /// The set of borrows extracted from the MIR borrow_set: &'a BorrowSet<'tcx>, @@ -775,6 +829,17 @@ struct MirBorrowckCtxt<'a, 'infcx, 'tcx> { polonius_output: Option<&'a PoloniusOutput>, /// When using `-Zpolonius=next`: the data used to compute errors and diagnostics. polonius_diagnostics: Option<&'a PoloniusDiagnosticsContext>, + + /// Region variable definitions. Where they come from, etc. + definitions: &'a IndexVec>, + + universal_region_relations: &'a Frozen>, + constraint_graph: NormalConstraintGraph, + outlives_constraints: &'a OutlivesConstraintSet<'tcx>, + liveness_constraints: &'a LivenessValues, + + /// Map universe indexes to information on why we created it. + universe_causes: &'a FxIndexMap>, } // Check that: @@ -2686,6 +2751,28 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> { tcx.emit_node_span_lint(UNUSED_MUT, lint_root, span, VarNeedNotMut { span: mut_span }) } } + + pub(crate) fn universal_regions(&self) -> &UniversalRegions<'tcx> { + &self.universal_region_relations.universal_regions + } + + fn universe_info(&self, universe: ty::UniverseIndex) -> UniverseInfo<'tcx> { + // Query canonicalization can create local superuniverses (for example in + // `InferCtx::query_response_instantiation_guess`), but they don't have an associated + // `UniverseInfo` explaining why they were created. + // This can cause ICEs if these causes are accessed in diagnostics, for example in issue + // #114907 where this happens via liveness and dropck outlives results. + // Therefore, we return a default value in case that happens, which should at worst emit a + // suboptimal error, instead of the ICE. + self.universe_causes.get(&universe).cloned().unwrap_or_else(UniverseInfo::other) + } + + fn name_regions_for_member_constraint(&self, ty: T) -> T + where + T: TypeFoldable>, + { + self.scc_values.name_regions_for_member_constraint(self.infcx.tcx, self.definitions, ty) + } } /// The degree of overlap between 2 places for borrow-checking. diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index 64e3b59acfff9..3e252cdc28cd4 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -7,11 +7,12 @@ use std::str::FromStr; use polonius_engine::{Algorithm, AllFacts, Output}; use rustc_data_structures::frozen::Frozen; -use rustc_index::IndexSlice; +use rustc_data_structures::fx::FxIndexMap; +use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::mir::pretty::PrettyPrintMirOptions; use rustc_middle::mir::{Body, MirDumper, PassWhere, Promoted}; use rustc_middle::ty::print::with_no_trimmed_paths; -use rustc_middle::ty::{self, TyCtxt}; +use rustc_middle::ty::{self, RegionVid, TyCtxt}; use rustc_mir_dataflow::move_paths::MoveData; use rustc_mir_dataflow::points::DenseLocationMap; use rustc_session::config::MirIncludeSpans; @@ -19,14 +20,19 @@ use rustc_span::sym; use tracing::{debug, instrument}; use crate::borrow_set::BorrowSet; +use crate::constraints::OutlivesConstraintSet; use crate::consumers::RustcFacts; -use crate::diagnostics::RegionErrors; -use crate::handle_placeholders::compute_sccs_applying_placeholder_outlives_constraints; +use crate::diagnostics::{RegionErrors, UniverseInfo}; +use crate::handle_placeholders::{ + LoweredConstraints, compute_sccs_applying_placeholder_outlives_constraints, +}; use crate::polonius::legacy::{ PoloniusFacts, PoloniusFactsExt, PoloniusLocationTable, PoloniusOutput, }; use crate::polonius::{PoloniusContext, PoloniusDiagnosticsContext}; -use crate::region_infer::RegionInferenceContext; +use crate::region_infer::graphviz::{dump_graphviz_raw_constraints, dump_graphviz_scc_constraints}; +use crate::region_infer::values::LivenessValues; +use crate::region_infer::{self, InferredRegions, RegionDefinition, RegionInferenceContext}; use crate::type_check::MirTypeckRegionConstraints; use crate::type_check::free_region_relations::UniversalRegionRelations; use crate::universal_regions::UniversalRegions; @@ -38,11 +44,15 @@ use crate::{ /// 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 scc_values: InferredRegions<'tcx>, pub polonius_input: Option>, pub polonius_output: Option>, pub opt_closure_req: Option>, pub nll_errors: RegionErrors<'tcx>, + pub(crate) definitions: Frozen>>, + pub(crate) liveness_constraints: LivenessValues, + pub(crate) outlives_constraints: Frozen>, + pub(crate) universe_causes: FxIndexMap>, /// When using `-Zpolonius=next`: the data used to compute errors and diagnostics, e.g. /// localized typeck and liveness constraints. @@ -86,24 +96,36 @@ pub(crate) fn compute_closure_requirements_modulo_opaques<'tcx>( infcx: &BorrowckInferCtxt<'tcx>, body: &Body<'tcx>, location_map: Rc, - universal_region_relations: &Frozen>, + universal_region_relations: Rc>>, constraints: &MirTypeckRegionConstraints<'tcx>, ) -> Option> { - // FIXME(#146079): we shouldn't have to clone all this stuff here. - // Computing the region graph should take at least some of it by reference/`Rc`. - let lowered_constraints = compute_sccs_applying_placeholder_outlives_constraints( - constraints.clone(), - &universal_region_relations, - infcx, - ); - let mut regioncx = RegionInferenceContext::new( - &infcx, - lowered_constraints, - universal_region_relations.clone(), - location_map, - ); - - let (closure_region_requirements, _nll_errors) = regioncx.solve(infcx, body, None); + // These clones can more or less be avoided, + // since they are probably idempotent. + let mut liveness_constraints = constraints.liveness_constraints.clone(); + let mut outlives_constraints = constraints.outlives_constraints.clone(); + let LoweredConstraints { constraint_sccs, definitions, scc_annotations } = + compute_sccs_applying_placeholder_outlives_constraints( + &mut liveness_constraints, + &mut outlives_constraints, + &universal_region_relations, + infcx, + ); + + let (closure_region_requirements, _nll_errors, _scc_values) = + RegionInferenceContext::infer_regions( + infcx, + constraint_sccs, + &definitions, + scc_annotations, + &outlives_constraints, + &constraints.type_tests, + &mut liveness_constraints, + universal_region_relations, + body, + None, + location_map, + constraints.placeholder_indices.clone(), + ); closure_region_requirements } @@ -118,7 +140,7 @@ pub(crate) fn compute_regions<'tcx>( move_data: &MoveData<'tcx>, borrow_set: &BorrowSet<'tcx>, location_map: Rc, - universal_region_relations: Frozen>, + universal_region_relations: Rc>>, constraints: MirTypeckRegionConstraints<'tcx>, mut polonius_facts: Option>, polonius_context: Option, @@ -126,12 +148,24 @@ pub(crate) fn compute_regions<'tcx>( let polonius_output = root_cx.consumer.as_ref().map_or(false, |c| c.polonius_output()) || infcx.tcx.sess.opts.unstable_opts.polonius.is_legacy_enabled(); + let MirTypeckRegionConstraints { + placeholder_indices, + placeholder_index_to_region: _, + mut liveness_constraints, + mut outlives_constraints, + universe_causes, + type_tests, + } = constraints; + let lowered_constraints = compute_sccs_applying_placeholder_outlives_constraints( - constraints, + &mut liveness_constraints, + &mut outlives_constraints, &universal_region_relations, infcx, ); + let outlives_constraints = Frozen::freeze(outlives_constraints); + // If requested, emit legacy polonius facts. polonius::legacy::emit_facts( &mut polonius_facts, @@ -141,20 +175,22 @@ pub(crate) fn compute_regions<'tcx>( borrow_set, move_data, &universal_region_relations, - &lowered_constraints, + &outlives_constraints, ); - let mut regioncx = RegionInferenceContext::new( - infcx, - lowered_constraints, - universal_region_relations, - location_map, - ); + let LoweredConstraints { constraint_sccs, definitions, scc_annotations } = lowered_constraints; // If requested for `-Zpolonius=next`, convert NLL constraints to localized outlives constraints // and use them to compute loan liveness. let polonius_diagnostics = polonius_context.map(|polonius_context| { - polonius_context.compute_loan_liveness(infcx.tcx, &mut regioncx, body, borrow_set) + polonius_context.compute_loan_liveness( + infcx.tcx, + &liveness_constraints, + &outlives_constraints, + &universal_region_relations.universal_regions, + body, + borrow_set, + ) }); // If requested: dump NLL facts, and run legacy polonius analysis. @@ -179,16 +215,33 @@ pub(crate) fn compute_regions<'tcx>( }); // Solve the region constraints. - let (closure_region_requirements, nll_errors) = - regioncx.solve(infcx, body, polonius_output.clone()); + let (closure_region_requirements, nll_errors, scc_values) = + RegionInferenceContext::infer_regions( + infcx, + constraint_sccs, + &definitions, + scc_annotations, + &outlives_constraints, + &type_tests, + &mut liveness_constraints, + universal_region_relations, + body, + polonius_output.clone(), + location_map, + placeholder_indices, + ); NllOutput { - regioncx, polonius_input: polonius_facts.map(Box::new), polonius_output, opt_closure_req: closure_region_requirements, nll_errors, polonius_diagnostics, + scc_values, + definitions, + liveness_constraints, + outlives_constraints, + universe_causes, } } @@ -204,9 +257,13 @@ pub(crate) fn compute_regions<'tcx>( pub(super) fn dump_nll_mir<'tcx>( infcx: &BorrowckInferCtxt<'tcx>, body: &Body<'tcx>, - regioncx: &RegionInferenceContext<'tcx>, + scc_values: &InferredRegions<'tcx>, closure_region_requirements: &Option>, borrow_set: &BorrowSet<'tcx>, + region_definitions: &IndexVec>, + universal_region_relations: &UniversalRegionRelations<'tcx>, + outlives_constraints: &OutlivesConstraintSet<'tcx>, + liveness_constraints: &LivenessValues, ) { let tcx = infcx.tcx; let Some(dumper) = MirDumper::new(tcx, "nll", body) else { return }; @@ -222,7 +279,18 @@ pub(super) fn dump_nll_mir<'tcx>( }; let extra_data = &|pass_where, out: &mut dyn std::io::Write| { - emit_nll_mir(tcx, regioncx, closure_region_requirements, borrow_set, pass_where, out) + emit_nll_mir( + tcx, + scc_values, + closure_region_requirements, + borrow_set, + region_definitions, + outlives_constraints, + universal_region_relations, + liveness_constraints, + pass_where, + out, + ) }; let dumper = dumper.set_extra_data(extra_data).set_options(options); @@ -232,29 +300,40 @@ pub(super) fn dump_nll_mir<'tcx>( // Also dump the region constraint graph as a graphviz file. let _: io::Result<()> = try { let mut file = dumper.create_dump_file("regioncx.all.dot", body)?; - regioncx.dump_graphviz_raw_constraints(tcx, &mut file)?; + dump_graphviz_raw_constraints(tcx, region_definitions, outlives_constraints, &mut file)?; }; // Also dump the region constraint SCC graph as a graphviz file. let _: io::Result<()> = try { let mut file = dumper.create_dump_file("regioncx.scc.dot", body)?; - regioncx.dump_graphviz_scc_constraints(tcx, &mut file)?; + dump_graphviz_scc_constraints(tcx, region_definitions, &scc_values.sccs, &mut file)?; }; } /// Produces the actual NLL MIR sections to emit during the dumping process. pub(crate) fn emit_nll_mir<'tcx>( tcx: TyCtxt<'tcx>, - regioncx: &RegionInferenceContext<'tcx>, + scc_values: &InferredRegions<'tcx>, closure_region_requirements: &Option>, borrow_set: &BorrowSet<'tcx>, + region_definitions: &IndexVec>, + outlives_constraints: &OutlivesConstraintSet<'tcx>, + universal_region_relations: &UniversalRegionRelations<'tcx>, + liveness_constraints: &LivenessValues, pass_where: PassWhere, out: &mut dyn io::Write, ) -> io::Result<()> { match pass_where { // Before the CFG, dump out the values for each region variable. PassWhere::BeforeCFG => { - regioncx.dump_mir(tcx, out)?; + region_infer::MirDumper { + tcx, + definitions: region_definitions, + universal_region_relations, + outlives_constraints, + liveness_constraints, + } + .dump_mir(scc_values, out)?; writeln!(out, "|")?; if let Some(closure_region_requirements) = closure_region_requirements { @@ -290,8 +369,8 @@ pub(crate) fn emit_nll_mir<'tcx>( pub(super) fn dump_annotation<'tcx, 'infcx>( infcx: &'infcx BorrowckInferCtxt<'tcx>, body: &Body<'tcx>, - regioncx: &RegionInferenceContext<'tcx>, closure_region_requirements: &Option>, + universal_regions: &UniversalRegions<'tcx>, ) { let tcx = infcx.tcx; let base_def_id = tcx.typeck_root_def_id(body.source.def_id()); @@ -310,7 +389,7 @@ pub(super) fn dump_annotation<'tcx, 'infcx>( let err = if let Some(closure_region_requirements) = closure_region_requirements { let mut err = infcx.dcx().struct_span_note(def_span, "external requirements"); - regioncx.annotate(tcx, &mut err); + universal_regions.annotate(tcx, &mut err); err.note(format!( "number of external vids: {}", @@ -328,7 +407,7 @@ pub(super) fn dump_annotation<'tcx, 'infcx>( err } else { let mut err = infcx.dcx().struct_span_note(def_span, "no external requirements"); - regioncx.annotate(tcx, &mut err); + universal_regions.annotate(tcx, &mut err); err }; diff --git a/compiler/rustc_borrowck/src/polonius/dump.rs b/compiler/rustc_borrowck/src/polonius/dump.rs index 62f9ae173474d..91d26fe7e5df3 100644 --- a/compiler/rustc_borrowck/src/polonius/dump.rs +++ b/compiler/rustc_borrowck/src/polonius/dump.rs @@ -9,21 +9,27 @@ use rustc_mir_dataflow::points::PointIndex; use rustc_session::config::MirIncludeSpans; use crate::borrow_set::BorrowSet; -use crate::constraints::OutlivesConstraint; +use crate::constraints::{OutlivesConstraint, OutlivesConstraintSet}; use crate::polonius::{ LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSet, PoloniusDiagnosticsContext, }; use crate::region_infer::values::LivenessValues; +use crate::region_infer::{InferredRegions, RegionDefinition}; use crate::type_check::Locations; -use crate::{BorrowckInferCtxt, ClosureRegionRequirements, RegionInferenceContext}; +use crate::type_check::free_region_relations::UniversalRegionRelations; +use crate::{BorrowckInferCtxt, ClosureRegionRequirements}; /// `-Zdump-mir=polonius` dumps MIR annotated with NLL and polonius specific information. pub(crate) fn dump_polonius_mir<'tcx>( infcx: &BorrowckInferCtxt<'tcx>, body: &Body<'tcx>, - regioncx: &RegionInferenceContext<'tcx>, + scc_values: &InferredRegions<'tcx>, closure_region_requirements: &Option>, borrow_set: &BorrowSet<'tcx>, + region_definitions: &IndexVec>, + liveness_constraints: &LivenessValues, + outlives_constraints: &OutlivesConstraintSet<'tcx>, + universal_region_relations: &UniversalRegionRelations<'tcx>, polonius_diagnostics: Option<&PoloniusDiagnosticsContext>, ) { let tcx = infcx.tcx; @@ -39,10 +45,14 @@ pub(crate) fn dump_polonius_mir<'tcx>( let extra_data = &|pass_where, out: &mut dyn io::Write| { emit_polonius_mir( tcx, - regioncx, + scc_values, closure_region_requirements, borrow_set, &polonius_diagnostics.localized_outlives_constraints, + ®ion_definitions, + &outlives_constraints, + &universal_region_relations, + liveness_constraints, pass_where, out, ) @@ -63,7 +73,10 @@ pub(crate) fn dump_polonius_mir<'tcx>( emit_polonius_dump( &dumper, body, - regioncx, + region_definitions, + liveness_constraints, + outlives_constraints, + scc_values, borrow_set, &polonius_diagnostics.localized_outlives_constraints, &mut file, @@ -80,7 +93,10 @@ pub(crate) fn dump_polonius_mir<'tcx>( fn emit_polonius_dump<'tcx>( dumper: &MirDumper<'_, '_, 'tcx>, body: &Body<'tcx>, - regioncx: &RegionInferenceContext<'tcx>, + region_definitions: &IndexVec>, + liveness_constraints: &LivenessValues, + outlives_constraints: &OutlivesConstraintSet<'tcx>, + scc_values: &InferredRegions<'tcx>, borrow_set: &BorrowSet<'tcx>, localized_outlives_constraints: &LocalizedOutlivesConstraintSet, out: &mut dyn io::Write, @@ -105,7 +121,7 @@ fn emit_polonius_dump<'tcx>( writeln!(out, "
")?;
     let edge_count = emit_mermaid_constraint_graph(
         borrow_set,
-        regioncx.liveness_constraints(),
+        liveness_constraints,
         &localized_outlives_constraints,
         out,
     )?;
@@ -124,7 +140,7 @@ fn emit_polonius_dump<'tcx>(
     writeln!(out, "
")?; writeln!(out, "NLL regions")?; writeln!(out, "
")?;
-    emit_mermaid_nll_regions(dumper.tcx(), regioncx, out)?;
+    emit_mermaid_nll_regions(dumper.tcx(), region_definitions, outlives_constraints, out)?;
     writeln!(out, "
")?; writeln!(out, "
")?; @@ -132,7 +148,7 @@ fn emit_polonius_dump<'tcx>( writeln!(out, "
")?; writeln!(out, "NLL SCCs")?; writeln!(out, "
")?;
-    emit_mermaid_nll_sccs(dumper.tcx(), regioncx, out)?;
+    emit_mermaid_nll_sccs(dumper.tcx(), region_definitions, scc_values, out)?;
     writeln!(out, "
")?; writeln!(out, "
")?; @@ -190,25 +206,31 @@ fn emit_html_mir<'tcx>( /// Produces the actual NLL + Polonius MIR sections to emit during the dumping process. fn emit_polonius_mir<'tcx>( tcx: TyCtxt<'tcx>, - regioncx: &RegionInferenceContext<'tcx>, + scc_values: &InferredRegions<'tcx>, closure_region_requirements: &Option>, borrow_set: &BorrowSet<'tcx>, localized_outlives_constraints: &LocalizedOutlivesConstraintSet, + region_definitions: &IndexVec>, + outlives_constraints: &OutlivesConstraintSet<'tcx>, + universal_region_relations: &UniversalRegionRelations<'tcx>, + liveness: &LivenessValues, pass_where: PassWhere, out: &mut dyn io::Write, ) -> io::Result<()> { // Emit the regular NLL front-matter crate::nll::emit_nll_mir( tcx, - regioncx, + scc_values, closure_region_requirements, borrow_set, + region_definitions, + outlives_constraints, + universal_region_relations, + liveness, pass_where, out, )?; - let liveness = regioncx.liveness_constraints(); - // Add localized outlives constraints match pass_where { PassWhere::BeforeCFG => { @@ -286,10 +308,10 @@ fn emit_mermaid_cfg(body: &Body<'_>, out: &mut dyn io::Write) -> io::Result<()> fn render_region<'tcx>( tcx: TyCtxt<'tcx>, region: RegionVid, - regioncx: &RegionInferenceContext<'tcx>, + region_definitions: &IndexVec>, out: &mut dyn io::Write, ) -> io::Result<()> { - let def = regioncx.region_definition(region); + let def = ®ion_definitions[region]; let universe = def.universe; write!(out, "'{}", region.as_usize())?; @@ -306,21 +328,23 @@ fn render_region<'tcx>( /// to the graphviz version. fn emit_mermaid_nll_regions<'tcx>( tcx: TyCtxt<'tcx>, - regioncx: &RegionInferenceContext<'tcx>, + region_definitions: &IndexVec>, + outlives_constraints: &OutlivesConstraintSet<'tcx>, out: &mut dyn io::Write, ) -> io::Result<()> { // The mermaid chart type: a top-down flowchart. writeln!(out, "flowchart TD")?; // Emit the region nodes. - for region in regioncx.definitions.indices() { + for region in region_definitions.indices() { write!(out, "{}[\"", region.as_usize())?; - render_region(tcx, region, regioncx, out)?; + render_region(tcx, region, region_definitions, out)?; writeln!(out, "\"]")?; } // Get a set of edges to check for the reverse edge being present. - let edges: FxHashSet<_> = regioncx.outlives_constraints().map(|c| (c.sup, c.sub)).collect(); + let edges: FxHashSet<_> = + outlives_constraints.outlives().iter().map(|c| (c.sup, c.sub)).collect(); // Order (and deduplicate) edges for traversal, to display them in a generally increasing order. let constraint_key = |c: &OutlivesConstraint<'_>| { @@ -328,7 +352,7 @@ fn emit_mermaid_nll_regions<'tcx>( let max = c.sup.max(c.sub); (min, max) }; - let mut ordered_edges: Vec<_> = regioncx.outlives_constraints().collect(); + let mut ordered_edges: Vec<_> = outlives_constraints.outlives().iter().collect(); ordered_edges.sort_by_key(|c| constraint_key(c)); ordered_edges.dedup_by_key(|c| constraint_key(c)); @@ -358,7 +382,8 @@ fn emit_mermaid_nll_regions<'tcx>( /// to the graphviz version. fn emit_mermaid_nll_sccs<'tcx>( tcx: TyCtxt<'tcx>, - regioncx: &RegionInferenceContext<'tcx>, + region_definitions: &IndexVec>, + scc_values: &InferredRegions<'tcx>, out: &mut dyn io::Write, ) -> io::Result<()> { // The mermaid chart type: a top-down flowchart. @@ -366,16 +391,16 @@ fn emit_mermaid_nll_sccs<'tcx>( // Gather and emit the SCC nodes. let mut nodes_per_scc: IndexVec<_, _> = - regioncx.constraint_sccs().all_sccs().map(|_| Vec::new()).collect(); - for region in regioncx.definitions.indices() { - let scc = regioncx.constraint_sccs().scc(region); + scc_values.sccs.all_sccs().map(|_| Vec::new()).collect(); + for region in region_definitions.indices() { + let scc = scc_values.scc(region); nodes_per_scc[scc].push(region); } for (scc, regions) in nodes_per_scc.iter_enumerated() { // The node label: the regions contained in the SCC. write!(out, "{scc}[\"SCC({scc}) = {{", scc = scc.as_usize())?; for (idx, ®ion) in regions.iter().enumerate() { - render_region(tcx, region, regioncx, out)?; + render_region(tcx, region, region_definitions, out)?; if idx < regions.len() - 1 { write!(out, ",")?; } @@ -384,8 +409,8 @@ fn emit_mermaid_nll_sccs<'tcx>( } // Emit the edges between SCCs. - let edges = regioncx.constraint_sccs().all_sccs().flat_map(|source| { - regioncx.constraint_sccs().successors(source).iter().map(move |&target| (source, target)) + let edges = scc_values.sccs.all_sccs().flat_map(|source| { + scc_values.sccs.successors(source).iter().map(move |&target| (source, target)) }); for (source, target) in edges { writeln!(out, "{} --> {}", source.as_usize(), target.as_usize())?; diff --git a/compiler/rustc_borrowck/src/polonius/legacy/mod.rs b/compiler/rustc_borrowck/src/polonius/legacy/mod.rs index 05fd6e39476b2..e7defd8fae8b0 100644 --- a/compiler/rustc_borrowck/src/polonius/legacy/mod.rs +++ b/compiler/rustc_borrowck/src/polonius/legacy/mod.rs @@ -12,8 +12,7 @@ use rustc_mir_dataflow::move_paths::{InitKind, InitLocation, MoveData}; use tracing::debug; use crate::borrow_set::BorrowSet; -use crate::constraints::OutlivesConstraint; -use crate::handle_placeholders::LoweredConstraints; +use crate::constraints::{OutlivesConstraint, OutlivesConstraintSet}; use crate::type_check::free_region_relations::UniversalRegionRelations; use crate::universal_regions::UniversalRegions; @@ -43,7 +42,7 @@ pub(crate) fn emit_facts<'tcx>( borrow_set: &BorrowSet<'tcx>, move_data: &MoveData<'tcx>, universal_region_relations: &UniversalRegionRelations<'tcx>, - constraints: &LoweredConstraints<'tcx>, + constraints: &OutlivesConstraintSet<'tcx>, ) { let Some(facts) = facts else { // We don't do anything if there are no facts to fill. @@ -203,9 +202,9 @@ pub(crate) fn emit_drop_facts<'tcx>( fn emit_outlives_facts<'tcx>( facts: &mut PoloniusFacts, location_table: &PoloniusLocationTable, - constraints: &LoweredConstraints<'tcx>, + constraints: &OutlivesConstraintSet<'tcx>, ) { - facts.subset_base.extend(constraints.outlives_constraints.outlives().iter().flat_map( + facts.subset_base.extend(constraints.outlives().iter().flat_map( |constraint: &OutlivesConstraint<'_>| { if let Some(from_location) = constraint.locations.from_location() { Either::Left(iter::once(( diff --git a/compiler/rustc_borrowck/src/polonius/loan_liveness.rs b/compiler/rustc_borrowck/src/polonius/loan_liveness.rs index bdc3047e5ba01..e9879ca3fa36e 100644 --- a/compiler/rustc_borrowck/src/polonius/loan_liveness.rs +++ b/compiler/rustc_borrowck/src/polonius/loan_liveness.rs @@ -4,7 +4,7 @@ use rustc_mir_dataflow::points::PointIndex; use super::{LiveLoans, LocalizedOutlivesConstraintSet}; use crate::BorrowSet; -use crate::constraints::OutlivesConstraint; +use crate::constraints::{OutlivesConstraint, OutlivesConstraintSet}; use crate::region_infer::values::LivenessValues; use crate::type_check::Locations; @@ -14,7 +14,7 @@ use crate::type_check::Locations; /// - with the constraints that hold at all points (the logical edges). pub(super) fn compute_loan_liveness<'tcx>( liveness: &LivenessValues, - outlives_constraints: impl Iterator>, + outlives_constraints: &OutlivesConstraintSet<'tcx>, borrow_set: &BorrowSet<'tcx>, localized_outlives_constraints: &LocalizedOutlivesConstraintSet, ) -> LiveLoans { @@ -22,8 +22,11 @@ pub(super) fn compute_loan_liveness<'tcx>( // Create the full graph with the physical edges we've localized earlier, and the logical edges // of constraints that hold at all points. - let logical_constraints = - outlives_constraints.filter(|c| matches!(c.locations, Locations::All(_))); + let logical_constraints = outlives_constraints + .outlives() + .iter() + .filter(|c| matches!(c.locations, Locations::All(_))) + .cloned(); let graph = LocalizedConstraintGraph::new(&localized_outlives_constraints, logical_constraints); let mut visited = FxHashSet::default(); let mut stack = Vec::new(); diff --git a/compiler/rustc_borrowck/src/polonius/mod.rs b/compiler/rustc_borrowck/src/polonius/mod.rs index a9092b1981e1d..8ddd3d8802ea3 100644 --- a/compiler/rustc_borrowck/src/polonius/mod.rs +++ b/compiler/rustc_borrowck/src/polonius/mod.rs @@ -64,8 +64,11 @@ pub(crate) use self::dump::dump_polonius_mir; use self::liveness_constraints::create_liveness_constraints; use self::loan_liveness::compute_loan_liveness; use self::typeck_constraints::convert_typeck_constraints; +use crate::BorrowSet; +use crate::constraints::OutlivesConstraintSet; use crate::dataflow::BorrowIndex; -use crate::{BorrowSet, RegionInferenceContext}; +use crate::region_infer::values::LivenessValues; +use crate::universal_regions::UniversalRegions; pub(crate) type LiveLoans = SparseBitMatrix; @@ -97,12 +100,14 @@ pub(crate) struct PoloniusContext { /// This struct holds the data needed by the borrowck error computation and diagnostics. Its data is /// computed from the [PoloniusContext] when computing NLL regions. -pub(crate) struct PoloniusDiagnosticsContext { +pub struct PoloniusDiagnosticsContext { /// The localized outlives constraints that were computed in the main analysis. localized_outlives_constraints: LocalizedOutlivesConstraintSet, /// The liveness data computed during MIR typeck: [PoloniusLivenessContext::boring_nll_locals]. pub(crate) boring_nll_locals: FxHashSet, + + pub(crate) live_loans: LiveLoans, } /// The direction a constraint can flow into. Used to create liveness constraints according to @@ -153,7 +158,9 @@ impl PoloniusContext { pub(crate) fn compute_loan_liveness<'tcx>( self, tcx: TyCtxt<'tcx>, - regioncx: &mut RegionInferenceContext<'tcx>, + liveness: &LivenessValues, + outlives_constraints: &OutlivesConstraintSet<'tcx>, + universal_regions: &UniversalRegions<'tcx>, body: &Body<'tcx>, borrow_set: &BorrowSet<'tcx>, ) -> PoloniusDiagnosticsContext { @@ -164,31 +171,30 @@ impl PoloniusContext { convert_typeck_constraints( tcx, body, - regioncx.liveness_constraints(), - regioncx.outlives_constraints(), - regioncx.universal_regions(), + liveness, + outlives_constraints, + universal_regions, &mut localized_outlives_constraints, ); create_liveness_constraints( body, - regioncx.liveness_constraints(), + liveness, &self.live_regions, &live_region_variances, - regioncx.universal_regions(), + universal_regions, &mut localized_outlives_constraints, ); // Now that we have a complete graph, we can compute reachability to trace the liveness of // loans for the next step in the chain, the NLL loan scope and active loans computations. let live_loans = compute_loan_liveness( - regioncx.liveness_constraints(), - regioncx.outlives_constraints(), + liveness, + outlives_constraints, borrow_set, &localized_outlives_constraints, ); - regioncx.record_live_loans(live_loans); - PoloniusDiagnosticsContext { localized_outlives_constraints, boring_nll_locals } + PoloniusDiagnosticsContext { live_loans, localized_outlives_constraints, boring_nll_locals } } } diff --git a/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs b/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs index cfe9376fb5029..c4f5f4f92ed83 100644 --- a/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs +++ b/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs @@ -4,7 +4,7 @@ use rustc_middle::ty::{TyCtxt, TypeVisitable}; use rustc_mir_dataflow::points::PointIndex; use super::{LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSet}; -use crate::constraints::OutlivesConstraint; +use crate::constraints::{OutlivesConstraint, OutlivesConstraintSet}; use crate::region_infer::values::LivenessValues; use crate::type_check::Locations; use crate::universal_regions::UniversalRegions; @@ -15,11 +15,11 @@ pub(super) fn convert_typeck_constraints<'tcx>( tcx: TyCtxt<'tcx>, body: &Body<'tcx>, liveness: &LivenessValues, - outlives_constraints: impl Iterator>, + outlives_constraints: &OutlivesConstraintSet<'tcx>, universal_regions: &UniversalRegions<'tcx>, localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet, ) { - for outlives_constraint in outlives_constraints { + for outlives_constraint in outlives_constraints.outlives() { match outlives_constraint.locations { Locations::All(_) => { // We don't turn constraints holding at all points into physical edges at every diff --git a/compiler/rustc_borrowck/src/region_infer/constraint_search.rs b/compiler/rustc_borrowck/src/region_infer/constraint_search.rs new file mode 100644 index 0000000000000..8dd1266a02a06 --- /dev/null +++ b/compiler/rustc_borrowck/src/region_infer/constraint_search.rs @@ -0,0 +1,479 @@ +//! This module contains code for searching the region +//! constraint graph, usually to find a good reason for why +//! one region is live or outlives another. + +use std::collections::VecDeque; + +use rustc_hir::def_id::CRATE_DEF_ID; +use rustc_index::IndexVec; +use rustc_infer::infer::NllRegionVariableOrigin; +use rustc_infer::traits::{ObligationCause, ObligationCauseCode}; +use rustc_middle::bug; +use rustc_middle::mir::{AnnotationSource, ConstraintCategory, Location, ReturnConstraint}; +use rustc_middle::ty::{self, RegionVid, TyCtxt}; +use rustc_span::{DUMMY_SP, DesugaringKind}; +use tracing::{debug, instrument, trace}; + +use crate::constraints::OutlivesConstraintSet; +use crate::constraints::graph::NormalConstraintGraph; +use crate::consumers::OutlivesConstraint; +use crate::handle_placeholders::RegionDefinitions; +use crate::region_infer::values::{LivenessValues, RegionElement}; +use crate::type_check::Locations; + +#[derive(Clone, Debug)] +pub(crate) struct BlameConstraint<'tcx> { + pub category: ConstraintCategory<'tcx>, + pub from_closure: bool, + pub cause: ObligationCause<'tcx>, + pub variance_info: ty::VarianceDiagInfo>, +} + +pub(crate) struct ConstraintSearch<'a, 'tcx> { + pub(crate) definitions: &'a RegionDefinitions<'tcx>, + pub(crate) fr_static: RegionVid, + pub(crate) constraint_graph: &'a NormalConstraintGraph, + pub(crate) constraints: &'a OutlivesConstraintSet<'tcx>, +} + +impl<'a, 'tcx> ConstraintSearch<'a, 'tcx> { + /// Get the region outlived by `longer_fr` and live at `element`. + pub(crate) fn region_from_element( + &self, + liveness_constraints: &LivenessValues, + longer_fr: RegionVid, + element: &RegionElement<'tcx>, + definitions: &RegionDefinitions<'tcx>, + ) -> RegionVid { + match *element { + RegionElement::Location(l) => { + self.find_sub_region_live_at(liveness_constraints, longer_fr, l) + } + RegionElement::RootUniversalRegion(r) => r, + RegionElement::PlaceholderRegion(error_placeholder) => definitions + .iter_enumerated() + .find_map(|(r, definition)| match definition.origin { + NllRegionVariableOrigin::Placeholder(p) if p == error_placeholder => Some(r), + _ => None, + }) + .unwrap(), + } + } + + /// Finds some region R such that `fr1: R` and `R` is live at `location`. + #[instrument(skip(self, liveness_constraints), level = "trace", ret)] + pub(crate) fn find_sub_region_live_at( + &self, + liveness_constraints: &LivenessValues, + fr1: RegionVid, + location: Location, + ) -> RegionVid { + self.constraint_path_to( + fr1, + |r| { + trace!(?r, liveness_constraints=?liveness_constraints.pretty_print_live_points(r)); + liveness_constraints.is_live_at(r, location) + }, + true, + ) + .unwrap() + .1 + } + + /// Walks the graph of constraints (where `'a: 'b` is considered + /// an edge `'a -> 'b`) to find a path from `from_region` to + /// `to_region`. + /// + /// Returns: a series of constraints as well as the region `R` + /// that passed the target test. + /// If `include_static_outlives_all` is `true`, then the synthetic + /// outlives constraints `'static -> a` for every region `a` are + /// considered in the search, otherwise they are ignored. + #[instrument(skip(self, target_test), ret)] + pub(crate) fn constraint_path_to( + &self, + from_region: RegionVid, + target_test: impl Fn(RegionVid) -> bool, + include_placeholder_static: bool, + ) -> Option<(Vec>, RegionVid)> { + self.find_constraint_path_between_regions_inner( + true, + from_region, + &target_test, + include_placeholder_static, + ) + .or_else(|| { + self.find_constraint_path_between_regions_inner( + false, + from_region, + &target_test, + include_placeholder_static, + ) + }) + } + + pub(crate) fn constraint_path_between_regions( + &self, + from_region: RegionVid, + to_region: RegionVid, + ) -> Option>> { + if from_region == to_region { + bug!("Tried to find a path between {from_region:?} and itself!"); + } + self.constraint_path_to(from_region, |t| t == to_region, true).map(|o| o.0) + } + + /// The constraints we get from equating the hidden type of each use of an opaque + /// with its final hidden type may end up getting preferred over other, potentially + /// longer constraint paths. + /// + /// Given that we compute the final hidden type by relying on this existing constraint + /// path, this can easily end up hiding the actual reason for why we require these regions + /// to be equal. + /// + /// To handle this, we first look at the path while ignoring these constraints and then + /// retry while considering them. This is not perfect, as the `from_region` may have already + /// been partially related to its argument region, so while we rely on a member constraint + /// to get a complete path, the most relevant step of that path already existed before then. + fn find_constraint_path_between_regions_inner( + &self, + ignore_opaque_type_constraints: bool, + from_region: RegionVid, + target_test: impl Fn(RegionVid) -> bool, + include_placeholder_static: bool, + ) -> Option<(Vec>, RegionVid)> { + let mut context = IndexVec::from_elem(Trace::NotVisited, self.definitions); + context[from_region] = Trace::StartRegion; + let fr_static = self.fr_static; + + // 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!("constraint_path_to: from_region={:?} r={:?}", from_region, 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; + // This loop is cold and runs at the end, which is why we delay + // `OutlivesConstraint` construction until now. + loop { + match context[p] { + Trace::FromGraph(c) => { + p = c.sup; + result.push(*c); + } + + Trace::FromStatic(sub) => { + let c = OutlivesConstraint { + sup: fr_static, + sub, + locations: Locations::All(DUMMY_SP), + span: DUMMY_SP, + category: ConstraintCategory::Internal, + variance_info: ty::VarianceDiagInfo::default(), + from_closure: false, + }; + p = c.sup; + result.push(c); + } + + Trace::StartRegion => { + result.reverse(); + return Some((result, r)); + } + + Trace::NotVisited => { + bug!("found unvisited region {:?} on path to {:?}", p, 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. + + // Always inline this closure because it can be hot. + let mut handle_trace = #[inline(always)] + |sub, trace| { + if let Trace::NotVisited = context[sub] { + context[sub] = trace; + deque.push_back(sub); + } + }; + + // If this is the `'static` region and the graph's direction is normal, then set up the + // Edges iterator to return all regions (#53178). + if r == fr_static && self.constraint_graph.is_normal() { + for sub in self.constraint_graph.outgoing_edges_from_static() { + handle_trace(sub, Trace::FromStatic(sub)); + } + } else { + let edges = self.constraint_graph.outgoing_edges_from_graph(r, self.constraints); + // This loop can be hot. + for constraint in edges { + match constraint.category { + ConstraintCategory::OutlivesUnnameablePlaceholder(_) + if !include_placeholder_static => + { + debug!("Ignoring illegal placeholder constraint: {constraint:?}"); + continue; + } + ConstraintCategory::OpaqueType if ignore_opaque_type_constraints => { + debug!("Ignoring member constraint: {constraint:?}"); + continue; + } + _ => {} + } + + debug_assert_eq!(constraint.sup, r); + handle_trace(constraint.sub, Trace::FromGraph(constraint)); + } + } + } + + None + } + + /// 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. + #[instrument(level = "debug", skip(self))] + pub(crate) fn best_blame_constraint( + &self, + from_region: RegionVid, + from_region_origin: NllRegionVariableOrigin<'tcx>, + to_region: RegionVid, + ) -> (BlameConstraint<'tcx>, Vec>) { + assert!(from_region != to_region, "Trying to blame a region for itself!"); + + let path = self.constraint_path_to(from_region, |t| t == to_region, true).unwrap().0; + + // If we are passing through a constraint added because we reached an unnameable placeholder `'unnameable`, + // redirect search towards `'unnameable`. + let due_to_placeholder_outlives = path.iter().find_map(|c| { + if let ConstraintCategory::OutlivesUnnameablePlaceholder(unnameable) = c.category { + Some(unnameable) + } else { + None + } + }); + + // Edge case: it's possible that `'from_region` is an unnameable placeholder. + let path = if let Some(unnameable) = due_to_placeholder_outlives + && unnameable != from_region + { + // We ignore the extra edges due to unnameable placeholders to get + // an explanation that was present in the original constraint graph. + self.constraint_path_to(from_region, |t| t == unnameable, false).unwrap().0 + } else { + path + }; + + // 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::WhereClause(CRATE_DEF_ID.to_def_id(), predicate_span)) + } else { + None + } + }) + .unwrap_or_else(|| ObligationCauseCode::Misc); + + // 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 => true, + NllRegionVariableOrigin::Placeholder(_) => false, + // `'existential: 'whatever` never results in a region error by itself. + // We may always infer it to `'static` afterall. This means while an error + // path may go through an existential, these existentials are never the + // `from_region`. + NllRegionVariableOrigin::Existential { name: _ } => { + unreachable!("existentials can outlive everything") + } + }; + + // To pick a constraint to blame, we organize constraints by how interesting we expect them + // to be in diagnostics, then pick the most interesting one closest to either the source or + // the target on our constraint path. + let constraint_interest = |constraint: &OutlivesConstraint<'tcx>| { + use AnnotationSource::*; + use ConstraintCategory::*; + // Try to avoid blaming constraints from desugarings, since they may not clearly match + // match what users have written. As an exception, allow blaming returns generated by + // `?` desugaring, since the correspondence is fairly clear. + let category = if let Some(kind) = constraint.span.desugaring_kind() + && (kind != DesugaringKind::QuestionMark + || !matches!(constraint.category, Return(_))) + { + Boring + } else { + constraint.category + }; + + let interest = match category { + // Returns usually provide a type to blame and have specially written diagnostics, + // so prioritize them. + Return(_) => 0, + // Unsizing coercions are interesting, since we have a note for that: + // `BorrowExplanation::add_object_lifetime_default_note`. + // FIXME(dianne): That note shouldn't depend on a coercion being blamed; see issue + // #131008 for an example of where we currently don't emit it but should. + // Once the note is handled properly, this case should be removed. Until then, it + // should be as limited as possible; the note is prone to false positives and this + // constraint usually isn't best to blame. + Cast { + is_raw_ptr_dyn_type_cast: _, + unsize_to: Some(unsize_ty), + is_implicit_coercion: true, + } if to_region == self.fr_static + // Mirror the note's condition, to minimize how often this diverts blame. + && let ty::Adt(_, args) = unsize_ty.kind() + && args.iter().any(|arg| arg.as_type().is_some_and(|ty| ty.is_trait())) + // Mimic old logic for this, to minimize false positives in tests. + && !path + .iter() + .any(|c| matches!(c.category, TypeAnnotation(_))) => + { + 1 + } + // Between other interesting constraints, order by their position on the `path`. + Yield + | UseAsConst + | UseAsStatic + | TypeAnnotation(Ascription | Declaration | OpaqueCast) + | Cast { .. } + | CallArgument(_) + | CopyBound + | SizedBound + | Assignment + | Usage + | ClosureUpvar(_) => 2, + // Generic arguments are unlikely to be what relates regions together + TypeAnnotation(GenericArg) => 3, + // We handle predicates and opaque types specially; don't prioritize them here. + Predicate(_) | OpaqueType => 4, + // `Boring` constraints can correspond to user-written code and have useful spans, + // but don't provide any other useful information for diagnostics. + Boring => 5, + // `BoringNoLocation` constraints can point to user-written code, but are less + // specific, and are not used for relations that would make sense to blame. + BoringNoLocation => 6, + // Do not blame internal constraints if we can avoid it. Never blame + // the `'region: 'static` constraints introduced by placeholder outlives. + Internal => 7, + OutlivesUnnameablePlaceholder(_) => 8, + }; + + debug!("constraint {constraint:?} category: {category:?}, interest: {interest:?}"); + + interest + }; + + let best_choice = if blame_source { + path.iter().enumerate().rev().min_by_key(|(_, c)| constraint_interest(c)).unwrap().0 + } else { + path.iter().enumerate().min_by_key(|(_, c)| constraint_interest(c)).unwrap().0 + }; + + debug!(?best_choice, ?blame_source); + + let best_constraint = if let Some(next) = path.get(best_choice + 1) + && matches!(path[best_choice].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. + *next + } else if path[best_choice].category == ConstraintCategory::Return(ReturnConstraint::Normal) + && let Some(field) = path.iter().find_map(|p| { + if let ConstraintCategory::ClosureUpvar(f) = p.category { Some(f) } else { None } + }) + { + OutlivesConstraint { + category: ConstraintCategory::Return(ReturnConstraint::ClosureUpvar(field)), + ..path[best_choice] + } + } else { + path[best_choice] + }; + + assert!( + !matches!( + best_constraint.category, + ConstraintCategory::OutlivesUnnameablePlaceholder(_) + ), + "Illegal placeholder constraint blamed; should have redirected to other region relation" + ); + + let blame_constraint = BlameConstraint { + category: best_constraint.category, + from_closure: best_constraint.from_closure, + cause: ObligationCause::new(best_constraint.span, CRATE_DEF_ID, cause_code.clone()), + variance_info: best_constraint.variance_info, + }; + (blame_constraint, path) + } +} + +#[derive(Clone, PartialEq, Eq, Debug)] +enum Trace<'a, 'tcx> { + StartRegion, + FromGraph(&'a OutlivesConstraint<'tcx>), + FromStatic(RegionVid), + NotVisited, +} diff --git a/compiler/rustc_borrowck/src/region_infer/dump_mir.rs b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs index 68f822aac403a..69ed31f9accd7 100644 --- a/compiler/rustc_borrowck/src/region_infer/dump_mir.rs +++ b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs @@ -6,19 +6,37 @@ use std::io::{self, Write}; use rustc_infer::infer::NllRegionVariableOrigin; -use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::{RegionVid, TyCtxt}; -use super::{OutlivesConstraint, RegionInferenceContext}; +use crate::constraints::OutlivesConstraintSet; +use crate::consumers::OutlivesConstraint; +use crate::handle_placeholders::RegionDefinitions; +use crate::region_infer::InferredRegions; +use crate::region_infer::values::LivenessValues; use crate::type_check::Locations; +use crate::type_check::free_region_relations::UniversalRegionRelations; +use crate::universal_regions::UniversalRegions; // 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> { +pub(crate) struct MirDumper<'a, 'tcx> { + pub(crate) tcx: TyCtxt<'tcx>, + pub(crate) definitions: &'a RegionDefinitions<'tcx>, + pub(crate) universal_region_relations: &'a UniversalRegionRelations<'tcx>, + pub(crate) outlives_constraints: &'a OutlivesConstraintSet<'tcx>, + pub(crate) liveness_constraints: &'a LivenessValues, +} + +impl<'a, 'tcx> MirDumper<'a, 'tcx> { /// Write out our state into the `.mir` files. - pub(crate) fn dump_mir(&self, tcx: TyCtxt<'tcx>, out: &mut dyn Write) -> io::Result<()> { + pub(crate) fn dump_mir( + &self, + scc_values: &InferredRegions<'tcx>, + out: &mut dyn Write, + ) -> io::Result<()> { writeln!(out, "| Free Region Mapping")?; for region in self.regions() { @@ -46,14 +64,14 @@ impl<'tcx> RegionInferenceContext<'tcx> { "| {r:rw$?} | {ui:4?} | {v}", r = region, rw = REGION_WIDTH, - ui = self.max_nameable_universe(self.constraint_sccs.scc(region)), - v = self.region_value_str(region), + ui = scc_values.max_nameable_universe(region), + v = scc_values.region_value_str(region), )?; } writeln!(out, "|")?; writeln!(out, "| Inference Constraints")?; - self.for_each_constraint(tcx, &mut |msg| writeln!(out, "| {msg}"))?; + self.for_each_constraint(self.tcx, &mut |msg| writeln!(out, "| {msg}"))?; Ok(()) } @@ -74,7 +92,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { } } - let mut constraints: Vec<_> = self.constraints.outlives().iter().collect(); + let mut constraints: Vec<_> = self.outlives_constraints.outlives().iter().collect(); constraints.sort_by_key(|c| (c.sup, c.sub)); for constraint in &constraints { let OutlivesConstraint { sup, sub, locations, category, span, .. } = constraint; @@ -89,4 +107,12 @@ impl<'tcx> RegionInferenceContext<'tcx> { Ok(()) } + fn universal_regions(&self) -> &UniversalRegions<'tcx> { + &self.universal_region_relations.universal_regions + } + + /// Returns an iterator over all the region indices. + pub(crate) fn regions(&self) -> impl Iterator + 'tcx { + self.definitions.indices() + } } diff --git a/compiler/rustc_borrowck/src/region_infer/graphviz.rs b/compiler/rustc_borrowck/src/region_infer/graphviz.rs index ceb33d82deba8..23b63f8351dd5 100644 --- a/compiler/rustc_borrowck/src/region_infer/graphviz.rs +++ b/compiler/rustc_borrowck/src/region_infer/graphviz.rs @@ -10,6 +10,7 @@ use rustc_graphviz as dot; use rustc_middle::ty::UniverseIndex; use super::*; +use crate::consumers::OutlivesConstraint; fn render_outlives_constraint(constraint: &OutlivesConstraint<'_>) -> String { if let ConstraintCategory::OutlivesUnnameablePlaceholder(unnameable) = constraint.category { @@ -33,19 +34,19 @@ fn render_universe(u: UniverseIndex) -> String { fn render_region_vid<'tcx>( tcx: TyCtxt<'tcx>, rvid: RegionVid, - regioncx: &RegionInferenceContext<'tcx>, + region_definitions: &IndexVec>, ) -> String { - let universe_str = render_universe(regioncx.region_definition(rvid).universe); + let universe_str = render_universe(region_definitions[rvid].universe); let external_name_str = if let Some(external_name) = - regioncx.region_definition(rvid).external_name.and_then(|e| e.get_name(tcx)) + region_definitions[rvid].external_name.and_then(|e| e.get_name(tcx)) { format!(" ({external_name})") } else { "".to_string() }; - let extra_info = match regioncx.region_definition(rvid).origin { + let extra_info = match region_definitions[rvid].origin { NllRegionVariableOrigin::FreeRegion => "".to_string(), NllRegionVariableOrigin::Placeholder(p) => match p.bound.kind { ty::BoundRegionKind::Named(def_id) => { @@ -63,37 +64,38 @@ fn render_region_vid<'tcx>( format!("{:?}{universe_str}{external_name_str}{extra_info}", rvid) } -impl<'tcx> RegionInferenceContext<'tcx> { - /// Write out the region constraint graph. - pub(crate) fn dump_graphviz_raw_constraints( - &self, - tcx: TyCtxt<'tcx>, - mut w: &mut dyn Write, - ) -> io::Result<()> { - dot::render(&RawConstraints { tcx, regioncx: self }, &mut w) - } - - /// Write out the region constraint SCC graph. - pub(crate) fn dump_graphviz_scc_constraints( - &self, - tcx: TyCtxt<'tcx>, - 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); - } +/// Write out the region constraint graph. +pub(crate) fn dump_graphviz_raw_constraints<'a, 'tcx>( + tcx: TyCtxt<'tcx>, + region_definitions: &'a IndexVec>, + outlives_constraints: &'a OutlivesConstraintSet<'tcx>, + mut w: &mut dyn Write, +) -> io::Result<()> { + dot::render(&RawConstraints { tcx, region_definitions, outlives_constraints }, &mut w) +} - dot::render(&SccConstraints { tcx, regioncx: self, nodes_per_scc }, &mut w) +/// Write out the region constraint SCC graph. +pub(crate) fn dump_graphviz_scc_constraints<'a, 'tcx>( + tcx: TyCtxt<'tcx>, + region_definitions: &'a IndexVec>, + constraint_sccs: &'a ConstraintSccs, + mut w: &mut dyn Write, +) -> io::Result<()> { + let mut nodes_per_scc: IndexVec = + constraint_sccs.all_sccs().map(|_| Vec::new()).collect(); + + for region in region_definitions.indices() { + let scc = constraint_sccs.scc(region); + nodes_per_scc[scc].push(region); } + + dot::render(&SccConstraints { tcx, region_definitions, constraint_sccs, nodes_per_scc }, &mut w) } struct RawConstraints<'a, 'tcx> { tcx: TyCtxt<'tcx>, - regioncx: &'a RegionInferenceContext<'tcx>, + region_definitions: &'a IndexVec>, + outlives_constraints: &'a OutlivesConstraintSet<'tcx>, } impl<'a, 'this, 'tcx> dot::Labeller<'this> for RawConstraints<'a, 'tcx> { @@ -110,7 +112,7 @@ impl<'a, 'this, 'tcx> dot::Labeller<'this> for RawConstraints<'a, 'tcx> { Some(dot::LabelText::LabelStr(Cow::Borrowed("box"))) } fn node_label(&'this self, n: &RegionVid) -> dot::LabelText<'this> { - dot::LabelText::LabelStr(render_region_vid(self.tcx, *n, self.regioncx).into()) + dot::LabelText::LabelStr(render_region_vid(self.tcx, *n, self.region_definitions).into()) } fn edge_label(&'this self, e: &OutlivesConstraint<'tcx>) -> dot::LabelText<'this> { dot::LabelText::LabelStr(render_outlives_constraint(e).into()) @@ -122,11 +124,11 @@ impl<'a, 'this, 'tcx> dot::GraphWalk<'this> for RawConstraints<'a, 'tcx> { type Edge = OutlivesConstraint<'tcx>; fn nodes(&'this self) -> dot::Nodes<'this, RegionVid> { - let vids: Vec = self.regioncx.definitions.indices().collect(); + let vids: Vec = self.region_definitions.indices().collect(); vids.into() } fn edges(&'this self) -> dot::Edges<'this, OutlivesConstraint<'tcx>> { - (&self.regioncx.constraints.outlives().raw[..]).into() + (&self.outlives_constraints.outlives().raw[..]).into() } // Render `a: b` as `a -> b`, indicating the flow @@ -143,8 +145,9 @@ impl<'a, 'this, 'tcx> dot::GraphWalk<'this> for RawConstraints<'a, 'tcx> { struct SccConstraints<'a, 'tcx> { tcx: TyCtxt<'tcx>, - regioncx: &'a RegionInferenceContext<'tcx>, nodes_per_scc: IndexVec>, + region_definitions: &'a IndexVec>, + constraint_sccs: &'a ConstraintSccs, } impl<'a, 'this, 'tcx> dot::Labeller<'this> for SccConstraints<'a, 'tcx> { @@ -163,7 +166,7 @@ impl<'a, 'this, 'tcx> dot::Labeller<'this> for SccConstraints<'a, 'tcx> { fn node_label(&'this self, n: &ConstraintSccIndex) -> dot::LabelText<'this> { let nodes_str = self.nodes_per_scc[*n] .iter() - .map(|n| render_region_vid(self.tcx, *n, self.regioncx)) + .map(|n| render_region_vid(self.tcx, *n, self.region_definitions)) .join(", "); dot::LabelText::LabelStr(format!("SCC({n}) = {{{nodes_str}}}", n = n.as_usize()).into()) } @@ -174,20 +177,15 @@ impl<'a, 'this, 'tcx> dot::GraphWalk<'this> for SccConstraints<'a, 'tcx> { type Edge = (ConstraintSccIndex, ConstraintSccIndex); fn nodes(&'this self) -> dot::Nodes<'this, ConstraintSccIndex> { - let vids: Vec = self.regioncx.constraint_sccs.all_sccs().collect(); + let vids: Vec = self.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)) + self.constraint_sccs.successors(scc_a).iter().map(move |&scc_b| (scc_a, scc_b)) }) .collect(); diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 6ed70b39c5b7f..8d11f91c7a779 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -1,35 +1,27 @@ -use std::collections::VecDeque; use std::rc::Rc; use rustc_data_structures::frozen::Frozen; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_data_structures::graph::scc::Sccs; -use rustc_errors::Diag; -use rustc_hir::def_id::CRATE_DEF_ID; use rustc_index::IndexVec; use rustc_infer::infer::outlives::test_type_match; use rustc_infer::infer::region_constraints::{GenericKind, VerifyBound, VerifyIfEq}; use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin}; use rustc_middle::bug; -use rustc_middle::mir::{ - AnnotationSource, BasicBlock, Body, ConstraintCategory, Local, Location, ReturnConstraint, - TerminatorKind, -}; -use rustc_middle::traits::{ObligationCause, ObligationCauseCode}; +use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, Local, Location, TerminatorKind}; use rustc_middle::ty::{self, RegionVid, Ty, TyCtxt, TypeFoldable, UniverseIndex, fold_regions}; use rustc_mir_dataflow::points::DenseLocationMap; -use rustc_span::hygiene::DesugaringKind; -use rustc_span::{DUMMY_SP, Span}; -use tracing::{Level, debug, enabled, instrument, trace}; - -use crate::constraints::graph::NormalConstraintGraph; -use crate::constraints::{ConstraintSccIndex, OutlivesConstraint, OutlivesConstraintSet}; -use crate::dataflow::BorrowIndex; -use crate::diagnostics::{RegionErrorKind, RegionErrors, UniverseInfo}; -use crate::handle_placeholders::{LoweredConstraints, RegionTracker}; -use crate::polonius::LiveLoans; +use rustc_span::Span; +use tracing::{Level, debug, enabled, instrument}; + +use crate::constraints::{ConstraintSccIndex, OutlivesConstraintSet}; +use crate::diagnostics::{RegionErrorKind, RegionErrors}; +use crate::handle_placeholders::{RegionDefinitions, RegionTracker}; use crate::polonius::legacy::PoloniusOutput; -use crate::region_infer::values::{LivenessValues, RegionElement, RegionValues, ToElementIndex}; +use crate::region_infer::universal_regions::UniversalRegionChecker; +use crate::region_infer::values::{ + LivenessValues, PlaceholderIndices, RegionValues, ToElementIndex, +}; use crate::type_check::Locations; use crate::type_check::free_region_relations::UniversalRegionRelations; use crate::universal_regions::UniversalRegions; @@ -39,11 +31,15 @@ use crate::{ }; mod dump_mir; -mod graphviz; +pub(crate) mod graphviz; pub(crate) mod opaque_types; mod reverse_sccs; pub(crate) mod values; +pub(crate) use dump_mir::MirDumper; +mod constraint_search; +mod universal_regions; +pub(crate) use constraint_search::{BlameConstraint, ConstraintSearch}; /// The representative region variable for an SCC, tagged by its origin. /// We prefer placeholders over existentially quantified variables, otherwise @@ -76,358 +72,209 @@ impl Representative { pub(crate) type ConstraintSccs = Sccs; -pub struct RegionInferenceContext<'tcx> { - /// 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. - pub(crate) definitions: Frozen>>, - - /// 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: ConstraintSccs, - - scc_annotations: IndexVec, +pub struct InferredRegions<'tcx> { + pub(crate) scc_values: RegionValues<'tcx, ConstraintSccIndex>, + pub(crate) sccs: ConstraintSccs, + annotations: IndexVec, + universal_region_relations: Rc>>, +} - /// Map universe indexes to information on why we created it. - universe_causes: FxIndexMap>, +impl<'tcx> InferredRegions<'tcx> { + /// Tries to find the terminator of the loop in which the region 'r' resides. + /// Returns the location of the terminator if found. + pub(crate) fn find_loop_terminator_location( + &self, + r: RegionVid, + body: &Body<'_>, + ) -> Option { + let locations = self.scc_values.locations_outlived_by(self.scc(r)); + for location in locations { + let bb = &body[location.block]; + if let Some(terminator) = &bb.terminator + // terminator of a loop should be TerminatorKind::FalseUnwind + && let TerminatorKind::FalseUnwind { .. } = terminator.kind + { + return Some(location); + } + } + None + } - /// 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<'tcx, ConstraintSccIndex>, + pub(crate) fn scc(&self, r: RegionVid) -> ConstraintSccIndex { + self.sccs.scc(r) + } - /// Type constraints that we check after solving. - type_tests: Vec>, + /// Returns the lowest statement index in `start..=end` which is not contained by `r`. + pub(crate) fn first_non_contained_inclusive( + &self, + r: RegionVid, + block: BasicBlock, + start: usize, + end: usize, + ) -> Option { + self.scc_values.first_non_contained_inclusive(self.scc(r), block, start, end) + } - /// Information about how the universally quantified regions in - /// scope on this function relate to one another. - universal_region_relations: Frozen>, -} + /// Returns `true` if the region `r` contains the point `p`. + pub(crate) fn region_contains(&self, r: RegionVid, p: impl ToElementIndex<'tcx>) -> bool { + self.scc_values.contains(self.scc(r), p) + } -#[derive(Debug)] -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<'tcx>, + /// Returns access to the value of `r` for debugging purposes. + pub(crate) fn region_value_str(&self, r: RegionVid) -> String { + self.scc_values.region_value_str(self.scc(r)) + } - /// 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, + pub(crate) fn placeholders_contained_in( + &self, + r: RegionVid, + ) -> impl Iterator> { + self.scc_values.placeholders_contained_in(self.scc(r)) + } - /// 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>, -} + /// Check if the SCC of `r` contains `upper`. + pub(crate) fn upper_bound_in_region_scc(&self, r: RegionVid, upper: RegionVid) -> bool { + self.scc_values.contains(self.scc(r), upper) + } -/// 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), + pub(crate) fn universal_regions_outlived_by( + &self, + r: RegionVid, + ) -> impl Iterator { + self.scc_values.universal_regions_outlived_by(self.scc(r)) + } - /// point inserted because Local was dropped at the given Location - DropVar(Local, Location), -} + fn max_nameable_universe(&self, vid: RegionVid) -> UniverseIndex { + self.annotations[self.scc(vid)].max_nameable_universe() + } -/// 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(crate) struct TypeTest<'tcx> { - /// The type `T` that must outlive the region. - pub generic_kind: GenericKind<'tcx>, + /// Evaluate whether `sup_region == sub_region`. + /// + // This is `pub` because it's used by unstable external borrowck data users, see `consumers.rs`. + pub fn eval_equal(&self, r1: RegionVid, r2: RegionVid) -> bool { + self.eval_outlives(r1, r2) && self.eval_outlives(r2, r1) + } - /// The region `'x` that the type must outlive. - pub lower_bound: RegionVid, + /// Evaluate whether `sup_region: sub_region`. + /// + // This is `pub` because it's used by unstable external borrowck data users, see `consumers.rs`. + #[instrument(skip(self), level = "debug", ret)] + pub fn eval_outlives(&self, sup_region: RegionVid, sub_region: RegionVid) -> bool { + let sub_region_scc = self.scc(sub_region); + let sup_region_scc = self.scc(sup_region); - /// The span to blame. - pub span: Span, + if sub_region_scc == sup_region_scc { + debug!("{sup_region:?}: {sub_region:?} holds trivially; they are in the same SCC"); + return true; + } - /// A test which, if met by the region `'x`, proves that this type - /// constraint is satisfied. - pub verify_bound: VerifyBound<'tcx>, -} + let fr_static = self.universal_region_relations.universal_regions.fr_static; -/// 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, -} + // 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. + // + // Avoid infinite recursion if `sub_region` is already `'static` + if sub_region != fr_static + && !self.annotations[sup_region_scc] + .can_name_all_placeholders(self.annotations[sub_region_scc]) + { + debug!( + "sub universe `{sub_region_scc:?}` is not nameable \ + by super `{sup_region_scc:?}`, promoting to static", + ); -#[derive(Clone, PartialEq, Eq, Debug)] -enum Trace<'a, 'tcx> { - StartRegion, - FromGraph(&'a OutlivesConstraint<'tcx>), - FromStatic(RegionVid), - NotVisited, -} + return self.eval_outlives(sup_region, fr_static); + } -#[instrument(skip(infcx, sccs), level = "debug")] -fn sccs_info<'tcx>(infcx: &BorrowckInferCtxt<'tcx>, sccs: &ConstraintSccs) { - use crate::renumber::RegionCtxt; + // 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.universal_regions_outlived_by(sup_region) + .any(|r2| self.universal_region_relations.outlives(r2, r1)) + }); - let var_to_origin = infcx.reg_var_to_origin.borrow(); + if !universal_outlives { + debug!("sub region contains a universal region not present in super"); + return false; + } - let mut var_to_origin_sorted = var_to_origin.clone().into_iter().collect::>(); - var_to_origin_sorted.sort_by_key(|vto| vto.0); + // Now we have to compare all the points in the sub region and make + // sure they exist in the sup region. - if enabled!(Level::DEBUG) { - let mut reg_vars_to_origins_str = "region variables to origins:\n".to_string(); - for (reg_var, origin) in var_to_origin_sorted.into_iter() { - reg_vars_to_origins_str.push_str(&format!("{reg_var:?}: {origin:?}\n")); + if self.universal_regions().is_universal_region(sup_region) { + // Micro-opt: universal regions contain all points. + debug!("super is universal and hence contains all points"); + return true; } - debug!("{}", reg_vars_to_origins_str); - } - let num_components = sccs.num_sccs(); - let mut components = vec![FxIndexSet::default(); num_components]; + debug!("comparison between points in sup/sub"); - for (reg_var, scc_idx) in sccs.scc_indices().iter_enumerated() { - let origin = var_to_origin.get(®_var).unwrap_or(&RegionCtxt::Unknown); - components[scc_idx.as_usize()].insert((reg_var, *origin)); + self.scc_values.contains_points(sup_region_scc, sub_region_scc) } - if enabled!(Level::DEBUG) { - let mut components_str = "strongly connected components:".to_string(); - for (scc_idx, reg_vars_origins) in components.iter().enumerate() { - let regions_info = reg_vars_origins.clone().into_iter().collect::>(); - components_str.push_str(&format!( - "{:?}: {:?},\n)", - ConstraintSccIndex::from_usize(scc_idx), - regions_info, - )) + fn eval_if_eq( + &self, + infcx: &InferCtxt<'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, &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, } - debug!("{}", components_str); } - // calculate the best representative for each component - let components_representatives = components - .into_iter() - .enumerate() - .map(|(scc_idx, region_ctxts)| { - let repr = region_ctxts - .into_iter() - .map(|reg_var_origin| reg_var_origin.1) - .max_by(|x, y| x.preference_value().cmp(&y.preference_value())) - .unwrap(); - - (ConstraintSccIndex::from_usize(scc_idx), repr) - }) - .collect::>(); - - let mut scc_node_to_edges = FxIndexMap::default(); - for (scc_idx, repr) in components_representatives.iter() { - let edge_representatives = sccs - .successors(*scc_idx) - .iter() - .map(|scc_idx| components_representatives[scc_idx]) - .collect::>(); - scc_node_to_edges.insert((scc_idx, repr), edge_representatives); - } - - debug!("SCC edges {:#?}", scc_node_to_edges); -} - -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( - infcx: &BorrowckInferCtxt<'tcx>, - lowered_constraints: LoweredConstraints<'tcx>, - universal_region_relations: Frozen>, - location_map: Rc, - ) -> Self { - let universal_regions = &universal_region_relations.universal_regions; - - let LoweredConstraints { - constraint_sccs, - definitions, - outlives_constraints, - scc_annotations, - type_tests, - liveness_constraints, - universe_causes, - placeholder_indices, - } = lowered_constraints; - - debug!("universal_regions: {:#?}", universal_region_relations.universal_regions); - debug!("outlives constraints: {:#?}", outlives_constraints); - debug!("placeholder_indices: {:#?}", placeholder_indices); - debug!("type tests: {:#?}", type_tests); - - let constraint_graph = Frozen::freeze(outlives_constraints.graph(definitions.len())); - - if cfg!(debug_assertions) { - sccs_info(infcx, &constraint_sccs); - } - - let mut scc_values = - RegionValues::new(location_map, universal_regions.len(), placeholder_indices); - - for region in liveness_constraints.regions() { - let scc = constraint_sccs.scc(region); - scc_values.merge_liveness(scc, region, &liveness_constraints); - } - - let mut result = Self { - definitions, - liveness_constraints, - constraints: outlives_constraints, - constraint_graph, - constraint_sccs, - scc_annotations, - universe_causes, - scc_values, - type_tests, - universal_region_relations, - }; - - result.init_free_and_bound_regions(); - - result - } - - /// 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: - /// ```ignore (illustrative) - /// 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. - /// - /// This bit of logic also handles invalid universe relations - /// for higher-kinded types. - /// - /// We 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`): - /// - /// ```ignore (illustrative) - /// static -----+ - /// | | - /// empty(U0) placeholder(U1) - /// | / - /// empty(U1) - /// ``` + /// 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). /// - /// In particular, imagine we have variables R0 in U0 and R1 - /// created in U1, and constraints like this; + /// 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 /// - /// ```ignore (illustrative) - /// R1: !1 // R1 outlives the placeholder in U1 - /// R1: R0 // R1 outlives R0 + /// ```text + /// >::Item: '1 /// ``` /// - /// Here, we wish for R1 to be `'static`, because it - /// cannot outlive `placeholder(U1)` and `empty(U0)` any other way. + /// 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. /// - /// Thanks to this loop, what happens is that the `R1: R0` - /// constraint has lowered the universe of `R1` to `U0`, which in turn - /// means that the `R1: !1` constraint here will cause - /// `R1` to become `'static`. - fn init_free_and_bound_regions(&mut self) { - 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) => { - self.scc_values.add_element(scc, placeholder); - } - - NllRegionVariableOrigin::Existential { .. } => { - // For existential, regions, nothing to do. - } - } - } - } - - /// Returns an iterator over all the region indices. - pub(crate) fn regions(&self) -> impl Iterator + 'tcx { - self.definitions.indices() + /// 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>, + { + fold_regions(tcx, value, |r, _db| { + ty::Region::new_var(tcx, self.to_representative(self.to_region_vid(r))) + }) } /// Given a universal region in scope on the MIR, returns the @@ -436,161 +283,59 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// Panics if `r` is not a registered universal region, most notably /// if it is a placeholder. Handling placeholders requires access to the /// `MirTypeckRegionConstraints`. - pub(crate) fn to_region_vid(&self, r: ty::Region<'tcx>) -> RegionVid { + fn to_region_vid(&self, r: ty::Region<'tcx>) -> RegionVid { self.universal_regions().to_region_vid(r) } - /// Returns an iterator over all the outlives constraints. - pub(crate) fn outlives_constraints(&self) -> impl Iterator> { - self.constraints.outlives().iter().copied() - } - - /// Adds annotations for `#[rustc_regions]`; see `UniversalRegions::annotate`. - pub(crate) fn annotate(&self, tcx: TyCtxt<'tcx>, err: &mut Diag<'_, ()>) { - 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: RegionVid, p: impl ToElementIndex<'tcx>) -> bool { - let scc = self.constraint_sccs.scc(r); - self.scc_values.contains(scc, p) - } - - /// Returns the lowest statement index in `start..=end` which is not contained by `r`. - /// - /// Panics if called before `solve()` executes. - pub(crate) fn first_non_contained_inclusive( - &self, - r: RegionVid, - block: BasicBlock, - start: usize, - end: usize, - ) -> Option { - let scc = self.constraint_sccs.scc(r); - self.scc_values.first_non_contained_inclusive(scc, block, start, end) - } - - /// 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); - self.scc_values.region_value_str(scc) + fn universal_regions(&self) -> &UniversalRegions<'tcx> { + &self.universal_region_relations.universal_regions } - pub(crate) fn placeholders_contained_in( + /// Tests if `test` is true when applied to `lower_bound` at + /// `point`. + fn eval_verify_bound( &self, - r: RegionVid, - ) -> impl Iterator> { - let scc = self.constraint_sccs.scc(r); - self.scc_values.placeholders_contained_in(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>, - body: &Body<'tcx>, - polonius_output: Option>, - ) -> (Option>, RegionErrors<'tcx>) { - let mir_def_id = body.source.def_id(); - self.propagate_constraints(); - - let mut errors_buffer = RegionErrors::new(infcx.tcx); - - // 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, outlives_requirements.as_mut(), &mut errors_buffer); - - debug!(?errors_buffer); - debug!(?outlives_requirements); - - // 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.is_legacy_enabled() { - self.check_polonius_subset_errors( - outlives_requirements.as_mut(), - &mut errors_buffer, - polonius_output - .as_ref() - .expect("Polonius output is unavailable despite `-Z polonius`"), - ); - } else { - self.check_universal_regions(outlives_requirements.as_mut(), &mut errors_buffer); - } + generic_ty: Ty<'tcx>, + lower_bound: RegionVid, + verify_bound: &VerifyBound<'tcx>, + ) -> bool { + debug!("eval_verify_bound(lower_bound={:?}, verify_bound={:?})", lower_bound, verify_bound); - debug!(?errors_buffer); + match verify_bound { + VerifyBound::IfEq(verify_if_eq_b) => { + self.eval_if_eq(infcx, generic_ty, lower_bound, *verify_if_eq_b) + } - let outlives_requirements = outlives_requirements.unwrap_or_default(); + VerifyBound::IsEmpty => { + self.scc_values.elements_contained_in(self.scc(lower_bound)).next().is_none() + } - 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, - ) - } - } + VerifyBound::OutlivedBy(r) => { + let r_vid = self.to_region_vid(*r); + self.eval_outlives(r_vid, lower_bound) + } - /// 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), level = "debug")] - fn propagate_constraints(&mut self) { - debug!("constraints={:#?}", { - let mut constraints: Vec<_> = self.outlives_constraints().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::>() - }); + VerifyBound::AnyBound(verify_bounds) => verify_bounds.iter().any(|verify_bound| { + self.eval_verify_bound(infcx, generic_ty, lower_bound, verify_bound) + }), - // To propagate constraints, we walk the DAG induced by the - // SCC. For each SCC `A`, we visit its successors and compute - // their values, then we union all those values to get our - // own. - for scc_a in self.constraint_sccs.all_sccs() { - // Walk each SCC `B` such that `A: B`... - for &scc_b in self.constraint_sccs.successors(scc_a) { - debug!(?scc_b); - self.scc_values.add_region(scc_a, scc_b); - } + VerifyBound::AllBounds(verify_bounds) => verify_bounds.iter().all(|verify_bound| { + self.eval_verify_bound(infcx, generic_ty, lower_bound, verify_bound) + }), } } - /// Returns `true` if all the placeholders 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 can_name_all_placeholders( - &self, - scc_a: ConstraintSccIndex, - scc_b: ConstraintSccIndex, - ) -> bool { - self.scc_annotations[scc_a].can_name_all_placeholders(self.scc_annotations[scc_b]) - } - - /// 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. + /// 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>, mut propagated_outlives_requirements: Option<&mut Vec>>, errors_buffer: &mut RegionErrors<'tcx>, + type_tests: &[TypeTest<'tcx>], ) { let tcx = infcx.tcx; @@ -599,7 +344,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // the user. Avoid that. let mut deduplicate_errors = FxIndexSet::default(); - for type_test in &self.type_tests { + for type_test in type_tests { debug!("check_type_test: {:?}", type_test); let generic_ty = type_test.generic_kind.to_ty(tcx); @@ -613,7 +358,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { } if let Some(propagated_outlives_requirements) = &mut propagated_outlives_requirements - && self.try_promote_type_test(infcx, type_test, propagated_outlives_requirements) + && self.try_promote_type_test(infcx, &type_test, propagated_outlives_requirements) { continue; } @@ -678,12 +423,11 @@ impl<'tcx> RegionInferenceContext<'tcx> { return false; }; - let r_scc = self.constraint_sccs.scc(lower_bound); debug!( "lower_bound = {:?} r_scc={:?} universe={:?}", lower_bound, - r_scc, - self.max_nameable_universe(r_scc) + self.scc(lower_bound), + self.max_nameable_universe(lower_bound) ); // If the type test requires that `T: 'a` where `'a` is a // placeholder from another universe, that effectively requires @@ -691,7 +435,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // // 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() { + if let Some(p) = self.placeholders_contained_in(lower_bound).next() { debug!("encountered placeholder in higher universe: {:?}, requiring 'static", p); let static_r = self.universal_regions().fr_static; propagated_outlives_requirements.push(ClosureOutlivesRequirement { @@ -710,7 +454,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // universal region (it may be the same region) and add it to // `ClosureOutlivesRequirement`. let mut found_outlived_universal_region = false; - for ur in self.scc_values.universal_regions_outlived_by(r_scc) { + for ur in self.universal_regions_outlived_by(lower_bound) { found_outlived_universal_region = true; debug!("universal_region_outlived_by ur={:?}", ur); let non_local_ub = self.universal_region_relations.non_local_upper_bounds(ur); @@ -756,15 +500,13 @@ impl<'tcx> RegionInferenceContext<'tcx> { let mut failed = false; let ty = fold_regions(tcx, ty, |r, _depth| { let r_vid = self.to_region_vid(r); - let r_scc = self.constraint_sccs.scc(r_vid); // The challenge is 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 simply check every candidate `u_r` for equality. - self.scc_values - .universal_regions_outlived_by(r_scc) + self.universal_regions_outlived_by(r_vid) .filter(|&u_r| !self.universal_regions().is_local_free_region(u_r)) .find(|&u_r| self.eval_equal(u_r, r_vid)) .map(|u_r| ty::Region::new_var(tcx, u_r)) @@ -786,1140 +528,354 @@ impl<'tcx> RegionInferenceContext<'tcx> { Some(ClosureOutlivesSubject::Ty(ClosureOutlivesSubjectTy::bind(tcx, ty))) } - /// 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). + /// Returns the representative `RegionVid` for a given region's SCC. + /// See `RegionTracker` for how a region variable ID is chosen. /// - /// 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. - #[instrument(level = "debug", skip(self))] - pub(crate) fn approx_universal_upper_bound(&self, r: RegionVid) -> RegionVid { - debug!("{}", 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!(?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!(?r, ?lub); - - lub + /// It is a hacky way to manage checking regions for equality, + /// 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). + fn to_representative(&self, r: RegionVid) -> RegionVid { + self.annotations[self.scc(r)].representative.rvid() } +} - /// Tests if `test` is true when applied to `lower_bound` at - /// `point`. - fn eval_verify_bound( - &self, - infcx: &InferCtxt<'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); +pub(crate) struct RegionInferenceContext<'a, 'tcx> { + /// 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: &'a RegionDefinitions<'tcx>, - match verify_bound { - VerifyBound::IfEq(verify_if_eq_b) => { - self.eval_if_eq(infcx, generic_ty, lower_bound, *verify_if_eq_b) - } + /// 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)`. + pub(crate) liveness_constraints: &'a mut LivenessValues, - VerifyBound::IsEmpty => { - let lower_bound_scc = self.constraint_sccs.scc(lower_bound); - self.scc_values.elements_contained_in(lower_bound_scc).next().is_none() - } + /// The outlives constraints computed by the type-check. + pub(crate) constraints: &'a OutlivesConstraintSet<'tcx>, - VerifyBound::OutlivedBy(r) => { - let r_vid = self.to_region_vid(*r); - self.eval_outlives(r_vid, lower_bound) - } + /// 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: ConstraintSccs, - VerifyBound::AnyBound(verify_bounds) => verify_bounds.iter().any(|verify_bound| { - self.eval_verify_bound(infcx, generic_ty, lower_bound, verify_bound) - }), + scc_annotations: IndexVec, - VerifyBound::AllBounds(verify_bounds) => verify_bounds.iter().all(|verify_bound| { - self.eval_verify_bound(infcx, generic_ty, lower_bound, verify_bound) - }), - } - } + /// Information about how the universally quantified regions in + /// scope on this function relate to one another. + universal_region_relations: Rc>>, +} - fn eval_if_eq( - &self, - infcx: &InferCtxt<'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, &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, - } - } +#[derive(Debug)] +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<'tcx>, - /// 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>, - { - fold_regions(tcx, value, |r, _db| { - let vid = self.to_region_vid(r); - let scc = self.constraint_sccs.scc(vid); - let repr = self.scc_representative(scc); - ty::Region::new_var(tcx, repr) - }) - } + /// 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, - /// Evaluate whether `sup_region == sub_region`. - /// - /// Panics if called before `solve()` executes, - // This is `pub` because it's used by unstable external borrowck data users, see `consumers.rs`. - pub fn eval_equal(&self, r1: RegionVid, r2: RegionVid) -> bool { - self.eval_outlives(r1, r2) && self.eval_outlives(r2, r1) - } + /// 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>, +} - /// Evaluate whether `sup_region: sub_region`. - /// - /// Panics if called before `solve()` executes, - // This is `pub` because it's used by unstable external borrowck data users, see `consumers.rs`. - #[instrument(skip(self), level = "debug", ret)] - pub fn eval_outlives(&self, sup_region: RegionVid, sub_region: RegionVid) -> bool { - debug!( - "sup_region's value = {:?} universal={:?}", - self.region_value_str(sup_region), - self.universal_regions().is_universal_region(sup_region), - ); - debug!( - "sub_region's value = {:?} universal={:?}", - self.region_value_str(sub_region), - self.universal_regions().is_universal_region(sub_region), - ); +/// 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), - let sub_region_scc = self.constraint_sccs.scc(sub_region); - let sup_region_scc = self.constraint_sccs.scc(sup_region); + /// point inserted because Local was dropped at the given Location + DropVar(Local, Location), +} - if sub_region_scc == sup_region_scc { - debug!("{sup_region:?}: {sub_region:?} holds trivially; they are in the same SCC"); - return true; - } +/// 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(crate) struct TypeTest<'tcx> { + /// The type `T` that must outlive the region. + pub generic_kind: GenericKind<'tcx>, - let fr_static = self.universal_regions().fr_static; + /// The region `'x` that the type must outlive. + pub lower_bound: RegionVid, - // 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. - // - // Avoid infinite recursion if `sub_region` is already `'static` - if sub_region != fr_static - && !self.can_name_all_placeholders(sup_region_scc, sub_region_scc) - { - debug!( - "sub universe `{sub_region_scc:?}` is not nameable \ - by super `{sup_region_scc:?}`, promoting to static", - ); + /// The span to blame. + pub span: Span, - return self.eval_outlives(sup_region, fr_static); - } + /// A test which, if met by the region `'x`, proves that this type + /// constraint is satisfied. + pub verify_bound: VerifyBound<'tcx>, +} - // 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)) - }); +#[instrument(skip(infcx, sccs), level = "debug")] +fn sccs_info<'tcx>(infcx: &BorrowckInferCtxt<'tcx>, sccs: &ConstraintSccs) { + use crate::renumber::RegionCtxt; - if !universal_outlives { - debug!("sub region contains a universal region not present in super"); - return false; - } + let var_to_origin = infcx.reg_var_to_origin.borrow(); - // Now we have to compare all the points in the sub region and make - // sure they exist in the sup region. + let mut var_to_origin_sorted = var_to_origin.clone().into_iter().collect::>(); + var_to_origin_sorted.sort_by_key(|vto| vto.0); - if self.universal_regions().is_universal_region(sup_region) { - // Micro-opt: universal regions contain all points. - debug!("super is universal and hence contains all points"); - return true; + if enabled!(Level::DEBUG) { + let mut reg_vars_to_origins_str = "region variables to origins:\n".to_string(); + for (reg_var, origin) in var_to_origin_sorted.into_iter() { + reg_vars_to_origins_str.push_str(&format!("{reg_var:?}: {origin:?}\n")); } - - debug!("comparison between points in sup/sub"); - - self.scc_values.contains_points(sup_region_scc, sub_region_scc) + debug!("{}", reg_vars_to_origins_str); } - /// 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 - /// 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, - mut propagated_outlives_requirements: Option<&mut Vec>>, - errors_buffer: &mut RegionErrors<'tcx>, - ) { - for (fr, fr_definition) in self.definitions.iter_enumerated() { - debug!(?fr, ?fr_definition); - 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( - fr, - &mut propagated_outlives_requirements, - errors_buffer, - ); - } - - NllRegionVariableOrigin::Placeholder(placeholder) => { - self.check_bound_universal_region(fr, placeholder, errors_buffer); - } + let num_components = sccs.num_sccs(); + let mut components = vec![FxIndexSet::default(); num_components]; - NllRegionVariableOrigin::Existential { .. } => { - // nothing to check here - } - } - } + for (reg_var, scc_idx) in sccs.scc_indices().iter_enumerated() { + let origin = var_to_origin.get(®_var).unwrap_or(&RegionCtxt::Unknown); + components[scc_idx.as_usize()].insert((reg_var, *origin)); } - /// 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 - /// 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, - mut propagated_outlives_requirements: Option<&mut Vec>>, - errors_buffer: &mut RegionErrors<'tcx>, - polonius_output: &PoloniusOutput, - ) { - debug!( - "check_polonius_subset_errors: {} subset_errors", - polonius_output.subset_errors.len() - ); - - // Similarly to `check_universal_regions`: a free region relation, which was not explicitly - // declared ("known") was found by Polonius, so emit an error, or propagate the - // requirements for our caller into the `propagated_outlives_requirements` vector. - // - // Polonius doesn't model regions ("origins") as CFG-subsets or durations, but the - // `longer_fr` and `shorter_fr` terminology will still be used here, for consistency with - // the rest of the NLL infrastructure. The "subset origin" is the "longer free region", - // and the "superset origin" is the outlived "shorter free region". - // - // Note: Polonius will produce a subset error at every point where the unexpected - // `longer_fr`'s "placeholder loan" is contained in the `shorter_fr`. This can be helpful - // for diagnostics in the future, e.g. to point more precisely at the key locations - // requiring this constraint to hold. However, the error and diagnostics code downstream - // expects that these errors are not duplicated (and that they are in a certain order). - // Otherwise, diagnostics messages such as the ones giving names like `'1` to elided or - // anonymous lifetimes for example, could give these names differently, while others like - // the outlives suggestions or the debug output from `#[rustc_regions]` would be - // duplicated. The polonius subset errors are deduplicated here, while keeping the - // CFG-location ordering. - // We can iterate the HashMap here because the result is sorted afterwards. - #[allow(rustc::potential_query_instability)] - 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.into(), - shorter_fr.into(), - &mut propagated_outlives_requirements, - ); - if propagated == RegionRelationCheckResult::Error { - errors_buffer.push(RegionErrorKind::RegionError { - longer_fr: longer_fr.into(), - shorter_fr: shorter_fr.into(), - 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 - } - } + if enabled!(Level::DEBUG) { + let mut components_str = "strongly connected components:".to_string(); + for (scc_idx, reg_vars_origins) in components.iter().enumerate() { + let regions_info = reg_vars_origins.clone().into_iter().collect::>(); + components_str.push_str(&format!( + "{:?}: {:?},\n)", + ConstraintSccIndex::from_usize(scc_idx), + regions_info, + )) } + debug!("{}", components_str); } - /// The largest universe of any region nameable from this SCC. - fn max_nameable_universe(&self, scc: ConstraintSccIndex) -> UniverseIndex { - self.scc_annotations[scc].max_nameable_universe() - } + // calculate the best representative for each component + let components_representatives = components + .into_iter() + .enumerate() + .map(|(scc_idx, region_ctxts)| { + let repr = region_ctxts + .into_iter() + .map(|reg_var_origin| reg_var_origin.1) + .max_by(|x, y| x.preference_value().cmp(&y.preference_value())) + .unwrap(); - /// 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, propagated_outlives_requirements, errors_buffer), level = "debug")] - fn check_universal_region( - &self, - 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.max_nameable_universe(longer_fr_scc).is_root()); - - // 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_representative(longer_fr_scc); - if representative != longer_fr { - if let RegionRelationCheckResult::Error = self.check_universal_region_relation( - longer_fr, - representative, - propagated_outlives_requirements, - ) { - errors_buffer.push(RegionErrorKind::RegionError { - longer_fr, - shorter_fr: representative, - fr_origin: NllRegionVariableOrigin::FreeRegion, - is_reported: true, - }); - } - return; - } + (ConstraintSccIndex::from_usize(scc_idx), repr) + }) + .collect::>(); - // 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, - 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; - } - } + let mut scc_node_to_edges = FxIndexMap::default(); + for (scc_idx, repr) in components_representatives.iter() { + let edge_representatives = sccs + .successors(*scc_idx) + .iter() + .map(|scc_idx| components_representatives[scc_idx]) + .collect::>(); + scc_node_to_edges.insert((scc_idx, repr), edge_representatives); } - /// 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, - 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, - propagated_outlives_requirements, - ) - } - } + debug!("SCC edges {:#?}", scc_node_to_edges); +} - /// 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, - propagated_outlives_requirements: &mut Option<&mut Vec>>, - ) -> RegionRelationCheckResult { - if let Some(propagated_outlives_requirements) = propagated_outlives_requirements { - // Shrink `longer_fr` until we find some non-local regions. - // We'll call them `longer_fr-` -- they are ever so slightly smaller than - // `longer_fr`. - let longer_fr_minus = self.universal_region_relations.non_local_lower_bounds(longer_fr); - - debug!("try_propagate_universal_region_error: fr_minus={:?}", longer_fr_minus); - - // If we don't find a any non-local regions, we should error out as there is nothing - // to propagate. - if longer_fr_minus.is_empty() { - return RegionRelationCheckResult::Error; +impl<'a, 'tcx> RegionInferenceContext<'a, 'tcx> { + /// 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( + infcx, + body, + polonius_output, + location_map, + placeholder_indices, + constraint_sccs, + liveness_constraints + ), + level = "debug" + )] + pub(super) fn infer_regions( + infcx: &BorrowckInferCtxt<'tcx>, + constraint_sccs: Sccs, + definitions: &'a RegionDefinitions<'tcx>, + scc_annotations: IndexVec, + outlives_constraints: &'a OutlivesConstraintSet<'tcx>, + type_tests: &'a [TypeTest<'tcx>], + liveness_constraints: &'a mut LivenessValues, + universal_region_relations: Rc>>, + body: &Body<'tcx>, + polonius_output: Option>, + location_map: Rc, + placeholder_indices: PlaceholderIndices<'tcx>, + ) -> (Option>, RegionErrors<'tcx>, InferredRegions<'tcx>) { + let num_external_vids = + universal_region_relations.universal_regions.num_global_and_external_regions(); + + let regioncx = { + if cfg!(debug_assertions) { + sccs_info(infcx, &constraint_sccs); } - let blame_constraint = self - .best_blame_constraint(longer_fr, NllRegionVariableOrigin::FreeRegion, shorter_fr) - .0; - - // Grow `shorter_fr` until we find some non-local regions. - // We will always find at least one: `'static`. 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); - - // We then create constraints `longer_fr-: shorter_fr+` that may or may not - // be propagated (see below). - let mut constraints = vec![]; - for fr_minus in longer_fr_minus { - for shorter_fr_plus in &shorter_fr_plus { - constraints.push((fr_minus, *shorter_fr_plus)); - } + Self { + definitions, + liveness_constraints, + constraints: outlives_constraints, + constraint_sccs, + scc_annotations, + universal_region_relations, } + }; - // We only need to propagate at least one of the constraints for - // soundness. However, we want to avoid arbitrary choices here - // and currently don't support returning OR constraints. - // - // If any of the `shorter_fr+` regions are already outlived by `longer_fr-`, - // we propagate only those. - // - // Consider this example (`'b: 'a` == `a -> b`), where we try to propagate `'d: 'a`: - // a --> b --> d - // \ - // \-> c - // Here, `shorter_fr+` of `'a` == `['b, 'c]`. - // Propagating `'d: 'b` is correct and should occur; `'d: 'c` is redundant because of - // `'d: 'b` and could reject valid code. - // - // So we filter the constraints to regions already outlived by `longer_fr-`, but if - // the filter yields an empty set, we fall back to the original one. - let subset: Vec<_> = constraints - .iter() - .filter(|&&(fr_minus, shorter_fr_plus)| { - self.eval_outlives(fr_minus, shorter_fr_plus) - }) - .copied() - .collect(); - let propagated_constraints = if subset.is_empty() { constraints } else { subset }; - debug!( - "try_propagate_universal_region_error: constraints={:?}", - propagated_constraints - ); - - assert!( - !propagated_constraints.is_empty(), - "Expected at least one constraint to propagate here" - ); + let mir_def_id = body.source.def_id(); + let scc_values = InferredRegions { + scc_values: regioncx.compute_region_values(location_map, placeholder_indices), + sccs: regioncx.constraint_sccs, + annotations: regioncx.scc_annotations, + universal_region_relations: regioncx.universal_region_relations, + }; - for (fr_minus, fr_plus) in propagated_constraints { - // Push the constraint `long_fr-: shorter_fr+` - propagated_outlives_requirements.push(ClosureOutlivesRequirement { - subject: ClosureOutlivesSubject::Region(fr_minus), - outlived_free_region: fr_plus, - blame_span: blame_constraint.cause.span, - category: blame_constraint.category, - }); - } - return RegionRelationCheckResult::Propagated; - } + let mut errors_buffer = RegionErrors::new(infcx.tcx); - RegionRelationCheckResult::Error - } + // 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.infcx.tcx.is_typeck_child(mir_def_id).then(Vec::new); - fn check_bound_universal_region( - &self, - longer_fr: RegionVid, - placeholder: ty::PlaceholderRegion<'tcx>, - 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! - if let Some(error_element) = self - .scc_values - .elements_contained_in(longer_fr_scc) - .find(|e| *e != RegionElement::PlaceholderRegion(placeholder)) - { - // Stop after the first error, it gets too noisy otherwise, and does not provide more information. - errors_buffer.push(RegionErrorKind::BoundUniversalRegionError { - longer_fr, - error_element, - placeholder, - }); - } else { - debug!("check_bound_universal_region: all bounds satisfied"); - } - } + scc_values.check_type_tests( + infcx, + outlives_requirements.as_mut(), + &mut errors_buffer, + type_tests, + ); - pub(crate) fn constraint_path_between_regions( - &self, - from_region: RegionVid, - to_region: RegionVid, - ) -> Option>> { - if from_region == to_region { - bug!("Tried to find a path between {from_region:?} and itself!"); - } - self.constraint_path_to(from_region, |to| to == to_region, true).map(|o| o.0) - } + debug!(?errors_buffer); + debug!(?outlives_requirements); - /// Walks the graph of constraints (where `'a: 'b` is considered - /// an edge `'a -> 'b`) to find a path from `from_region` to - /// `to_region`. - /// - /// Returns: a series of constraints as well as the region `R` - /// that passed the target test. - /// If `include_static_outlives_all` is `true`, then the synthetic - /// outlives constraints `'static -> a` for every region `a` are - /// considered in the search, otherwise they are ignored. - #[instrument(skip(self, target_test), ret)] - pub(crate) fn constraint_path_to( - &self, - from_region: RegionVid, - target_test: impl Fn(RegionVid) -> bool, - include_placeholder_static: bool, - ) -> Option<(Vec>, RegionVid)> { - self.find_constraint_path_between_regions_inner( - true, - from_region, - &target_test, - include_placeholder_static, + UniversalRegionChecker::new( + &mut errors_buffer, + definitions, + outlives_constraints, + &scc_values, ) - .or_else(|| { - self.find_constraint_path_between_regions_inner( - false, - from_region, - &target_test, - include_placeholder_static, - ) - }) - } - - /// The constraints we get from equating the hidden type of each use of an opaque - /// with its final hidden type may end up getting preferred over other, potentially - /// longer constraint paths. - /// - /// Given that we compute the final hidden type by relying on this existing constraint - /// path, this can easily end up hiding the actual reason for why we require these regions - /// to be equal. - /// - /// To handle this, we first look at the path while ignoring these constraints and then - /// retry while considering them. This is not perfect, as the `from_region` may have already - /// been partially related to its argument region, so while we rely on a member constraint - /// to get a complete path, the most relevant step of that path already existed before then. - fn find_constraint_path_between_regions_inner( - &self, - ignore_opaque_type_constraints: bool, - from_region: RegionVid, - target_test: impl Fn(RegionVid) -> bool, - include_placeholder_static: bool, - ) -> Option<(Vec>, RegionVid)> { - let mut context = IndexVec::from_elem(Trace::NotVisited, &self.definitions); - context[from_region] = Trace::StartRegion; - - let fr_static = self.universal_regions().fr_static; - - // 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!( - "constraint_path_to: 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; - // This loop is cold and runs at the end, which is why we delay - // `OutlivesConstraint` construction until now. - loop { - match context[p] { - Trace::FromGraph(c) => { - p = c.sup; - result.push(*c); - } - - Trace::FromStatic(sub) => { - let c = OutlivesConstraint { - sup: fr_static, - sub, - locations: Locations::All(DUMMY_SP), - span: DUMMY_SP, - category: ConstraintCategory::Internal, - variance_info: ty::VarianceDiagInfo::default(), - from_closure: false, - }; - p = c.sup; - result.push(c); - } - - Trace::StartRegion => { - result.reverse(); - return Some((result, r)); - } - - Trace::NotVisited => { - bug!("found unvisited region {:?} on path to {:?}", p, 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. - - // Always inline this closure because it can be hot. - let mut handle_trace = #[inline(always)] - |sub, trace| { - if let Trace::NotVisited = context[sub] { - context[sub] = trace; - deque.push_back(sub); - } - }; - - // If this is the `'static` region and the graph's direction is normal, then set up the - // Edges iterator to return all regions (#53178). - if r == fr_static && self.constraint_graph.is_normal() { - for sub in self.constraint_graph.outgoing_edges_from_static() { - handle_trace(sub, Trace::FromStatic(sub)); - } - } else { - let edges = self.constraint_graph.outgoing_edges_from_graph(r, &self.constraints); - // This loop can be hot. - for constraint in edges { - match constraint.category { - ConstraintCategory::OutlivesUnnameablePlaceholder(_) - if !include_placeholder_static => - { - debug!("Ignoring illegal placeholder constraint: {constraint:?}"); - continue; - } - ConstraintCategory::OpaqueType if ignore_opaque_type_constraints => { - debug!("Ignoring member constraint: {constraint:?}"); - continue; - } - _ => {} - } - - debug_assert_eq!(constraint.sup, r); - handle_trace(constraint.sub, Trace::FromGraph(constraint)); - } - } - } + .check(polonius_output, outlives_requirements.as_mut()); - None - } + debug!(?errors_buffer); - /// Finds some region R such that `fr1: R` and `R` is live at `location`. - #[instrument(skip(self), level = "trace", ret)] - pub(crate) fn find_sub_region_live_at(&self, fr1: RegionVid, location: Location) -> RegionVid { - trace!(scc = ?self.constraint_sccs.scc(fr1)); - trace!(universe = ?self.max_nameable_universe(self.constraint_sccs.scc(fr1))); - self.constraint_path_to(fr1, |r| { - trace!(?r, liveness_constraints=?self.liveness_constraints.pretty_print_live_points(r)); - self.liveness_constraints.is_live_at(r, location) - }, true).unwrap().1 - } + let outlives_requirements = outlives_requirements.unwrap_or_default(); - /// Get the region outlived by `longer_fr` and live at `element`. - pub(crate) fn region_from_element( - &self, - longer_fr: RegionVid, - element: &RegionElement<'tcx>, - ) -> 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(), + if outlives_requirements.is_empty() { + (None, errors_buffer, scc_values) + } else { + ( + Some(ClosureRegionRequirements { num_external_vids, outlives_requirements }), + errors_buffer, + scc_values, + ) } } - /// 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_region_relations.universal_regions - } - - /// 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. - #[instrument(level = "debug", skip(self))] - pub(crate) fn best_blame_constraint( + /// 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, location_map, placeholder_indices), level = "debug")] + fn compute_region_values( &self, - from_region: RegionVid, - from_region_origin: NllRegionVariableOrigin<'tcx>, - to_region: RegionVid, - ) -> (BlameConstraint<'tcx>, Vec>) { - assert!(from_region != to_region, "Trying to blame a region for itself!"); - - let path = self.constraint_path_between_regions(from_region, to_region).unwrap(); - - // If we are passing through a constraint added because we reached an unnameable placeholder `'unnameable`, - // redirect search towards `'unnameable`. - let due_to_placeholder_outlives = path.iter().find_map(|c| { - if let ConstraintCategory::OutlivesUnnameablePlaceholder(unnameable) = c.category { - Some(unnameable) - } else { - None - } + location_map: Rc, + placeholder_indices: PlaceholderIndices<'tcx>, + ) -> RegionValues<'tcx, ConstraintSccIndex> { + 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::>() }); - // Edge case: it's possible that `'from_region` is an unnameable placeholder. - let path = if let Some(unnameable) = due_to_placeholder_outlives - && unnameable != from_region - { - // We ignore the extra edges due to unnameable placeholders to get - // an explanation that was present in the original constraint graph. - self.constraint_path_to(from_region, |r| r == unnameable, false).unwrap().0 - } else { - path - }; + let mut scc_values = + RegionValues::new(location_map, self.universal_regions().len(), placeholder_indices); - debug!( - "path={:#?}", - path.iter() - .map(|c| format!( - "{:?} ({:?}: {:?})", - c, - self.constraint_sccs.scc(c.sup), - self.constraint_sccs.scc(c.sub), - )) - .collect::>() - ); + for region in self.liveness_constraints.regions() { + scc_values.merge_liveness(self.scc(region), region, &self.liveness_constraints); + } - // 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::WhereClause(CRATE_DEF_ID.to_def_id(), predicate_span)) - } else { - None - } - }) - .unwrap_or_else(|| ObligationCauseCode::Misc); - - // 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 => true, - NllRegionVariableOrigin::Placeholder(_) => false, - // `'existential: 'whatever` never results in a region error by itself. - // We may always infer it to `'static` afterall. This means while an error - // path may go through an existential, these existentials are never the - // `from_region`. - NllRegionVariableOrigin::Existential { name: _ } => { - unreachable!("existentials can outlive everything") - } - }; + for variable in self.definitions.indices() { + match self.definitions[variable].origin { + NllRegionVariableOrigin::FreeRegion => { + // For each free, universally quantified region X: + scc_values.add_all_points(self.scc(variable)); - // To pick a constraint to blame, we organize constraints by how interesting we expect them - // to be in diagnostics, then pick the most interesting one closest to either the source or - // the target on our constraint path. - let constraint_interest = |constraint: &OutlivesConstraint<'tcx>| { - // Try to avoid blaming constraints from desugarings, since they may not clearly match - // match what users have written. As an exception, allow blaming returns generated by - // `?` desugaring, since the correspondence is fairly clear. - let category = if let Some(kind) = constraint.span.desugaring_kind() - && (kind != DesugaringKind::QuestionMark - || !matches!(constraint.category, ConstraintCategory::Return(_))) - { - ConstraintCategory::Boring - } else { - constraint.category - }; - - let interest = match category { - // Returns usually provide a type to blame and have specially written diagnostics, - // so prioritize them. - ConstraintCategory::Return(_) => 0, - // Unsizing coercions are interesting, since we have a note for that: - // `BorrowExplanation::add_object_lifetime_default_note`. - // FIXME(dianne): That note shouldn't depend on a coercion being blamed; see issue - // #131008 for an example of where we currently don't emit it but should. - // Once the note is handled properly, this case should be removed. Until then, it - // should be as limited as possible; the note is prone to false positives and this - // constraint usually isn't best to blame. - ConstraintCategory::Cast { - is_raw_ptr_dyn_type_cast: _, - unsize_to: Some(unsize_ty), - is_implicit_coercion: true, - } if to_region == self.universal_regions().fr_static - // Mirror the note's condition, to minimize how often this diverts blame. - && let ty::Adt(_, args) = unsize_ty.kind() - && args.iter().any(|arg| arg.as_type().is_some_and(|ty| ty.is_trait())) - // Mimic old logic for this, to minimize false positives in tests. - && !path - .iter() - .any(|c| matches!(c.category, ConstraintCategory::TypeAnnotation(_))) => - { - 1 + // Add `end(X)` into the set for X. + scc_values.add_element(self.scc(variable), variable); } - // Between other interesting constraints, order by their position on the `path`. - ConstraintCategory::Yield - | ConstraintCategory::UseAsConst - | ConstraintCategory::UseAsStatic - | ConstraintCategory::TypeAnnotation( - AnnotationSource::Ascription - | AnnotationSource::Declaration - | AnnotationSource::OpaqueCast, - ) - | ConstraintCategory::Cast { .. } - | ConstraintCategory::CallArgument(_) - | ConstraintCategory::CopyBound - | ConstraintCategory::SizedBound - | ConstraintCategory::Assignment - | ConstraintCategory::Usage - | ConstraintCategory::ClosureUpvar(_) => 2, - // Generic arguments are unlikely to be what relates regions together - ConstraintCategory::TypeAnnotation(AnnotationSource::GenericArg) => 3, - // We handle predicates and opaque types specially; don't prioritize them here. - ConstraintCategory::Predicate(_) | ConstraintCategory::OpaqueType => 4, - // `Boring` constraints can correspond to user-written code and have useful spans, - // but don't provide any other useful information for diagnostics. - ConstraintCategory::Boring => 5, - // `BoringNoLocation` constraints can point to user-written code, but are less - // specific, and are not used for relations that would make sense to blame. - ConstraintCategory::BoringNoLocation => 6, - // Do not blame internal constraints if we can avoid it. Never blame - // the `'region: 'static` constraints introduced by placeholder outlives. - ConstraintCategory::Internal => 7, - ConstraintCategory::OutlivesUnnameablePlaceholder(_) => 8, - }; - - debug!("constraint {constraint:?} category: {category:?}, interest: {interest:?}"); - - interest - }; - let best_choice = if blame_source { - path.iter().enumerate().rev().min_by_key(|(_, c)| constraint_interest(c)).unwrap().0 - } else { - path.iter().enumerate().min_by_key(|(_, c)| constraint_interest(c)).unwrap().0 - }; - - debug!(?best_choice, ?blame_source); + NllRegionVariableOrigin::Placeholder(placeholder) => { + scc_values.add_element(self.scc(variable), placeholder); + } - let best_constraint = if let Some(next) = path.get(best_choice + 1) - && matches!(path[best_choice].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. - *next - } else if path[best_choice].category == ConstraintCategory::Return(ReturnConstraint::Normal) - && let Some(field) = path.iter().find_map(|p| { - if let ConstraintCategory::ClosureUpvar(f) = p.category { Some(f) } else { None } - }) - { - OutlivesConstraint { - category: ConstraintCategory::Return(ReturnConstraint::ClosureUpvar(field)), - ..path[best_choice] + NllRegionVariableOrigin::Existential { .. } => { + // For existential, regions, nothing to do. + } } - } else { - path[best_choice] - }; - - assert!( - !matches!( - best_constraint.category, - ConstraintCategory::OutlivesUnnameablePlaceholder(_) - ), - "Illegal placeholder constraint blamed; should have redirected to other region relation" - ); - - let blame_constraint = BlameConstraint { - category: best_constraint.category, - from_closure: best_constraint.from_closure, - cause: ObligationCause::new(best_constraint.span, CRATE_DEF_ID, cause_code.clone()), - variance_info: best_constraint.variance_info, - }; - (blame_constraint, path) - } - - pub(crate) fn universe_info(&self, universe: ty::UniverseIndex) -> UniverseInfo<'tcx> { - // Query canonicalization can create local superuniverses (for example in - // `InferCtx::query_response_instantiation_guess`), but they don't have an associated - // `UniverseInfo` explaining why they were created. - // This can cause ICEs if these causes are accessed in diagnostics, for example in issue - // #114907 where this happens via liveness and dropck outlives results. - // Therefore, we return a default value in case that happens, which should at worst emit a - // suboptimal error, instead of the ICE. - self.universe_causes.get(&universe).cloned().unwrap_or_else(UniverseInfo::other) - } + } - /// Tries to find the terminator of the loop in which the region 'r' resides. - /// Returns the location of the terminator if found. - pub(crate) fn find_loop_terminator_location( - &self, - r: RegionVid, - body: &Body<'_>, - ) -> Option { - let scc = self.constraint_sccs.scc(r); - let locations = self.scc_values.locations_outlived_by(scc); - for location in locations { - let bb = &body[location.block]; - if let Some(terminator) = &bb.terminator - // terminator of a loop should be TerminatorKind::FalseUnwind - && let TerminatorKind::FalseUnwind { .. } = terminator.kind - { - return Some(location); + // To propagate constraints, we walk the DAG induced by the + // SCC. For each SCC `A`, we visit its successors and compute + // their values, then we union all those values to get our + // own. + for scc_a in self.constraint_sccs.all_sccs() { + // Walk each SCC `B` such that `A: B`... + for &scc_b in self.constraint_sccs.successors(scc_a) { + debug!(?scc_b); + scc_values.add_region(scc_a, scc_b); } } - None - } - - /// Access to the SCC constraint graph. - /// This can be used to quickly under-approximate the regions which are equal to each other - /// and their relative orderings. - // This is `pub` because it's used by unstable external borrowck data users, see `consumers.rs`. - pub fn constraint_sccs(&self) -> &ConstraintSccs { - &self.constraint_sccs + scc_values } - /// Returns the representative `RegionVid` for a given SCC. - /// See `RegionTracker` for how a region variable ID is chosen. - /// - /// It is a hacky way to manage checking regions for equality, - /// 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). - fn scc_representative(&self, scc: ConstraintSccIndex) -> RegionVid { - self.scc_annotations[scc].representative.rvid() - } - - pub(crate) fn liveness_constraints(&self) -> &LivenessValues { - &self.liveness_constraints - } - - /// When using `-Zpolonius=next`, records the given live loans for the loan scopes and active - /// loans dataflow computations. - pub(crate) fn record_live_loans(&mut self, live_loans: LiveLoans) { - self.liveness_constraints.record_live_loans(live_loans); + fn universal_regions(&self) -> &UniversalRegions<'tcx> { + &self.universal_region_relations.universal_regions } - /// Returns whether the `loan_idx` is live at the given `location`: whether its issuing - /// region is contained within the type of a variable that is live at this point. - /// Note: for now, the sets of live loans is only available when using `-Zpolonius=next`. - pub(crate) fn is_loan_live_at(&self, loan_idx: BorrowIndex, location: Location) -> bool { - let point = self.liveness_constraints.point_from_location(location); - self.liveness_constraints.is_loan_live_at(loan_idx, point) + fn scc(&self, r: RegionVid) -> ConstraintSccIndex { + self.constraint_sccs.scc(r) } } - -#[derive(Clone, Debug)] -pub(crate) struct BlameConstraint<'tcx> { - pub category: ConstraintCategory<'tcx>, - pub from_closure: bool, - pub cause: ObligationCause<'tcx>, - pub variance_info: ty::VarianceDiagInfo>, -} diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs index 0c4a82f3d2f36..d887e5cfa4bee 100644 --- a/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs @@ -25,7 +25,8 @@ use tracing::{debug, instrument}; use super::reverse_sccs::ReverseSccGraph; use crate::BorrowckInferCtxt; -use crate::consumers::RegionInferenceContext; +use crate::handle_placeholders::RegionDefinitions; +use crate::region_infer::InferredRegions; use crate::session_diagnostics::LifetimeMismatchOpaqueParam; use crate::type_check::canonical::fully_perform_op_raw; use crate::type_check::free_region_relations::UniversalRegionRelations; @@ -595,7 +596,61 @@ pub(crate) fn detect_opaque_types_added_while_handling_opaque_types<'tcx>( let _ = infcx.take_opaque_types(); } -impl<'tcx> RegionInferenceContext<'tcx> { +impl<'tcx> InferredRegions<'tcx> { + /// 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. + #[instrument(level = "debug", skip(self))] + pub(crate) fn approx_universal_upper_bound( + &self, + r: RegionVid, + definitions: &RegionDefinitions<'tcx>, + ) -> RegionVid { + debug!("{}", 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 static_r = self.universal_regions().fr_static; + for ur in self.universal_regions_outlived_by(r) { + let new_lub = self.universal_region_relations.postdom_upper_bound(lub, ur); + debug!(?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 definitions[ur].external_name.is_some() { + lub = ur; + } else if definitions[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!(?r, ?lub); + + lub + } + /// 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 args for the opaque type. It also doesn't double check @@ -608,17 +663,20 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// that universal region. This is useful for member region constraints since /// we want to suggest a universal region name to capture even if it's technically /// not equal to the error region. - pub(crate) fn name_regions_for_member_constraint(&self, tcx: TyCtxt<'tcx>, ty: T) -> T + pub(crate) fn name_regions_for_member_constraint( + &self, + tcx: TyCtxt<'tcx>, + definitions: &RegionDefinitions<'tcx>, + ty: T, + ) -> T where T: TypeFoldable>, { fold_regions(tcx, ty, |region, _| match region.kind() { ty::ReVar(vid) => { - let scc = self.constraint_sccs.scc(vid); - // Special handling of higher-ranked regions. - if !self.max_nameable_universe(scc).is_root() { - match self.scc_values.placeholders_contained_in(scc).enumerate().last() { + if !self.max_nameable_universe(vid).is_root() { + match self.placeholders_contained_in(vid).enumerate().last() { // If the region contains a single placeholder then they're equal. Some((0, placeholder)) => { return ty::Region::new_placeholder(tcx, placeholder); @@ -630,8 +688,8 @@ impl<'tcx> RegionInferenceContext<'tcx> { } // Find something that we can name - let upper_bound = self.approx_universal_upper_bound(vid); - if let Some(universal_region) = self.definitions[upper_bound].external_name { + let upper_bound = self.approx_universal_upper_bound(vid, definitions); + if let Some(universal_region) = definitions[upper_bound].external_name { return universal_region; } @@ -639,12 +697,10 @@ impl<'tcx> RegionInferenceContext<'tcx> { // If there's >1 universal region, then we probably are dealing w/ an intersection // region which cannot be mapped back to a universal. // FIXME: We could probably compute the LUB if there is one. - let scc = self.constraint_sccs.scc(vid); - let rev_scc_graph = - ReverseSccGraph::compute(&self.constraint_sccs, self.universal_regions()); + let rev_scc_graph = ReverseSccGraph::compute(&self.sccs, self.universal_regions()); let upper_bounds: Vec<_> = rev_scc_graph - .upper_bounds(scc) - .filter_map(|vid| self.definitions[vid].external_name) + .upper_bounds(self.scc(vid)) + .filter_map(|vid| definitions[vid].external_name) .filter(|r| !r.is_static()) .collect(); match &upper_bounds[..] { diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types/region_ctxt.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types/region_ctxt.rs index ada8908e220ac..3ea91ee2c5a5d 100644 --- a/compiler/rustc_borrowck/src/region_infer/opaque_types/region_ctxt.rs +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types/region_ctxt.rs @@ -43,7 +43,11 @@ impl<'a, 'tcx> RegionCtxt<'a, 'tcx> { ) -> RegionCtxt<'a, 'tcx> { let mut outlives_constraints = constraints.outlives_constraints.clone(); let universal_regions = &universal_region_relations.universal_regions; - let (definitions, _has_placeholders) = region_definitions(infcx, universal_regions); + let (definitions, _has_placeholders) = region_definitions( + infcx, + universal_regions, + &mut constraints.liveness_constraints.clone(), + ); let compute_sccs = |outlives_constraints: &OutlivesConstraintSet<'tcx>, diff --git a/compiler/rustc_borrowck/src/region_infer/universal_regions.rs b/compiler/rustc_borrowck/src/region_infer/universal_regions.rs new file mode 100644 index 0000000000000..6882364bd3071 --- /dev/null +++ b/compiler/rustc_borrowck/src/region_infer/universal_regions.rs @@ -0,0 +1,436 @@ +//! This module contains methods for checking universal +//! regions after region inference has executed. +use rustc_infer::infer::NllRegionVariableOrigin; +use rustc_middle::ty::{self, RegionVid}; +use tracing::{debug, instrument}; + +use crate::constraints::OutlivesConstraintSet; +use crate::constraints::graph::NormalConstraintGraph; +use crate::consumers::PoloniusOutput; +use crate::diagnostics::{RegionErrorKind, RegionErrors}; +use crate::handle_placeholders::RegionDefinitions; +use crate::region_infer::InferredRegions; +use crate::region_infer::constraint_search::ConstraintSearch; +use crate::region_infer::values::RegionElement; +use crate::{ClosureOutlivesRequirement, ClosureOutlivesSubject}; + +type MaybeOutlivesRequirements<'a, 'tcx> = Option<&'a mut Vec>>; + +/// 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, +} + +/// Context for checking universal region accesses. +pub(crate) struct UniversalRegionChecker<'a, 'tcx> { + errors_buffer: &'a mut RegionErrors<'tcx>, + region_definitions: &'a RegionDefinitions<'tcx>, + constraints: &'a OutlivesConstraintSet<'tcx>, + fr_static: RegionVid, + values: &'a InferredRegions<'tcx>, + constraint_graph: NormalConstraintGraph, +} + +impl<'a, 'tcx> UniversalRegionChecker<'a, 'tcx> { + /// Check universal region relations either after running Polonius, + /// or after running regular borrow checking. See documentation for + /// the methods below for an explanation of precisely what is checked. + pub(super) fn check( + mut self, + polonius_output: Option>, + outlives_requirements: MaybeOutlivesRequirements<'a, 'tcx>, + ) { + // 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 let Some(polonius_output) = polonius_output { + self.check_polonius_subset_errors(polonius_output.as_ref(), outlives_requirements); + } else { + self.check_universal_regions(outlives_requirements); + } + } + + pub(super) fn new( + errors_buffer: &'a mut RegionErrors<'tcx>, + region_definitions: &'a RegionDefinitions<'tcx>, + constraints: &'a OutlivesConstraintSet<'tcx>, + values: &'a InferredRegions<'tcx>, + ) -> Self { + Self { + errors_buffer, + region_definitions, + constraints, + fr_static: values.universal_region_relations.universal_regions.fr_static, + constraint_graph: constraints.graph(region_definitions.len()), + values, + } + } + + /// 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 + /// 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( + &mut self, + mut outlives_requirements: MaybeOutlivesRequirements<'a, 'tcx>, + ) { + for (fr, fr_definition) in self.region_definitions.iter_enumerated() { + debug!(?fr, ?fr_definition); + 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(fr, &mut outlives_requirements); + } + + NllRegionVariableOrigin::Placeholder(placeholder) => { + self.check_bound_universal_region(fr, placeholder); + } + + 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 + /// 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( + &mut self, + polonius_output: &PoloniusOutput, + mut outlives_requirements: MaybeOutlivesRequirements<'a, 'tcx>, + ) { + 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. + // We can iterate the HashMap here because the result is sorted afterwards. + #[allow(rustc::potential_query_instability)] + 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.into(), + shorter_fr.into(), + &mut outlives_requirements, + ); + if propagated == RegionRelationCheckResult::Error { + self.errors_buffer.push(RegionErrorKind::RegionError { + longer_fr: longer_fr.into(), + shorter_fr: shorter_fr.into(), + 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.region_definitions.iter_enumerated() { + match fr_definition.origin { + NllRegionVariableOrigin::FreeRegion => { + // handled by polonius above + } + + NllRegionVariableOrigin::Placeholder(placeholder) => { + self.check_bound_universal_region(fr, placeholder); + } + + 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), level = "debug")] + fn check_universal_region( + &mut self, + longer_fr: RegionVid, + mut outlives_requirements: &mut MaybeOutlivesRequirements<'a, 'tcx>, + ) { + // Because this free region must be in the ROOT universe, we + // know it cannot contain any bound universes. + assert!(self.values.max_nameable_universe(longer_fr).is_root()); + + // 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.values.to_representative(longer_fr); + if representative != longer_fr { + if let RegionRelationCheckResult::Error = self.check_universal_region_relation( + longer_fr, + representative, + &mut outlives_requirements, + ) { + self.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.values.universal_regions_outlived_by(longer_fr) { + if let RegionRelationCheckResult::Error = self.check_universal_region_relation( + longer_fr, + shorter_fr, + &mut 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... + self.errors_buffer.push(RegionErrorKind::RegionError { + longer_fr, + shorter_fr, + fr_origin: NllRegionVariableOrigin::FreeRegion, + is_reported: !error_reported, + }); + + error_reported = true; + } + } + } + + fn check_bound_universal_region( + &mut self, + longer_fr: RegionVid, + placeholder: ty::PlaceholderRegion<'tcx>, + ) { + debug!("check_bound_universal_region(fr={:?}, placeholder={:?})", longer_fr, placeholder,); + + let longer_fr_scc = self.values.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! + if let Some(error_element) = self + .values + .scc_values + .elements_contained_in(longer_fr_scc) + .find(|e| *e != RegionElement::PlaceholderRegion(placeholder)) + { + // Stop after the first error, it gets too noisy otherwise, and does not provide more information. + self.errors_buffer.push(RegionErrorKind::BoundUniversalRegionError { + longer_fr, + error_element, + placeholder, + }); + } else { + debug!("check_bound_universal_region: all bounds satisfied"); + } + } + + /// 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( + &mut self, + longer_fr: RegionVid, + shorter_fr: RegionVid, + outlives_requirements: &mut MaybeOutlivesRequirements<'a, 'tcx>, + ) -> RegionRelationCheckResult { + // If it is known that `fr: o`, carry on. + if self.values.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, 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( + &mut self, + longer_fr: RegionVid, + shorter_fr: RegionVid, + outlives_requirements: &mut MaybeOutlivesRequirements<'a, 'tcx>, + ) -> RegionRelationCheckResult { + if let Some(propagated_outlives_requirements) = outlives_requirements { + // Shrink `longer_fr` until we find some non-local regions. + // We'll call them `longer_fr-` -- they are ever so slightly smaller than + // `longer_fr`. + let longer_fr_minus = + self.values.universal_region_relations.non_local_lower_bounds(longer_fr); + + debug!("try_propagate_universal_region_error: fr_minus={:?}", longer_fr_minus); + + // If we don't find a any non-local regions, we should error out as there is nothing + // to propagate. + if longer_fr_minus.is_empty() { + return RegionRelationCheckResult::Error; + } + + let blame_constraint = self + .constraint_search() + .best_blame_constraint(longer_fr, NllRegionVariableOrigin::FreeRegion, shorter_fr) + .0; + + // Grow `shorter_fr` until we find some non-local regions. + // We will always find at least one: `'static`. We'll call + // them `shorter_fr+` -- they're ever so slightly larger + // than `shorter_fr`. + let shorter_fr_plus = + self.values.universal_region_relations.non_local_upper_bounds(shorter_fr); + debug!("try_propagate_universal_region_error: shorter_fr_plus={:?}", shorter_fr_plus); + + // We then create constraints `longer_fr-: shorter_fr+` that may or may not + // be propagated (see below). + let mut constraints = vec![]; + for fr_minus in longer_fr_minus { + for shorter_fr_plus in &shorter_fr_plus { + constraints.push((fr_minus, *shorter_fr_plus)); + } + } + + // We only need to propagate at least one of the constraints for + // soundness. However, we want to avoid arbitrary choices here + // and currently don't support returning OR constraints. + // + // If any of the `shorter_fr+` regions are already outlived by `longer_fr-`, + // we propagate only those. + // + // Consider this example (`'b: 'a` == `a -> b`), where we try to propagate `'d: 'a`: + // a --> b --> d + // \ + // \-> c + // Here, `shorter_fr+` of `'a` == `['b, 'c]`. + // Propagating `'d: 'b` is correct and should occur; `'d: 'c` is redundant because of + // `'d: 'b` and could reject valid code. + // + // So we filter the constraints to regions already outlived by `longer_fr-`, but if + // the filter yields an empty set, we fall back to the original one. + let subset: Vec<_> = constraints + .iter() + .filter(|&&(fr_minus, shorter_fr_plus)| { + self.values.eval_outlives(fr_minus, shorter_fr_plus) + }) + .copied() + .collect(); + let propagated_constraints = if subset.is_empty() { constraints } else { subset }; + debug!( + "try_propagate_universal_region_error: constraints={:?}", + propagated_constraints + ); + + assert!( + !propagated_constraints.is_empty(), + "Expected at least one constraint to propagate here" + ); + + for (fr_minus, fr_plus) in propagated_constraints { + // Push the constraint `long_fr-: shorter_fr+` + propagated_outlives_requirements.push(ClosureOutlivesRequirement { + subject: ClosureOutlivesSubject::Region(fr_minus), + outlived_free_region: fr_plus, + blame_span: blame_constraint.cause.span, + category: blame_constraint.category, + }); + } + return RegionRelationCheckResult::Propagated; + } + + RegionRelationCheckResult::Error + } + fn constraint_search(&'a self) -> ConstraintSearch<'a, 'tcx> { + ConstraintSearch { + definitions: self.region_definitions, + fr_static: self.fr_static, + constraint_graph: &self.constraint_graph, + constraints: self.constraints, + } + } +} diff --git a/compiler/rustc_borrowck/src/region_infer/values.rs b/compiler/rustc_borrowck/src/region_infer/values.rs index 1dd3bc831f45a..c3922051ed8b4 100644 --- a/compiler/rustc_borrowck/src/region_infer/values.rs +++ b/compiler/rustc_borrowck/src/region_infer/values.rs @@ -10,9 +10,6 @@ use rustc_middle::ty::{self, RegionVid}; use rustc_mir_dataflow::points::{DenseLocationMap, PointIndex}; use tracing::debug; -use crate::BorrowIndex; -use crate::polonius::LiveLoans; - rustc_index::newtype_index! { /// A single integer representing a `ty::Placeholder`. #[debug_format = "PlaceholderIndex({})"] @@ -51,9 +48,6 @@ pub(crate) struct LivenessValues { /// This is not initialized for promoteds, because we don't care *where* within a promoted a /// region is live, only that it is. points: Option>, - - /// When using `-Zpolonius=next`, the set of loans that are live at a given point in the CFG. - live_loans: Option, } impl LivenessValues { @@ -63,7 +57,6 @@ impl LivenessValues { live_regions: None, points: Some(SparseIntervalMatrix::new(location_map.num_points())), location_map, - live_loans: None, } } @@ -72,12 +65,7 @@ impl LivenessValues { /// Unlike `with_specific_points`, does not track exact locations where something is live, only /// which regions are live. pub(crate) fn without_specific_points(location_map: Rc) -> Self { - LivenessValues { - live_regions: Some(Default::default()), - points: None, - location_map, - live_loans: None, - } + LivenessValues { live_regions: Some(Default::default()), points: None, location_map } } /// Returns the liveness matrix of points where each region is live. Panics if the liveness @@ -175,20 +163,6 @@ impl LivenessValues { pub(crate) fn location_from_point(&self, point: PointIndex) -> Location { self.location_map.to_location(point) } - - /// When using `-Zpolonius=next`, records the given live loans for the loan scopes and active - /// loans dataflow computations. - pub(crate) fn record_live_loans(&mut self, live_loans: LiveLoans) { - self.live_loans = Some(live_loans); - } - - /// When using `-Zpolonius=next`, returns whether the `loan_idx` is live at the given `point`. - pub(crate) fn is_loan_live_at(&self, loan_idx: BorrowIndex, point: PointIndex) -> bool { - self.live_loans - .as_ref() - .expect("Accessing live loans requires `-Zpolonius=next`") - .contains(point, loan_idx) - } } /// Maps from `ty::PlaceholderRegion` values that are used in the rest of diff --git a/compiler/rustc_borrowck/src/root_cx.rs b/compiler/rustc_borrowck/src/root_cx.rs index 4d42055df1687..868357009a581 100644 --- a/compiler/rustc_borrowck/src/root_cx.rs +++ b/compiler/rustc_borrowck/src/root_cx.rs @@ -212,7 +212,7 @@ impl<'tcx> BorrowCheckRootCtxt<'tcx> { &input.infcx, &input.body_owned, Rc::clone(&input.location_map), - &input.universal_region_relations, + Rc::clone(&input.universal_region_relations), &input.constraints, ) }