//! Logic for lowering higher-kinded outlives constraints //! (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; use rustc_infer::infer::RegionVariableOrigin; 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, Representative, 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: Frozen>, 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 largest universe nameable from this SCC. /// It is the smallest nameable universes of all /// existential regions reachable from it. max_nameable_universe: UniverseIndex, /// The representative Region Variable Id for this SCC. pub(crate) representative: Representative, } impl RegionTracker { pub(crate) fn new(rvid: RegionVid, definition: &RegionDefinition<'_>) -> Self { let placeholder_universe = if matches!(definition.origin, NllRegionVariableOrigin::Placeholder(_)) { definition.universe } else { UniverseIndex::ROOT }; Self { max_placeholder_universe_reached: placeholder_universe, max_nameable_universe: definition.universe, representative: Representative::new(rvid, definition), } } /// 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 } pub(crate) fn max_placeholder_universe_reached(self) -> UniverseIndex { self.max_placeholder_universe_reached } 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.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 nameable universe of any /// reachable existential region. pub(crate) fn has_incompatible_universes(&self) -> bool { self.max_nameable_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.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, 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 { // No update to in-component values, only add seen values. self.merge_min_max_seen(&other); self } } /// Determines if the region variable definitions contain /// placeholders, 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 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); } (Frozen::freeze(definitions), has_placeholders) } /// 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 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! /// /// 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: /// /// 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 compute_sccs_applying_placeholder_outlives_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, ) }; 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!"); return LoweredConstraints { type_tests, member_constraints, 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!"); let added_constraints = rewrite_placeholder_outlives( &constraint_sccs, &scc_annotations, fr_static, &mut outlives_constraints, ); let (constraint_sccs, scc_annotations) = if added_constraints { 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!"); (constraint_sccs, scc_annotations.scc_to_annotation) }; LoweredConstraints { constraint_sccs, definitions, scc_annotations, member_constraints, outlives_constraints: Frozen::freeze(outlives_constraints), type_tests, liveness_constraints, universe_causes, placeholder_indices, } } fn rewrite_placeholder_outlives<'tcx>( sccs: &Sccs, annotations: &SccAnnotations<'_, '_, RegionTracker>, fr_static: RegionVid, outlives_constraints: &mut OutlivesConstraintSet<'tcx>, ) -> bool { // Changed to `true` if we added any constraints and need to // recompute SCCs. let mut added_constraints = false; 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. let scc_representative_outlives_static = OutlivesConstraint { sup: annotation.representative.rvid(), 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); added_constraints = true; debug!("Added {:?}: 'static!", annotation.representative.rvid()); } } added_constraints }