Skip to content

Commit f258cb4

Browse files
committed
Auto merge of rust-lang#130227 - amandasystems:remove-placeholders-completely, r=<try>
[WIP] Remove placeholders completely This PR does shotgun surgery on borrowck to remove all special handling of placeholders, completely replacing them with a preprocessing step that rewrites placeholder leaks into constraints, removing constraint propagation of placeholders and all logic used to detect placeholder violations during error reporting. This finishes what rust-lang#123720 started. The new method works like this: 1. during SCC construction, some information about SCC membership and reachability is retained 2. just after SCC construction, a constraint `r - (from: to_invalid) - > 'static` is added when `r` is the representative of an SCC and 1. that SCC either has had its universe shrunk because it reaches a region with a smaller one (in which case `to_invalid` is the smallest-universed region it reaches), or if it reaches a region with a too large universe that isn't part of the SCC (in which case `to_invalid` is the region with a too large universe). In either case, `from` is also `r`. 2. some region `reaches` in `r`'s SCC reaches another placeholder, `reached`, in which case the added constraint is `r -> (reaches: reached) 'static`. Through clever choice of defaults (chosing minimum elements), `reached` will be `r` if at all possible. When tracing errors for diagnostics one of these special constraints along a path are treated much like a HTTP redirect: if we are explaining `from: to` and reach an edge with `reaches: invalid` we stop the search and start following `reaches: invalid` instead. When doing this the implicit edges `x: 'static` for every region `x` are ignored, since the search would otherwise be able to cheat by going through 'static and re-find the same edge again. A bunch of optimisations are possible: - ~~Conservatively add constraints, e.g. one per SCC. May worsen error tracing!~~ - as a final pass, allow fusing the annotations for the SCC after adding the extra constraints to remove unnecessary information and save memory. This could be done cheaply since we already iterate over the entire set of SCCs. - currently, if constraints are added the entire set of SCCs are recomputed. This is of course rather wasteful, and we could do better. Especially since SCCs are added in dependency order. This would require a fully separate SCC module since the dynamic SCC combo we'd need now shares almost no properties with regular SCC computation. Given that this is meant to be a temporary work-around, that seems like too much work. There are a bunch of rather nice bonuses: - We now don't need to expose region indices in `MirTypeckRegionConstraints` to the entire crate. The only entry point is `placeholder_region()` so correctness of the indices is now guaranteed - A lot of things that were previously iterations over lists is now a single lookup - The constraint graph search functions are simple and at least one of them can now take a proper region as target rather than a predicate function. The only case that needs the predicate argument to `find_constraint_path_to()` is `find_sub_region_live_at()`, which may or may not be possible to work around. r​? nikomatsakis
2 parents 8549802 + 372912b commit f258cb4

File tree

18 files changed

+933
-769
lines changed

18 files changed

+933
-769
lines changed

compiler/rustc_borrowck/src/constraints/graph.rs

+15-5
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,17 @@ impl<D: ConstraintGraphDirection> ConstraintGraph<D> {
106106
}
107107

108108
/// Given a region `R`, iterate over all constraints `R: R1`.
109+
/// if `static_region` is `None`, do not yield implicit
110+
/// `'static -> a` edges.
109111
pub(crate) fn outgoing_edges<'a, 'tcx>(
110112
&'a self,
111113
region_sup: RegionVid,
112114
constraints: &'a OutlivesConstraintSet<'tcx>,
113-
static_region: RegionVid,
115+
static_region: Option<RegionVid>,
114116
) -> Edges<'a, 'tcx, D> {
115117
//if this is the `'static` region and the graph's direction is normal,
116118
//then setup the Edges iterator to return all regions #53178
117-
if region_sup == static_region && D::is_normal() {
119+
if Some(region_sup) == static_region && D::is_normal() {
118120
Edges {
119121
graph: self,
120122
constraints,
@@ -135,7 +137,7 @@ pub(crate) struct Edges<'a, 'tcx, D: ConstraintGraphDirection> {
135137
constraints: &'a OutlivesConstraintSet<'tcx>,
136138
pointer: Option<OutlivesConstraintIndex>,
137139
next_static_idx: Option<usize>,
138-
static_region: RegionVid,
140+
static_region: Option<RegionVid>,
139141
}
140142

141143
impl<'a, 'tcx, D: ConstraintGraphDirection> Iterator for Edges<'a, 'tcx, D> {
@@ -153,8 +155,12 @@ impl<'a, 'tcx, D: ConstraintGraphDirection> Iterator for Edges<'a, 'tcx, D> {
153155
Some(next_static_idx + 1)
154156
};
155157

158+
let Some(static_region) = self.static_region else {
159+
return None;
160+
};
161+
156162
Some(OutlivesConstraint {
157-
sup: self.static_region,
163+
sup: static_region,
158164
sub: next_static_idx.into(),
159165
locations: Locations::All(DUMMY_SP),
160166
span: DUMMY_SP,
@@ -194,7 +200,11 @@ impl<'a, 'tcx, D: ConstraintGraphDirection> RegionGraph<'a, 'tcx, D> {
194200
/// there exists a constraint `R: R1`.
195201
pub(crate) fn outgoing_regions(&self, region_sup: RegionVid) -> Successors<'a, 'tcx, D> {
196202
Successors {
197-
edges: self.constraint_graph.outgoing_edges(region_sup, self.set, self.static_region),
203+
edges: self.constraint_graph.outgoing_edges(
204+
region_sup,
205+
self.set,
206+
Some(self.static_region),
207+
),
198208
}
199209
}
200210
}

compiler/rustc_borrowck/src/constraints/mod.rs

+102-42
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
use std::fmt;
22
use std::ops::Index;
33

4+
use rustc_data_structures::fx::FxHashSet;
5+
use rustc_data_structures::graph::scc;
46
use rustc_index::{IndexSlice, IndexVec};
7+
use rustc_infer::infer::NllRegionVariableOrigin;
58
use rustc_middle::mir::ConstraintCategory;
69
use rustc_middle::ty::{RegionVid, TyCtxt, VarianceDiagInfo};
710
use rustc_span::Span;
811
use tracing::{debug, instrument};
912

10-
use crate::region_infer::{ConstraintSccs, RegionDefinition, RegionTracker};
13+
use crate::region_infer::{RegionDefinition, RegionTracker, SccAnnotations};
1114
use crate::type_check::Locations;
1215
use crate::universal_regions::UniversalRegions;
1316

@@ -57,16 +60,39 @@ impl<'tcx> OutlivesConstraintSet<'tcx> {
5760
/// Computes cycles (SCCs) in the graph of regions. In particular,
5861
/// find all regions R1, R2 such that R1: R2 and R2: R1 and group
5962
/// them into an SCC, and find the relationships between SCCs.
60-
pub(crate) fn compute_sccs(
63+
pub(crate) fn compute_sccs<
64+
A: scc::Annotation,
65+
AA: scc::Annotations<RegionVid, ConstraintSccIndex, A>,
66+
>(
6167
&self,
6268
static_region: RegionVid,
63-
definitions: &IndexVec<RegionVid, RegionDefinition<'tcx>>,
64-
) -> ConstraintSccs {
65-
let constraint_graph = self.graph(definitions.len());
69+
num_region_vars: usize,
70+
annotations: &mut AA,
71+
) -> scc::Sccs<RegionVid, ConstraintSccIndex> {
72+
let constraint_graph = self.graph(num_region_vars);
6673
let region_graph = &constraint_graph.region_graph(self, static_region);
67-
ConstraintSccs::new_with_annotation(&region_graph, |r| {
68-
RegionTracker::new(r, &definitions[r])
69-
})
74+
scc::Sccs::new_with_annotation(&region_graph, annotations)
75+
}
76+
77+
/// There is a placeholder violation; add a requirement
78+
/// that some SCC outlive static and explain which region
79+
/// reaching which other region caused that.
80+
fn add_placeholder_violation_constraint(
81+
&mut self,
82+
outlives_static: RegionVid,
83+
blame_from: RegionVid,
84+
blame_to: RegionVid,
85+
fr_static: RegionVid,
86+
) {
87+
self.push(OutlivesConstraint {
88+
sup: outlives_static,
89+
sub: fr_static,
90+
category: ConstraintCategory::IllegalPlaceholder(blame_from, blame_to),
91+
locations: Locations::All(rustc_span::DUMMY_SP),
92+
span: rustc_span::DUMMY_SP,
93+
variance_info: VarianceDiagInfo::None,
94+
from_closure: false,
95+
});
7096
}
7197

7298
/// This method handles Universe errors by rewriting the constraint
@@ -79,7 +105,7 @@ impl<'tcx> OutlivesConstraintSet<'tcx> {
79105
/// eventually go away.
80106
///
81107
/// For a more precise definition, see the documentation for
82-
/// [`RegionTracker::has_incompatible_universes()`].
108+
/// [`RegionTracker`] and its methods!.
83109
///
84110
/// This edge case used to be handled during constraint propagation
85111
/// by iterating over the strongly connected components in the constraint
@@ -104,57 +130,91 @@ impl<'tcx> OutlivesConstraintSet<'tcx> {
104130
/// Every constraint added by this method is an
105131
/// internal `IllegalUniverse` constraint.
106132
#[instrument(skip(self, universal_regions, definitions))]
107-
pub(crate) fn add_outlives_static(
133+
pub(crate) fn add_outlives_static<'d>(
108134
&mut self,
109135
universal_regions: &UniversalRegions<'tcx>,
110-
definitions: &IndexVec<RegionVid, RegionDefinition<'tcx>>,
111-
) -> ConstraintSccs {
136+
definitions: &'d IndexVec<RegionVid, RegionDefinition<'tcx>>,
137+
) -> (scc::Sccs<RegionVid, ConstraintSccIndex>, IndexVec<ConstraintSccIndex, RegionTracker>)
138+
{
112139
let fr_static = universal_regions.fr_static;
113-
let sccs = self.compute_sccs(fr_static, definitions);
140+
let mut annotations = SccAnnotations::init(definitions);
141+
let sccs = self.compute_sccs(fr_static, definitions.len(), &mut annotations);
114142

115-
// Changed to `true` if we added any constraints to `self` and need to
116-
// recompute SCCs.
117-
let mut added_constraints = false;
143+
// Is this SCC already outliving static directly or transitively?
144+
let mut outlives_static = FxHashSet::default();
118145

119146
for scc in sccs.all_sccs() {
120-
// No point in adding 'static: 'static!
121-
// This micro-optimisation makes somewhat sense
122-
// because static outlives *everything*.
147+
let annotation: RegionTracker = annotations.scc_to_annotation[scc];
123148
if scc == sccs.scc(fr_static) {
149+
// No use adding 'static: 'static.
124150
continue;
125151
}
126152

127-
let annotation = sccs.annotation(scc);
128-
129-
// If this SCC participates in a universe violation,
153+
// If this SCC participates in a universe violation
130154
// e.g. if it reaches a region with a universe smaller than
131155
// the largest region reached, add a requirement that it must
132-
// outlive `'static`.
133-
if annotation.has_incompatible_universes() {
134-
// Optimisation opportunity: this will add more constraints than
135-
// needed for correctness, since an SCC upstream of another with
136-
// a universe violation will "infect" its downstream SCCs to also
137-
// outlive static.
138-
added_constraints = true;
139-
let scc_representative_outlives_static = OutlivesConstraint {
140-
sup: annotation.representative,
141-
sub: fr_static,
142-
category: ConstraintCategory::IllegalUniverse,
143-
locations: Locations::All(rustc_span::DUMMY_SP),
144-
span: rustc_span::DUMMY_SP,
145-
variance_info: VarianceDiagInfo::None,
146-
from_closure: false,
147-
};
148-
self.push(scc_representative_outlives_static);
156+
// outlive `'static`. Here we get to know which reachable region
157+
// caused the violation.
158+
if let Some(to) = annotation.universe_violation() {
159+
outlives_static.insert(scc);
160+
self.add_placeholder_violation_constraint(
161+
annotation.representative_rvid(),
162+
annotation.representative_rvid(),
163+
to,
164+
fr_static,
165+
);
166+
}
167+
}
168+
169+
// Note: it's possible to sort this iterator by SCC and get dependency order,
170+
// which makes it easy to only add only one constraint per future cycle.
171+
// However, this worsens diagnostics and requires iterating over
172+
// all successors to determine if we outlive static transitively,
173+
// a cost you pay even if you have no placeholders.
174+
let placeholders_and_sccs =
175+
definitions.iter_enumerated().filter_map(|(rvid, definition)| {
176+
if matches!(definition.origin, NllRegionVariableOrigin::Placeholder { .. }) {
177+
Some((sccs.scc(rvid), rvid))
178+
} else {
179+
None
180+
}
181+
});
182+
183+
// The second kind of violation: a placeholder reaching another placeholder.
184+
for (scc, rvid) in placeholders_and_sccs {
185+
let annotation = annotations.scc_to_annotation[scc];
186+
187+
if sccs.scc(fr_static) == scc || outlives_static.contains(&scc) {
188+
debug!("{:?} already outlives (or is) static", annotation.representative_rvid());
189+
continue;
149190
}
191+
192+
if let Some(other_placeholder) = annotation.reaches_other_placeholder(rvid) {
193+
debug!(
194+
"Placeholder {rvid:?} of SCC {scc:?} reaches other placeholder {other_placeholder:?}"
195+
);
196+
outlives_static.insert(scc);
197+
self.add_placeholder_violation_constraint(
198+
annotation.representative_rvid(),
199+
rvid,
200+
other_placeholder,
201+
fr_static,
202+
);
203+
};
150204
}
151205

152-
if added_constraints {
206+
if !outlives_static.is_empty() {
207+
debug!("The following SCCs had :'static constraints added: {:?}", outlives_static);
208+
let mut annotations = SccAnnotations::init(definitions);
209+
153210
// We changed the constraint set and so must recompute SCCs.
154-
self.compute_sccs(fr_static, definitions)
211+
(
212+
self.compute_sccs(fr_static, definitions.len(), &mut annotations),
213+
annotations.scc_to_annotation,
214+
)
155215
} else {
156216
// If we didn't add any back-edges; no more work needs doing
157-
sccs
217+
(sccs, annotations.scc_to_annotation)
158218
}
159219
}
160220
}

compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs

+3-15
Original file line numberDiff line numberDiff line change
@@ -161,24 +161,12 @@ pub(crate) trait TypeOpInfo<'tcx> {
161161
bound: placeholder.bound,
162162
});
163163

164-
let error_region =
165-
if let RegionElement::PlaceholderRegion(error_placeholder) = error_element {
166-
let adjusted_universe =
167-
error_placeholder.universe.as_u32().checked_sub(base_universe.as_u32());
168-
adjusted_universe.map(|adjusted| {
169-
ty::Region::new_placeholder(tcx, ty::Placeholder {
170-
universe: adjusted.into(),
171-
bound: error_placeholder.bound,
172-
})
173-
})
174-
} else {
175-
None
176-
};
177-
178164
debug!(?placeholder_region);
179165

166+
// FIXME: this is obviously weird; this whole argument now does nothing and maybe
167+
// it should?
180168
let span = cause.span;
181-
let nice_error = self.nice_error(mbcx, cause, placeholder_region, error_region);
169+
let nice_error = self.nice_error(mbcx, cause, placeholder_region, None);
182170

183171
debug!(?nice_error);
184172

compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
491491
let (blame_constraint, extra_info) = self.regioncx.best_blame_constraint(
492492
borrow_region,
493493
NllRegionVariableOrigin::FreeRegion,
494-
|r| self.regioncx.provides_universal_region(r, borrow_region, outlived_region),
494+
outlived_region,
495495
);
496496
let BlameConstraint { category, from_closure, cause, .. } = blame_constraint;
497497

compiler/rustc_borrowck/src/diagnostics/opaque_suggestions.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,9 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for FindOpaqueRegion<'_, 'tcx> {
156156
let opaque_region_vid = self.regioncx.to_region_vid(opaque_region);
157157

158158
// Find a path between the borrow region and our opaque capture.
159-
if let Some((path, _)) =
160-
self.regioncx.find_constraint_paths_between_regions(self.borrow_region, |r| {
161-
r == opaque_region_vid
162-
})
159+
if let Some((path, _)) = self
160+
.regioncx
161+
.constraint_path_between_regions(self.borrow_region, opaque_region_vid)
163162
{
164163
for constraint in path {
165164
// If we find a call in this path, then check if it defines the opaque.

0 commit comments

Comments
 (0)