From aca36fd12a3277ce17eb94e4e89567843bf137f7 Mon Sep 17 00:00:00 2001 From: Amanda Stjerna Date: Tue, 29 Apr 2025 17:12:12 +0200 Subject: [PATCH 1/2] Move placeholder handling to a proper preprocessing step This commit breaks out the logic of placheolder rewriting into its own preprocessing step. The only functional change from this is that the preprocessing step (where extra `r: 'static` constraints are added) is performed upstream of Polonius legacy, finally affecting Polonius. That is mostly a by-product, though. --- .../rustc_borrowck/src/constraints/mod.rs | 110 +----- .../src/eliminate_placeholders.rs | 371 ++++++++++++++++++ compiler/rustc_borrowck/src/lib.rs | 1 + compiler/rustc_borrowck/src/nll.rs | 17 +- .../rustc_borrowck/src/polonius/legacy/mod.rs | 6 +- .../rustc_borrowck/src/region_infer/mod.rs | 208 ++-------- 6 files changed, 414 insertions(+), 299 deletions(-) create mode 100644 compiler/rustc_borrowck/src/eliminate_placeholders.rs diff --git a/compiler/rustc_borrowck/src/constraints/mod.rs b/compiler/rustc_borrowck/src/constraints/mod.rs index 514bbfe359b1d..99ddccabd15fe 100644 --- a/compiler/rustc_borrowck/src/constraints/mod.rs +++ b/compiler/rustc_borrowck/src/constraints/mod.rs @@ -5,11 +5,9 @@ use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::mir::ConstraintCategory; use rustc_middle::ty::{RegionVid, TyCtxt, VarianceDiagInfo}; use rustc_span::Span; -use tracing::{debug, instrument}; +use tracing::debug; -use crate::region_infer::{AnnotatedSccs, ConstraintSccs, RegionDefinition, SccAnnotations}; use crate::type_check::Locations; -use crate::universal_regions::UniversalRegions; pub(crate) mod graph; @@ -53,112 +51,6 @@ impl<'tcx> OutlivesConstraintSet<'tcx> { ) -> &IndexSlice> { &self.outlives } - - /// Computes cycles (SCCs) in the graph of regions. In particular, - /// find all regions R1, R2 such that R1: R2 and R2: R1 and group - /// them into an SCC, and find the relationships between SCCs. - pub(crate) fn compute_sccs( - &self, - static_region: RegionVid, - definitions: &IndexVec>, - ) -> AnnotatedSccs { - let constraint_graph = self.graph(definitions.len()); - let region_graph = &constraint_graph.region_graph(self, static_region); - let mut annotation_visitor = SccAnnotations::new(definitions); - ( - ConstraintSccs::new_with_annotation(®ion_graph, &mut annotation_visitor), - annotation_visitor.scc_to_annotation, - ) - } - - /// This method handles Universe errors by rewriting the constraint - /// graph. For each strongly connected component in the constraint - /// graph such that there is a series of constraints - /// A: B: C: ... : X where - /// A's universe is smaller than X's and A is a placeholder, - /// add a constraint that A: 'static. This is a safe upper bound - /// in the face of borrow checker/trait solver limitations that will - /// eventually go away. - /// - /// For a more precise definition, see the documentation for - /// [`crate::region_infer::RegionTracker`]. - /// - /// This edge case used to be handled during constraint propagation - /// by iterating over the strongly connected components in the constraint - /// graph while maintaining a set of bookkeeping mappings similar - /// to what is stored in `RegionTracker` and manually adding 'static as - /// needed. - /// - /// It was rewritten as part of the Polonius project with the goal of moving - /// higher-kindedness concerns out of the path of the borrow checker, - /// for two reasons: - /// - /// 1. Implementing Polonius is difficult enough without also - /// handling them. - /// 2. The long-term goal is to handle higher-kinded concerns - /// in the trait solver, where they belong. This avoids - /// logic duplication and allows future trait solvers - /// to compute better bounds than for example our - /// "must outlive 'static" here. - /// - /// This code is a stop-gap measure in preparation for the future trait solver. - /// - /// Every constraint added by this method is an - /// internal `IllegalUniverse` constraint. - #[instrument(skip(self, universal_regions, definitions))] - pub(crate) fn add_outlives_static( - &mut self, - universal_regions: &UniversalRegions<'tcx>, - definitions: &IndexVec>, - ) -> AnnotatedSccs { - let fr_static = universal_regions.fr_static; - let (sccs, annotations) = self.compute_sccs(fr_static, definitions); - - // Changed to `true` if we added any constraints to `self` and need to - // recompute SCCs. - let mut added_constraints = false; - - for scc in sccs.all_sccs() { - // No point in adding 'static: 'static! - // This micro-optimisation makes somewhat sense - // because static outlives *everything*. - if scc == sccs.scc(fr_static) { - continue; - } - - let annotation = annotations[scc]; - - // If this SCC participates in a universe violation, - // e.g. if it reaches a region with a universe smaller than - // the largest region reached, add a requirement that it must - // outlive `'static`. - if annotation.has_incompatible_universes() { - // Optimisation opportunity: this will add more constraints than - // needed for correctness, since an SCC upstream of another with - // a universe violation will "infect" its downstream SCCs to also - // outlive static. - added_constraints = true; - let scc_representative_outlives_static = OutlivesConstraint { - sup: annotation.representative, - sub: fr_static, - category: ConstraintCategory::IllegalUniverse, - locations: Locations::All(rustc_span::DUMMY_SP), - span: rustc_span::DUMMY_SP, - variance_info: VarianceDiagInfo::None, - from_closure: false, - }; - self.push(scc_representative_outlives_static); - } - } - - if added_constraints { - // We changed the constraint set and so must recompute SCCs. - self.compute_sccs(fr_static, definitions) - } else { - // If we didn't add any back-edges; no more work needs doing - (sccs, annotations) - } - } } impl<'tcx> Index for OutlivesConstraintSet<'tcx> { diff --git a/compiler/rustc_borrowck/src/eliminate_placeholders.rs b/compiler/rustc_borrowck/src/eliminate_placeholders.rs new file mode 100644 index 0000000000000..e8e1acbd7a5ad --- /dev/null +++ b/compiler/rustc_borrowck/src/eliminate_placeholders.rs @@ -0,0 +1,371 @@ +//! Logic for lowering higher-kinded outlives constraints +//! (with placeholders and universes) and turn them into regular +//! outlives constraints. +//! +//! This logic is provisional and should be removed once the trait +//! solver can handle this kind of constraint. +use rustc_data_structures::frozen::Frozen; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; +use rustc_data_structures::graph::scc; +use rustc_data_structures::graph::scc::Sccs; +use rustc_index::IndexVec; +use rustc_middle::mir::ConstraintCategory; +use rustc_middle::ty::{RegionVid, UniverseIndex}; +use tracing::debug; + +use crate::constraints::{ConstraintSccIndex, OutlivesConstraintSet}; +use crate::consumers::OutlivesConstraint; +use crate::diagnostics::UniverseInfo; +use crate::member_constraints::MemberConstraintSet; +use crate::region_infer::values::{LivenessValues, PlaceholderIndices}; +use crate::region_infer::{ConstraintSccs, RegionDefinition, TypeTest}; +use crate::ty::VarianceDiagInfo; +use crate::type_check::free_region_relations::UniversalRegionRelations; +use crate::type_check::{Locations, MirTypeckRegionConstraints}; +use crate::universal_regions::UniversalRegions; +use crate::{BorrowckInferCtxt, NllRegionVariableOrigin}; + +/// A set of outlives constraints after rewriting to remove +/// higher-kinded constraints. +pub(crate) struct LoweredConstraints<'tcx> { + pub(crate) constraint_sccs: Sccs, + pub(crate) definitions: Frozen>>, + pub(crate) scc_annotations: IndexVec, + pub(crate) member_constraints: MemberConstraintSet<'tcx, RegionVid>, + pub(crate) outlives_constraints: OutlivesConstraintSet<'tcx>, + pub(crate) type_tests: Vec>, + pub(crate) liveness_constraints: LivenessValues, + pub(crate) universe_causes: FxIndexMap>, + pub(crate) placeholder_indices: PlaceholderIndices, +} + +impl<'d, 'tcx, A: scc::Annotation> SccAnnotations<'d, 'tcx, A> { + pub(crate) fn init(definitions: &'d IndexVec>) -> Self { + Self { scc_to_annotation: IndexVec::new(), definitions } + } +} + +/// A Visitor for SCC annotation construction. +pub(crate) struct SccAnnotations<'d, 'tcx, A: scc::Annotation> { + pub(crate) scc_to_annotation: IndexVec, + definitions: &'d IndexVec>, +} + +impl scc::Annotations for SccAnnotations<'_, '_, RegionTracker> { + fn new(&self, element: RegionVid) -> RegionTracker { + RegionTracker::new(element, &self.definitions[element]) + } + + fn annotate_scc(&mut self, scc: ConstraintSccIndex, annotation: RegionTracker) { + let idx = self.scc_to_annotation.push(annotation); + assert!(idx == scc); + } + + type Ann = RegionTracker; + type SccIdx = ConstraintSccIndex; +} + +/// An annotation for region graph SCCs that tracks +/// the values of its elements. This annotates a single SCC. +#[derive(Copy, Debug, Clone)] +pub(crate) struct RegionTracker { + /// The largest universe of a placeholder reached from this SCC. + /// This includes placeholders within this SCC. + max_placeholder_universe_reached: UniverseIndex, + + /// The smallest universe index reachable form the nodes of this SCC. + min_reachable_universe: UniverseIndex, + + /// The representative Region Variable Id for this SCC. We prefer + /// placeholders over existentially quantified variables, otherwise + /// it's the one with the smallest Region Variable ID. + pub(crate) representative: RegionVid, + + /// Is the current representative a placeholder? + representative_is_placeholder: bool, + + /// Is the current representative existentially quantified? + representative_is_existential: bool, +} + +impl RegionTracker { + pub(crate) fn new(rvid: RegionVid, definition: &RegionDefinition<'_>) -> Self { + let (representative_is_placeholder, representative_is_existential) = match definition.origin + { + NllRegionVariableOrigin::FreeRegion => (false, false), + NllRegionVariableOrigin::Placeholder(_) => (true, false), + NllRegionVariableOrigin::Existential { .. } => (false, true), + }; + + let placeholder_universe = + if representative_is_placeholder { definition.universe } else { UniverseIndex::ROOT }; + + Self { + max_placeholder_universe_reached: placeholder_universe, + min_reachable_universe: definition.universe, + representative: rvid, + representative_is_placeholder, + representative_is_existential, + } + } + + /// The smallest-indexed universe reachable from and/or in this SCC. + pub(crate) fn min_universe(self) -> UniverseIndex { + self.min_reachable_universe + } + + fn merge_min_max_seen(&mut self, other: &Self) { + self.max_placeholder_universe_reached = std::cmp::max( + self.max_placeholder_universe_reached, + other.max_placeholder_universe_reached, + ); + + self.min_reachable_universe = + std::cmp::min(self.min_reachable_universe, other.min_reachable_universe); + } + + /// Returns `true` if during the annotated SCC reaches a placeholder + /// with a universe larger than the smallest reachable one, `false` otherwise. + pub(crate) fn has_incompatible_universes(&self) -> bool { + self.min_universe().cannot_name(self.max_placeholder_universe_reached) + } + + /// Determine if the tracked universes of the two SCCs + /// are compatible. + pub(crate) fn universe_compatible_with(&self, other: Self) -> bool { + self.min_universe().can_name(other.min_universe()) + || self.min_universe().can_name(other.max_placeholder_universe_reached) + } +} + +impl scc::Annotation for RegionTracker { + fn merge_scc(mut self, mut other: Self) -> Self { + // Prefer any placeholder over any existential + if other.representative_is_placeholder && self.representative_is_existential { + other.merge_min_max_seen(&self); + return other; + } + + if self.representative_is_placeholder && other.representative_is_existential + || (self.representative <= other.representative) + { + self.merge_min_max_seen(&other); + return self; + } + other.merge_min_max_seen(&self); + other + } + + fn merge_reached(mut self, other: Self) -> Self { + // No update to in-component values, only add seen values. + self.merge_min_max_seen(&other); + self + } +} + +/// Determines if the region variable definitions contain +/// placeholers, and compute them for later use. +fn region_definitions<'tcx>( + universal_regions: &UniversalRegions<'tcx>, + infcx: &BorrowckInferCtxt<'tcx>, +) -> (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 + // is in `RegionInferenceContext::new()`, probably. + let mut definitions = IndexVec::with_capacity(var_infos.len()); + let mut has_placeholders = false; + + for info in var_infos.iter() { + let definition = RegionDefinition::new(info); + has_placeholders |= matches!(definition.origin, NllRegionVariableOrigin::Placeholder(_)); + definitions.push(definition); + } + + // Add external names from universal regions in fun function definitions. + for (external_name, variable) in universal_regions.named_universal_regions_iter() { + debug!("region {:?} has external name {:?}", variable, external_name); + definitions[variable].external_name = Some(external_name); + } + (Frozen::freeze(definitions), has_placeholders) +} + +/// This method handles Universe errors by rewriting the constraint +/// graph. For each strongly connected component in the constraint +/// graph such that there is a series of constraints +/// A: B: C: ... : X where +/// A's universe is smaller than X's and A is a placeholder, +/// add a constraint that A: 'static. This is a safe upper bound +/// in the face of borrow checker/trait solver limitations that will +/// eventually go away. +/// +/// For a more precise definition, see the documentation for +/// [`RegionTracker`] and its methods!. +/// +/// Since universes can also be involved in errors (if one placeholder +/// transitively outlives another), this function also flags those. +/// +/// Additionally, it similarly rewrites type-tests. +/// +/// This edge case used to be handled during constraint propagation +/// by iterating over the strongly connected components in the constraint +/// graph while maintaining a set of bookkeeping mappings similar +/// to what is stored in `RegionTracker` and manually adding 'sttaic as +/// needed. +/// +/// It was rewritten as part of the Polonius project with the goal of moving +/// higher-kindedness concerns out of the path of the borrow checker, +/// for two reasons: +/// +/// 1. Implementing Polonius is difficult enough without also +/// handling them. +/// 2. The long-term goal is to handle higher-kinded concerns +/// in the trait solver, where they belong. This avoids +/// logic duplication and allows future trait solvers +/// to compute better bounds than for example our +/// "must outlive 'static" here. +/// +/// This code is a stop-gap measure in preparation for the future trait solver. +/// +/// Every constraint added by this method is an internal `IllegalUniverse` constraint. +pub(crate) fn rewrite_higher_kinded_outlives_as_constraints<'tcx>( + constraints: MirTypeckRegionConstraints<'tcx>, + universal_region_relations: &Frozen>, + infcx: &BorrowckInferCtxt<'tcx>, +) -> LoweredConstraints<'tcx> { + let universal_regions = &universal_region_relations.universal_regions; + let (definitions, has_placeholders) = region_definitions(universal_regions, infcx); + + let MirTypeckRegionConstraints { + placeholder_indices, + placeholder_index_to_region: _, + liveness_constraints, + mut outlives_constraints, + mut member_constraints, + universe_causes, + type_tests, + } = constraints; + + if let Some(guar) = universal_regions.tainted_by_errors() { + debug!("Universal regions tainted by errors; removing constraints!"); + // Suppress unhelpful extra errors in `infer_opaque_types` by clearing out all + // outlives bounds that we may end up checking. + outlives_constraints = Default::default(); + member_constraints = Default::default(); + + // Also taint the entire scope. + infcx.set_tainted_by_errors(guar); + } + + let fr_static = universal_regions.fr_static; + let compute_sccs = + |constraints: &OutlivesConstraintSet<'tcx>, + annotations: &mut SccAnnotations<'_, 'tcx, RegionTracker>| { + ConstraintSccs::new_with_annotation( + &constraints.graph(definitions.len()).region_graph(constraints, fr_static), + annotations, + ) + }; + + // This code structure is a bit convoluted because it allows for a planned + // future change where the early return here has a different type of annotation + // that does much less work. + if !has_placeholders { + debug!("No placeholder regions found; skipping rewriting logic!"); + let mut scc_annotations = SccAnnotations::init(&definitions); + let constraint_sccs = compute_sccs(&outlives_constraints, &mut scc_annotations); + + return LoweredConstraints { + type_tests, + member_constraints, + constraint_sccs, + scc_annotations: scc_annotations.scc_to_annotation, + definitions, + outlives_constraints, + liveness_constraints, + universe_causes, + placeholder_indices, + }; + } + debug!("Placeholders present; activating placeholder handling logic!"); + + let mut annotations = SccAnnotations::init(&definitions); + let sccs = compute_sccs(&outlives_constraints, &mut annotations); + + let outlives_static = + rewrite_outlives(&sccs, &annotations, fr_static, &mut outlives_constraints); + + let (sccs, scc_annotations) = if !outlives_static.is_empty() { + debug!("The following SCCs had :'static constraints added: {:?}", outlives_static); + let mut annotations = SccAnnotations::init(&definitions); + + // We changed the constraint set and so must recompute SCCs. + // Optimisation opportunity: if we can add them incrementally (and that's + // possible because edges to 'static always only merge SCCs into 'static), + // we would potentially save a lot of work here. + (compute_sccs(&outlives_constraints, &mut annotations), annotations.scc_to_annotation) + } else { + // If we didn't add any back-edges; no more work needs doing + debug!("No constraints rewritten!"); + (sccs, annotations.scc_to_annotation) + }; + + LoweredConstraints { + constraint_sccs: sccs, + definitions, + scc_annotations, + member_constraints, + outlives_constraints, + type_tests, + liveness_constraints, + universe_causes, + placeholder_indices, + } +} + +fn rewrite_outlives<'tcx>( + sccs: &Sccs, + annotations: &SccAnnotations<'_, '_, RegionTracker>, + fr_static: RegionVid, + outlives_constraints: &mut OutlivesConstraintSet<'tcx>, +) -> FxHashSet { + // Changed to `true` if we added any constraints to `self` and need to + // recompute SCCs. + let mut outlives_static = FxHashSet::default(); + + let annotations = &annotations.scc_to_annotation; + + for scc in sccs.all_sccs() { + // No point in adding 'static: 'static! + // This micro-optimisation makes somewhat sense + // because static outlives *everything*. + if scc == sccs.scc(fr_static) { + continue; + } + + let annotation = annotations[scc]; + + // If this SCC participates in a universe violation, + // e.g. if it reaches a region with a universe smaller than + // the largest region reached, add a requirement that it must + // outlive `'static`. + if annotation.has_incompatible_universes() { + // Optimisation opportunity: this will add more constraints than + // needed for correctness, since an SCC upstream of another with + // a universe violation will "infect" its downstream SCCs to also + // outlive static. + outlives_static.insert(scc); + let scc_representative_outlives_static = OutlivesConstraint { + sup: annotation.representative, + sub: fr_static, + category: ConstraintCategory::IllegalUniverse, + locations: Locations::All(rustc_span::DUMMY_SP), + span: rustc_span::DUMMY_SP, + variance_info: VarianceDiagInfo::None, + from_closure: false, + }; + outlives_constraints.push(scc_representative_outlives_static); + } + } + outlives_static +} diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 44af1b7653995..edf5494e8867c 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -75,6 +75,7 @@ mod constraints; mod dataflow; mod def_use; mod diagnostics; +mod eliminate_placeholders; mod member_constraints; mod nll; mod path_utils; diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index fe899bb054fa9..9b1a5df8d06b9 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -22,6 +22,7 @@ use tracing::{debug, instrument}; use crate::borrow_set::BorrowSet; use crate::consumers::ConsumerOptions; use crate::diagnostics::RegionErrors; +use crate::eliminate_placeholders::rewrite_higher_kinded_outlives_as_constraints; use crate::polonius::PoloniusDiagnosticsContext; use crate::polonius::legacy::{ PoloniusFacts, PoloniusFactsExt, PoloniusLocationTable, PoloniusOutput, @@ -117,6 +118,12 @@ pub(crate) fn compute_regions<'a, 'tcx>( Rc::clone(&location_map), ); + let lowered_constraints = rewrite_higher_kinded_outlives_as_constraints( + constraints, + &universal_region_relations, + infcx, + ); + // If requested, emit legacy polonius facts. polonius::legacy::emit_facts( &mut polonius_facts, @@ -126,11 +133,15 @@ pub(crate) fn compute_regions<'a, 'tcx>( borrow_set, move_data, &universal_region_relations, - &constraints, + &lowered_constraints, ); - let mut regioncx = - RegionInferenceContext::new(infcx, constraints, universal_region_relations, location_map); + let mut regioncx = RegionInferenceContext::new( + infcx, + lowered_constraints, + universal_region_relations, + location_map, + ); // 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/polonius/legacy/mod.rs b/compiler/rustc_borrowck/src/polonius/legacy/mod.rs index 95820c07a02f8..67da43ca57887 100644 --- a/compiler/rustc_borrowck/src/polonius/legacy/mod.rs +++ b/compiler/rustc_borrowck/src/polonius/legacy/mod.rs @@ -13,7 +13,7 @@ use tracing::debug; use crate::borrow_set::BorrowSet; use crate::constraints::OutlivesConstraint; -use crate::type_check::MirTypeckRegionConstraints; +use crate::eliminate_placeholders::LoweredConstraints; use crate::type_check::free_region_relations::UniversalRegionRelations; use crate::universal_regions::UniversalRegions; @@ -43,7 +43,7 @@ pub(crate) fn emit_facts<'tcx>( borrow_set: &BorrowSet<'tcx>, move_data: &MoveData<'tcx>, universal_region_relations: &UniversalRegionRelations<'tcx>, - constraints: &MirTypeckRegionConstraints<'tcx>, + constraints: &LoweredConstraints<'tcx>, ) { let Some(facts) = facts else { // We don't do anything if there are no facts to fill. @@ -203,7 +203,7 @@ pub(crate) fn emit_drop_facts<'tcx>( fn emit_outlives_facts<'tcx>( facts: &mut PoloniusFacts, location_table: &PoloniusLocationTable, - constraints: &MirTypeckRegionConstraints<'tcx>, + constraints: &LoweredConstraints<'tcx>, ) { facts.subset_base.extend(constraints.outlives_constraints.outlives().iter().flat_map( |constraint: &OutlivesConstraint<'_>| { diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index b4ff3d66f3d5b..cfad071460b42 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -4,12 +4,14 @@ use std::rc::Rc; use rustc_data_structures::binary_search_util; use rustc_data_structures::frozen::Frozen; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; -use rustc_data_structures::graph::scc::{self, Sccs}; +use rustc_data_structures::graph::scc::Sccs; use rustc_errors::Diag; use rustc_hir::def_id::CRATE_DEF_ID; use rustc_index::IndexVec; use rustc_infer::infer::outlives::test_type_match; -use rustc_infer::infer::region_constraints::{GenericKind, VerifyBound, VerifyIfEq}; +use rustc_infer::infer::region_constraints::{ + GenericKind, RegionVariableInfo, VerifyBound, VerifyIfEq, +}; use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin, RegionVariableOrigin}; use rustc_middle::bug; use rustc_middle::mir::{ @@ -27,13 +29,14 @@ use crate::constraints::graph::{self, NormalConstraintGraph, RegionGraph}; use crate::constraints::{ConstraintSccIndex, OutlivesConstraint, OutlivesConstraintSet}; use crate::dataflow::BorrowIndex; use crate::diagnostics::{RegionErrorKind, RegionErrors, UniverseInfo}; +use crate::eliminate_placeholders::{LoweredConstraints, RegionTracker}; use crate::member_constraints::{MemberConstraintSet, NllMemberConstraintIndex}; use crate::polonius::LiveLoans; use crate::polonius::legacy::PoloniusOutput; use crate::region_infer::reverse_sccs::ReverseSccGraph; use crate::region_infer::values::{LivenessValues, RegionElement, RegionValues, ToElementIndex}; +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, ClosureOutlivesRequirement, ClosureOutlivesSubject, @@ -48,124 +51,6 @@ mod reverse_sccs; pub(crate) mod values; pub(crate) type ConstraintSccs = Sccs; -pub(crate) type AnnotatedSccs = (ConstraintSccs, IndexVec); - -/// An annotation for region graph SCCs that tracks -/// the values of its elements. This annotates a single SCC. -#[derive(Copy, Debug, Clone)] -pub(crate) struct RegionTracker { - /// The largest universe of a placeholder reached from this SCC. - /// This includes placeholders within this SCC. - max_placeholder_universe_reached: UniverseIndex, - - /// The smallest universe index reachable form the nodes of this SCC. - min_reachable_universe: UniverseIndex, - - /// The representative Region Variable Id for this SCC. We prefer - /// placeholders over existentially quantified variables, otherwise - /// it's the one with the smallest Region Variable ID. - pub(crate) representative: RegionVid, - - /// Is the current representative a placeholder? - representative_is_placeholder: bool, - - /// Is the current representative existentially quantified? - representative_is_existential: bool, -} - -impl scc::Annotation for RegionTracker { - fn merge_scc(mut self, mut other: Self) -> Self { - // Prefer any placeholder over any existential - if other.representative_is_placeholder && self.representative_is_existential { - other.merge_min_max_seen(&self); - return other; - } - - if self.representative_is_placeholder && other.representative_is_existential - || (self.representative <= other.representative) - { - self.merge_min_max_seen(&other); - return self; - } - other.merge_min_max_seen(&self); - other - } - - fn merge_reached(mut self, other: Self) -> Self { - // No update to in-component values, only add seen values. - self.merge_min_max_seen(&other); - self - } -} - -/// A Visitor for SCC annotation construction. -pub(crate) struct SccAnnotations<'d, 'tcx, A: scc::Annotation> { - pub(crate) scc_to_annotation: IndexVec, - definitions: &'d IndexVec>, -} - -impl<'d, 'tcx, A: scc::Annotation> SccAnnotations<'d, 'tcx, A> { - pub(crate) fn new(definitions: &'d IndexVec>) -> Self { - Self { scc_to_annotation: IndexVec::new(), definitions } - } -} - -impl scc::Annotations for SccAnnotations<'_, '_, RegionTracker> { - fn new(&self, element: RegionVid) -> RegionTracker { - RegionTracker::new(element, &self.definitions[element]) - } - - fn annotate_scc(&mut self, scc: ConstraintSccIndex, annotation: RegionTracker) { - let idx = self.scc_to_annotation.push(annotation); - assert!(idx == scc); - } - - type Ann = RegionTracker; - type SccIdx = ConstraintSccIndex; -} - -impl RegionTracker { - pub(crate) fn new(rvid: RegionVid, definition: &RegionDefinition<'_>) -> Self { - let (representative_is_placeholder, representative_is_existential) = match definition.origin - { - NllRegionVariableOrigin::FreeRegion => (false, false), - NllRegionVariableOrigin::Placeholder(_) => (true, false), - NllRegionVariableOrigin::Existential { .. } => (false, true), - }; - - let placeholder_universe = - if representative_is_placeholder { definition.universe } else { UniverseIndex::ROOT }; - - Self { - max_placeholder_universe_reached: placeholder_universe, - min_reachable_universe: definition.universe, - representative: rvid, - representative_is_placeholder, - representative_is_existential, - } - } - - /// The smallest-indexed universe reachable from and/or in this SCC. - fn min_universe(self) -> UniverseIndex { - self.min_reachable_universe - } - - fn merge_min_max_seen(&mut self, other: &Self) { - self.max_placeholder_universe_reached = std::cmp::max( - self.max_placeholder_universe_reached, - other.max_placeholder_universe_reached, - ); - - self.min_reachable_universe = - std::cmp::min(self.min_reachable_universe, other.min_reachable_universe); - } - - /// Returns `true` if during the annotated SCC reaches a placeholder - /// with a universe larger than the smallest reachable one, `false` otherwise. - pub(crate) fn has_incompatible_universes(&self) -> bool { - self.min_universe().cannot_name(self.max_placeholder_universe_reached) - } -} pub struct RegionInferenceContext<'tcx> { /// Contains the definition for every region variable. Region @@ -413,26 +298,6 @@ fn sccs_info<'tcx>(infcx: &BorrowckInferCtxt<'tcx>, sccs: &ConstraintSccs) { debug!("SCC edges {:#?}", scc_node_to_edges); } -fn create_definitions<'tcx>( - infcx: &BorrowckInferCtxt<'tcx>, - universal_regions: &UniversalRegions<'tcx>, -) -> Frozen>> { - // Create a RegionDefinition for each inference variable. - let mut definitions: IndexVec<_, _> = infcx - .get_region_var_infos() - .iter() - .map(|info| RegionDefinition::new(info.universe, info.origin)) - .collect(); - - // Add the external name for all universal regions. - for (external_name, variable) in universal_regions.named_universal_regions_iter() { - debug!("region {variable:?} has external name {external_name:?}"); - definitions[variable].external_name = Some(external_name); - } - - Frozen::freeze(definitions) -} - impl<'tcx> RegionInferenceContext<'tcx> { /// Creates a new region inference context with a total of /// `num_region_variables` valid inference variables; the first N @@ -443,42 +308,30 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// of constraints produced by the MIR type check. pub(crate) fn new( infcx: &BorrowckInferCtxt<'tcx>, - constraints: MirTypeckRegionConstraints<'tcx>, + lowered_constraints: LoweredConstraints<'tcx>, universal_region_relations: Frozen>, location_map: Rc, ) -> Self { let universal_regions = &universal_region_relations.universal_regions; - let MirTypeckRegionConstraints { - placeholder_indices, - placeholder_index_to_region: _, + + let LoweredConstraints { + constraint_sccs, + definitions, + outlives_constraints, + scc_annotations, + type_tests, liveness_constraints, - mut outlives_constraints, - mut member_constraints, universe_causes, - type_tests, - } = constraints; + placeholder_indices, + member_constraints, + } = 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); - if let Some(guar) = universal_region_relations.universal_regions.tainted_by_errors() { - // Suppress unhelpful extra errors in `infer_opaque_types` by clearing out all - // outlives bounds that we may end up checking. - outlives_constraints = Default::default(); - member_constraints = Default::default(); - - // Also taint the entire scope. - infcx.set_tainted_by_errors(guar); - } - - let definitions = create_definitions(infcx, &universal_regions); - - let (constraint_sccs, scc_annotations) = - outlives_constraints.add_outlives_static(&universal_regions, &definitions); - let constraints = Frozen::freeze(outlives_constraints); - let constraint_graph = Frozen::freeze(constraints.graph(definitions.len())); + let constraint_graph = Frozen::freeze(outlives_constraints.graph(definitions.len())); if cfg!(debug_assertions) { sccs_info(infcx, &constraint_sccs); @@ -498,7 +351,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { let mut result = Self { definitions, liveness_constraints, - constraints, + constraints: Frozen::freeze(outlives_constraints), constraint_graph, constraint_sccs, scc_annotations, @@ -904,20 +757,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// in `scc_a`. Used during constraint propagation, and only once /// the value of `scc_b` has been computed. fn universe_compatible(&self, scc_b: ConstraintSccIndex, scc_a: ConstraintSccIndex) -> bool { - let a_annotation = self.scc_annotations[scc_a]; - let b_annotation = self.scc_annotations[scc_b]; - let a_universe = a_annotation.min_universe(); - - // If scc_b's declared universe is a subset of - // scc_a's declared universe (typically, both are ROOT), then - // it cannot contain any problematic universe elements. - if a_universe.can_name(b_annotation.min_universe()) { - return true; - } - - // Otherwise, there can be no placeholder in `b` with a too high - // universe index to name from `a`. - a_universe.can_name(b_annotation.max_placeholder_universe_reached) + self.scc_annotations[scc_a].universe_compatible_with(self.scc_annotations[scc_b]) } /// Once regions have been propagated, this method is used to see @@ -2269,17 +2109,17 @@ impl<'tcx> RegionInferenceContext<'tcx> { } impl<'tcx> RegionDefinition<'tcx> { - fn new(universe: ty::UniverseIndex, rv_origin: RegionVariableOrigin) -> Self { + pub(crate) fn new(rv_info: &RegionVariableInfo) -> Self { // Create a new region definition. Note that, for free // regions, the `external_name` field gets updated later in - // `init_free_and_bound_regions`. + // [[crate::eliminate_placeholders]]. - let origin = match rv_origin { + let origin = match rv_info.origin { RegionVariableOrigin::Nll(origin) => origin, _ => NllRegionVariableOrigin::Existential { from_forall: false }, }; - Self { origin, universe, external_name: None } + Self { origin, universe: rv_info.universe, external_name: None } } } From f455c4fc39bcc04d104bc440ebc1c23f183f20f9 Mon Sep 17 00:00:00 2001 From: Amanda Stjerna Date: Fri, 2 May 2025 10:50:03 +0200 Subject: [PATCH 2/2] Use an enum for SCC representatives, plus other code review Co-authored-by: lcnr --- ...placeholders.rs => handle_placeholders.rs} | 165 ++++++++---------- compiler/rustc_borrowck/src/lib.rs | 2 +- compiler/rustc_borrowck/src/nll.rs | 4 +- .../rustc_borrowck/src/polonius/legacy/mod.rs | 2 +- .../src/region_infer/dump_mir.rs | 2 +- .../rustc_borrowck/src/region_infer/mod.rs | 90 ++++++---- .../src/region_infer/opaque_types.rs | 2 +- 7 files changed, 131 insertions(+), 136 deletions(-) rename compiler/rustc_borrowck/src/{eliminate_placeholders.rs => handle_placeholders.rs} (70%) diff --git a/compiler/rustc_borrowck/src/eliminate_placeholders.rs b/compiler/rustc_borrowck/src/handle_placeholders.rs similarity index 70% rename from compiler/rustc_borrowck/src/eliminate_placeholders.rs rename to compiler/rustc_borrowck/src/handle_placeholders.rs index e8e1acbd7a5ad..aaaf2f45c869d 100644 --- a/compiler/rustc_borrowck/src/eliminate_placeholders.rs +++ b/compiler/rustc_borrowck/src/handle_placeholders.rs @@ -1,14 +1,13 @@ //! Logic for lowering higher-kinded outlives constraints //! (with placeholders and universes) and turn them into regular //! outlives constraints. -//! -//! This logic is provisional and should be removed once the trait -//! solver can handle this kind of constraint. + use rustc_data_structures::frozen::Frozen; -use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; +use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::graph::scc; use rustc_data_structures::graph::scc::Sccs; use rustc_index::IndexVec; +use rustc_infer::infer::RegionVariableOrigin; use rustc_middle::mir::ConstraintCategory; use rustc_middle::ty::{RegionVid, UniverseIndex}; use tracing::debug; @@ -18,7 +17,7 @@ use crate::consumers::OutlivesConstraint; use crate::diagnostics::UniverseInfo; use crate::member_constraints::MemberConstraintSet; use crate::region_infer::values::{LivenessValues, PlaceholderIndices}; -use crate::region_infer::{ConstraintSccs, RegionDefinition, TypeTest}; +use crate::region_infer::{ConstraintSccs, RegionDefinition, Representative, TypeTest}; use crate::ty::VarianceDiagInfo; use crate::type_check::free_region_relations::UniversalRegionRelations; use crate::type_check::{Locations, MirTypeckRegionConstraints}; @@ -32,7 +31,7 @@ pub(crate) struct LoweredConstraints<'tcx> { pub(crate) definitions: Frozen>>, pub(crate) scc_annotations: IndexVec, pub(crate) member_constraints: MemberConstraintSet<'tcx, RegionVid>, - pub(crate) outlives_constraints: OutlivesConstraintSet<'tcx>, + pub(crate) outlives_constraints: Frozen>, pub(crate) type_tests: Vec>, pub(crate) liveness_constraints: LivenessValues, pub(crate) universe_causes: FxIndexMap>, @@ -73,45 +72,35 @@ pub(crate) struct RegionTracker { /// This includes placeholders within this SCC. max_placeholder_universe_reached: UniverseIndex, - /// The smallest universe index reachable form the nodes of this SCC. - min_reachable_universe: UniverseIndex, - - /// The representative Region Variable Id for this SCC. We prefer - /// placeholders over existentially quantified variables, otherwise - /// it's the one with the smallest Region Variable ID. - pub(crate) representative: RegionVid, - - /// Is the current representative a placeholder? - representative_is_placeholder: bool, + /// The largest universe nameable from this SCC. + /// It is the smallest nameable universes of all + /// existential regions reachable from it. + max_nameable_universe: UniverseIndex, - /// Is the current representative existentially quantified? - representative_is_existential: bool, + /// The representative Region Variable Id for this SCC. + pub(crate) representative: Representative, } impl RegionTracker { pub(crate) fn new(rvid: RegionVid, definition: &RegionDefinition<'_>) -> Self { - let (representative_is_placeholder, representative_is_existential) = match definition.origin - { - NllRegionVariableOrigin::FreeRegion => (false, false), - NllRegionVariableOrigin::Placeholder(_) => (true, false), - NllRegionVariableOrigin::Existential { .. } => (false, true), - }; - let placeholder_universe = - if representative_is_placeholder { definition.universe } else { UniverseIndex::ROOT }; + if matches!(definition.origin, NllRegionVariableOrigin::Placeholder(_)) { + definition.universe + } else { + UniverseIndex::ROOT + }; Self { max_placeholder_universe_reached: placeholder_universe, - min_reachable_universe: definition.universe, - representative: rvid, - representative_is_placeholder, - representative_is_existential, + max_nameable_universe: definition.universe, + representative: Representative::new(rvid, definition), } } - /// The smallest-indexed universe reachable from and/or in this SCC. - pub(crate) fn min_universe(self) -> UniverseIndex { - self.min_reachable_universe + /// The largest universe this SCC can name. It's the smallest + /// largest nameable uninverse of any reachable region. + pub(crate) fn max_nameable_universe(self) -> UniverseIndex { + self.max_nameable_universe } fn merge_min_max_seen(&mut self, other: &Self) { @@ -120,40 +109,29 @@ impl RegionTracker { other.max_placeholder_universe_reached, ); - self.min_reachable_universe = - std::cmp::min(self.min_reachable_universe, other.min_reachable_universe); + self.max_nameable_universe = + std::cmp::min(self.max_nameable_universe, other.max_nameable_universe); } /// Returns `true` if during the annotated SCC reaches a placeholder - /// with a universe larger than the smallest reachable one, `false` otherwise. + /// with a universe larger than the smallest nameable universe of any + /// reachable existential region. pub(crate) fn has_incompatible_universes(&self) -> bool { - self.min_universe().cannot_name(self.max_placeholder_universe_reached) + self.max_nameable_universe().cannot_name(self.max_placeholder_universe_reached) } - /// Determine if the tracked universes of the two SCCs - /// are compatible. + /// Determine if the tracked universes of the two SCCs are compatible. pub(crate) fn universe_compatible_with(&self, other: Self) -> bool { - self.min_universe().can_name(other.min_universe()) - || self.min_universe().can_name(other.max_placeholder_universe_reached) + self.max_nameable_universe().can_name(other.max_nameable_universe()) + || self.max_nameable_universe().can_name(other.max_placeholder_universe_reached) } } impl scc::Annotation for RegionTracker { - fn merge_scc(mut self, mut other: Self) -> Self { - // Prefer any placeholder over any existential - if other.representative_is_placeholder && self.representative_is_existential { - other.merge_min_max_seen(&self); - return other; - } - - if self.representative_is_placeholder && other.representative_is_existential - || (self.representative <= other.representative) - { - self.merge_min_max_seen(&other); - return self; - } - other.merge_min_max_seen(&self); - other + fn merge_scc(mut self, other: Self) -> Self { + self.representative = self.representative.merge_scc(other.representative); + self.merge_min_max_seen(&other); + self } fn merge_reached(mut self, other: Self) -> Self { @@ -164,7 +142,7 @@ impl scc::Annotation for RegionTracker { } /// Determines if the region variable definitions contain -/// placeholers, and compute them for later use. +/// placeholders, and compute them for later use. fn region_definitions<'tcx>( universal_regions: &UniversalRegions<'tcx>, infcx: &BorrowckInferCtxt<'tcx>, @@ -177,12 +155,19 @@ fn region_definitions<'tcx>( let mut has_placeholders = false; for info in var_infos.iter() { - let definition = RegionDefinition::new(info); - has_placeholders |= matches!(definition.origin, NllRegionVariableOrigin::Placeholder(_)); + let origin = match info.origin { + RegionVariableOrigin::Nll(origin) => origin, + _ => NllRegionVariableOrigin::Existential { from_forall: false }, + }; + + 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() { debug!("region {:?} has external name {:?}", variable, external_name); definitions[variable].external_name = Some(external_name); @@ -190,29 +175,19 @@ fn region_definitions<'tcx>( (Frozen::freeze(definitions), has_placeholders) } -/// This method handles Universe errors by rewriting the constraint +/// This method handles placeholders by rewriting the constraint /// graph. For each strongly connected component in the constraint /// graph such that there is a series of constraints /// A: B: C: ... : X where -/// A's universe is smaller than X's and A is a placeholder, +/// A contains a placeholder whose universe cannot be named by X, /// add a constraint that A: 'static. This is a safe upper bound /// in the face of borrow checker/trait solver limitations that will /// eventually go away. /// /// For a more precise definition, see the documentation for -/// [`RegionTracker`] and its methods!. -/// -/// Since universes can also be involved in errors (if one placeholder -/// transitively outlives another), this function also flags those. -/// -/// Additionally, it similarly rewrites type-tests. -/// -/// This edge case used to be handled during constraint propagation -/// by iterating over the strongly connected components in the constraint -/// graph while maintaining a set of bookkeeping mappings similar -/// to what is stored in `RegionTracker` and manually adding 'sttaic as -/// needed. +/// [`RegionTracker`] and its methods! /// +/// This edge case used to be handled during constraint propagation. /// It was rewritten as part of the Polonius project with the goal of moving /// higher-kindedness concerns out of the path of the borrow checker, /// for two reasons: @@ -228,7 +203,7 @@ fn region_definitions<'tcx>( /// This code is a stop-gap measure in preparation for the future trait solver. /// /// Every constraint added by this method is an internal `IllegalUniverse` constraint. -pub(crate) fn rewrite_higher_kinded_outlives_as_constraints<'tcx>( +pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>( constraints: MirTypeckRegionConstraints<'tcx>, universal_region_relations: &Frozen>, infcx: &BorrowckInferCtxt<'tcx>, @@ -267,13 +242,14 @@ pub(crate) fn rewrite_higher_kinded_outlives_as_constraints<'tcx>( ) }; + let mut scc_annotations = SccAnnotations::init(&definitions); + let constraint_sccs = compute_sccs(&outlives_constraints, &mut scc_annotations); + // This code structure is a bit convoluted because it allows for a planned // future change where the early return here has a different type of annotation // that does much less work. if !has_placeholders { debug!("No placeholder regions found; skipping rewriting logic!"); - let mut scc_annotations = SccAnnotations::init(&definitions); - let constraint_sccs = compute_sccs(&outlives_constraints, &mut scc_annotations); return LoweredConstraints { type_tests, @@ -281,7 +257,7 @@ pub(crate) fn rewrite_higher_kinded_outlives_as_constraints<'tcx>( constraint_sccs, scc_annotations: scc_annotations.scc_to_annotation, definitions, - outlives_constraints, + outlives_constraints: Frozen::freeze(outlives_constraints), liveness_constraints, universe_causes, placeholder_indices, @@ -289,14 +265,14 @@ pub(crate) fn rewrite_higher_kinded_outlives_as_constraints<'tcx>( } debug!("Placeholders present; activating placeholder handling logic!"); - let mut annotations = SccAnnotations::init(&definitions); - let sccs = compute_sccs(&outlives_constraints, &mut annotations); - - let outlives_static = - rewrite_outlives(&sccs, &annotations, fr_static, &mut outlives_constraints); + let added_constraints = rewrite_placeholder_outlives( + &constraint_sccs, + &scc_annotations, + fr_static, + &mut outlives_constraints, + ); - let (sccs, scc_annotations) = if !outlives_static.is_empty() { - debug!("The following SCCs had :'static constraints added: {:?}", outlives_static); + let (constraint_sccs, scc_annotations) = if added_constraints { let mut annotations = SccAnnotations::init(&definitions); // We changed the constraint set and so must recompute SCCs. @@ -307,15 +283,15 @@ pub(crate) fn rewrite_higher_kinded_outlives_as_constraints<'tcx>( } else { // If we didn't add any back-edges; no more work needs doing debug!("No constraints rewritten!"); - (sccs, annotations.scc_to_annotation) + (constraint_sccs, scc_annotations.scc_to_annotation) }; LoweredConstraints { - constraint_sccs: sccs, + constraint_sccs, definitions, scc_annotations, member_constraints, - outlives_constraints, + outlives_constraints: Frozen::freeze(outlives_constraints), type_tests, liveness_constraints, universe_causes, @@ -323,15 +299,15 @@ pub(crate) fn rewrite_higher_kinded_outlives_as_constraints<'tcx>( } } -fn rewrite_outlives<'tcx>( +fn rewrite_placeholder_outlives<'tcx>( sccs: &Sccs, annotations: &SccAnnotations<'_, '_, RegionTracker>, fr_static: RegionVid, outlives_constraints: &mut OutlivesConstraintSet<'tcx>, -) -> FxHashSet { - // Changed to `true` if we added any constraints to `self` and need to +) -> bool { + // Changed to `true` if we added any constraints and need to // recompute SCCs. - let mut outlives_static = FxHashSet::default(); + let mut added_constraints = false; let annotations = &annotations.scc_to_annotation; @@ -354,9 +330,8 @@ fn rewrite_outlives<'tcx>( // needed for correctness, since an SCC upstream of another with // a universe violation will "infect" its downstream SCCs to also // outlive static. - outlives_static.insert(scc); let scc_representative_outlives_static = OutlivesConstraint { - sup: annotation.representative, + sup: annotation.representative.rvid(), sub: fr_static, category: ConstraintCategory::IllegalUniverse, locations: Locations::All(rustc_span::DUMMY_SP), @@ -365,7 +340,9 @@ fn rewrite_outlives<'tcx>( from_closure: false, }; outlives_constraints.push(scc_representative_outlives_static); + added_constraints = true; + debug!("Added {:?}: 'static!", annotation.representative.rvid()); } } - outlives_static + added_constraints } diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index edf5494e8867c..8c4d5c0debee0 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -75,7 +75,7 @@ mod constraints; mod dataflow; mod def_use; mod diagnostics; -mod eliminate_placeholders; +mod handle_placeholders; mod member_constraints; mod nll; mod path_utils; diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index 9b1a5df8d06b9..66ab39476b111 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -22,7 +22,7 @@ use tracing::{debug, instrument}; use crate::borrow_set::BorrowSet; use crate::consumers::ConsumerOptions; use crate::diagnostics::RegionErrors; -use crate::eliminate_placeholders::rewrite_higher_kinded_outlives_as_constraints; +use crate::handle_placeholders::compute_sccs_applying_placeholder_outlives_constraints; use crate::polonius::PoloniusDiagnosticsContext; use crate::polonius::legacy::{ PoloniusFacts, PoloniusFactsExt, PoloniusLocationTable, PoloniusOutput, @@ -118,7 +118,7 @@ pub(crate) fn compute_regions<'a, 'tcx>( Rc::clone(&location_map), ); - let lowered_constraints = rewrite_higher_kinded_outlives_as_constraints( + let lowered_constraints = compute_sccs_applying_placeholder_outlives_constraints( constraints, &universal_region_relations, infcx, diff --git a/compiler/rustc_borrowck/src/polonius/legacy/mod.rs b/compiler/rustc_borrowck/src/polonius/legacy/mod.rs index 67da43ca57887..05fd6e39476b2 100644 --- a/compiler/rustc_borrowck/src/polonius/legacy/mod.rs +++ b/compiler/rustc_borrowck/src/polonius/legacy/mod.rs @@ -13,7 +13,7 @@ use tracing::debug; use crate::borrow_set::BorrowSet; use crate::constraints::OutlivesConstraint; -use crate::eliminate_placeholders::LoweredConstraints; +use crate::handle_placeholders::LoweredConstraints; use crate::type_check::free_region_relations::UniversalRegionRelations; use crate::universal_regions::UniversalRegions; diff --git a/compiler/rustc_borrowck/src/region_infer/dump_mir.rs b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs index ef3d6309c19c2..a9ab30fd8fa36 100644 --- a/compiler/rustc_borrowck/src/region_infer/dump_mir.rs +++ b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs @@ -46,7 +46,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { "| {r:rw$?} | {ui:4?} | {v}", r = region, rw = REGION_WIDTH, - ui = self.region_universe(region), + ui = self.max_nameable_universe(self.constraint_sccs.scc(region)), v = self.region_value_str(region), )?; } diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index cfad071460b42..dcb09d5be140d 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -4,15 +4,13 @@ use std::rc::Rc; use rustc_data_structures::binary_search_util; use rustc_data_structures::frozen::Frozen; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; -use rustc_data_structures::graph::scc::Sccs; +use rustc_data_structures::graph::scc::{self, Sccs}; use rustc_errors::Diag; use rustc_hir::def_id::CRATE_DEF_ID; use rustc_index::IndexVec; use rustc_infer::infer::outlives::test_type_match; -use rustc_infer::infer::region_constraints::{ - GenericKind, RegionVariableInfo, VerifyBound, VerifyIfEq, -}; -use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin, RegionVariableOrigin}; +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, @@ -29,7 +27,7 @@ use crate::constraints::graph::{self, NormalConstraintGraph, RegionGraph}; use crate::constraints::{ConstraintSccIndex, OutlivesConstraint, OutlivesConstraintSet}; use crate::dataflow::BorrowIndex; use crate::diagnostics::{RegionErrorKind, RegionErrors, UniverseInfo}; -use crate::eliminate_placeholders::{LoweredConstraints, RegionTracker}; +use crate::handle_placeholders::{LoweredConstraints, RegionTracker}; use crate::member_constraints::{MemberConstraintSet, NllMemberConstraintIndex}; use crate::polonius::LiveLoans; use crate::polonius::legacy::PoloniusOutput; @@ -50,6 +48,47 @@ mod reverse_sccs; pub(crate) mod values; +/// The representative region variable for an SCC, tagged by its origin. +/// We prefer placeholders over existentially quantified variables, otherwise +/// it's the one with the smallest Region Variable ID. In other words, +/// the order of this enumeration really matters! +#[derive(Copy, Debug, Clone, PartialEq, PartialOrd, Eq, Ord)] +pub(crate) enum Representative { + FreeRegion(RegionVid), + Placeholder(RegionVid), + Existential(RegionVid), +} + +impl Representative { + pub(crate) fn rvid(self) -> RegionVid { + match self { + Representative::FreeRegion(region_vid) + | Representative::Placeholder(region_vid) + | Representative::Existential(region_vid) => region_vid, + } + } + + pub(crate) fn new(r: RegionVid, definition: &RegionDefinition<'_>) -> Self { + match definition.origin { + NllRegionVariableOrigin::FreeRegion => Representative::FreeRegion(r), + NllRegionVariableOrigin::Placeholder(_) => Representative::Placeholder(r), + NllRegionVariableOrigin::Existential { .. } => Representative::Existential(r), + } + } +} + +impl scc::Annotation for Representative { + fn merge_scc(self, other: Self) -> Self { + // Just pick the smallest one. Note that we order by tag first! + std::cmp::min(self, other) + } + + // For reachability, we do nothing since the representative doesn't change. + fn merge_reached(self, _other: Self) -> Self { + self + } +} + pub(crate) type ConstraintSccs = Sccs; pub struct RegionInferenceContext<'tcx> { @@ -351,7 +390,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { let mut result = Self { definitions, liveness_constraints, - constraints: Frozen::freeze(outlives_constraints), + constraints: outlives_constraints, constraint_graph, constraint_sccs, scc_annotations, @@ -510,11 +549,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { self.scc_values.placeholders_contained_in(scc) } - /// Returns access to the value of `r` for debugging purposes. - pub(crate) fn region_universe(&self, r: RegionVid) -> ty::UniverseIndex { - self.scc_universe(self.constraint_sccs.scc(r)) - } - /// Once region solving has completed, this function will return the member constraints that /// were applied to the value of a given SCC `scc`. See `AppliedMemberConstraint`. pub(crate) fn applied_member_constraints( @@ -681,7 +715,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // If the member region lives in a higher universe, we currently choose // the most conservative option by leaving it unchanged. - if !self.scc_universe(scc).is_root() { + if !self.max_nameable_universe(scc).is_root() { return; } @@ -861,7 +895,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { "lower_bound = {:?} r_scc={:?} universe={:?}", lower_bound, r_scc, - self.scc_universe(r_scc) + self.max_nameable_universe(r_scc) ); // If the type test requires that `T: 'a` where `'a` is a // placeholder from another universe, that effectively requires @@ -1339,10 +1373,9 @@ impl<'tcx> RegionInferenceContext<'tcx> { } } - /// The minimum universe of any variable reachable from this - /// SCC, inside or outside of it. - fn scc_universe(&self, scc: ConstraintSccIndex) -> UniverseIndex { - self.scc_annotations[scc].min_universe() + /// 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() } /// Checks the final value for the free region `fr` to see if it @@ -1364,7 +1397,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // Because this free region must be in the ROOT universe, we // know it cannot contain any bound universes. - assert!(self.scc_universe(longer_fr_scc).is_root()); + 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 @@ -1755,7 +1788,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { #[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.region_universe(fr1)); + trace!(universe = ?self.max_nameable_universe(self.constraint_sccs.scc(fr1))); self.find_constraint_paths_between_regions(fr1, |r| { // First look for some `r` such that `fr1: r` and `r` is live at `location` trace!(?r, liveness_constraints=?self.liveness_constraints.pretty_print_live_points(r)); @@ -2086,7 +2119,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// 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 + self.scc_annotations[scc].representative.rvid() } pub(crate) fn liveness_constraints(&self) -> &LivenessValues { @@ -2108,21 +2141,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { } } -impl<'tcx> RegionDefinition<'tcx> { - pub(crate) fn new(rv_info: &RegionVariableInfo) -> Self { - // Create a new region definition. Note that, for free - // regions, the `external_name` field gets updated later in - // [[crate::eliminate_placeholders]]. - - let origin = match rv_info.origin { - RegionVariableOrigin::Nll(origin) => origin, - _ => NllRegionVariableOrigin::Existential { from_forall: false }, - }; - - Self { origin, universe: rv_info.universe, external_name: None } - } -} - #[derive(Clone, Debug)] pub(crate) struct BlameConstraint<'tcx> { pub category: ConstraintCategory<'tcx>, diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs index 550c57338d301..3c05fa2ae0fa4 100644 --- a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs @@ -191,7 +191,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { let scc = self.constraint_sccs.scc(vid); // Special handling of higher-ranked regions. - if !self.scc_universe(scc).is_root() { + if !self.max_nameable_universe(scc).is_root() { match self.scc_values.placeholders_contained_in(scc).enumerate().last() { // If the region contains a single placeholder then they're equal. Some((0, placeholder)) => {