From 0832b5641c9bee051f7fc6634e9b04f1b4771aa8 Mon Sep 17 00:00:00 2001 From: Amanda Stjerna Date: Fri, 23 Jan 2026 17:41:22 +0100 Subject: [PATCH 1/9] Region inference: split results from RegionInferenceContext This ensures all of region inference is immutable, and makes every operation that requires region inference to have been executed to run explicitly require the results. --- compiler/rustc_borrowck/src/consumers.rs | 5 + compiler/rustc_borrowck/src/dataflow.rs | 22 +- .../src/diagnostics/explain_borrow.rs | 7 +- .../src/diagnostics/find_use.rs | 9 +- .../src/diagnostics/opaque_types.rs | 22 +- .../src/diagnostics/region_errors.rs | 6 +- compiler/rustc_borrowck/src/lib.rs | 16 +- compiler/rustc_borrowck/src/nll.rs | 41 +- compiler/rustc_borrowck/src/polonius/dump.rs | 5 + .../src/region_infer/dump_mir.rs | 10 +- .../rustc_borrowck/src/region_infer/mod.rs | 392 +++++++++--------- .../src/region_infer/opaque_types/mod.rs | 12 +- 12 files changed, 308 insertions(+), 239 deletions(-) diff --git a/compiler/rustc_borrowck/src/consumers.rs b/compiler/rustc_borrowck/src/consumers.rs index 548973714105b..2c2bf56d0c733 100644 --- a/compiler/rustc_borrowck/src/consumers.rs +++ b/compiler/rustc_borrowck/src/consumers.rs @@ -18,6 +18,7 @@ pub use super::polonius::legacy::{ }; 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. @@ -92,6 +93,10 @@ pub struct BodyWithBorrowckFacts<'tcx> { /// Context generated during borrowck, intended to be passed to /// [`calculate_borrows_out_of_scope_at_location`]. pub region_inference_context: RegionInferenceContext<'tcx>, + /// The inferred region values. These are included because they + /// are necesssary as input to + /// [`calculate_borrows_out_of_scope_at_location`]. + 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..ebd9967f519ee 100644 --- a/compiler/rustc_borrowck/src/dataflow.rs +++ b/compiler/rustc_borrowck/src/dataflow.rs @@ -14,6 +14,7 @@ use rustc_mir_dataflow::impls::{ use rustc_mir_dataflow::{Analysis, GenKill, JoinSemiLattice}; use tracing::debug; +use crate::region_infer::InferredRegions; use crate::{BorrowSet, PlaceConflictBias, PlaceExt, RegionInferenceContext, places_conflict}; // This analysis is different to most others. Its results aren't computed with @@ -190,6 +191,7 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> { fn compute( body: &Body<'tcx>, regioncx: &RegionInferenceContext<'tcx>, + scc_values: &InferredRegions<'tcx>, borrow_set: &BorrowSet<'tcx>, ) -> FxIndexMap> { let mut prec = OutOfScopePrecomputer { @@ -202,7 +204,7 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> { 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<'tcx>, borrow_index: BorrowIndex, borrow_region: RegionVid, first_location: Location, @@ -223,6 +226,7 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> { let first_hi = first_bb_data.statements.len(); if let Some(kill_stmt) = self.regioncx.first_non_contained_inclusive( + scc_values, borrow_region, first_block, first_lo, @@ -254,9 +258,13 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> { while let Some(block) = self.visit_stack.pop() { 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) - { + if let Some(kill_stmt) = self.regioncx.first_non_contained_inclusive( + scc_values, + 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 // successor locations. @@ -286,9 +294,10 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> { pub fn calculate_borrows_out_of_scope_at_location<'tcx>( body: &Body<'tcx>, regioncx: &RegionInferenceContext<'tcx>, + scc_values: &InferredRegions<'tcx>, borrow_set: &BorrowSet<'tcx>, ) -> FxIndexMap> { - OutOfScopePrecomputer::compute(body, regioncx, borrow_set) + OutOfScopePrecomputer::compute(body, regioncx, scc_values, borrow_set) } struct PoloniusOutOfScopePrecomputer<'a, 'tcx> { @@ -430,11 +439,12 @@ impl<'a, 'tcx> Borrows<'a, 'tcx> { tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, regioncx: &RegionInferenceContext<'tcx>, + scc_values: &InferredRegions<'tcx>, 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) + calculate_borrows_out_of_scope_at_location(body, regioncx, scc_values, borrow_set) } else { PoloniusOutOfScopePrecomputer::compute(body, regioncx, borrow_set) }; diff --git a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs index bbd0a8ae07108..439b61da947e6 100644 --- a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs +++ b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs @@ -629,8 +629,9 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { // is issued is the same location that invalidates the reference), this is likely a // 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) + if let Some(loop_terminator_location) = self + .scc_values + .find_loop_terminator_location(self.regioncx.scc(borrow.region), body) { region_sub = self .regioncx @@ -658,7 +659,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { false } }; - match find_use::find(body, regioncx, tcx, region_sub, use_location) { + match find_use::find(body, regioncx, 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..05fe9f4fe42e3 100644 --- a/compiler/rustc_borrowck/src/diagnostics/find_use.rs +++ b/compiler/rustc_borrowck/src/diagnostics/find_use.rs @@ -6,16 +6,18 @@ 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, RegionInferenceContext}; pub(crate) fn find<'tcx>( body: &Body<'tcx>, regioncx: &RegionInferenceContext<'tcx>, + scc_values: &InferredRegions<'tcx>, 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, regioncx, tcx, region_vid, start_point, scc_values: &scc_values }; uf.find() } @@ -23,6 +25,7 @@ pub(crate) fn find<'tcx>( struct UseFinder<'a, 'tcx> { body: &'a Body<'tcx>, regioncx: &'a RegionInferenceContext<'tcx>, + scc_values: &'a InferredRegions<'tcx>, tcx: TyCtxt<'tcx>, region_vid: RegionVid, start_point: Location, @@ -35,7 +38,7 @@ impl<'a, 'tcx> UseFinder<'a, 'tcx> { queue.push_back(self.start_point); while let Some(p) = queue.pop_front() { - if !self.regioncx.region_contains(self.region_vid, p) { + if !self.regioncx.region_contains(self.scc_values, self.region_vid, p) { continue; } diff --git a/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs b/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs index 62ba4d172a3f4..783591cbaf80f 100644 --- a/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs +++ b/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs @@ -41,13 +41,21 @@ 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.regioncx.name_regions_for_member_constraint( + self.scc_values, + infcx.tcx, + hidden_type.ty, + ); + let named_key = self.regioncx.name_regions_for_member_constraint( + self.scc_values, + infcx.tcx, + opaque_type_key, + ); + let named_region = self.regioncx.name_regions_for_member_constraint( + self.scc_values, + infcx.tcx, + member_region, + ); let diag = unexpected_hidden_region_diagnostic( infcx, self.mir_def_id(), diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs index ae389d1a6e10c..85e133d43a458 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs @@ -159,9 +159,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } 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.regioncx.approx_universal_upper_bound(self.scc_values, r); - if self.regioncx.upper_bound_in_region_scc(r, upper_bound) { + if self.regioncx.upper_bound_in_region_scc(self.scc_values, r, upper_bound) { self.to_error_region_vid(upper_bound) } else { None @@ -206,7 +206,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { // find generic associated types in the given region 'lower_bound' let gat_id_and_generics = self .regioncx - .placeholders_contained_in(lower_bound) + .placeholders_contained_in(self.scc_values, lower_bound) .map(|placeholder| { if let Some(id) = placeholder.bound.kind.get_id() && let Some(placeholder_id) = id.as_local() diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 9186d82974d77..56231941552e7 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -68,8 +68,8 @@ 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::{InferredRegions, RegionInferenceContext}; use crate::renumber::RegionCtxt; use crate::session_diagnostics::VarNeedNotMut; use crate::type_check::free_region_relations::UniversalRegionRelations; @@ -415,6 +415,7 @@ fn borrowck_check_region_constraints<'tcx>( // by typechecking the MIR body. let nll::NllOutput { regioncx, + scc_values, polonius_input, polonius_output, opt_closure_req, @@ -436,11 +437,12 @@ 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, ®ioncx, &scc_values, &opt_closure_req, &borrow_set); polonius::dump_polonius_mir( &infcx, body, ®ioncx, + &scc_values, &opt_closure_req, &borrow_set, polonius_diagnostics.as_ref(), @@ -475,6 +477,7 @@ fn borrowck_check_region_constraints<'tcx>( 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, @@ -514,6 +517,7 @@ fn borrowck_check_region_constraints<'tcx>( 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, @@ -534,7 +538,7 @@ 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, ®ioncx, &scc_values); visit_results( body, traversal::reverse_postorder(body).map(|(bb, _)| bb), @@ -584,6 +588,7 @@ fn borrowck_check_region_constraints<'tcx>( location_table: polonius_input.as_ref().map(|_| location_table), input_facts: polonius_input, output_facts: polonius_output, + inferred_regions: scc_values, }, ); } @@ -599,10 +604,11 @@ fn get_flow_results<'a, 'tcx>( move_data: &'a MoveData<'tcx>, borrow_set: &'a BorrowSet<'tcx>, regioncx: &RegionInferenceContext<'tcx>, + scc_values: &InferredRegions<'tcx>, ) -> Results<'tcx, Borrowck<'a, 'tcx>> { // 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( + let borrows = Borrows::new(tcx, body, regioncx, scc_values, borrow_set).iterate_to_fixpoint( tcx, body, Some("borrowck"), @@ -752,6 +758,8 @@ struct MirBorrowckCtxt<'a, 'infcx, 'tcx> { /// 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>, diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index 64e3b59acfff9..3b53499c1d141 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -26,7 +26,7 @@ use crate::polonius::legacy::{ PoloniusFacts, PoloniusFactsExt, PoloniusLocationTable, PoloniusOutput, }; use crate::polonius::{PoloniusContext, PoloniusDiagnosticsContext}; -use crate::region_infer::RegionInferenceContext; +use crate::region_infer::{InferredRegions, RegionInferenceContext}; use crate::type_check::MirTypeckRegionConstraints; use crate::type_check::free_region_relations::UniversalRegionRelations; use crate::universal_regions::UniversalRegions; @@ -39,6 +39,7 @@ use crate::{ /// 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>, @@ -96,14 +97,17 @@ pub(crate) fn compute_closure_requirements_modulo_opaques<'tcx>( &universal_region_relations, infcx, ); - let mut regioncx = RegionInferenceContext::new( + + let placeholder_indices = lowered_constraints.placeholder_indices.clone(); // FIXME!!! + + let regioncx = RegionInferenceContext::new( &infcx, lowered_constraints, universal_region_relations.clone(), - location_map, ); - let (closure_region_requirements, _nll_errors) = regioncx.solve(infcx, body, None); + let (closure_region_requirements, _nll_errors, _scc_values) = + regioncx.solve(infcx, body, None, location_map, placeholder_indices); closure_region_requirements } @@ -144,12 +148,10 @@ pub(crate) fn compute_regions<'tcx>( &lowered_constraints, ); - let mut regioncx = RegionInferenceContext::new( - infcx, - lowered_constraints, - universal_region_relations, - location_map, - ); + let placeholder_indices = lowered_constraints.placeholder_indices.clone(); + + let mut regioncx = + RegionInferenceContext::new(infcx, lowered_constraints, universal_region_relations); // If requested for `-Zpolonius=next`, convert NLL constraints to localized outlives constraints // and use them to compute loan liveness. @@ -179,8 +181,8 @@ 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) = + regioncx.solve(infcx, body, polonius_output.clone(), location_map, placeholder_indices); NllOutput { regioncx, @@ -189,6 +191,7 @@ pub(crate) fn compute_regions<'tcx>( opt_closure_req: closure_region_requirements, nll_errors, polonius_diagnostics, + scc_values, } } @@ -205,6 +208,7 @@ 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>, ) { @@ -222,7 +226,15 @@ 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, + regioncx, + scc_values, + closure_region_requirements, + borrow_set, + pass_where, + out, + ) }; let dumper = dumper.set_extra_data(extra_data).set_options(options); @@ -246,6 +258,7 @@ pub(super) fn dump_nll_mir<'tcx>( 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>, pass_where: PassWhere, @@ -254,7 +267,7 @@ pub(crate) fn emit_nll_mir<'tcx>( match pass_where { // Before the CFG, dump out the values for each region variable. PassWhere::BeforeCFG => { - regioncx.dump_mir(tcx, out)?; + regioncx.dump_mir(scc_values, tcx, out)?; writeln!(out, "|")?; if let Some(closure_region_requirements) = closure_region_requirements { diff --git a/compiler/rustc_borrowck/src/polonius/dump.rs b/compiler/rustc_borrowck/src/polonius/dump.rs index 62f9ae173474d..12bb882675da6 100644 --- a/compiler/rustc_borrowck/src/polonius/dump.rs +++ b/compiler/rustc_borrowck/src/polonius/dump.rs @@ -13,6 +13,7 @@ use crate::constraints::OutlivesConstraint; use crate::polonius::{ LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSet, PoloniusDiagnosticsContext, }; +use crate::region_infer::InferredRegions; use crate::region_infer::values::LivenessValues; use crate::type_check::Locations; use crate::{BorrowckInferCtxt, ClosureRegionRequirements, RegionInferenceContext}; @@ -22,6 +23,7 @@ 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>, polonius_diagnostics: Option<&PoloniusDiagnosticsContext>, @@ -40,6 +42,7 @@ pub(crate) fn dump_polonius_mir<'tcx>( emit_polonius_mir( tcx, regioncx, + scc_values, closure_region_requirements, borrow_set, &polonius_diagnostics.localized_outlives_constraints, @@ -191,6 +194,7 @@ fn emit_html_mir<'tcx>( 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, @@ -201,6 +205,7 @@ fn emit_polonius_mir<'tcx>( crate::nll::emit_nll_mir( tcx, regioncx, + scc_values, closure_region_requirements, borrow_set, pass_where, diff --git a/compiler/rustc_borrowck/src/region_infer/dump_mir.rs b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs index 68f822aac403a..a269c75355979 100644 --- a/compiler/rustc_borrowck/src/region_infer/dump_mir.rs +++ b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs @@ -9,6 +9,7 @@ use rustc_infer::infer::NllRegionVariableOrigin; use rustc_middle::ty::TyCtxt; use super::{OutlivesConstraint, RegionInferenceContext}; +use crate::region_infer::InferredRegions; use crate::type_check::Locations; // Room for "'_#NNNNr" before things get misaligned. @@ -18,7 +19,12 @@ const REGION_WIDTH: usize = 8; impl<'tcx> RegionInferenceContext<'tcx> { /// Write out our state into the `.mir` files. - pub(crate) fn dump_mir(&self, tcx: TyCtxt<'tcx>, out: &mut dyn Write) -> io::Result<()> { + pub(crate) fn dump_mir( + &self, + scc_values: &InferredRegions<'tcx>, + tcx: TyCtxt<'tcx>, + out: &mut dyn Write, + ) -> io::Result<()> { writeln!(out, "| Free Region Mapping")?; for region in self.regions() { @@ -47,7 +53,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { r = region, rw = REGION_WIDTH, ui = self.max_nameable_universe(self.constraint_sccs.scc(region)), - v = self.region_value_str(region), + v = self.region_value_str(scc_values, region), )?; } diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 6ed70b39c5b7f..452ad05a9d836 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -29,7 +29,9 @@ use crate::diagnostics::{RegionErrorKind, RegionErrors, UniverseInfo}; use crate::handle_placeholders::{LoweredConstraints, RegionTracker}; use crate::polonius::LiveLoans; use crate::polonius::legacy::PoloniusOutput; -use crate::region_infer::values::{LivenessValues, RegionElement, RegionValues, ToElementIndex}; +use crate::region_infer::values::{ + LivenessValues, PlaceholderIndices, RegionElement, RegionValues, ToElementIndex, +}; use crate::type_check::Locations; use crate::type_check::free_region_relations::UniversalRegionRelations; use crate::universal_regions::UniversalRegions; @@ -76,6 +78,30 @@ impl Representative { pub(crate) type ConstraintSccs = Sccs; +pub struct InferredRegions<'tcx>(pub(crate) RegionValues<'tcx, ConstraintSccIndex>); + +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, + scc: ConstraintSccIndex, + body: &Body<'_>, + ) -> Option { + let locations = self.0.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); + } + } + None + } +} + pub struct RegionInferenceContext<'tcx> { /// Contains the definition for every region variable. Region /// variables are identified by their index (`RegionVid`). The @@ -107,11 +133,6 @@ pub struct RegionInferenceContext<'tcx> { /// Map universe indexes to information on why we created it. universe_causes: FxIndexMap>, - /// 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>, - /// Type constraints that we check after solving. type_tests: Vec>, @@ -293,17 +314,14 @@ impl<'tcx> RegionInferenceContext<'tcx> { 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, + mut liveness_constraints, universe_causes, placeholder_indices, } = lowered_constraints; @@ -318,16 +336,14 @@ impl<'tcx> RegionInferenceContext<'tcx> { 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); + for variable in definitions.indices() { + if let NllRegionVariableOrigin::FreeRegion = definitions[variable].origin { + // Add all nodes in the CFG to liveness constraints + liveness_constraints.add_all_points(variable); + } } - let mut result = Self { + Self { definitions, liveness_constraints, constraints: outlives_constraints, @@ -335,93 +351,8 @@ impl<'tcx> RegionInferenceContext<'tcx> { 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) - /// ``` - /// - /// In particular, imagine we have variables R0 in U0 and R1 - /// created in U1, and constraints like this; - /// - /// ```ignore (illustrative) - /// R1: !1 // R1 outlives the placeholder in U1 - /// R1: R0 // R1 outlives R0 - /// ``` - /// - /// Here, we wish for R1 to be `'static`, because it - /// cannot outlive `placeholder(U1)` and `empty(U0)` any other way. - /// - /// Thanks to this loop, what happens is that the `R1: R0` - /// constraint 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. - } - } } } @@ -451,53 +382,61 @@ impl<'tcx> RegionInferenceContext<'tcx> { } /// 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) + pub(crate) fn region_contains( + &self, + scc_values: &InferredRegions<'tcx>, + r: RegionVid, + p: impl ToElementIndex<'tcx>, + ) -> bool { + scc_values.0.contains(self.scc(r), 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, + scc_values: &InferredRegions<'tcx>, 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) + scc_values.0.first_non_contained_inclusive(self.scc(r), 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) + pub(crate) fn region_value_str( + &self, + scc_values: &InferredRegions<'tcx>, + r: RegionVid, + ) -> String { + scc_values.0.region_value_str(self.scc(r)) } pub(crate) fn placeholders_contained_in( &self, + scc_values: &InferredRegions<'tcx>, r: RegionVid, ) -> impl Iterator> { - let scc = self.constraint_sccs.scc(r); - self.scc_values.placeholders_contained_in(scc) + scc_values.0.placeholders_contained_in(self.scc(r)) } /// 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")] + #[instrument( + skip(self, infcx, body, polonius_output, location_map, placeholder_indices), + level = "debug" + )] pub(super) fn solve( - &mut self, + &self, infcx: &InferCtxt<'tcx>, body: &Body<'tcx>, polonius_output: Option>, - ) -> (Option>, RegionErrors<'tcx>) { + location_map: Rc, + placeholder_indices: PlaceholderIndices<'tcx>, + ) -> (Option>, RegionErrors<'tcx>, InferredRegions<'tcx>) { let mir_def_id = body.source.def_id(); - self.propagate_constraints(); + let scc_values = self.compute_region_values(location_map, placeholder_indices); let mut errors_buffer = RegionErrors::new(infcx.tcx); @@ -508,7 +447,12 @@ impl<'tcx> RegionInferenceContext<'tcx> { // 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); + self.check_type_tests( + &scc_values, + infcx, + outlives_requirements.as_mut(), + &mut errors_buffer, + ); debug!(?errors_buffer); debug!(?outlives_requirements); @@ -518,6 +462,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // 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( + &scc_values, outlives_requirements.as_mut(), &mut errors_buffer, polonius_output @@ -525,7 +470,11 @@ impl<'tcx> RegionInferenceContext<'tcx> { .expect("Polonius output is unavailable despite `-Z polonius`"), ); } else { - self.check_universal_regions(outlives_requirements.as_mut(), &mut errors_buffer); + self.check_universal_regions( + &scc_values, + outlives_requirements.as_mut(), + &mut errors_buffer, + ); } debug!(?errors_buffer); @@ -533,12 +482,13 @@ impl<'tcx> RegionInferenceContext<'tcx> { let outlives_requirements = outlives_requirements.unwrap_or_default(); if outlives_requirements.is_empty() { - (None, errors_buffer) + (None, errors_buffer, scc_values) } else { let num_external_vids = self.universal_regions().num_global_and_external_regions(); ( Some(ClosureRegionRequirements { num_external_vids, outlives_requirements }), errors_buffer, + scc_values, ) } } @@ -547,8 +497,12 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// 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) { + #[instrument(skip(self, location_map, placeholder_indices), level = "debug")] + fn compute_region_values( + &self, + location_map: Rc, + placeholder_indices: PlaceholderIndices<'tcx>, + ) -> InferredRegions<'tcx> { debug!("constraints={:#?}", { let mut constraints: Vec<_> = self.outlives_constraints().collect(); constraints.sort_by_key(|c| (c.sup, c.sub)); @@ -558,6 +512,33 @@ impl<'tcx> RegionInferenceContext<'tcx> { .collect::>() }); + let mut scc_values = + RegionValues::new(location_map, self.universal_regions().len(), placeholder_indices); + + for region in self.liveness_constraints.regions() { + scc_values.merge_liveness(self.scc(region), region, &self.liveness_constraints); + } + + 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)); + + // Add `end(X)` into the set for X. + scc_values.add_element(self.scc(variable), variable); + } + + NllRegionVariableOrigin::Placeholder(placeholder) => { + scc_values.add_element(self.scc(variable), placeholder); + } + + NllRegionVariableOrigin::Existential { .. } => { + // For existential, regions, nothing to do. + } + } + } + // 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 @@ -566,9 +547,10 @@ impl<'tcx> RegionInferenceContext<'tcx> { // 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); + scc_values.add_region(scc_a, scc_b); } } + InferredRegions(scc_values) } /// Returns `true` if all the placeholders in the value of `scc_b` are nameable @@ -588,6 +570,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// 'a`. See `TypeTest` for more details. fn check_type_tests( &self, + scc_values: &InferredRegions<'tcx>, infcx: &InferCtxt<'tcx>, mut propagated_outlives_requirements: Option<&mut Vec>>, errors_buffer: &mut RegionErrors<'tcx>, @@ -604,6 +587,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { let generic_ty = type_test.generic_kind.to_ty(tcx); if self.eval_verify_bound( + scc_values, infcx, generic_ty, type_test.lower_bound, @@ -613,7 +597,12 @@ 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( + scc_values, + infcx, + type_test, + propagated_outlives_requirements, + ) { continue; } @@ -663,9 +652,10 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// The idea then is to lower the `T: 'X` constraint into multiple /// bounds -- e.g., if `'X` is the union of two free lifetimes, /// `'1` and `'2`, then we would create `T: '1` and `T: '2`. - #[instrument(level = "debug", skip(self, infcx, propagated_outlives_requirements))] + #[instrument(level = "debug", skip(self, infcx, propagated_outlives_requirements, scc_values))] fn try_promote_type_test( &self, + scc_values: &InferredRegions<'tcx>, infcx: &InferCtxt<'tcx>, type_test: &TypeTest<'tcx>, propagated_outlives_requirements: &mut Vec>, @@ -674,7 +664,8 @@ impl<'tcx> RegionInferenceContext<'tcx> { let TypeTest { generic_kind, lower_bound, span: blame_span, verify_bound: _ } = *type_test; let generic_ty = generic_kind.to_ty(tcx); - let Some(subject) = self.try_promote_type_test_subject(infcx, generic_ty) else { + let Some(subject) = self.try_promote_type_test_subject(scc_values, infcx, generic_ty) + else { return false; }; @@ -691,7 +682,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) = scc_values.0.placeholders_contained_in(r_scc).next() { debug!("encountered placeholder in higher universe: {:?}, requiring 'static", p); let static_r = self.universal_regions().fr_static; propagated_outlives_requirements.push(ClosureOutlivesRequirement { @@ -710,7 +701,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 scc_values.0.universal_regions_outlived_by(r_scc) { 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); @@ -746,9 +737,10 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// variables in the type `T` with an equal universal region from the /// closure signature. /// This is not always possible, so this is a fallible process. - #[instrument(level = "debug", skip(self, infcx), ret)] + #[instrument(level = "debug", skip(self, infcx, scc_values), ret)] fn try_promote_type_test_subject( &self, + scc_values: &InferredRegions<'tcx>, infcx: &InferCtxt<'tcx>, ty: Ty<'tcx>, ) -> Option> { @@ -763,10 +755,11 @@ impl<'tcx> RegionInferenceContext<'tcx> { // 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 + scc_values + .0 .universal_regions_outlived_by(r_scc) .filter(|&u_r| !self.universal_regions().is_local_free_region(u_r)) - .find(|&u_r| self.eval_equal(u_r, r_vid)) + .find(|&u_r| self.eval_equal(scc_values, u_r, r_vid)) .map(|u_r| ty::Region::new_var(tcx, u_r)) // In case we could not find a named region to map to, // we will return `None` below. @@ -797,16 +790,20 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// 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)); + #[instrument(level = "debug", skip(self, scc_values))] + pub(crate) fn approx_universal_upper_bound( + &self, + scc_values: &InferredRegions<'tcx>, + r: RegionVid, + ) -> RegionVid { + debug!("{}", self.region_value_str(scc_values, 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) { + for ur in scc_values.0.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 @@ -841,6 +838,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// `point`. fn eval_verify_bound( &self, + scc_values: &InferredRegions<'tcx>, infcx: &InferCtxt<'tcx>, generic_ty: Ty<'tcx>, lower_bound: RegionVid, @@ -850,31 +848,32 @@ impl<'tcx> RegionInferenceContext<'tcx> { match verify_bound { VerifyBound::IfEq(verify_if_eq_b) => { - self.eval_if_eq(infcx, generic_ty, lower_bound, *verify_if_eq_b) + self.eval_if_eq(scc_values, infcx, generic_ty, lower_bound, *verify_if_eq_b) } VerifyBound::IsEmpty => { let lower_bound_scc = self.constraint_sccs.scc(lower_bound); - self.scc_values.elements_contained_in(lower_bound_scc).next().is_none() + scc_values.0.elements_contained_in(lower_bound_scc).next().is_none() } VerifyBound::OutlivedBy(r) => { let r_vid = self.to_region_vid(*r); - self.eval_outlives(r_vid, lower_bound) + self.eval_outlives(scc_values, r_vid, lower_bound) } VerifyBound::AnyBound(verify_bounds) => verify_bounds.iter().any(|verify_bound| { - self.eval_verify_bound(infcx, generic_ty, lower_bound, verify_bound) + self.eval_verify_bound(scc_values, infcx, generic_ty, lower_bound, verify_bound) }), VerifyBound::AllBounds(verify_bounds) => verify_bounds.iter().all(|verify_bound| { - self.eval_verify_bound(infcx, generic_ty, lower_bound, verify_bound) + self.eval_verify_bound(scc_values, infcx, generic_ty, lower_bound, verify_bound) }), } } fn eval_if_eq( &self, + scc_values: &InferredRegions<'tcx>, infcx: &InferCtxt<'tcx>, generic_ty: Ty<'tcx>, lower_bound: RegionVid, @@ -885,7 +884,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { 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) + self.eval_outlives(scc_values, r_vid, lower_bound) } None => false, } @@ -937,26 +936,34 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// 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) + pub fn eval_equal( + &self, + scc_values: &InferredRegions<'tcx>, + r1: RegionVid, + r2: RegionVid, + ) -> bool { + self.eval_outlives(scc_values, r1, r2) && self.eval_outlives(scc_values, r2, r1) } /// 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 { + #[instrument(skip(self, scc_values), level = "debug", ret)] + pub fn eval_outlives( + &self, + scc_values: &InferredRegions<'tcx>, + sup_region: RegionVid, + sub_region: RegionVid, + ) -> bool { debug!( "sup_region's value = {:?} universal={:?}", - self.region_value_str(sup_region), + self.region_value_str(scc_values, sup_region), self.universal_regions().is_universal_region(sup_region), ); debug!( "sub_region's value = {:?} universal={:?}", - self.region_value_str(sub_region), + self.region_value_str(scc_values, sub_region), self.universal_regions().is_universal_region(sub_region), ); @@ -983,7 +990,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { by super `{sup_region_scc:?}`, promoting to static", ); - return self.eval_outlives(sup_region, fr_static); + return self.eval_outlives(scc_values, sup_region, fr_static); } // Both the `sub_region` and `sup_region` consist of the union @@ -993,8 +1000,9 @@ impl<'tcx> RegionInferenceContext<'tcx> { // 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 + scc_values.0.universal_regions_outlived_by(sub_region_scc).all(|r1| { + scc_values + .0 .universal_regions_outlived_by(sup_region_scc) .any(|r2| self.universal_region_relations.outlives(r2, r1)) }); @@ -1015,7 +1023,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { debug!("comparison between points in sup/sub"); - self.scc_values.contains_points(sup_region_scc, sub_region_scc) + scc_values.0.contains_points(sup_region_scc, sub_region_scc) } /// Once regions have been propagated, this method is used to see @@ -1037,6 +1045,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// report them as errors. fn check_universal_regions( &self, + scc_values: &InferredRegions<'tcx>, mut propagated_outlives_requirements: Option<&mut Vec>>, errors_buffer: &mut RegionErrors<'tcx>, ) { @@ -1048,6 +1057,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // they did not grow too large, accumulating any requirements // for our caller into the `outlives_requirements` vector. self.check_universal_region( + scc_values, fr, &mut propagated_outlives_requirements, errors_buffer, @@ -1055,7 +1065,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { } NllRegionVariableOrigin::Placeholder(placeholder) => { - self.check_bound_universal_region(fr, placeholder, errors_buffer); + self.check_bound_universal_region(scc_values, fr, placeholder, errors_buffer); } NllRegionVariableOrigin::Existential { .. } => { @@ -1088,6 +1098,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// report them as errors. fn check_polonius_subset_errors( &self, + scc_values: &InferredRegions<'tcx>, mut propagated_outlives_requirements: Option<&mut Vec>>, errors_buffer: &mut RegionErrors<'tcx>, polonius_output: &PoloniusOutput, @@ -1134,6 +1145,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { ); let propagated = self.try_propagate_universal_region_error( + scc_values, longer_fr.into(), shorter_fr.into(), &mut propagated_outlives_requirements, @@ -1157,7 +1169,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { } NllRegionVariableOrigin::Placeholder(placeholder) => { - self.check_bound_universal_region(fr, placeholder, errors_buffer); + self.check_bound_universal_region(scc_values, fr, placeholder, errors_buffer); } NllRegionVariableOrigin::Existential { .. } => { @@ -1180,9 +1192,13 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// /// Things that are to be propagated are accumulated into the /// `outlives_requirements` vector. - #[instrument(skip(self, propagated_outlives_requirements, errors_buffer), level = "debug")] + #[instrument( + skip(self, propagated_outlives_requirements, errors_buffer, scc_values), + level = "debug" + )] fn check_universal_region( &self, + scc_values: &InferredRegions<'tcx>, longer_fr: RegionVid, propagated_outlives_requirements: &mut Option<&mut Vec>>, errors_buffer: &mut RegionErrors<'tcx>, @@ -1202,6 +1218,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { let representative = self.scc_representative(longer_fr_scc); if representative != longer_fr { if let RegionRelationCheckResult::Error = self.check_universal_region_relation( + scc_values, longer_fr, representative, propagated_outlives_requirements, @@ -1219,8 +1236,9 @@ impl<'tcx> RegionInferenceContext<'tcx> { // 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) { + for shorter_fr in scc_values.0.universal_regions_outlived_by(longer_fr_scc) { if let RegionRelationCheckResult::Error = self.check_universal_region_relation( + scc_values, longer_fr, shorter_fr, propagated_outlives_requirements, @@ -1245,6 +1263,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// error. fn check_universal_region_relation( &self, + scc_values: &InferredRegions<'tcx>, longer_fr: RegionVid, shorter_fr: RegionVid, propagated_outlives_requirements: &mut Option<&mut Vec>>, @@ -1260,6 +1279,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // 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( + scc_values, longer_fr, shorter_fr, propagated_outlives_requirements, @@ -1271,6 +1291,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// creator. If we cannot, then the caller should report an error to the user. fn try_propagate_universal_region_error( &self, + scc_values: &InferredRegions<'tcx>, longer_fr: RegionVid, shorter_fr: RegionVid, propagated_outlives_requirements: &mut Option<&mut Vec>>, @@ -1330,7 +1351,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { let subset: Vec<_> = constraints .iter() .filter(|&&(fr_minus, shorter_fr_plus)| { - self.eval_outlives(fr_minus, shorter_fr_plus) + self.eval_outlives(scc_values, fr_minus, shorter_fr_plus) }) .copied() .collect(); @@ -1362,6 +1383,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { fn check_bound_universal_region( &self, + scc_values: &InferredRegions<'tcx>, longer_fr: RegionVid, placeholder: ty::PlaceholderRegion<'tcx>, errors_buffer: &mut RegionErrors<'tcx>, @@ -1374,8 +1396,8 @@ impl<'tcx> RegionInferenceContext<'tcx> { // 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 + if let Some(error_element) = scc_values + .0 .elements_contained_in(longer_fr_scc) .find(|e| *e != RegionElement::PlaceholderRegion(placeholder)) { @@ -1464,12 +1486,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { 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), - ); + 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. @@ -1597,9 +1614,13 @@ impl<'tcx> RegionInferenceContext<'tcx> { } /// 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 upper_bound_in_region_scc( + &self, + scc_values: &InferredRegions<'tcx>, + r: RegionVid, + upper: RegionVid, + ) -> bool { + scc_values.0.contains(self.scc(r), upper) } pub(crate) fn universal_regions(&self) -> &UniversalRegions<'tcx> { @@ -1856,27 +1877,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { 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); - } - } - 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. @@ -1914,6 +1914,10 @@ impl<'tcx> RegionInferenceContext<'tcx> { let point = self.liveness_constraints.point_from_location(location); self.liveness_constraints.is_loan_live_at(loan_idx, point) } + + pub(crate) fn scc(&self, r: RegionVid) -> ConstraintSccIndex { + self.constraint_sccs.scc(r) + } } #[derive(Clone, Debug)] 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..b31452150d446 100644 --- a/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs @@ -26,6 +26,7 @@ use tracing::{debug, instrument}; use super::reverse_sccs::ReverseSccGraph; use crate::BorrowckInferCtxt; use crate::consumers::RegionInferenceContext; +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; @@ -608,7 +609,12 @@ 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, + scc_values: &InferredRegions<'tcx>, + tcx: TyCtxt<'tcx>, + ty: T, + ) -> T where T: TypeFoldable>, { @@ -618,7 +624,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // Special handling of higher-ranked regions. if !self.max_nameable_universe(scc).is_root() { - match self.scc_values.placeholders_contained_in(scc).enumerate().last() { + match scc_values.0.placeholders_contained_in(scc).enumerate().last() { // If the region contains a single placeholder then they're equal. Some((0, placeholder)) => { return ty::Region::new_placeholder(tcx, placeholder); @@ -630,7 +636,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { } // Find something that we can name - let upper_bound = self.approx_universal_upper_bound(vid); + let upper_bound = self.approx_universal_upper_bound(scc_values, vid); if let Some(universal_region) = self.definitions[upper_bound].external_name { return universal_region; } From 097381769ab93c6905d56f664fb10c9df3cfc4c2 Mon Sep 17 00:00:00 2001 From: Amanda Stjerna Date: Mon, 26 Jan 2026 15:41:14 +0100 Subject: [PATCH 2/9] Work around the clones --- compiler/rustc_borrowck/src/consumers.rs | 2 +- compiler/rustc_borrowck/src/nll.rs | 51 +++++++++++++++---- .../rustc_borrowck/src/region_infer/mod.rs | 22 +++----- 3 files changed, 51 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_borrowck/src/consumers.rs b/compiler/rustc_borrowck/src/consumers.rs index 2c2bf56d0c733..c2743c4463322 100644 --- a/compiler/rustc_borrowck/src/consumers.rs +++ b/compiler/rustc_borrowck/src/consumers.rs @@ -94,7 +94,7 @@ pub struct BodyWithBorrowckFacts<'tcx> { /// [`calculate_borrows_out_of_scope_at_location`]. pub region_inference_context: RegionInferenceContext<'tcx>, /// The inferred region values. These are included because they - /// are necesssary as input to + /// are necessary as input to /// [`calculate_borrows_out_of_scope_at_location`]. pub inferred_regions: InferredRegions<'tcx>, /// The table that maps Polonius points to locations in the table. diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index 3b53499c1d141..a47ab51f78b92 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -21,7 +21,9 @@ use tracing::{debug, instrument}; use crate::borrow_set::BorrowSet; use crate::consumers::RustcFacts; use crate::diagnostics::RegionErrors; -use crate::handle_placeholders::compute_sccs_applying_placeholder_outlives_constraints; +use crate::handle_placeholders::{ + LoweredConstraints, compute_sccs_applying_placeholder_outlives_constraints, +}; use crate::polonius::legacy::{ PoloniusFacts, PoloniusFactsExt, PoloniusLocationTable, PoloniusOutput, }; @@ -92,17 +94,30 @@ pub(crate) fn compute_closure_requirements_modulo_opaques<'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( + let LoweredConstraints { + constraint_sccs, + definitions, + scc_annotations, + outlives_constraints, + type_tests, + liveness_constraints, + universe_causes, + placeholder_indices, + } = compute_sccs_applying_placeholder_outlives_constraints( constraints.clone(), &universal_region_relations, infcx, ); - let placeholder_indices = lowered_constraints.placeholder_indices.clone(); // FIXME!!! - let regioncx = RegionInferenceContext::new( &infcx, - lowered_constraints, + constraint_sccs, + definitions, + scc_annotations, + outlives_constraints, + type_tests, + liveness_constraints, + universe_causes, universal_region_relations.clone(), ); @@ -148,10 +163,28 @@ pub(crate) fn compute_regions<'tcx>( &lowered_constraints, ); - let placeholder_indices = lowered_constraints.placeholder_indices.clone(); - - let mut regioncx = - RegionInferenceContext::new(infcx, lowered_constraints, universal_region_relations); + let LoweredConstraints { + constraint_sccs, + definitions, + scc_annotations, + outlives_constraints, + type_tests, + liveness_constraints, + universe_causes, + placeholder_indices, + } = lowered_constraints; + + let mut regioncx = RegionInferenceContext::new( + &infcx, + constraint_sccs, + definitions, + scc_annotations, + outlives_constraints, + type_tests, + liveness_constraints, + universe_causes, + universal_region_relations, + ); // If requested for `-Zpolonius=next`, convert NLL constraints to localized outlives constraints // and use them to compute loan liveness. diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 452ad05a9d836..ef42da40fb3de 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -26,7 +26,7 @@ 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::handle_placeholders::RegionTracker; use crate::polonius::LiveLoans; use crate::polonius::legacy::PoloniusOutput; use crate::region_infer::values::{ @@ -312,23 +312,17 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// of constraints produced by the MIR type check. pub(crate) fn new( infcx: &BorrowckInferCtxt<'tcx>, - lowered_constraints: LoweredConstraints<'tcx>, + constraint_sccs: Sccs, + definitions: Frozen>>, + scc_annotations: IndexVec, + outlives_constraints: Frozen>, + type_tests: Vec>, + mut liveness_constraints: LivenessValues, + universe_causes: FxIndexMap>, universal_region_relations: Frozen>, ) -> Self { - let LoweredConstraints { - constraint_sccs, - definitions, - outlives_constraints, - scc_annotations, - type_tests, - mut 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())); From c92bbfd9450f889de99467868c87304da62e8bb0 Mon Sep 17 00:00:00 2001 From: Amanda Stjerna Date: Wed, 28 Jan 2026 09:43:55 +0100 Subject: [PATCH 3/9] Split out all the SCC components into `InferredRegions`. Since this collapses `RegionInferenceContext`, `::solve()` now consumes the inference context. --- compiler/rustc_borrowck/src/consumers.rs | 3 - compiler/rustc_borrowck/src/dataflow.rs | 45 +- .../src/diagnostics/bound_region_errors.rs | 4 +- .../src/diagnostics/conflict_errors.rs | 2 +- .../src/diagnostics/explain_borrow.rs | 37 +- .../src/diagnostics/find_use.rs | 9 +- .../rustc_borrowck/src/diagnostics/mod.rs | 2 +- .../src/diagnostics/opaque_types.rs | 34 +- .../src/diagnostics/region_errors.rs | 48 +- .../src/diagnostics/region_name.rs | 42 +- .../src/diagnostics/var_name.rs | 41 +- compiler/rustc_borrowck/src/lib.rs | 146 ++++- compiler/rustc_borrowck/src/nll.rs | 80 ++- compiler/rustc_borrowck/src/polonius/dump.rs | 76 ++- compiler/rustc_borrowck/src/polonius/mod.rs | 2 +- .../src/region_infer/dump_mir.rs | 38 +- .../src/region_infer/graphviz.rs | 79 ++- .../rustc_borrowck/src/region_infer/mod.rs | 535 ++++++++---------- .../src/region_infer/opaque_types/mod.rs | 26 +- 19 files changed, 669 insertions(+), 580 deletions(-) diff --git a/compiler/rustc_borrowck/src/consumers.rs b/compiler/rustc_borrowck/src/consumers.rs index c2743c4463322..d57432b64c40c 100644 --- a/compiler/rustc_borrowck/src/consumers.rs +++ b/compiler/rustc_borrowck/src/consumers.rs @@ -90,9 +90,6 @@ 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 - /// [`calculate_borrows_out_of_scope_at_location`]. - pub region_inference_context: RegionInferenceContext<'tcx>, /// The inferred region values. These are included because they /// are necessary as input to /// [`calculate_borrows_out_of_scope_at_location`]. diff --git a/compiler/rustc_borrowck/src/dataflow.rs b/compiler/rustc_borrowck/src/dataflow.rs index ebd9967f519ee..4030e651e15e5 100644 --- a/compiler/rustc_borrowck/src/dataflow.rs +++ b/compiler/rustc_borrowck/src/dataflow.rs @@ -15,7 +15,8 @@ use rustc_mir_dataflow::{Analysis, GenKill, JoinSemiLattice}; use tracing::debug; use crate::region_infer::InferredRegions; -use crate::{BorrowSet, PlaceConflictBias, PlaceExt, RegionInferenceContext, places_conflict}; +use crate::region_infer::values::LivenessValues; +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 @@ -183,14 +184,12 @@ 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<'tcx>, borrow_set: &BorrowSet<'tcx>, ) -> FxIndexMap> { @@ -198,7 +197,6 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> { 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() { @@ -225,13 +223,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( - scc_values, - 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. @@ -258,13 +252,9 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> { while let Some(block) = self.visit_stack.pop() { let bb_data = &self.body[block]; let num_stmts = bb_data.statements.len(); - if let Some(kill_stmt) = self.regioncx.first_non_contained_inclusive( - scc_values, - borrow_region, - block, - 0, - num_stmts, - ) { + if let Some(kill_stmt) = + 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 // successor locations. @@ -293,26 +283,24 @@ 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<'tcx>, borrow_set: &BorrowSet<'tcx>, ) -> FxIndexMap> { - OutOfScopePrecomputer::compute(body, regioncx, scc_values, 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>, - + liveness_values: &'a LivenessValues, loans_out_of_scope_at_location: FxIndexMap>, } impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> { fn compute( body: &Body<'tcx>, - regioncx: &RegionInferenceContext<'tcx>, + liveness_values: &LivenessValues, borrow_set: &BorrowSet<'tcx>, ) -> FxIndexMap> { // The in-tree polonius analysis computes loans going out of scope using the @@ -321,7 +309,7 @@ impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> { visited: DenseBitSet::new_empty(body.basic_blocks.len()), visit_stack: vec![], body, - regioncx, + liveness_values, loans_out_of_scope_at_location: FxIndexMap::default(), }; for (loan_idx, loan_data) in borrow_set.iter_enumerated() { @@ -421,7 +409,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.liveness_values.point_from_location(location); + if self.liveness_values.is_loan_live_at(loan_idx, point) { continue; } @@ -438,15 +427,15 @@ impl<'a, 'tcx> Borrows<'a, 'tcx> { pub fn new( tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, - regioncx: &RegionInferenceContext<'tcx>, + liveness_values: &'a LivenessValues, scc_values: &InferredRegions<'tcx>, 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, scc_values, borrow_set) + calculate_borrows_out_of_scope_at_location(body, scc_values, borrow_set) } else { - PoloniusOutOfScopePrecomputer::compute(body, regioncx, borrow_set) + PoloniusOutOfScopePrecomputer::compute(body, liveness_values, 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 439b61da947e6..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; @@ -629,13 +641,14 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { // is issued is the same location that invalidates the reference), this is likely a // loop iteration. In this case, try using the loop terminator location in // `find_sub_region_live_at`. - if let Some(loop_terminator_location) = self - .scc_values - .find_loop_terminator_location(self.regioncx.scc(borrow.region), body) + if let Some(loop_terminator_location) = + 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; @@ -659,7 +672,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { false } }; - match find_use::find(body, regioncx, self.scc_values, 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 05fe9f4fe42e3..8ab79fbffd406 100644 --- a/compiler/rustc_borrowck/src/diagnostics/find_use.rs +++ b/compiler/rustc_borrowck/src/diagnostics/find_use.rs @@ -6,25 +6,22 @@ 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, InferredRegions, RegionInferenceContext}; +use crate::region_infer::{Cause, InferredRegions}; pub(crate) fn find<'tcx>( body: &Body<'tcx>, - regioncx: &RegionInferenceContext<'tcx>, scc_values: &InferredRegions<'tcx>, tcx: TyCtxt<'tcx>, region_vid: RegionVid, start_point: Location, ) -> Option { - let mut uf = - UseFinder { body, regioncx, tcx, region_vid, start_point, scc_values: &scc_values }; + let mut uf = UseFinder { body, tcx, region_vid, start_point, scc_values: &scc_values }; uf.find() } struct UseFinder<'a, 'tcx> { body: &'a Body<'tcx>, - regioncx: &'a RegionInferenceContext<'tcx>, scc_values: &'a InferredRegions<'tcx>, tcx: TyCtxt<'tcx>, region_vid: RegionVid, @@ -38,7 +35,7 @@ impl<'a, 'tcx> UseFinder<'a, 'tcx> { queue.push_back(self.start_point); while let Some(p) = queue.pop_front() { - if !self.regioncx.region_contains(self.scc_values, 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 783591cbaf80f..14768cfb33bc6 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,21 +42,12 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { hidden_type, member_region, } => { - let named_ty = self.regioncx.name_regions_for_member_constraint( - self.scc_values, - infcx.tcx, - hidden_type.ty, - ); - let named_key = self.regioncx.name_regions_for_member_constraint( - self.scc_values, - infcx.tcx, - opaque_type_key, - ); - let named_region = self.regioncx.name_regions_for_member_constraint( - self.scc_values, - infcx.tcx, - member_region, - ); + let named_ty = + self.name_regions_for_member_constraint(infcx.tcx, hidden_type.ty); + let named_key = + self.name_regions_for_member_constraint(infcx.tcx, opaque_type_key); + let named_region = + self.name_regions_for_member_constraint(infcx.tcx, member_region); let diag = unexpected_hidden_region_diagnostic( infcx, self.mir_def_id(), @@ -115,9 +107,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; @@ -217,8 +210,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> { @@ -245,11 +239,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 85e133d43a458..68bab2763e20c 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(self.scc_values, r); + let upper_bound = self.approx_universal_upper_bound(r); - if self.regioncx.upper_bound_in_region_scc(self.scc_values, 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,8 +205,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { // find generic associated types in the given region 'lower_bound' let gat_id_and_generics = self - .regioncx - .placeholders_contained_in(self.scc_values, lower_bound) + .scc_values + .placeholders_contained_in(lower_bound) .map(|placeholder| { if let Some(id) = placeholder.bound.kind.get_id() && let Some(placeholder_id) = id.as_local() @@ -365,11 +365,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 +384,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 +435,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 +452,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 +585,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 +608,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 +654,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 +669,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 +777,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/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 56231941552e7..8ae17e9328be9 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -55,10 +55,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; @@ -69,11 +72,13 @@ use crate::polonius::legacy::{ use crate::polonius::{PoloniusContext, PoloniusDiagnosticsContext}; use crate::prefixes::PrefixSet; use crate::region_infer::opaque_types::DeferredOpaqueTypeError; -use crate::region_infer::{InferredRegions, RegionInferenceContext}; +use crate::region_infer::values::LivenessValues; +use crate::region_infer::{InferredRegions, RegionDefinition, RegionInferenceContext}; 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; @@ -414,13 +419,16 @@ fn borrowck_check_region_constraints<'tcx>( // 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, @@ -429,7 +437,7 @@ fn borrowck_check_region_constraints<'tcx>( &move_data, &borrow_set, location_map, - universal_region_relations, + &universal_region_relations, constraints, polonius_facts, polonius_context, @@ -437,20 +445,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, &scc_values, &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; @@ -476,7 +502,6 @@ 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(), @@ -489,6 +514,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>, @@ -516,7 +547,6 @@ 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(), @@ -529,6 +559,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. @@ -538,7 +574,8 @@ 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, &scc_values); + let flow_results = + get_flow_results(tcx, body, &move_data, &borrow_set, &liveness_constraints, &scc_values); visit_results( body, traversal::reverse_postorder(body).map(|(bb, _)| bb), @@ -584,7 +621,6 @@ 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, @@ -603,16 +639,13 @@ fn get_flow_results<'a, 'tcx>( body: &'a Body<'tcx>, move_data: &'a MoveData<'tcx>, borrow_set: &'a BorrowSet<'tcx>, - regioncx: &RegionInferenceContext<'tcx>, + liveness_values: &'a LivenessValues, scc_values: &InferredRegions<'tcx>, ) -> Results<'tcx, Borrowck<'a, 'tcx>> { // 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, scc_values, borrow_set).iterate_to_fixpoint( - tcx, - body, - Some("borrowck"), - ); + let borrows = Borrows::new(tcx, body, liveness_values, scc_values, borrow_set) + .iterate_to_fixpoint(tcx, body, Some("borrowck")); let uninits = MaybeUninitializedPlaces::new(tcx, body, move_data).iterate_to_fixpoint( tcx, body, @@ -754,10 +787,9 @@ 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. - /// find out which CFG points are contained in each borrow region. - regioncx: &'a RegionInferenceContext<'tcx>, + /// This contains the results from region inference and lets us e.g. + /// find out which CFG points are contained in each borrow region. scc_values: &'a InferredRegions<'tcx>, /// The set of borrows extracted from the MIR @@ -783,6 +815,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: @@ -2694,6 +2737,71 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> { tcx.emit_node_span_lint(UNUSED_MUT, lint_root, span, VarNeedNotMut { span: mut_span }) } } + + /// 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) -> RegionVid { + debug!("{}", self.scc_values.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.scc_values.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 self.definitions[ur].external_name.is_some() { + lub = ur; + } else if self.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 + } + + 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) + } } /// 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 a47ab51f78b92..f4e42084f9c21 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,8 +20,9 @@ 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::diagnostics::{RegionErrors, UniverseInfo}; use crate::handle_placeholders::{ LoweredConstraints, compute_sccs_applying_placeholder_outlives_constraints, }; @@ -28,7 +30,9 @@ use crate::polonius::legacy::{ PoloniusFacts, PoloniusFactsExt, PoloniusLocationTable, PoloniusOutput, }; use crate::polonius::{PoloniusContext, PoloniusDiagnosticsContext}; -use crate::region_infer::{InferredRegions, 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; @@ -40,12 +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. @@ -100,8 +107,8 @@ pub(crate) fn compute_closure_requirements_modulo_opaques<'tcx>( scc_annotations, outlives_constraints, type_tests, - liveness_constraints, - universe_causes, + mut liveness_constraints, + universe_causes: _, placeholder_indices, } = compute_sccs_applying_placeholder_outlives_constraints( constraints.clone(), @@ -112,13 +119,12 @@ pub(crate) fn compute_closure_requirements_modulo_opaques<'tcx>( let regioncx = RegionInferenceContext::new( &infcx, constraint_sccs, - definitions, + &definitions, scc_annotations, - outlives_constraints, + &outlives_constraints, type_tests, - liveness_constraints, - universe_causes, - universal_region_relations.clone(), + &mut liveness_constraints, + universal_region_relations, ); let (closure_region_requirements, _nll_errors, _scc_values) = @@ -137,7 +143,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: &Frozen>, constraints: MirTypeckRegionConstraints<'tcx>, mut polonius_facts: Option>, polonius_context: Option, @@ -169,7 +175,7 @@ pub(crate) fn compute_regions<'tcx>( scc_annotations, outlives_constraints, type_tests, - liveness_constraints, + mut liveness_constraints, universe_causes, placeholder_indices, } = lowered_constraints; @@ -177,12 +183,11 @@ pub(crate) fn compute_regions<'tcx>( let mut regioncx = RegionInferenceContext::new( &infcx, constraint_sccs, - definitions, + &definitions, scc_annotations, - outlives_constraints, + &outlives_constraints, type_tests, - liveness_constraints, - universe_causes, + &mut liveness_constraints, universal_region_relations, ); @@ -218,13 +223,16 @@ pub(crate) fn compute_regions<'tcx>( regioncx.solve(infcx, 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, } } @@ -240,10 +248,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 }; @@ -261,10 +272,13 @@ pub(super) fn dump_nll_mir<'tcx>( let extra_data = &|pass_where, out: &mut dyn std::io::Write| { emit_nll_mir( tcx, - regioncx, scc_values, closure_region_requirements, borrow_set, + region_definitions, + outlives_constraints, + universal_region_relations, + liveness_constraints, pass_where, out, ) @@ -277,30 +291,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(scc_values, 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 { @@ -336,8 +360,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()); @@ -356,7 +380,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: {}", @@ -374,7 +398,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 12bb882675da6..91d26fe7e5df3 100644 --- a/compiler/rustc_borrowck/src/polonius/dump.rs +++ b/compiler/rustc_borrowck/src/polonius/dump.rs @@ -9,23 +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::InferredRegions; 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; @@ -41,11 +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, ) @@ -66,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, @@ -83,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, @@ -108,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,
     )?;
@@ -127,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, "
")?; @@ -135,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, "
")?; @@ -193,27 +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 => { @@ -291,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())?; @@ -311,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<'_>| { @@ -333,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)); @@ -363,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. @@ -371,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, ",")?; } @@ -389,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/mod.rs b/compiler/rustc_borrowck/src/polonius/mod.rs index a9092b1981e1d..0ef1b29331d6d 100644 --- a/compiler/rustc_borrowck/src/polonius/mod.rs +++ b/compiler/rustc_borrowck/src/polonius/mod.rs @@ -153,7 +153,7 @@ impl PoloniusContext { pub(crate) fn compute_loan_liveness<'tcx>( self, tcx: TyCtxt<'tcx>, - regioncx: &mut RegionInferenceContext<'tcx>, + regioncx: &mut RegionInferenceContext<'_, 'tcx>, body: &Body<'tcx>, borrow_set: &BorrowSet<'tcx>, ) -> PoloniusDiagnosticsContext { diff --git a/compiler/rustc_borrowck/src/region_infer/dump_mir.rs b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs index a269c75355979..0437995ae2b8f 100644 --- a/compiler/rustc_borrowck/src/region_infer/dump_mir.rs +++ b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs @@ -5,24 +5,36 @@ use std::io::{self, Write}; +use rustc_index::IndexVec; use rustc_infer::infer::NllRegionVariableOrigin; -use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::{RegionVid, TyCtxt}; -use super::{OutlivesConstraint, RegionInferenceContext}; -use crate::region_infer::InferredRegions; +use super::OutlivesConstraint; +use crate::constraints::OutlivesConstraintSet; +use crate::region_infer::values::LivenessValues; +use crate::region_infer::{InferredRegions, RegionDefinition}; 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 IndexVec>, + 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, scc_values: &InferredRegions<'tcx>, - tcx: TyCtxt<'tcx>, out: &mut dyn Write, ) -> io::Result<()> { writeln!(out, "| Free Region Mapping")?; @@ -52,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(scc_values, 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(()) } @@ -80,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; @@ -95,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..4a6b74ddb6515 100644 --- a/compiler/rustc_borrowck/src/region_infer/graphviz.rs +++ b/compiler/rustc_borrowck/src/region_infer/graphviz.rs @@ -33,19 +33,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 +63,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 +111,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 +123,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 +144,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 +165,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 +176,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 ef42da40fb3de..adcfad48a9966 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -4,7 +4,6 @@ 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; @@ -25,7 +24,7 @@ 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::diagnostics::{RegionErrorKind, RegionErrors}; use crate::handle_placeholders::RegionTracker; use crate::polonius::LiveLoans; use crate::polonius::legacy::PoloniusOutput; @@ -41,11 +40,12 @@ 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; /// The representative region variable for an SCC, tagged by its origin. /// We prefer placeholders over existentially quantified variables, otherwise @@ -78,17 +78,21 @@ impl Representative { pub(crate) type ConstraintSccs = Sccs; -pub struct InferredRegions<'tcx>(pub(crate) RegionValues<'tcx, ConstraintSccIndex>); +pub struct InferredRegions<'tcx> { + pub(crate) scc_values: RegionValues<'tcx, ConstraintSccIndex>, + pub(crate) sccs: ConstraintSccs, + annotations: IndexVec, +} 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, - scc: ConstraintSccIndex, + r: RegionVid, body: &Body<'_>, ) -> Option { - let locations = self.0.locations_outlived_by(scc); + 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 @@ -100,23 +104,71 @@ impl<'tcx> InferredRegions<'tcx> { } None } + + pub(crate) fn scc(&self, r: RegionVid) -> ConstraintSccIndex { + self.sccs.scc(r) + } + + /// 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) + } + + /// 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) + } + + /// 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)) + } + + pub(crate) fn placeholders_contained_in( + &self, + r: RegionVid, + ) -> impl Iterator> { + self.scc_values.placeholders_contained_in(self.scc(r)) + } + + /// 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) + } + + pub(crate) fn universal_regions_outlived_by( + &self, + r: RegionVid, + ) -> impl Iterator { + self.scc_values.universal_regions_outlived_by(self.scc(r)) + } + + fn max_nameable_universe(&self, vid: RegionVid) -> UniverseIndex { + self.annotations[self.scc(vid)].max_nameable_universe() + } } -pub struct RegionInferenceContext<'tcx> { +pub 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. - pub(crate) definitions: Frozen>>, + pub(crate) definitions: &'a 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, + liveness_constraints: &'a mut LivenessValues, /// The outlives constraints computed by the type-check. - constraints: Frozen>, + constraints: &'a OutlivesConstraintSet<'tcx>, /// The constraint-set, but in graph form, making it easy to traverse /// the constraints adjacent to a particular region. Used to construct @@ -130,15 +182,12 @@ pub struct RegionInferenceContext<'tcx> { scc_annotations: IndexVec, - /// Map universe indexes to information on why we created it. - universe_causes: FxIndexMap>, - /// Type constraints that we check after solving. type_tests: Vec>, /// Information about how the universally quantified regions in /// scope on this function relate to one another. - universal_region_relations: Frozen>, + universal_region_relations: &'a Frozen>, } #[derive(Debug)] @@ -302,7 +351,7 @@ fn sccs_info<'tcx>(infcx: &BorrowckInferCtxt<'tcx>, sccs: &ConstraintSccs) { debug!("SCC edges {:#?}", scc_node_to_edges); } -impl<'tcx> RegionInferenceContext<'tcx> { +impl<'a, 'tcx> RegionInferenceContext<'a, '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 @@ -313,13 +362,12 @@ impl<'tcx> RegionInferenceContext<'tcx> { pub(crate) fn new( infcx: &BorrowckInferCtxt<'tcx>, constraint_sccs: Sccs, - definitions: Frozen>>, + definitions: &'a Frozen>>, scc_annotations: IndexVec, - outlives_constraints: Frozen>, + outlives_constraints: &'a OutlivesConstraintSet<'tcx>, type_tests: Vec>, - mut liveness_constraints: LivenessValues, - universe_causes: FxIndexMap>, - universal_region_relations: Frozen>, + liveness_constraints: &'a mut LivenessValues, + universal_region_relations: &'a Frozen>, ) -> Self { debug!("universal_regions: {:#?}", universal_region_relations.universal_regions); debug!("outlives constraints: {:#?}", outlives_constraints); @@ -344,17 +392,11 @@ impl<'tcx> RegionInferenceContext<'tcx> { constraint_graph, constraint_sccs, scc_annotations, - universe_causes, type_tests, universal_region_relations, } } - /// Returns an iterator over all the region indices. - pub(crate) fn regions(&self) -> impl Iterator + 'tcx { - self.definitions.indices() - } - /// Given a universal region in scope on the MIR, returns the /// corresponding index. /// @@ -370,50 +412,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { 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`. - pub(crate) fn region_contains( - &self, - scc_values: &InferredRegions<'tcx>, - r: RegionVid, - p: impl ToElementIndex<'tcx>, - ) -> bool { - scc_values.0.contains(self.scc(r), p) - } - - /// Returns the lowest statement index in `start..=end` which is not contained by `r`. - pub(crate) fn first_non_contained_inclusive( - &self, - scc_values: &InferredRegions<'tcx>, - r: RegionVid, - block: BasicBlock, - start: usize, - end: usize, - ) -> Option { - scc_values.0.first_non_contained_inclusive(self.scc(r), block, start, end) - } - - /// Returns access to the value of `r` for debugging purposes. - pub(crate) fn region_value_str( - &self, - scc_values: &InferredRegions<'tcx>, - r: RegionVid, - ) -> String { - scc_values.0.region_value_str(self.scc(r)) - } - - pub(crate) fn placeholders_contained_in( - &self, - scc_values: &InferredRegions<'tcx>, - r: RegionVid, - ) -> impl Iterator> { - scc_values.0.placeholders_contained_in(self.scc(r)) - } - /// 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. @@ -422,7 +420,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { level = "debug" )] pub(super) fn solve( - &self, + self, infcx: &InferCtxt<'tcx>, body: &Body<'tcx>, polonius_output: Option>, @@ -474,11 +472,17 @@ impl<'tcx> RegionInferenceContext<'tcx> { debug!(?errors_buffer); let outlives_requirements = outlives_requirements.unwrap_or_default(); + let scc_values = InferredRegions { + scc_values, + sccs: self.constraint_sccs, + annotations: self.scc_annotations, + }; if outlives_requirements.is_empty() { (None, errors_buffer, scc_values) } else { - let num_external_vids = self.universal_regions().num_global_and_external_regions(); + let num_external_vids = + self.universal_region_relations.universal_regions.num_global_and_external_regions(); ( Some(ClosureRegionRequirements { num_external_vids, outlives_requirements }), errors_buffer, @@ -496,7 +500,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { &self, location_map: Rc, placeholder_indices: PlaceholderIndices<'tcx>, - ) -> InferredRegions<'tcx> { + ) -> RegionValues<'tcx, ConstraintSccIndex> { debug!("constraints={:#?}", { let mut constraints: Vec<_> = self.outlives_constraints().collect(); constraints.sort_by_key(|c| (c.sup, c.sub)); @@ -544,7 +548,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { scc_values.add_region(scc_a, scc_b); } } - InferredRegions(scc_values) + scc_values } /// Returns `true` if all the placeholders in the value of `scc_b` are nameable @@ -564,7 +568,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// 'a`. See `TypeTest` for more details. fn check_type_tests( &self, - scc_values: &InferredRegions<'tcx>, + scc_values: &RegionValues<'tcx, ConstraintSccIndex>, infcx: &InferCtxt<'tcx>, mut propagated_outlives_requirements: Option<&mut Vec>>, errors_buffer: &mut RegionErrors<'tcx>, @@ -649,7 +653,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { #[instrument(level = "debug", skip(self, infcx, propagated_outlives_requirements, scc_values))] fn try_promote_type_test( &self, - scc_values: &InferredRegions<'tcx>, + scc_values: &RegionValues<'tcx, ConstraintSccIndex>, infcx: &InferCtxt<'tcx>, type_test: &TypeTest<'tcx>, propagated_outlives_requirements: &mut Vec>, @@ -676,7 +680,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) = scc_values.0.placeholders_contained_in(r_scc).next() { + if let Some(p) = scc_values.placeholders_contained_in(r_scc).next() { debug!("encountered placeholder in higher universe: {:?}, requiring 'static", p); let static_r = self.universal_regions().fr_static; propagated_outlives_requirements.push(ClosureOutlivesRequirement { @@ -695,7 +699,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 scc_values.0.universal_regions_outlived_by(r_scc) { + for ur in scc_values.universal_regions_outlived_by(r_scc) { 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); @@ -734,7 +738,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { #[instrument(level = "debug", skip(self, infcx, scc_values), ret)] fn try_promote_type_test_subject( &self, - scc_values: &InferredRegions<'tcx>, + scc_values: &RegionValues<'tcx, ConstraintSccIndex>, infcx: &InferCtxt<'tcx>, ty: Ty<'tcx>, ) -> Option> { @@ -750,7 +754,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { // any of the named regions found in the closure. // To do so, we simply check every candidate `u_r` for equality. scc_values - .0 .universal_regions_outlived_by(r_scc) .filter(|&u_r| !self.universal_regions().is_local_free_region(u_r)) .find(|&u_r| self.eval_equal(scc_values, u_r, r_vid)) @@ -773,66 +776,11 @@ 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). - /// - /// 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, scc_values))] - pub(crate) fn approx_universal_upper_bound( - &self, - scc_values: &InferredRegions<'tcx>, - r: RegionVid, - ) -> RegionVid { - debug!("{}", self.region_value_str(scc_values, 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 scc_values.0.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 - } - /// Tests if `test` is true when applied to `lower_bound` at /// `point`. fn eval_verify_bound( &self, - scc_values: &InferredRegions<'tcx>, + scc_values: &RegionValues<'tcx, ConstraintSccIndex>, infcx: &InferCtxt<'tcx>, generic_ty: Ty<'tcx>, lower_bound: RegionVid, @@ -847,7 +795,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { VerifyBound::IsEmpty => { let lower_bound_scc = self.constraint_sccs.scc(lower_bound); - scc_values.0.elements_contained_in(lower_bound_scc).next().is_none() + scc_values.elements_contained_in(lower_bound_scc).next().is_none() } VerifyBound::OutlivedBy(r) => { @@ -867,7 +815,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { fn eval_if_eq( &self, - scc_values: &InferredRegions<'tcx>, + scc_values: &RegionValues<'tcx, ConstraintSccIndex>, infcx: &InferCtxt<'tcx>, generic_ty: Ty<'tcx>, lower_bound: RegionVid, @@ -933,7 +881,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // This is `pub` because it's used by unstable external borrowck data users, see `consumers.rs`. pub fn eval_equal( &self, - scc_values: &InferredRegions<'tcx>, + scc_values: &RegionValues<'tcx, ConstraintSccIndex>, r1: RegionVid, r2: RegionVid, ) -> bool { @@ -946,21 +894,10 @@ impl<'tcx> RegionInferenceContext<'tcx> { #[instrument(skip(self, scc_values), level = "debug", ret)] pub fn eval_outlives( &self, - scc_values: &InferredRegions<'tcx>, + scc_values: &RegionValues<'tcx, ConstraintSccIndex>, sup_region: RegionVid, sub_region: RegionVid, ) -> bool { - debug!( - "sup_region's value = {:?} universal={:?}", - self.region_value_str(scc_values, sup_region), - self.universal_regions().is_universal_region(sup_region), - ); - debug!( - "sub_region's value = {:?} universal={:?}", - self.region_value_str(scc_values, sub_region), - self.universal_regions().is_universal_region(sub_region), - ); - let sub_region_scc = self.constraint_sccs.scc(sub_region); let sup_region_scc = self.constraint_sccs.scc(sup_region); @@ -994,9 +931,8 @@ impl<'tcx> RegionInferenceContext<'tcx> { // for each universal region R1 in the sub-region, there // exists some region R2 in the sup-region that outlives R1. let universal_outlives = - scc_values.0.universal_regions_outlived_by(sub_region_scc).all(|r1| { + scc_values.universal_regions_outlived_by(sub_region_scc).all(|r1| { scc_values - .0 .universal_regions_outlived_by(sup_region_scc) .any(|r2| self.universal_region_relations.outlives(r2, r1)) }); @@ -1017,7 +953,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { debug!("comparison between points in sup/sub"); - scc_values.0.contains_points(sup_region_scc, sub_region_scc) + scc_values.contains_points(sup_region_scc, sub_region_scc) } /// Once regions have been propagated, this method is used to see @@ -1039,7 +975,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// report them as errors. fn check_universal_regions( &self, - scc_values: &InferredRegions<'tcx>, + scc_values: &RegionValues<'tcx, ConstraintSccIndex>, mut propagated_outlives_requirements: Option<&mut Vec>>, errors_buffer: &mut RegionErrors<'tcx>, ) { @@ -1092,7 +1028,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// report them as errors. fn check_polonius_subset_errors( &self, - scc_values: &InferredRegions<'tcx>, + scc_values: &RegionValues<'tcx, ConstraintSccIndex>, mut propagated_outlives_requirements: Option<&mut Vec>>, errors_buffer: &mut RegionErrors<'tcx>, polonius_output: &PoloniusOutput, @@ -1192,7 +1128,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { )] fn check_universal_region( &self, - scc_values: &InferredRegions<'tcx>, + scc_values: &RegionValues<'tcx, ConstraintSccIndex>, longer_fr: RegionVid, propagated_outlives_requirements: &mut Option<&mut Vec>>, errors_buffer: &mut RegionErrors<'tcx>, @@ -1230,7 +1166,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // Find every region `o` such that `fr: o` // (because `fr` includes `end(o)`). let mut error_reported = false; - for shorter_fr in scc_values.0.universal_regions_outlived_by(longer_fr_scc) { + for shorter_fr in scc_values.universal_regions_outlived_by(longer_fr_scc) { if let RegionRelationCheckResult::Error = self.check_universal_region_relation( scc_values, longer_fr, @@ -1257,7 +1193,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// error. fn check_universal_region_relation( &self, - scc_values: &InferredRegions<'tcx>, + scc_values: &RegionValues<'tcx, ConstraintSccIndex>, longer_fr: RegionVid, shorter_fr: RegionVid, propagated_outlives_requirements: &mut Option<&mut Vec>>, @@ -1285,7 +1221,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// creator. If we cannot, then the caller should report an error to the user. fn try_propagate_universal_region_error( &self, - scc_values: &InferredRegions<'tcx>, + scc_values: &RegionValues<'tcx, ConstraintSccIndex>, longer_fr: RegionVid, shorter_fr: RegionVid, propagated_outlives_requirements: &mut Option<&mut Vec>>, @@ -1305,6 +1241,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { } let blame_constraint = self + .constraint_search() .best_blame_constraint(longer_fr, NllRegionVariableOrigin::FreeRegion, shorter_fr) .0; @@ -1377,7 +1314,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { fn check_bound_universal_region( &self, - scc_values: &InferredRegions<'tcx>, + scc_values: &RegionValues<'tcx, ConstraintSccIndex>, longer_fr: RegionVid, placeholder: ty::PlaceholderRegion<'tcx>, errors_buffer: &mut RegionErrors<'tcx>, @@ -1391,7 +1328,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { // elements it can contain is itself -- we don't know anything // else about it! if let Some(error_element) = scc_values - .0 .elements_contained_in(longer_fr_scc) .find(|e| *e != RegionElement::PlaceholderRegion(placeholder)) { @@ -1406,15 +1342,135 @@ impl<'tcx> RegionInferenceContext<'tcx> { } } - pub(crate) fn constraint_path_between_regions( + fn constraint_search(&'a self) -> ConstraintSearch<'a, 'tcx> { + ConstraintSearch { + definitions: &self.definitions, + fr_static: self.universal_regions().fr_static, + constraint_graph: &self.constraint_graph, + constraints: &self.constraints, + } + } + + fn constraint_path_to( &self, from_region: RegionVid, to_region: RegionVid, + follow_outlives_static: bool, ) -> Option>> { - if from_region == to_region { - bug!("Tried to find a path between {from_region:?} and itself!"); + self.constraint_search() + .constraint_path_to(from_region, |t| t == to_region, follow_outlives_static) + .map(|o| o.0) + } + + /// Get the region definition of `r`. + pub(crate) fn region_definition(&self, r: RegionVid) -> &RegionDefinition<'tcx> { + &self.definitions[r] + } + + pub(crate) fn universal_regions(&self) -> &UniversalRegions<'tcx> { + &self.universal_region_relations.universal_regions + } + + /// 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 + } + + /// 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); + } + + /// 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) + } + + pub(crate) 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>, +} + +pub(crate) struct ConstraintSearch<'a, 'tcx> { + pub(crate) definitions: &'a IndexVec>, + 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: &IndexVec>, + ) -> 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(), } - self.constraint_path_to(from_region, |to| to == to_region, true).map(|o| o.0) + } + + /// 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 @@ -1449,6 +1505,17 @@ impl<'tcx> RegionInferenceContext<'tcx> { }) } + 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. @@ -1468,10 +1535,9 @@ impl<'tcx> RegionInferenceContext<'tcx> { target_test: impl Fn(RegionVid) -> bool, include_placeholder_static: bool, ) -> Option<(Vec>, RegionVid)> { - let mut context = IndexVec::from_elem(Trace::NotVisited, &self.definitions); + let mut context = IndexVec::from_elem(Trace::NotVisited, self.definitions); context[from_region] = Trace::StartRegion; - - let fr_static = self.universal_regions().fr_static; + 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 @@ -1545,7 +1611,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { handle_trace(sub, Trace::FromStatic(sub)); } } else { - let edges = self.constraint_graph.outgoing_edges_from_graph(r, &self.constraints); + let edges = self.constraint_graph.outgoing_edges_from_graph(r, self.constraints); // This loop can be hot. for constraint in edges { match constraint.category { @@ -1571,56 +1637,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { None } - /// 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 - } - - /// 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(), - } - } - - /// 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, - scc_values: &InferredRegions<'tcx>, - r: RegionVid, - upper: RegionVid, - ) -> bool { - scc_values.0.contains(self.scc(r), 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, @@ -1636,7 +1652,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { ) -> (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(); + 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`. @@ -1654,23 +1670,11 @@ impl<'tcx> RegionInferenceContext<'tcx> { { // 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 + self.constraint_path_to(from_region, |t| t == unnameable, false).unwrap().0 } else { path }; - debug!( - "path={:#?}", - path.iter() - .map(|c| format!( - "{:?} ({:?}: {:?})", - c, - self.constraint_sccs.scc(c.sup), - self.constraint_sccs.scc(c.sub), - )) - .collect::>() - ); - // We try to avoid reporting a `ConstraintCategory::Predicate` as our best constraint. // Instead, we use it to produce an improved `ObligationCauseCode`. // FIXME - determine what we should do if we encounter multiple @@ -1767,7 +1771,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { is_raw_ptr_dyn_type_cast: _, unsize_to: Some(unsize_ty), is_implicit_coercion: true, - } if to_region == self.universal_regions().fr_static + } 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())) @@ -1859,65 +1863,4 @@ impl<'tcx> RegionInferenceContext<'tcx> { }; (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) - } - - /// 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 - } - - /// 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); - } - - /// 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) - } - - pub(crate) 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 b31452150d446..d12a74bd1f836 100644 --- a/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs @@ -24,14 +24,12 @@ use rustc_trait_selection::traits::query::type_op::custom::CustomTypeOp; use tracing::{debug, instrument}; use super::reverse_sccs::ReverseSccGraph; -use crate::BorrowckInferCtxt; -use crate::consumers::RegionInferenceContext; -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; use crate::type_check::{Locations, MirTypeckRegionConstraints}; use crate::universal_regions::{RegionClassification, UniversalRegions}; +use crate::{BorrowckInferCtxt, MirBorrowckCtxt}; mod member_constraints; mod region_ctxt; @@ -596,7 +594,7 @@ pub(crate) fn detect_opaque_types_added_while_handling_opaque_types<'tcx>( let _ = infcx.take_opaque_types(); } -impl<'tcx> RegionInferenceContext<'tcx> { +impl<'a, 'infcx, 'tcx> MirBorrowckCtxt<'a, 'infcx, 'tcx> { /// 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 @@ -609,22 +607,15 @@ 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, - scc_values: &InferredRegions<'tcx>, - tcx: TyCtxt<'tcx>, - ty: T, - ) -> T + pub(crate) fn name_regions_for_member_constraint(&self, tcx: TyCtxt<'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 scc_values.0.placeholders_contained_in(scc).enumerate().last() { + if !self.scc_values.max_nameable_universe(vid).is_root() { + match self.scc_values.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); @@ -636,7 +627,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { } // Find something that we can name - let upper_bound = self.approx_universal_upper_bound(scc_values, vid); + let upper_bound = self.approx_universal_upper_bound(vid); if let Some(universal_region) = self.definitions[upper_bound].external_name { return universal_region; } @@ -645,11 +636,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()); + ReverseSccGraph::compute(&self.scc_values.sccs, self.universal_regions()); let upper_bounds: Vec<_> = rev_scc_graph - .upper_bounds(scc) + .upper_bounds(self.scc_values.scc(vid)) .filter_map(|vid| self.definitions[vid].external_name) .filter(|r| !r.is_static()) .collect(); From ba9fe002378f177393b7169a9870b5c4515e705a Mon Sep 17 00:00:00 2001 From: Amanda Stjerna Date: Thu, 5 Feb 2026 14:15:27 +0100 Subject: [PATCH 4/9] Re-implement the public interface of `RegionInferenceContext` on `InferredRegions`. --- compiler/rustc_borrowck/src/consumers.rs | 1 - compiler/rustc_borrowck/src/dataflow.rs | 48 +- .../src/diagnostics/find_use.rs | 8 +- .../src/diagnostics/region_errors.rs | 17 +- .../rustc_borrowck/src/handle_placeholders.rs | 19 +- compiler/rustc_borrowck/src/lib.rs | 34 +- compiler/rustc_borrowck/src/nll.rs | 7 +- .../src/polonius/loan_liveness.rs | 11 +- compiler/rustc_borrowck/src/polonius/mod.rs | 14 +- .../src/polonius/typeck_constraints.rs | 6 +- .../src/region_infer/constraint_search.rs | 479 ++++ .../src/region_infer/dump_mir.rs | 6 +- .../rustc_borrowck/src/region_infer/mod.rs | 2022 +++++------------ .../region_infer/opaque_types/region_ctxt.rs | 6 +- .../src/region_infer/universal_regions.rs | 436 ++++ .../rustc_borrowck/src/region_infer/values.rs | 26 +- compiler/rustc_borrowck/src/root_cx.rs | 2 +- 17 files changed, 1556 insertions(+), 1586 deletions(-) create mode 100644 compiler/rustc_borrowck/src/region_infer/constraint_search.rs create mode 100644 compiler/rustc_borrowck/src/region_infer/universal_regions.rs diff --git a/compiler/rustc_borrowck/src/consumers.rs b/compiler/rustc_borrowck/src/consumers.rs index d57432b64c40c..ecce77bb79942 100644 --- a/compiler/rustc_borrowck/src/consumers.rs +++ b/compiler/rustc_borrowck/src/consumers.rs @@ -16,7 +16,6 @@ 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; diff --git a/compiler/rustc_borrowck/src/dataflow.rs b/compiler/rustc_borrowck/src/dataflow.rs index 4030e651e15e5..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,11 +12,12 @@ 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::polonius::{LiveLoans, PoloniusDiagnosticsContext}; use crate::region_infer::InferredRegions; -use crate::region_infer::values::LivenessValues; use crate::{BorrowSet, PlaceConflictBias, PlaceExt, places_conflict}; // This analysis is different to most others. Its results aren't computed with @@ -190,7 +192,7 @@ struct OutOfScopePrecomputer<'a, 'tcx> { impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> { fn compute( body: &Body<'tcx>, - scc_values: &InferredRegions<'tcx>, + scc_values: &InferredRegions<'_>, borrow_set: &BorrowSet<'tcx>, ) -> FxIndexMap> { let mut prec = OutOfScopePrecomputer { @@ -210,7 +212,7 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> { fn precompute_borrows_out_of_scope( &mut self, - scc_values: &InferredRegions<'tcx>, + scc_values: &InferredRegions<'_>, borrow_index: BorrowIndex, borrow_region: RegionVid, first_location: Location, @@ -283,7 +285,7 @@ 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>, - scc_values: &InferredRegions<'tcx>, + scc_values: &InferredRegions<'_>, borrow_set: &BorrowSet<'tcx>, ) -> FxIndexMap> { OutOfScopePrecomputer::compute(body, scc_values, borrow_set) @@ -293,14 +295,16 @@ struct PoloniusOutOfScopePrecomputer<'a, 'tcx> { visited: DenseBitSet, visit_stack: Vec, body: &'a Body<'tcx>, - liveness_values: &'a LivenessValues, + live_loans: &'a LiveLoans, + location_map: &'a DenseLocationMap, loans_out_of_scope_at_location: FxIndexMap>, } impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> { fn compute( body: &Body<'tcx>, - liveness_values: &LivenessValues, + polonius_diagnostics: &PoloniusDiagnosticsContext, + location_map: &DenseLocationMap, borrow_set: &BorrowSet<'tcx>, ) -> FxIndexMap> { // The in-tree polonius analysis computes loans going out of scope using the @@ -309,7 +313,8 @@ impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> { visited: DenseBitSet::new_empty(body.basic_blocks.len()), visit_stack: vec![], body, - liveness_values, + 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() { @@ -409,8 +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. - let point = self.liveness_values.point_from_location(location); - if self.liveness_values.is_loan_live_at(loan_idx, point) { + let point = self.location_map.point_from_location(location); + if self.is_loan_live_at(loan_idx, point) { continue; } @@ -421,22 +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>, - liveness_values: &'a LivenessValues, - scc_values: &InferredRegions<'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, scc_values, borrow_set) - } else { - PoloniusOutOfScopePrecomputer::compute(body, liveness_values, 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/find_use.rs b/compiler/rustc_borrowck/src/diagnostics/find_use.rs index 8ab79fbffd406..f5c31f38fa679 100644 --- a/compiler/rustc_borrowck/src/diagnostics/find_use.rs +++ b/compiler/rustc_borrowck/src/diagnostics/find_use.rs @@ -10,7 +10,7 @@ use crate::region_infer::{Cause, InferredRegions}; pub(crate) fn find<'tcx>( body: &Body<'tcx>, - scc_values: &InferredRegions<'tcx>, + scc_values: &InferredRegions<'_>, tcx: TyCtxt<'tcx>, region_vid: RegionVid, start_point: Location, @@ -20,15 +20,15 @@ pub(crate) fn find<'tcx>( uf.find() } -struct UseFinder<'a, 'tcx> { +struct UseFinder<'a, 'b, 'tcx> { body: &'a Body<'tcx>, - scc_values: &'a InferredRegions<'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(); diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs index 68bab2763e20c..d54eb6fff8550 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs @@ -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; } } } diff --git a/compiler/rustc_borrowck/src/handle_placeholders.rs b/compiler/rustc_borrowck/src/handle_placeholders.rs index 2a7dc8ba10162..1dc5e32dc5275 100644 --- a/compiler/rustc_borrowck/src/handle_placeholders.rs +++ b/compiler/rustc_borrowck/src/handle_placeholders.rs @@ -41,6 +41,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 +176,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 +185,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() { @@ -237,17 +244,19 @@ pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>( 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 liveness_constraints, mut outlives_constraints, universe_causes, type_tests, } = constraints; + let (definitions, has_placeholders) = + region_definitions(infcx, universal_regions, &mut liveness_constraints); + let fr_static = universal_regions.fr_static; let compute_sccs = |constraints: &OutlivesConstraintSet<'tcx>, diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 8ae17e9328be9..550af6181d432 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; @@ -73,7 +74,7 @@ use crate::polonius::{PoloniusContext, PoloniusDiagnosticsContext}; use crate::prefixes::PrefixSet; use crate::region_infer::opaque_types::DeferredOpaqueTypeError; use crate::region_infer::values::LivenessValues; -use crate::region_infer::{InferredRegions, RegionDefinition, RegionInferenceContext}; +use crate::region_infer::{InferredRegions, RegionDefinition}; use crate::renumber::RegionCtxt; use crate::session_diagnostics::VarNeedNotMut; use crate::type_check::free_region_relations::UniversalRegionRelations; @@ -297,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>, @@ -376,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, @@ -416,6 +417,8 @@ 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 { @@ -436,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, @@ -574,8 +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, &liveness_constraints, &scc_values); + 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), @@ -639,12 +652,13 @@ fn get_flow_results<'a, 'tcx>( body: &'a Body<'tcx>, move_data: &'a MoveData<'tcx>, borrow_set: &'a BorrowSet<'tcx>, - liveness_values: &'a LivenessValues, - scc_values: &InferredRegions<'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, liveness_values, scc_values, borrow_set) + 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, diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index f4e42084f9c21..311fe2604024c 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -96,9 +96,10 @@ 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(amandasystems) FIX THIS!!!!! // 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 LoweredConstraints { @@ -116,7 +117,7 @@ pub(crate) fn compute_closure_requirements_modulo_opaques<'tcx>( infcx, ); - let regioncx = RegionInferenceContext::new( + let regioncx = region_infer::RegionInferenceContext::new( &infcx, constraint_sccs, &definitions, @@ -143,7 +144,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, 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 0ef1b29331d6d..96e3814ec6323 100644 --- a/compiler/rustc_borrowck/src/polonius/mod.rs +++ b/compiler/rustc_borrowck/src/polonius/mod.rs @@ -64,8 +64,9 @@ 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::dataflow::BorrowIndex; -use crate::{BorrowSet, RegionInferenceContext}; +use crate::region_infer::RegionInferenceContext; pub(crate) type LiveLoans = SparseBitMatrix; @@ -97,12 +98,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 @@ -165,7 +168,7 @@ impl PoloniusContext { tcx, body, regioncx.liveness_constraints(), - regioncx.outlives_constraints(), + regioncx.constraints, regioncx.universal_regions(), &mut localized_outlives_constraints, ); @@ -183,12 +186,11 @@ impl PoloniusContext { // 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(), + regioncx.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 0437995ae2b8f..dd2a9ebdcbd21 100644 --- a/compiler/rustc_borrowck/src/region_infer/dump_mir.rs +++ b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs @@ -5,14 +5,14 @@ use std::io::{self, Write}; -use rustc_index::IndexVec; use rustc_infer::infer::NllRegionVariableOrigin; use rustc_middle::ty::{RegionVid, TyCtxt}; use super::OutlivesConstraint; use crate::constraints::OutlivesConstraintSet; +use crate::handle_placeholders::RegionDefinitions; +use crate::region_infer::InferredRegions; use crate::region_infer::values::LivenessValues; -use crate::region_infer::{InferredRegions, RegionDefinition}; use crate::type_check::Locations; use crate::type_check::free_region_relations::UniversalRegionRelations; use crate::universal_regions::UniversalRegions; @@ -24,7 +24,7 @@ const REGION_WIDTH: usize = 8; pub(crate) struct MirDumper<'a, 'tcx> { pub(crate) tcx: TyCtxt<'tcx>, - pub(crate) definitions: &'a IndexVec>, + 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, diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index adcfad48a9966..b28aa57576bde 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -1,10 +1,8 @@ -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_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}; @@ -23,11 +21,10 @@ 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}; -use crate::handle_placeholders::RegionTracker; -use crate::polonius::LiveLoans; +use crate::handle_placeholders::{RegionDefinitions, RegionTracker}; use crate::polonius::legacy::PoloniusOutput; +use crate::region_infer::universal_regions::UniversalRegionChecker; use crate::region_infer::values::{ LivenessValues, PlaceholderIndices, RegionElement, RegionValues, ToElementIndex, }; @@ -46,6 +43,9 @@ 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 @@ -82,6 +82,7 @@ pub struct InferredRegions<'tcx> { pub(crate) scc_values: RegionValues<'tcx, ConstraintSccIndex>, pub(crate) sccs: ConstraintSccs, annotations: IndexVec, + universal_region_relations: Rc>>, } impl<'tcx> InferredRegions<'tcx> { @@ -152,249 +153,134 @@ impl<'tcx> InferredRegions<'tcx> { fn max_nameable_universe(&self, vid: RegionVid) -> UniverseIndex { self.annotations[self.scc(vid)].max_nameable_universe() } -} - -pub 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. - pub(crate) definitions: &'a 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: &'a mut LivenessValues, - - /// The outlives constraints computed by the type-check. - constraints: &'a OutlivesConstraintSet<'tcx>, - - /// The constraint-set, but in graph form, making it easy to traverse - /// the constraints adjacent to a particular region. Used to construct - /// the SCC (see `constraint_sccs`) and for error reporting. - constraint_graph: Frozen, - - /// 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, - - /// Type constraints that we check after solving. - type_tests: Vec>, - - /// Information about how the universally quantified regions in - /// scope on this function relate to one another. - universal_region_relations: &'a Frozen>, -} - -#[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>, - - /// Which universe is this region variable defined in? This is - /// most often `ty::UniverseIndex::ROOT`, but when we encounter - /// forall-quantifiers like `for<'a> { 'a = 'b }`, we would create - /// the variable for `'a` in a fresh universe that extends ROOT. - pub(crate) universe: ty::UniverseIndex, - - /// If this is 'static or an early-bound region, then this is - /// `Some(X)` where `X` is the name of the region. - pub(crate) external_name: Option>, -} - -/// N.B., the variants in `Cause` are intentionally ordered. Lower -/// values are preferred when it comes to error messages. Do not -/// reorder willy nilly. -#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] -pub(crate) enum Cause { - /// point inserted because Local was live at the given Location - LiveVar(Local, Location), - - /// point inserted because Local was dropped at the given Location - DropVar(Local, Location), -} - -/// A "type test" corresponds to an outlives constraint between a type -/// and a lifetime, like `T: 'x` or `::Bar: 'x`. They are -/// translated from the `Verify` region constraints in the ordinary -/// inference context. -/// -/// These sorts of constraints are handled differently than ordinary -/// constraints, at least at present. During type checking, the -/// `InferCtxt::process_registered_region_obligations` method will -/// attempt to convert a type test like `T: 'x` into an ordinary -/// outlives constraint when possible (for example, `&'a T: 'b` will -/// be converted into `'a: 'b` and registered as a `Constraint`). -/// -/// In some cases, however, there are outlives relationships that are -/// not converted into a region constraint, but rather into one of -/// these "type tests". The distinction is that a type test does not -/// influence the inference result, but instead just examines the -/// values that we ultimately inferred for each region variable and -/// checks that they meet certain extra criteria. If not, an error -/// can be issued. -/// -/// One reason for this is that these type tests typically boil down -/// to a check like `'a: 'x` where `'a` is a universally quantified -/// region -- and therefore not one whose value is really meant to be -/// *inferred*, precisely (this is not always the case: one can have a -/// type test like `>::Bar: 'x`, where `'?0` is an -/// inference variable). Another reason is that these type tests can -/// involve *disjunction* -- that is, they can be satisfied in more -/// than one way. -/// -/// For more information about this translation, see -/// `InferCtxt::process_registered_region_obligations` and -/// `InferCtxt::type_must_outlive` in `rustc_infer::infer::InferCtxt`. -#[derive(Clone, Debug)] -pub(crate) struct TypeTest<'tcx> { - /// The type `T` that must outlive the region. - pub generic_kind: GenericKind<'tcx>, - - /// The region `'x` that the type must outlive. - pub lower_bound: RegionVid, - /// The span to blame. - pub span: Span, + /// 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) + } - /// A test which, if met by the region `'x`, proves that this type - /// constraint is satisfied. - pub verify_bound: VerifyBound<'tcx>, -} + /// 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); -/// 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 sub_region_scc == sup_region_scc { + debug!("{sup_region:?}: {sub_region:?} holds trivially; they are in the same SCC"); + return true; + } -#[derive(Clone, PartialEq, Eq, Debug)] -enum Trace<'a, 'tcx> { - StartRegion, - FromGraph(&'a OutlivesConstraint<'tcx>), - FromStatic(RegionVid), - NotVisited, -} + let fr_static = self.universal_region_relations.universal_regions.fr_static; -#[instrument(skip(infcx, sccs), level = "debug")] -fn sccs_info<'tcx>(infcx: &BorrowckInferCtxt<'tcx>, sccs: &ConstraintSccs) { - use crate::renumber::RegionCtxt; + // 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", + ); - let var_to_origin = infcx.reg_var_to_origin.borrow(); + return self.eval_outlives(sup_region, fr_static); + } - let mut var_to_origin_sorted = var_to_origin.clone().into_iter().collect::>(); - var_to_origin_sorted.sort_by_key(|vto| vto.0); + // 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)) + }); - 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 !universal_outlives { + debug!("sub region contains a universal region not present in super"); + return false; } - debug!("{}", reg_vars_to_origins_str); - } - let num_components = sccs.num_sccs(); - let mut components = vec![FxIndexSet::default(); num_components]; - - 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)); - } + // 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 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, - )) + 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!("{}", 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::>(); + debug!("comparison between points in sup/sub"); - 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); + self.scc_values.contains_points(sup_region_scc, sub_region_scc) } - debug!("SCC edges {:#?}", scc_node_to_edges); -} - -impl<'a, 'tcx> RegionInferenceContext<'a, '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>, - constraint_sccs: Sccs, - definitions: &'a Frozen>>, - scc_annotations: IndexVec, - outlives_constraints: &'a OutlivesConstraintSet<'tcx>, - type_tests: Vec>, - liveness_constraints: &'a mut LivenessValues, - universal_region_relations: &'a Frozen>, - ) -> Self { - debug!("universal_regions: {:#?}", universal_region_relations.universal_regions); - debug!("outlives constraints: {:#?}", outlives_constraints); - debug!("type tests: {:#?}", type_tests); - - let constraint_graph = Frozen::freeze(outlives_constraints.graph(definitions.len())); - - if cfg!(debug_assertions) { - sccs_info(infcx, &constraint_sccs); - } - for variable in definitions.indices() { - if let NllRegionVariableOrigin::FreeRegion = definitions[variable].origin { - // Add all nodes in the CFG to liveness constraints - liveness_constraints.add_all_points(variable); + 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, } + } - Self { - definitions, - liveness_constraints, - constraints: outlives_constraints, - constraint_graph, - constraint_sccs, - scc_annotations, - type_tests, - universal_region_relations, - } + /// 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| { + ty::Region::new_var(tcx, self.to_representative(self.to_region_vid(r))) + }) } /// Given a universal region in scope on the MIR, returns the @@ -403,175 +289,59 @@ impl<'a, 'tcx> RegionInferenceContext<'a, '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() - } - - /// 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, location_map, placeholder_indices), - level = "debug" - )] - pub(super) fn solve( - self, - infcx: &InferCtxt<'tcx>, - body: &Body<'tcx>, - polonius_output: Option>, - location_map: Rc, - placeholder_indices: PlaceholderIndices<'tcx>, - ) -> (Option>, RegionErrors<'tcx>, InferredRegions<'tcx>) { - let mir_def_id = body.source.def_id(); - let scc_values = self.compute_region_values(location_map, placeholder_indices); - - 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( - &scc_values, - 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( - &scc_values, - outlives_requirements.as_mut(), - &mut errors_buffer, - polonius_output - .as_ref() - .expect("Polonius output is unavailable despite `-Z polonius`"), - ); - } else { - self.check_universal_regions( - &scc_values, - outlives_requirements.as_mut(), - &mut errors_buffer, - ); - } - - debug!(?errors_buffer); - - let outlives_requirements = outlives_requirements.unwrap_or_default(); - let scc_values = InferredRegions { - scc_values, - sccs: self.constraint_sccs, - annotations: self.scc_annotations, - }; - - if outlives_requirements.is_empty() { - (None, errors_buffer, scc_values) - } else { - let num_external_vids = - self.universal_region_relations.universal_regions.num_global_and_external_regions(); - ( - Some(ClosureRegionRequirements { num_external_vids, outlives_requirements }), - errors_buffer, - scc_values, - ) - } + fn universal_regions(&self) -> &UniversalRegions<'tcx> { + &self.universal_region_relations.universal_regions } - /// 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( + /// Tests if `test` is true when applied to `lower_bound` at + /// `point`. + fn eval_verify_bound( &self, - location_map: Rc, - placeholder_indices: PlaceholderIndices<'tcx>, - ) -> RegionValues<'tcx, ConstraintSccIndex> { - 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::>() - }); - - let mut scc_values = - RegionValues::new(location_map, self.universal_regions().len(), placeholder_indices); - - for region in self.liveness_constraints.regions() { - scc_values.merge_liveness(self.scc(region), region, &self.liveness_constraints); - } - - 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)); - - // Add `end(X)` into the set for X. - scc_values.add_element(self.scc(variable), variable); - } + 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); - NllRegionVariableOrigin::Placeholder(placeholder) => { - scc_values.add_element(self.scc(variable), placeholder); - } + match verify_bound { + VerifyBound::IfEq(verify_if_eq_b) => { + self.eval_if_eq(infcx, generic_ty, lower_bound, *verify_if_eq_b) + } - NllRegionVariableOrigin::Existential { .. } => { - // For existential, regions, nothing to do. - } + VerifyBound::IsEmpty => { + self.scc_values.elements_contained_in(self.scc(lower_bound)).next().is_none() } - } - // 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); + VerifyBound::OutlivedBy(r) => { + let r_vid = self.to_region_vid(*r); + self.eval_outlives(r_vid, lower_bound) } - } - scc_values - } - /// 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]) + VerifyBound::AnyBound(verify_bounds) => verify_bounds.iter().any(|verify_bound| { + self.eval_verify_bound(infcx, generic_ty, lower_bound, verify_bound) + }), + + VerifyBound::AllBounds(verify_bounds) => verify_bounds.iter().all(|verify_bound| { + self.eval_verify_bound(infcx, generic_ty, lower_bound, verify_bound) + }), + } } - /// 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, - scc_values: &RegionValues<'tcx, ConstraintSccIndex>, infcx: &InferCtxt<'tcx>, mut propagated_outlives_requirements: Option<&mut Vec>>, errors_buffer: &mut RegionErrors<'tcx>, + type_tests: Vec>, ) { let tcx = infcx.tcx; @@ -580,12 +350,11 @@ impl<'a, 'tcx> RegionInferenceContext<'a, '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); if self.eval_verify_bound( - scc_values, infcx, generic_ty, type_test.lower_bound, @@ -595,12 +364,7 @@ impl<'a, 'tcx> RegionInferenceContext<'a, 'tcx> { } if let Some(propagated_outlives_requirements) = &mut propagated_outlives_requirements - && self.try_promote_type_test( - scc_values, - infcx, - type_test, - propagated_outlives_requirements, - ) + && self.try_promote_type_test(infcx, &type_test, propagated_outlives_requirements) { continue; } @@ -650,10 +414,9 @@ impl<'a, 'tcx> RegionInferenceContext<'a, 'tcx> { /// The idea then is to lower the `T: 'X` constraint into multiple /// bounds -- e.g., if `'X` is the union of two free lifetimes, /// `'1` and `'2`, then we would create `T: '1` and `T: '2`. - #[instrument(level = "debug", skip(self, infcx, propagated_outlives_requirements, scc_values))] + #[instrument(level = "debug", skip(self, infcx, propagated_outlives_requirements))] fn try_promote_type_test( &self, - scc_values: &RegionValues<'tcx, ConstraintSccIndex>, infcx: &InferCtxt<'tcx>, type_test: &TypeTest<'tcx>, propagated_outlives_requirements: &mut Vec>, @@ -662,17 +425,15 @@ impl<'a, 'tcx> RegionInferenceContext<'a, 'tcx> { let TypeTest { generic_kind, lower_bound, span: blame_span, verify_bound: _ } = *type_test; let generic_ty = generic_kind.to_ty(tcx); - let Some(subject) = self.try_promote_type_test_subject(scc_values, infcx, generic_ty) - else { + let Some(subject) = self.try_promote_type_test_subject(infcx, generic_ty) else { 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 @@ -680,7 +441,7 @@ impl<'a, 'tcx> RegionInferenceContext<'a, 'tcx> { // // It doesn't matter *what* universe because the promoted `T` will // always be in the root universe. - if let Some(p) = 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 { @@ -690,1177 +451,450 @@ impl<'a, 'tcx> RegionInferenceContext<'a, 'tcx> { category: ConstraintCategory::Boring, }); - // we can return here -- the code below might push add'l constraints - // but they would all be weaker than this one. - return true; - } - - // For each region outlived by lower_bound find a non-local, - // universal region (it may be the same region) and add it to - // `ClosureOutlivesRequirement`. - let mut found_outlived_universal_region = false; - for ur in scc_values.universal_regions_outlived_by(r_scc) { - 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); - debug!(?non_local_ub); - - // This is slightly too conservative. To show T: '1, given `'2: '1` - // and `'3: '1` we only need to prove that T: '2 *or* T: '3, but to - // avoid potential non-determinism we approximate this by requiring - // T: '1 and T: '2. - for upper_bound in non_local_ub { - debug_assert!(self.universal_regions().is_universal_region(upper_bound)); - debug_assert!(!self.universal_regions().is_local_free_region(upper_bound)); - - let requirement = ClosureOutlivesRequirement { - subject, - outlived_free_region: upper_bound, - blame_span, - category: ConstraintCategory::Boring, - }; - debug!(?requirement, "adding closure requirement"); - propagated_outlives_requirements.push(requirement); - } - } - // If we succeed to promote the subject, i.e. it only contains non-local regions, - // and fail to prove the type test inside of the closure, the `lower_bound` has to - // also be at least as large as some universal region, as the type test is otherwise - // trivial. - assert!(found_outlived_universal_region); - true - } - - /// When we promote a type test `T: 'r`, we have to replace all region - /// variables in the type `T` with an equal universal region from the - /// closure signature. - /// This is not always possible, so this is a fallible process. - #[instrument(level = "debug", skip(self, infcx, scc_values), ret)] - fn try_promote_type_test_subject( - &self, - scc_values: &RegionValues<'tcx, ConstraintSccIndex>, - infcx: &InferCtxt<'tcx>, - ty: Ty<'tcx>, - ) -> Option> { - let tcx = infcx.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. - scc_values - .universal_regions_outlived_by(r_scc) - .filter(|&u_r| !self.universal_regions().is_local_free_region(u_r)) - .find(|&u_r| self.eval_equal(scc_values, u_r, r_vid)) - .map(|u_r| ty::Region::new_var(tcx, u_r)) - // In case we could not find a named region to map to, - // we will return `None` below. - .unwrap_or_else(|| { - failed = true; - r - }) - }); - - debug!("try_promote_type_test_subject: folded ty = {:?}", ty); - - // This will be true if we failed to promote some region. - if failed { - return None; - } - - Some(ClosureOutlivesSubject::Ty(ClosureOutlivesSubjectTy::bind(tcx, ty))) - } - - /// Tests if `test` is true when applied to `lower_bound` at - /// `point`. - fn eval_verify_bound( - &self, - scc_values: &RegionValues<'tcx, ConstraintSccIndex>, - 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); - - match verify_bound { - VerifyBound::IfEq(verify_if_eq_b) => { - self.eval_if_eq(scc_values, infcx, generic_ty, lower_bound, *verify_if_eq_b) - } - - VerifyBound::IsEmpty => { - let lower_bound_scc = self.constraint_sccs.scc(lower_bound); - scc_values.elements_contained_in(lower_bound_scc).next().is_none() - } - - VerifyBound::OutlivedBy(r) => { - let r_vid = self.to_region_vid(*r); - self.eval_outlives(scc_values, r_vid, lower_bound) - } - - VerifyBound::AnyBound(verify_bounds) => verify_bounds.iter().any(|verify_bound| { - self.eval_verify_bound(scc_values, infcx, generic_ty, lower_bound, verify_bound) - }), - - VerifyBound::AllBounds(verify_bounds) => verify_bounds.iter().all(|verify_bound| { - self.eval_verify_bound(scc_values, infcx, generic_ty, lower_bound, verify_bound) - }), - } - } - - fn eval_if_eq( - &self, - scc_values: &RegionValues<'tcx, ConstraintSccIndex>, - 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(scc_values, r_vid, lower_bound) - } - None => false, - } - } - - /// This is a conservative normalization procedure. It takes every - /// free region in `value` and replaces it with the - /// "representative" of its SCC (see `scc_representatives` field). - /// We are guaranteed that if two values normalize to the same - /// thing, then they are equal; this is a conservative check in - /// that they could still be equal even if they normalize to - /// different results. (For example, there might be two regions - /// with the same value that are not in the same SCC). - /// - /// N.B., this is not an ideal approach and I would like to revisit - /// it. However, it works pretty well in practice. In particular, - /// this is needed to deal with projection outlives bounds like - /// - /// ```text - /// >::Item: '1 - /// ``` - /// - /// In particular, this routine winds up being important when - /// there are bounds like `where >::Item: 'b` in the - /// environment. In this case, if we can show that `'0 == 'a`, - /// and that `'b: '1`, then we know that the clause is - /// satisfied. In such cases, particularly due to limitations of - /// the trait solver =), we usually wind up with a where-clause like - /// `T: Foo<'a>` in scope, which thus forces `'0 == 'a` to be added as - /// a constraint, and thus ensures that they are in the same SCC. - /// - /// So why can't we do a more correct routine? Well, we could - /// *almost* use the `relate_tys` code, but the way it is - /// currently setup it creates inference variables to deal with - /// higher-ranked things and so forth, and right now the inference - /// context is not permitted to make more inference variables. So - /// we use this kind of hacky solution. - fn normalize_to_scc_representatives(&self, tcx: TyCtxt<'tcx>, value: T) -> T - where - T: TypeFoldable>, - { - 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) - }) - } - - /// 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, - scc_values: &RegionValues<'tcx, ConstraintSccIndex>, - r1: RegionVid, - r2: RegionVid, - ) -> bool { - self.eval_outlives(scc_values, r1, r2) && self.eval_outlives(scc_values, r2, r1) - } - - /// 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, scc_values), level = "debug", ret)] - pub fn eval_outlives( - &self, - scc_values: &RegionValues<'tcx, ConstraintSccIndex>, - sup_region: RegionVid, - sub_region: RegionVid, - ) -> bool { - let sub_region_scc = self.constraint_sccs.scc(sub_region); - let sup_region_scc = self.constraint_sccs.scc(sup_region); - - if sub_region_scc == sup_region_scc { - debug!("{sup_region:?}: {sub_region:?} holds trivially; they are in the same SCC"); - return true; - } - - let fr_static = self.universal_regions().fr_static; - - // 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", - ); - - return self.eval_outlives(scc_values, sup_region, fr_static); - } - - // Both the `sub_region` and `sup_region` consist of the union - // of some number of universal regions (along with the union - // of various points in the CFG; ignore those points for - // now). Therefore, the sup-region outlives the sub-region if, - // for each universal region R1 in the sub-region, there - // exists some region R2 in the sup-region that outlives R1. - let universal_outlives = - scc_values.universal_regions_outlived_by(sub_region_scc).all(|r1| { - scc_values - .universal_regions_outlived_by(sup_region_scc) - .any(|r2| self.universal_region_relations.outlives(r2, r1)) - }); - - if !universal_outlives { - debug!("sub region contains a universal region not present in super"); - return false; - } - - // Now we have to compare all the points in the sub region and make - // sure they exist in the sup region. - - if self.universal_regions().is_universal_region(sup_region) { - // Micro-opt: universal regions contain all points. - debug!("super is universal and hence contains all points"); + // we can return here -- the code below might push add'l constraints + // but they would all be weaker than this one. return true; } - debug!("comparison between points in sup/sub"); + // For each region outlived by lower_bound find a non-local, + // universal region (it may be the same region) and add it to + // `ClosureOutlivesRequirement`. + let mut found_outlived_universal_region = false; + 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); + debug!(?non_local_ub); + + // This is slightly too conservative. To show T: '1, given `'2: '1` + // and `'3: '1` we only need to prove that T: '2 *or* T: '3, but to + // avoid potential non-determinism we approximate this by requiring + // T: '1 and T: '2. + for upper_bound in non_local_ub { + debug_assert!(self.universal_regions().is_universal_region(upper_bound)); + debug_assert!(!self.universal_regions().is_local_free_region(upper_bound)); - scc_values.contains_points(sup_region_scc, sub_region_scc) + let requirement = ClosureOutlivesRequirement { + subject, + outlived_free_region: upper_bound, + blame_span, + category: ConstraintCategory::Boring, + }; + debug!(?requirement, "adding closure requirement"); + propagated_outlives_requirements.push(requirement); + } + } + // If we succeed to promote the subject, i.e. it only contains non-local regions, + // and fail to prove the type test inside of the closure, the `lower_bound` has to + // also be at least as large as some universal region, as the type test is otherwise + // trivial. + assert!(found_outlived_universal_region); + true } - /// 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( + /// When we promote a type test `T: 'r`, we have to replace all region + /// variables in the type `T` with an equal universal region from the + /// closure signature. + /// This is not always possible, so this is a fallible process. + #[instrument(level = "debug", skip(self, infcx), ret)] + fn try_promote_type_test_subject( &self, - scc_values: &RegionValues<'tcx, ConstraintSccIndex>, - 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( - scc_values, - fr, - &mut propagated_outlives_requirements, - errors_buffer, - ); - } + infcx: &InferCtxt<'tcx>, + ty: Ty<'tcx>, + ) -> Option> { + let tcx = infcx.tcx; + let mut failed = false; + let ty = fold_regions(tcx, ty, |r, _depth| { + let r_vid = self.to_region_vid(r); - NllRegionVariableOrigin::Placeholder(placeholder) => { - self.check_bound_universal_region(scc_values, fr, placeholder, errors_buffer); - } + // 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.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)) + // In case we could not find a named region to map to, + // we will return `None` below. + .unwrap_or_else(|| { + failed = true; + r + }) + }); - NllRegionVariableOrigin::Existential { .. } => { - // nothing to check here - } - } + debug!("try_promote_type_test_subject: folded ty = {:?}", ty); + + // This will be true if we failed to promote some region. + if failed { + return None; } + + Some(ClosureOutlivesSubject::Ty(ClosureOutlivesSubjectTy::bind(tcx, ty))) } - /// 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. + /// Returns the representative `RegionVid` for a given region's SCC. + /// See `RegionTracker` for how a region variable ID is chosen. /// - /// 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, - scc_values: &RegionValues<'tcx, ConstraintSccIndex>, - 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() - ); + /// 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() + } +} - // 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(); +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>, - 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 - ); + /// 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, - let propagated = self.try_propagate_universal_region_error( - scc_values, - 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, - }); - } - } + /// The outlives constraints computed by the type-check. + pub(crate) constraints: &'a OutlivesConstraintSet<'tcx>, - // 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 - } + /// 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, - NllRegionVariableOrigin::Placeholder(placeholder) => { - self.check_bound_universal_region(scc_values, fr, placeholder, errors_buffer); - } + scc_annotations: IndexVec, - NllRegionVariableOrigin::Existential { .. } => { - // nothing to check here - } - } - } - } + /// Type constraints that we check after solving. + type_tests: Vec>, - /// 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() - } + /// Information about how the universally quantified regions in + /// scope on this function relate to one another. + universal_region_relations: Rc>>, +} - /// 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, scc_values), - level = "debug" - )] - fn check_universal_region( - &self, - scc_values: &RegionValues<'tcx, ConstraintSccIndex>, - 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( - scc_values, - longer_fr, - representative, - propagated_outlives_requirements, - ) { - errors_buffer.push(RegionErrorKind::RegionError { - longer_fr, - shorter_fr: representative, - fr_origin: NllRegionVariableOrigin::FreeRegion, - is_reported: true, - }); - } - return; - } +#[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>, - // Find every region `o` such that `fr: o` - // (because `fr` includes `end(o)`). - let mut error_reported = false; - for shorter_fr in scc_values.universal_regions_outlived_by(longer_fr_scc) { - if let RegionRelationCheckResult::Error = self.check_universal_region_relation( - scc_values, - 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; - } - } - } + /// 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, - /// 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, - scc_values: &RegionValues<'tcx, ConstraintSccIndex>, - 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( - scc_values, - longer_fr, - shorter_fr, - propagated_outlives_requirements, - ) - } - } + /// 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>, +} - /// 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, - scc_values: &RegionValues<'tcx, ConstraintSccIndex>, - 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; - } +/// 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 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.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)); - } - } + /// point inserted because Local was dropped at the given Location + DropVar(Local, Location), +} - // 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(scc_values, 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 - ); +/// 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>, - assert!( - !propagated_constraints.is_empty(), - "Expected at least one constraint to propagate here" - ); + /// The region `'x` that the type must outlive. + pub lower_bound: RegionVid, - 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; - } + /// The span to blame. + pub span: Span, - RegionRelationCheckResult::Error - } + /// A test which, if met by the region `'x`, proves that this type + /// constraint is satisfied. + pub verify_bound: VerifyBound<'tcx>, +} - fn check_bound_universal_region( - &self, - scc_values: &RegionValues<'tcx, ConstraintSccIndex>, - longer_fr: RegionVid, - placeholder: ty::PlaceholderRegion<'tcx>, - errors_buffer: &mut RegionErrors<'tcx>, - ) { - debug!("check_bound_universal_region(fr={:?}, placeholder={:?})", longer_fr, placeholder,); +#[instrument(skip(infcx, sccs), level = "debug")] +fn sccs_info<'tcx>(infcx: &BorrowckInferCtxt<'tcx>, sccs: &ConstraintSccs) { + use crate::renumber::RegionCtxt; + + let var_to_origin = infcx.reg_var_to_origin.borrow(); + + let mut var_to_origin_sorted = var_to_origin.clone().into_iter().collect::>(); + var_to_origin_sorted.sort_by_key(|vto| vto.0); + + 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!("{}", reg_vars_to_origins_str); + } - let longer_fr_scc = self.constraint_sccs.scc(longer_fr); - debug!("check_bound_universal_region: longer_fr_scc={:?}", longer_fr_scc,); + let num_components = sccs.num_sccs(); + let mut components = vec![FxIndexSet::default(); num_components]; - // 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) = 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"); - } + 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)); } - fn constraint_search(&'a self) -> ConstraintSearch<'a, 'tcx> { - ConstraintSearch { - definitions: &self.definitions, - fr_static: self.universal_regions().fr_static, - constraint_graph: &self.constraint_graph, - constraints: &self.constraints, + 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); } - fn constraint_path_to( - &self, - from_region: RegionVid, - to_region: RegionVid, - follow_outlives_static: bool, - ) -> Option>> { - self.constraint_search() - .constraint_path_to(from_region, |t| t == to_region, follow_outlives_static) - .map(|o| o.0) - } + // 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(); - /// Get the region definition of `r`. - pub(crate) fn region_definition(&self, r: RegionVid) -> &RegionDefinition<'tcx> { - &self.definitions[r] - } + (ConstraintSccIndex::from_usize(scc_idx), repr) + }) + .collect::>(); - pub(crate) fn universal_regions(&self) -> &UniversalRegions<'tcx> { - &self.universal_region_relations.universal_regions + 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); } - /// 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 - } + debug!("SCC edges {:#?}", scc_node_to_edges); +} - /// Returns the representative `RegionVid` for a given SCC. - /// See `RegionTracker` for how a region variable ID is chosen. +impl<'a, 'tcx> RegionInferenceContext<'a, '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`. /// - /// 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() - } + /// 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>, + constraint_sccs: Sccs, + definitions: &'a RegionDefinitions<'tcx>, + scc_annotations: IndexVec, + outlives_constraints: &'a OutlivesConstraintSet<'tcx>, + type_tests: Vec>, + liveness_constraints: &'a mut LivenessValues, + universal_region_relations: Rc>>, + ) -> Self { + debug!("universal_regions: {:#?}", universal_region_relations.universal_regions); + debug!("outlives constraints: {:#?}", outlives_constraints); + debug!("type tests: {:#?}", type_tests); - pub(crate) fn liveness_constraints(&self) -> &LivenessValues { - &self.liveness_constraints - } + if cfg!(debug_assertions) { + sccs_info(infcx, &constraint_sccs); + } - /// 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); + Self { + definitions, + liveness_constraints, + constraints: outlives_constraints, + constraint_sccs, + scc_annotations, + type_tests, + universal_region_relations, + } } - /// 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) - } + /// 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, location_map, placeholder_indices), + level = "debug" + )] + pub(super) fn solve( + self, + infcx: &InferCtxt<'tcx>, + body: &Body<'tcx>, + polonius_output: Option>, + location_map: Rc, + placeholder_indices: PlaceholderIndices<'tcx>, + ) -> (Option>, RegionErrors<'tcx>, InferredRegions<'tcx>) { + let mir_def_id = body.source.def_id(); + let scc_values = InferredRegions { + scc_values: self.compute_region_values(location_map, placeholder_indices), + sccs: self.constraint_sccs, + annotations: self.scc_annotations, + universal_region_relations: Rc::clone(&self.universal_region_relations), + }; - pub(crate) fn scc(&self, r: RegionVid) -> ConstraintSccIndex { - self.constraint_sccs.scc(r) - } -} + let mut errors_buffer = RegionErrors::new(infcx.tcx); -#[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>, -} + // 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); -pub(crate) struct ConstraintSearch<'a, 'tcx> { - pub(crate) definitions: &'a IndexVec>, - pub(crate) fr_static: RegionVid, - pub(crate) constraint_graph: &'a NormalConstraintGraph, - pub(crate) constraints: &'a OutlivesConstraintSet<'tcx>, -} + scc_values.check_type_tests( + infcx, + outlives_requirements.as_mut(), + &mut errors_buffer, + self.type_tests, + ); -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: &IndexVec>, - ) -> 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(), - } - } + debug!(?errors_buffer); + debug!(?outlives_requirements); - /// 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, + UniversalRegionChecker::new( + &mut errors_buffer, + self.definitions, + self.constraints, + &scc_values, ) - .unwrap() - .1 - } + .check(polonius_output, outlives_requirements.as_mut()); - /// 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, - ) - }) - } + debug!(?errors_buffer); - 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!"); + let outlives_requirements = outlives_requirements.unwrap_or_default(); + + if outlives_requirements.is_empty() { + (None, errors_buffer, scc_values) + } else { + let num_external_vids = + self.universal_region_relations.universal_regions.num_global_and_external_regions(); + ( + Some(ClosureRegionRequirements { num_external_vids, outlives_requirements }), + errors_buffer, + scc_values, + ) } - 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( + /// 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, - 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) - } - } - } - } + 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::>() + }); + + let mut scc_values = + RegionValues::new(location_map, self.universal_regions().len(), placeholder_indices); - // Otherwise, walk over the outgoing constraints and - // enqueue any regions we find, keeping track of how we - // reached them. + for region in self.liveness_constraints.regions() { + scc_values.merge_liveness(self.scc(region), region, &self.liveness_constraints); + } - // A constraint like `'r: 'x` can come from our constraint - // graph. + 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)); - // 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); + // Add `end(X)` into the set for X. + scc_values.add_element(self.scc(variable), variable); } - }; - // 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)); + NllRegionVariableOrigin::Placeholder(placeholder) => { + scc_values.add_element(self.scc(variable), placeholder); } - } 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)); + + NllRegionVariableOrigin::Existential { .. } => { + // For existential, regions, nothing to do. } } } - 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 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); } - }; - - // 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.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 - } - // 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); + } + scc_values + } - 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] - }; + pub(crate) fn universal_regions(&self) -> &UniversalRegions<'tcx> { + &self.universal_region_relations.universal_regions + } - assert!( - !matches!( - best_constraint.category, - ConstraintCategory::OutlivesUnnameablePlaceholder(_) - ), - "Illegal placeholder constraint blamed; should have redirected to other region relation" - ); + pub(crate) fn liveness_constraints(&self) -> &LivenessValues { + &self.liveness_constraints + } - 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 scc(&self, r: RegionVid) -> ConstraintSccIndex { + self.constraint_sccs.scc(r) } } 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..077cdd2522fb4 100644 --- a/compiler/rustc_borrowck/src/region_infer/values.rs +++ b/compiler/rustc_borrowck/src/region_infer/values.rs @@ -12,6 +12,7 @@ use tracing::debug; use crate::BorrowIndex; use crate::polonius::LiveLoans; +use crate::TyCtxt; rustc_index::newtype_index! { /// A single integer representing a `ty::Placeholder`. @@ -51,9 +52,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 +61,6 @@ impl LivenessValues { live_regions: None, points: Some(SparseIntervalMatrix::new(location_map.num_points())), location_map, - live_loans: None, } } @@ -72,12 +69,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 +167,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, ) } From 49ea7dd758607bc743f1f13d33d433192674e4f0 Mon Sep 17 00:00:00 2001 From: Amanda Stjerna Date: Thu, 5 Feb 2026 14:40:30 +0100 Subject: [PATCH 5/9] Address review comment --- .../src/diagnostics/opaque_types.rs | 9 +-- compiler/rustc_borrowck/src/lib.rs | 57 ++----------- .../src/region_infer/opaque_types/mod.rs | 80 ++++++++++++++++--- 3 files changed, 80 insertions(+), 66 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs b/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs index 14768cfb33bc6..c3ff8a81bcfdf 100644 --- a/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs +++ b/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs @@ -42,12 +42,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { hidden_type, member_region, } => { - let named_ty = - self.name_regions_for_member_constraint(infcx.tcx, hidden_type.ty); - let named_key = - self.name_regions_for_member_constraint(infcx.tcx, opaque_type_key); - let named_region = - self.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(), diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 550af6181d432..c2a95266e785c 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -2752,56 +2752,6 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, '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) -> RegionVid { - debug!("{}", self.scc_values.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.scc_values.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 self.definitions[ur].external_name.is_some() { - lub = ur; - } else if self.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 - } - pub(crate) fn universal_regions(&self) -> &UniversalRegions<'tcx> { &self.universal_region_relations.universal_regions } @@ -2816,6 +2766,13 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> { // 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/region_infer/opaque_types/mod.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs index d12a74bd1f836..3195df0c99153 100644 --- a/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs @@ -24,6 +24,8 @@ use rustc_trait_selection::traits::query::type_op::custom::CustomTypeOp; use tracing::{debug, instrument}; use super::reverse_sccs::ReverseSccGraph; +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; @@ -594,7 +596,61 @@ pub(crate) fn detect_opaque_types_added_while_handling_opaque_types<'tcx>( let _ = infcx.take_opaque_types(); } -impl<'a, 'infcx, 'tcx> MirBorrowckCtxt<'a, 'infcx, '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 @@ -607,15 +663,20 @@ impl<'a, 'infcx, 'tcx> MirBorrowckCtxt<'a, 'infcx, '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) => { // Special handling of higher-ranked regions. - if !self.scc_values.max_nameable_universe(vid).is_root() { - match self.scc_values.placeholders_contained_in(vid).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); @@ -627,8 +688,8 @@ impl<'a, 'infcx, 'tcx> MirBorrowckCtxt<'a, 'infcx, '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; } @@ -636,11 +697,10 @@ impl<'a, 'infcx, 'tcx> MirBorrowckCtxt<'a, 'infcx, '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 rev_scc_graph = - ReverseSccGraph::compute(&self.scc_values.sccs, self.universal_regions()); + let rev_scc_graph = ReverseSccGraph::compute(&self.sccs, self.universal_regions()); let upper_bounds: Vec<_> = rev_scc_graph - .upper_bounds(self.scc_values.scc(vid)) - .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[..] { From 540582e2f0bf5a23b43bdf1c55323b7487a27419 Mon Sep 17 00:00:00 2001 From: Amanda Stjerna Date: Thu, 5 Feb 2026 16:00:09 +0100 Subject: [PATCH 6/9] Restructure to avoid opaque types cloning --- .../src/diagnostics/region_errors.rs | 2 +- .../rustc_borrowck/src/handle_placeholders.rs | 45 +++----------- compiler/rustc_borrowck/src/nll.rs | 61 +++++++++---------- .../rustc_borrowck/src/polonius/legacy/mod.rs | 9 ++- .../src/region_infer/dump_mir.rs | 2 +- .../src/region_infer/graphviz.rs | 1 + .../rustc_borrowck/src/region_infer/mod.rs | 22 +++---- .../src/region_infer/opaque_types/mod.rs | 2 +- 8 files changed, 53 insertions(+), 91 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs index d54eb6fff8550..3393dbf832962 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs @@ -159,7 +159,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } else { // We just want something nameable, even if it's not // actually an upper bound. - let upper_bound = self.approx_universal_upper_bound(r); + let upper_bound = self.scc_values.approx_universal_upper_bound(r, self.definitions); if self.scc_values.upper_bound_in_region_scc(r, upper_bound) { self.to_error_region_vid(upper_bound) diff --git a/compiler/rustc_borrowck/src/handle_placeholders.rs b/compiler/rustc_borrowck/src/handle_placeholders.rs index 1dc5e32dc5275..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> { @@ -239,23 +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 MirTypeckRegionConstraints { - placeholder_indices, - placeholder_index_to_region: _, - mut liveness_constraints, - mut outlives_constraints, - universe_causes, - type_tests, - } = constraints; - let (definitions, has_placeholders) = - region_definitions(infcx, universal_regions, &mut liveness_constraints); + region_definitions(infcx, universal_regions, liveness_constraints); let fr_static = universal_regions.fr_static; let compute_sccs = @@ -277,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!"); @@ -293,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 { @@ -310,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/nll.rs b/compiler/rustc_borrowck/src/nll.rs index 311fe2604024c..773657661bd5d 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -99,23 +99,17 @@ pub(crate) fn compute_closure_requirements_modulo_opaques<'tcx>( universal_region_relations: Rc>>, constraints: &MirTypeckRegionConstraints<'tcx>, ) -> Option> { - // FIXME(amandasystems) FIX THIS!!!!! - // 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 LoweredConstraints { - constraint_sccs, - definitions, - scc_annotations, - outlives_constraints, - type_tests, - mut liveness_constraints, - universe_causes: _, - placeholder_indices, - } = compute_sccs_applying_placeholder_outlives_constraints( - constraints.clone(), - &universal_region_relations, - infcx, - ); + // 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 regioncx = region_infer::RegionInferenceContext::new( &infcx, @@ -123,13 +117,13 @@ pub(crate) fn compute_closure_requirements_modulo_opaques<'tcx>( &definitions, scc_annotations, &outlives_constraints, - type_tests, + &constraints.type_tests, &mut liveness_constraints, universal_region_relations, ); let (closure_region_requirements, _nll_errors, _scc_values) = - regioncx.solve(infcx, body, None, location_map, placeholder_indices); + regioncx.solve(infcx, body, None, location_map, constraints.placeholder_indices.clone()); closure_region_requirements } @@ -152,12 +146,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, @@ -167,19 +173,10 @@ pub(crate) fn compute_regions<'tcx>( borrow_set, move_data, &universal_region_relations, - &lowered_constraints, + &outlives_constraints, ); - let LoweredConstraints { - constraint_sccs, - definitions, - scc_annotations, - outlives_constraints, - type_tests, - mut liveness_constraints, - universe_causes, - placeholder_indices, - } = lowered_constraints; + let LoweredConstraints { constraint_sccs, definitions, scc_annotations } = lowered_constraints; let mut regioncx = RegionInferenceContext::new( &infcx, @@ -187,7 +184,7 @@ pub(crate) fn compute_regions<'tcx>( &definitions, scc_annotations, &outlives_constraints, - type_tests, + &type_tests, &mut liveness_constraints, universal_region_relations, ); 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/region_infer/dump_mir.rs b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs index dd2a9ebdcbd21..69ed31f9accd7 100644 --- a/compiler/rustc_borrowck/src/region_infer/dump_mir.rs +++ b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs @@ -8,8 +8,8 @@ use std::io::{self, Write}; use rustc_infer::infer::NllRegionVariableOrigin; use rustc_middle::ty::{RegionVid, TyCtxt}; -use super::OutlivesConstraint; use crate::constraints::OutlivesConstraintSet; +use crate::consumers::OutlivesConstraint; use crate::handle_placeholders::RegionDefinitions; use crate::region_infer::InferredRegions; use crate::region_infer::values::LivenessValues; diff --git a/compiler/rustc_borrowck/src/region_infer/graphviz.rs b/compiler/rustc_borrowck/src/region_infer/graphviz.rs index 4a6b74ddb6515..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 { diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index b28aa57576bde..66c8cabdc5cd7 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -8,25 +8,19 @@ 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 rustc_span::Span; +use tracing::{Level, debug, enabled, instrument}; -use crate::constraints::graph::NormalConstraintGraph; -use crate::constraints::{ConstraintSccIndex, OutlivesConstraint, OutlivesConstraintSet}; +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::universal_regions::UniversalRegionChecker; use crate::region_infer::values::{ - LivenessValues, PlaceholderIndices, RegionElement, RegionValues, ToElementIndex, + LivenessValues, PlaceholderIndices, RegionValues, ToElementIndex, }; use crate::type_check::Locations; use crate::type_check::free_region_relations::UniversalRegionRelations; @@ -341,7 +335,7 @@ impl<'tcx> InferredRegions<'tcx> { infcx: &InferCtxt<'tcx>, mut propagated_outlives_requirements: Option<&mut Vec>>, errors_buffer: &mut RegionErrors<'tcx>, - type_tests: Vec>, + type_tests: &[TypeTest<'tcx>], ) { let tcx = infcx.tcx; @@ -571,7 +565,7 @@ pub(crate) struct RegionInferenceContext<'a, 'tcx> { scc_annotations: IndexVec, /// Type constraints that we check after solving. - type_tests: Vec>, + type_tests: &'a [TypeTest<'tcx>], /// Information about how the universally quantified regions in /// scope on this function relate to one another. @@ -736,7 +730,7 @@ impl<'a, 'tcx> RegionInferenceContext<'a, 'tcx> { definitions: &'a RegionDefinitions<'tcx>, scc_annotations: IndexVec, outlives_constraints: &'a OutlivesConstraintSet<'tcx>, - type_tests: Vec>, + type_tests: &'a [TypeTest<'tcx>], liveness_constraints: &'a mut LivenessValues, universal_region_relations: Rc>>, ) -> Self { 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 3195df0c99153..d887e5cfa4bee 100644 --- a/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs @@ -24,6 +24,7 @@ use rustc_trait_selection::traits::query::type_op::custom::CustomTypeOp; use tracing::{debug, instrument}; use super::reverse_sccs::ReverseSccGraph; +use crate::BorrowckInferCtxt; use crate::handle_placeholders::RegionDefinitions; use crate::region_infer::InferredRegions; use crate::session_diagnostics::LifetimeMismatchOpaqueParam; @@ -31,7 +32,6 @@ use crate::type_check::canonical::fully_perform_op_raw; use crate::type_check::free_region_relations::UniversalRegionRelations; use crate::type_check::{Locations, MirTypeckRegionConstraints}; use crate::universal_regions::{RegionClassification, UniversalRegions}; -use crate::{BorrowckInferCtxt, MirBorrowckCtxt}; mod member_constraints; mod region_ctxt; From 174023c6e2b7489dc00f4dd2bb80925e8f852738 Mon Sep 17 00:00:00 2001 From: Amanda Stjerna Date: Thu, 5 Feb 2026 16:34:30 +0100 Subject: [PATCH 7/9] Update consumer options --- compiler/rustc_borrowck/src/consumers.rs | 6 +++--- compiler/rustc_borrowck/src/region_infer/values.rs | 4 ---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_borrowck/src/consumers.rs b/compiler/rustc_borrowck/src/consumers.rs index ecce77bb79942..a233bb3dd3765 100644 --- a/compiler/rustc_borrowck/src/consumers.rs +++ b/compiler/rustc_borrowck/src/consumers.rs @@ -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. diff --git a/compiler/rustc_borrowck/src/region_infer/values.rs b/compiler/rustc_borrowck/src/region_infer/values.rs index 077cdd2522fb4..c3922051ed8b4 100644 --- a/compiler/rustc_borrowck/src/region_infer/values.rs +++ b/compiler/rustc_borrowck/src/region_infer/values.rs @@ -10,10 +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; -use crate::TyCtxt; - rustc_index::newtype_index! { /// A single integer representing a `ty::Placeholder`. #[debug_format = "PlaceholderIndex({})"] From 1dea889667badeb88ea22c50991933c27bc45cad Mon Sep 17 00:00:00 2001 From: Amanda Stjerna Date: Mon, 9 Feb 2026 15:36:53 +0100 Subject: [PATCH 8/9] Remove the final references to RegionInferenceContext from nll --- compiler/rustc_borrowck/src/nll.rs | 33 +++++++++++-------- compiler/rustc_borrowck/src/polonius/mod.rs | 22 ++++++++----- .../rustc_borrowck/src/region_infer/mod.rs | 8 ++--- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index 773657661bd5d..46b3c9ef68197 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -178,21 +178,17 @@ pub(crate) fn compute_regions<'tcx>( let LoweredConstraints { constraint_sccs, definitions, scc_annotations } = lowered_constraints; - let mut regioncx = RegionInferenceContext::new( - &infcx, - constraint_sccs, - &definitions, - scc_annotations, - &outlives_constraints, - &type_tests, - &mut liveness_constraints, - universal_region_relations, - ); - // 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. @@ -217,8 +213,17 @@ pub(crate) fn compute_regions<'tcx>( }); // Solve the region constraints. - let (closure_region_requirements, nll_errors, scc_values) = - regioncx.solve(infcx, body, polonius_output.clone(), location_map, placeholder_indices); + let (closure_region_requirements, nll_errors, scc_values) = RegionInferenceContext::new( + &infcx, + constraint_sccs, + &definitions, + scc_annotations, + &outlives_constraints, + &type_tests, + &mut liveness_constraints, + universal_region_relations, + ) + .solve(infcx, body, polonius_output.clone(), location_map, placeholder_indices); NllOutput { polonius_input: polonius_facts.map(Box::new), diff --git a/compiler/rustc_borrowck/src/polonius/mod.rs b/compiler/rustc_borrowck/src/polonius/mod.rs index 96e3814ec6323..8ddd3d8802ea3 100644 --- a/compiler/rustc_borrowck/src/polonius/mod.rs +++ b/compiler/rustc_borrowck/src/polonius/mod.rs @@ -65,8 +65,10 @@ 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::region_infer::RegionInferenceContext; +use crate::region_infer::values::LivenessValues; +use crate::universal_regions::UniversalRegions; pub(crate) type LiveLoans = SparseBitMatrix; @@ -156,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 { @@ -167,26 +171,26 @@ impl PoloniusContext { convert_typeck_constraints( tcx, body, - regioncx.liveness_constraints(), - regioncx.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.constraints, + liveness, + outlives_constraints, borrow_set, &localized_outlives_constraints, ); diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 66c8cabdc5cd7..97dbac2f2cdb7 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -880,15 +880,11 @@ impl<'a, 'tcx> RegionInferenceContext<'a, 'tcx> { scc_values } - pub(crate) fn universal_regions(&self) -> &UniversalRegions<'tcx> { + fn universal_regions(&self) -> &UniversalRegions<'tcx> { &self.universal_region_relations.universal_regions } - pub(crate) fn liveness_constraints(&self) -> &LivenessValues { - &self.liveness_constraints - } - - pub(crate) fn scc(&self, r: RegionVid) -> ConstraintSccIndex { + fn scc(&self, r: RegionVid) -> ConstraintSccIndex { self.constraint_sccs.scc(r) } } From e647018e70209271ff54b1f9f9b5efa1cb850c0e Mon Sep 17 00:00:00 2001 From: Amanda Stjerna Date: Mon, 9 Feb 2026 16:03:49 +0100 Subject: [PATCH 9/9] Combine RegionInferenceContext::new() and ::solve(). --- compiler/rustc_borrowck/src/nll.rs | 52 ++++++----- .../rustc_borrowck/src/region_infer/mod.rs | 93 +++++++++---------- 2 files changed, 71 insertions(+), 74 deletions(-) diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index 46b3c9ef68197..3e252cdc28cd4 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -111,19 +111,21 @@ pub(crate) fn compute_closure_requirements_modulo_opaques<'tcx>( infcx, ); - let regioncx = region_infer::RegionInferenceContext::new( - &infcx, - constraint_sccs, - &definitions, - scc_annotations, - &outlives_constraints, - &constraints.type_tests, - &mut liveness_constraints, - universal_region_relations, - ); - let (closure_region_requirements, _nll_errors, _scc_values) = - regioncx.solve(infcx, body, None, location_map, constraints.placeholder_indices.clone()); + 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 } @@ -213,17 +215,21 @@ pub(crate) fn compute_regions<'tcx>( }); // Solve the region constraints. - let (closure_region_requirements, nll_errors, scc_values) = RegionInferenceContext::new( - &infcx, - constraint_sccs, - &definitions, - scc_annotations, - &outlives_constraints, - &type_tests, - &mut liveness_constraints, - universal_region_relations, - ) - .solve(infcx, body, polonius_output.clone(), location_map, placeholder_indices); + 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 { polonius_input: polonius_facts.map(Box::new), diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 97dbac2f2cdb7..8d11f91c7a779 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -564,9 +564,6 @@ pub(crate) struct RegionInferenceContext<'a, 'tcx> { scc_annotations: IndexVec, - /// Type constraints that we check after solving. - type_tests: &'a [TypeTest<'tcx>], - /// Information about how the universally quantified regions in /// scope on this function relate to one another. universal_region_relations: Rc>>, @@ -717,14 +714,22 @@ fn sccs_info<'tcx>(infcx: &BorrowckInferCtxt<'tcx>, sccs: &ConstraintSccs) { } impl<'a, 'tcx> RegionInferenceContext<'a, '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( + /// 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>, @@ -733,47 +738,35 @@ impl<'a, 'tcx> RegionInferenceContext<'a, 'tcx> { type_tests: &'a [TypeTest<'tcx>], liveness_constraints: &'a mut LivenessValues, universal_region_relations: Rc>>, - ) -> Self { - debug!("universal_regions: {:#?}", universal_region_relations.universal_regions); - debug!("outlives constraints: {:#?}", outlives_constraints); - debug!("type tests: {:#?}", type_tests); - - if cfg!(debug_assertions) { - sccs_info(infcx, &constraint_sccs); - } - - Self { - definitions, - liveness_constraints, - constraints: outlives_constraints, - constraint_sccs, - scc_annotations, - type_tests, - universal_region_relations, - } - } - - /// 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, location_map, placeholder_indices), - level = "debug" - )] - pub(super) fn solve( - self, - infcx: &InferCtxt<'tcx>, 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); + } + + Self { + definitions, + liveness_constraints, + constraints: outlives_constraints, + constraint_sccs, + scc_annotations, + universal_region_relations, + } + }; + let mir_def_id = body.source.def_id(); let scc_values = InferredRegions { - scc_values: self.compute_region_values(location_map, placeholder_indices), - sccs: self.constraint_sccs, - annotations: self.scc_annotations, - universal_region_relations: Rc::clone(&self.universal_region_relations), + 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, }; let mut errors_buffer = RegionErrors::new(infcx.tcx); @@ -783,13 +776,13 @@ impl<'a, 'tcx> RegionInferenceContext<'a, 'tcx> { // 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); + let mut outlives_requirements = infcx.infcx.tcx.is_typeck_child(mir_def_id).then(Vec::new); scc_values.check_type_tests( infcx, outlives_requirements.as_mut(), &mut errors_buffer, - self.type_tests, + type_tests, ); debug!(?errors_buffer); @@ -797,8 +790,8 @@ impl<'a, 'tcx> RegionInferenceContext<'a, 'tcx> { UniversalRegionChecker::new( &mut errors_buffer, - self.definitions, - self.constraints, + definitions, + outlives_constraints, &scc_values, ) .check(polonius_output, outlives_requirements.as_mut()); @@ -810,8 +803,6 @@ impl<'a, 'tcx> RegionInferenceContext<'a, 'tcx> { if outlives_requirements.is_empty() { (None, errors_buffer, scc_values) } else { - let num_external_vids = - self.universal_region_relations.universal_regions.num_global_and_external_regions(); ( Some(ClosureRegionRequirements { num_external_vids, outlives_requirements }), errors_buffer,