Skip to content

Commit 67f0d43

Browse files
committed
Auto merge of rust-lang#123720 - amandasystems:dyn-enable-refactor, r=nikomatsakis
Rewrite handling of universe-leaking placeholder regions into outlives constraints This commit prepares for Polonius by moving handling of leak check/universe errors out of the inference step by rewriting any universe error into an outlives-static constraint. This variant is a work in progress but seems to pass most tests. Note that a few debug assertions no longer hold; a few extra eyes on those changes are appreciated!
2 parents 6292b2a + 9be3a3d commit 67f0d43

File tree

4 files changed

+140
-73
lines changed

4 files changed

+140
-73
lines changed

compiler/rustc_borrowck/src/constraints/mod.rs

+106
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
use crate::region_infer::{ConstraintSccs, RegionDefinition, RegionTracker};
12
use crate::type_check::Locations;
3+
use crate::universal_regions::UniversalRegions;
24
use rustc_index::{IndexSlice, IndexVec};
35
use rustc_middle::mir::ConstraintCategory;
46
use rustc_middle::ty::{RegionVid, TyCtxt, VarianceDiagInfo};
@@ -48,6 +50,110 @@ impl<'tcx> OutlivesConstraintSet<'tcx> {
4850
) -> &IndexSlice<OutlivesConstraintIndex, OutlivesConstraint<'tcx>> {
4951
&self.outlives
5052
}
53+
54+
/// Computes cycles (SCCs) in the graph of regions. In particular,
55+
/// find all regions R1, R2 such that R1: R2 and R2: R1 and group
56+
/// them into an SCC, and find the relationships between SCCs.
57+
pub(crate) fn compute_sccs(
58+
&self,
59+
static_region: RegionVid,
60+
definitions: &IndexVec<RegionVid, RegionDefinition<'tcx>>,
61+
) -> ConstraintSccs {
62+
let constraint_graph = self.graph(definitions.len());
63+
let region_graph = &constraint_graph.region_graph(self, static_region);
64+
ConstraintSccs::new_with_annotation(&region_graph, |r| {
65+
RegionTracker::new(r, &definitions[r])
66+
})
67+
}
68+
69+
/// This method handles Universe errors by rewriting the constraint
70+
/// graph. For each strongly connected component in the constraint
71+
/// graph such that there is a series of constraints
72+
/// A: B: C: ... : X where
73+
/// A's universe is smaller than X's and A is a placeholder,
74+
/// add a constraint that A: 'static. This is a safe upper bound
75+
/// in the face of borrow checker/trait solver limitations that will
76+
/// eventually go away.
77+
///
78+
/// For a more precise definition, see the documentation for
79+
/// [`RegionTracker::has_incompatible_universes()`].
80+
///
81+
/// This edge case used to be handled during constraint propagation
82+
/// by iterating over the strongly connected components in the constraint
83+
/// graph while maintaining a set of bookkeeping mappings similar
84+
/// to what is stored in `RegionTracker` and manually adding 'sttaic as
85+
/// needed.
86+
///
87+
/// It was rewritten as part of the Polonius project with the goal of moving
88+
/// higher-kindedness concerns out of the path of the borrow checker,
89+
/// for two reasons:
90+
///
91+
/// 1. Implementing Polonius is difficult enough without also
92+
/// handling them.
93+
/// 2. The long-term goal is to handle higher-kinded concerns
94+
/// in the trait solver, where they belong. This avoids
95+
/// logic duplication and allows future trait solvers
96+
/// to compute better bounds than for example our
97+
/// "must outlive 'static" here.
98+
///
99+
/// This code is a stop-gap measure in preparation for the future trait solver.
100+
///
101+
/// Every constraint added by this method is an
102+
/// internal `IllegalUniverse` constraint.
103+
#[instrument(skip(self, universal_regions, definitions))]
104+
pub(crate) fn add_outlives_static(
105+
&mut self,
106+
universal_regions: &UniversalRegions<'tcx>,
107+
definitions: &IndexVec<RegionVid, RegionDefinition<'tcx>>,
108+
) -> ConstraintSccs {
109+
let fr_static = universal_regions.fr_static;
110+
let sccs = self.compute_sccs(fr_static, definitions);
111+
112+
// Changed to `true` if we added any constraints to `self` and need to
113+
// recompute SCCs.
114+
let mut added_constraints = false;
115+
116+
for scc in sccs.all_sccs() {
117+
// No point in adding 'static: 'static!
118+
// This micro-optimisation makes somewhat sense
119+
// because static outlives *everything*.
120+
if scc == sccs.scc(fr_static) {
121+
continue;
122+
}
123+
124+
let annotation = sccs.annotation(scc);
125+
126+
// If this SCC participates in a universe violation,
127+
// e.g. if it reaches a region with a universe smaller than
128+
// the largest region reached, add a requirement that it must
129+
// outlive `'static`.
130+
if annotation.has_incompatible_universes() {
131+
// Optimisation opportunity: this will add more constraints than
132+
// needed for correctness, since an SCC upstream of another with
133+
// a universe violation will "infect" its downstream SCCs to also
134+
// outlive static.
135+
added_constraints = true;
136+
let scc_representative_outlives_static = OutlivesConstraint {
137+
sup: annotation.representative,
138+
sub: fr_static,
139+
category: ConstraintCategory::IllegalUniverse,
140+
locations: Locations::All(rustc_span::DUMMY_SP),
141+
span: rustc_span::DUMMY_SP,
142+
variance_info: VarianceDiagInfo::None,
143+
from_closure: false,
144+
};
145+
self.push(scc_representative_outlives_static);
146+
}
147+
}
148+
149+
if added_constraints {
150+
// We changed the constraint set and so must recompute SCCs.
151+
self.compute_sccs(fr_static, definitions)
152+
} else {
153+
// If we didn't add any back-edges; no more work needs doing
154+
sccs
155+
}
156+
}
51157
}
52158

53159
impl<'tcx> Index<OutlivesConstraintIndex> for OutlivesConstraintSet<'tcx> {

compiler/rustc_borrowck/src/diagnostics/region_errors.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ impl<'tcx> ConstraintDescription for ConstraintCategory<'tcx> {
6666
ConstraintCategory::Predicate(_)
6767
| ConstraintCategory::Boring
6868
| ConstraintCategory::BoringNoLocation
69-
| ConstraintCategory::Internal => "",
69+
| ConstraintCategory::Internal
70+
| ConstraintCategory::IllegalUniverse => "",
7071
}
7172
}
7273
}

compiler/rustc_borrowck/src/region_infer/mod.rs

+29-72
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ pub struct RegionTracker {
6262
/// The representative Region Variable Id for this SCC. We prefer
6363
/// placeholders over existentially quantified variables, otherwise
6464
/// it's the one with the smallest Region Variable ID.
65-
representative: RegionVid,
65+
pub(crate) representative: RegionVid,
6666

6767
/// Is the current representative a placeholder?
6868
representative_is_placeholder: bool,
@@ -97,7 +97,7 @@ impl scc::Annotation for RegionTracker {
9797
}
9898

9999
impl RegionTracker {
100-
fn new(rvid: RegionVid, definition: &RegionDefinition<'_>) -> Self {
100+
pub(crate) fn new(rvid: RegionVid, definition: &RegionDefinition<'_>) -> Self {
101101
let (representative_is_placeholder, representative_is_existential) = match definition.origin
102102
{
103103
rustc_infer::infer::NllRegionVariableOrigin::FreeRegion => (false, false),
@@ -116,7 +116,9 @@ impl RegionTracker {
116116
representative_is_existential,
117117
}
118118
}
119-
fn universe(self) -> UniverseIndex {
119+
120+
/// The smallest-indexed universe reachable from and/or in this SCC.
121+
fn min_universe(self) -> UniverseIndex {
120122
self.min_reachable_universe
121123
}
122124

@@ -132,8 +134,8 @@ impl RegionTracker {
132134

133135
/// Returns `true` if during the annotated SCC reaches a placeholder
134136
/// with a universe larger than the smallest reachable one, `false` otherwise.
135-
pub fn has_incompatible_universes(&self) -> bool {
136-
self.universe().cannot_name(self.max_placeholder_universe_reached)
137+
pub(crate) fn has_incompatible_universes(&self) -> bool {
138+
self.min_universe().cannot_name(self.max_placeholder_universe_reached)
137139
}
138140
}
139141

@@ -163,7 +165,7 @@ pub struct RegionInferenceContext<'tcx> {
163165
/// The SCC computed from `constraints` and the constraint
164166
/// graph. We have an edge from SCC A to SCC B if `A: B`. Used to
165167
/// compute the values of each region.
166-
constraint_sccs: Rc<ConstraintSccs>,
168+
constraint_sccs: ConstraintSccs,
167169

168170
/// Reverse of the SCC constraint graph -- i.e., an edge `A -> B` exists if
169171
/// `B: A`. This is used to compute the universal regions that are required
@@ -401,7 +403,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
401403
universal_regions: Rc<UniversalRegions<'tcx>>,
402404
placeholder_indices: Rc<PlaceholderIndices>,
403405
universal_region_relations: Frozen<UniversalRegionRelations<'tcx>>,
404-
outlives_constraints: OutlivesConstraintSet<'tcx>,
406+
mut outlives_constraints: OutlivesConstraintSet<'tcx>,
405407
member_constraints_in: MemberConstraintSet<'tcx, RegionVid>,
406408
universe_causes: FxIndexMap<ty::UniverseIndex, UniverseInfo<'tcx>>,
407409
type_tests: Vec<TypeTest<'tcx>>,
@@ -419,17 +421,10 @@ impl<'tcx> RegionInferenceContext<'tcx> {
419421
.map(|info| RegionDefinition::new(info.universe, info.origin))
420422
.collect();
421423

422-
let fr_static = universal_regions.fr_static;
424+
let constraint_sccs =
425+
outlives_constraints.add_outlives_static(&universal_regions, &definitions);
423426
let constraints = Frozen::freeze(outlives_constraints);
424427
let constraint_graph = Frozen::freeze(constraints.graph(definitions.len()));
425-
let constraint_sccs = {
426-
let constraint_graph = constraints.graph(definitions.len());
427-
let region_graph = &constraint_graph.region_graph(&constraints, fr_static);
428-
let sccs = ConstraintSccs::new_with_annotation(&region_graph, |r| {
429-
RegionTracker::new(r, &definitions[r])
430-
});
431-
Rc::new(sccs)
432-
};
433428

434429
if cfg!(debug_assertions) {
435430
sccs_info(infcx, &constraint_sccs);
@@ -548,21 +543,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
548543
}
549544

550545
NllRegionVariableOrigin::Placeholder(placeholder) => {
551-
// Each placeholder region is only visible from
552-
// its universe `ui` and its extensions. So we
553-
// can't just add it into `scc` unless the
554-
// universe of the scc can name this region.
555-
let scc_universe = self.scc_universe(scc);
556-
if scc_universe.can_name(placeholder.universe) {
557-
self.scc_values.add_element(scc, placeholder);
558-
} else {
559-
debug!(
560-
"init_free_and_bound_regions: placeholder {:?} is \
561-
not compatible with universe {:?} of its SCC {:?}",
562-
placeholder, scc_universe, scc,
563-
);
564-
self.add_incompatible_universe(scc);
565-
}
546+
self.scc_values.add_element(scc, placeholder);
566547
}
567548

568549
NllRegionVariableOrigin::Existential { .. } => {
@@ -744,23 +725,10 @@ impl<'tcx> RegionInferenceContext<'tcx> {
744725
/// (which is assured by iterating over SCCs in dependency order).
745726
#[instrument(skip(self), level = "debug")]
746727
fn compute_value_for_scc(&mut self, scc_a: ConstraintSccIndex) {
747-
let constraint_sccs = self.constraint_sccs.clone();
748-
749728
// Walk each SCC `B` such that `A: B`...
750-
for &scc_b in constraint_sccs.successors(scc_a) {
729+
for &scc_b in self.constraint_sccs.successors(scc_a) {
751730
debug!(?scc_b);
752-
753-
// ...and add elements from `B` into `A`. One complication
754-
// arises because of universes: If `B` contains something
755-
// that `A` cannot name, then `A` can only contain `B` if
756-
// it outlives static.
757-
if self.universe_compatible(scc_b, scc_a) {
758-
// `A` can name everything that is in `B`, so just
759-
// merge the bits.
760-
self.scc_values.add_region(scc_a, scc_b);
761-
} else {
762-
self.add_incompatible_universe(scc_a);
763-
}
731+
self.scc_values.add_region(scc_a, scc_b);
764732
}
765733

766734
// Now take member constraints into account.
@@ -814,7 +782,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
814782
// If the member region lives in a higher universe, we currently choose
815783
// the most conservative option by leaving it unchanged.
816784

817-
if !self.constraint_sccs().annotation(scc).universe().is_root() {
785+
if !self.constraint_sccs().annotation(scc).min_universe().is_root() {
818786
return;
819787
}
820788

@@ -886,35 +854,20 @@ impl<'tcx> RegionInferenceContext<'tcx> {
886854
/// in `scc_a`. Used during constraint propagation, and only once
887855
/// the value of `scc_b` has been computed.
888856
fn universe_compatible(&self, scc_b: ConstraintSccIndex, scc_a: ConstraintSccIndex) -> bool {
889-
let universe_a = self.constraint_sccs().annotation(scc_a).universe();
890-
let universe_b = self.constraint_sccs().annotation(scc_b).universe();
857+
let a_annotation = self.constraint_sccs().annotation(scc_a);
858+
let b_annotation = self.constraint_sccs().annotation(scc_b);
859+
let a_universe = a_annotation.min_universe();
891860

892-
// Quick check: if scc_b's declared universe is a subset of
861+
// If scc_b's declared universe is a subset of
893862
// scc_a's declared universe (typically, both are ROOT), then
894863
// it cannot contain any problematic universe elements.
895-
if universe_a.can_name(universe_b) {
864+
if a_universe.can_name(b_annotation.min_universe()) {
896865
return true;
897866
}
898867

899-
// Otherwise, we have to iterate over the universe elements in
900-
// B's value, and check whether all of them are nameable
901-
// from universe_a
902-
self.scc_values.placeholders_contained_in(scc_b).all(|p| universe_a.can_name(p.universe))
903-
}
904-
905-
/// Extend `scc` so that it can outlive some placeholder region
906-
/// from a universe it can't name; at present, the only way for
907-
/// this to be true is if `scc` outlives `'static`. This is
908-
/// actually stricter than necessary: ideally, we'd support bounds
909-
/// like `for<'a: 'b>` that might then allow us to approximate
910-
/// `'a` with `'b` and not `'static`. But it will have to do for
911-
/// now.
912-
fn add_incompatible_universe(&mut self, scc: ConstraintSccIndex) {
913-
debug!("add_incompatible_universe(scc={:?})", scc);
914-
915-
let fr_static = self.universal_regions.fr_static;
916-
self.scc_values.add_all_points(scc);
917-
self.scc_values.add_element(scc, fr_static);
868+
// Otherwise, there can be no placeholder in `b` with a too high
869+
// universe index to name from `a`.
870+
a_universe.can_name(b_annotation.max_placeholder_universe_reached)
918871
}
919872

920873
/// Once regions have been propagated, this method is used to see
@@ -1022,7 +975,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
1022975
"lower_bound = {:?} r_scc={:?} universe={:?}",
1023976
lower_bound,
1024977
r_scc,
1025-
self.constraint_sccs.annotation(r_scc).universe()
978+
self.constraint_sccs.annotation(r_scc).min_universe()
1026979
);
1027980

1028981
// If the type test requires that `T: 'a` where `'a` is a
@@ -1539,7 +1492,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
15391492
/// The minimum universe of any variable reachable from this
15401493
/// SCC, inside or outside of it.
15411494
fn scc_universe(&self, scc: ConstraintSccIndex) -> UniverseIndex {
1542-
self.constraint_sccs().annotation(scc).universe()
1495+
self.constraint_sccs().annotation(scc).min_universe()
15431496
}
15441497
/// Checks the final value for the free region `fr` to see if it
15451498
/// grew too large. In particular, examine what `end(X)` points
@@ -1896,6 +1849,10 @@ impl<'tcx> RegionInferenceContext<'tcx> {
18961849

18971850
// This loop can be hot.
18981851
for constraint in outgoing_edges_from_graph {
1852+
if matches!(constraint.category, ConstraintCategory::IllegalUniverse) {
1853+
debug!("Ignoring illegal universe constraint: {constraint:?}");
1854+
continue;
1855+
}
18991856
handle_constraint(constraint);
19001857
}
19011858

compiler/rustc_middle/src/mir/query.rs

+3
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,9 @@ pub enum ConstraintCategory<'tcx> {
274274

275275
/// A constraint that doesn't correspond to anything the user sees.
276276
Internal,
277+
278+
/// An internal constraint derived from an illegal universe relation.
279+
IllegalUniverse,
277280
}
278281

279282
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]

0 commit comments

Comments
 (0)