mirror of
https://github.com/rust-lang/rust.git
synced 2025-11-01 13:34:38 +00:00
349 lines
14 KiB
Rust
349 lines
14 KiB
Rust
//! 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<RegionVid, ConstraintSccIndex>,
|
|
pub(crate) definitions: Frozen<IndexVec<RegionVid, RegionDefinition<'tcx>>>,
|
|
pub(crate) scc_annotations: IndexVec<ConstraintSccIndex, RegionTracker>,
|
|
pub(crate) member_constraints: MemberConstraintSet<'tcx, RegionVid>,
|
|
pub(crate) outlives_constraints: Frozen<OutlivesConstraintSet<'tcx>>,
|
|
pub(crate) type_tests: Vec<TypeTest<'tcx>>,
|
|
pub(crate) liveness_constraints: LivenessValues,
|
|
pub(crate) universe_causes: FxIndexMap<UniverseIndex, UniverseInfo<'tcx>>,
|
|
pub(crate) placeholder_indices: PlaceholderIndices,
|
|
}
|
|
|
|
impl<'d, 'tcx, A: scc::Annotation> SccAnnotations<'d, 'tcx, A> {
|
|
pub(crate) fn init(definitions: &'d IndexVec<RegionVid, RegionDefinition<'tcx>>) -> 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<ConstraintSccIndex, A>,
|
|
definitions: &'d IndexVec<RegionVid, RegionDefinition<'tcx>>,
|
|
}
|
|
|
|
impl scc::Annotations<RegionVid> 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
|
|
}
|
|
|
|
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<IndexVec<RegionVid, RegionDefinition<'tcx>>>, 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<UniversalRegionRelations<'tcx>>,
|
|
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<RegionVid, ConstraintSccIndex>,
|
|
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
|
|
}
|