Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ae2232c

Browse files
committedMar 7, 2025
Auto merge of #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 most of the logic used to detect placeholder violations during error reporting. This finishes what #123720 started. Due to the already sprawling scope of this PR, not all the breaks are clean. In particular, some of the error reporting code can almost certainly be further simplified. 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. Type-tests are also rewritten to account for placeholder issues. In particular, if a bound implies `: 'static`, this is flagged using a new variant, and if a test is guaranteed to always fail (e.g. if an equals bound reaches different placeholders), it is replaced with a bound that is always unsatisfied. 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 91a0e16 + 91e5a1f commit ae2232c

32 files changed

+1866
-1121
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

+11-99
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
use std::fmt;
22
use std::ops::Index;
33

4+
use rustc_data_structures::graph::scc;
45
use rustc_index::{IndexSlice, IndexVec};
56
use rustc_middle::mir::ConstraintCategory;
67
use rustc_middle::ty::{RegionVid, TyCtxt, VarianceDiagInfo};
78
use rustc_span::Span;
8-
use tracing::{debug, instrument};
9+
use tracing::debug;
910

10-
use crate::region_infer::{ConstraintSccs, RegionDefinition, RegionTracker};
1111
use crate::type_check::Locations;
12-
use crate::universal_regions::UniversalRegions;
1312

1413
pub(crate) mod graph;
1514

@@ -57,105 +56,18 @@ impl<'tcx> OutlivesConstraintSet<'tcx> {
5756
/// Computes cycles (SCCs) in the graph of regions. In particular,
5857
/// find all regions R1, R2 such that R1: R2 and R2: R1 and group
5958
/// them into an SCC, and find the relationships between SCCs.
60-
pub(crate) fn compute_sccs(
59+
pub(crate) fn compute_sccs<
60+
A: scc::Annotation,
61+
AA: scc::Annotations<RegionVid, ConstraintSccIndex, A>,
62+
>(
6163
&self,
6264
static_region: RegionVid,
63-
definitions: &IndexVec<RegionVid, RegionDefinition<'tcx>>,
64-
) -> ConstraintSccs {
65-
let constraint_graph = self.graph(definitions.len());
65+
num_region_vars: usize,
66+
annotations: &mut AA,
67+
) -> scc::Sccs<RegionVid, ConstraintSccIndex> {
68+
let constraint_graph = self.graph(num_region_vars);
6669
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-
})
70-
}
71-
72-
/// This method handles Universe errors by rewriting the constraint
73-
/// graph. For each strongly connected component in the constraint
74-
/// graph such that there is a series of constraints
75-
/// A: B: C: ... : X where
76-
/// A's universe is smaller than X's and A is a placeholder,
77-
/// add a constraint that A: 'static. This is a safe upper bound
78-
/// in the face of borrow checker/trait solver limitations that will
79-
/// eventually go away.
80-
///
81-
/// For a more precise definition, see the documentation for
82-
/// [`RegionTracker::has_incompatible_universes()`].
83-
///
84-
/// This edge case used to be handled during constraint propagation
85-
/// by iterating over the strongly connected components in the constraint
86-
/// graph while maintaining a set of bookkeeping mappings similar
87-
/// to what is stored in `RegionTracker` and manually adding 'sttaic as
88-
/// needed.
89-
///
90-
/// It was rewritten as part of the Polonius project with the goal of moving
91-
/// higher-kindedness concerns out of the path of the borrow checker,
92-
/// for two reasons:
93-
///
94-
/// 1. Implementing Polonius is difficult enough without also
95-
/// handling them.
96-
/// 2. The long-term goal is to handle higher-kinded concerns
97-
/// in the trait solver, where they belong. This avoids
98-
/// logic duplication and allows future trait solvers
99-
/// to compute better bounds than for example our
100-
/// "must outlive 'static" here.
101-
///
102-
/// This code is a stop-gap measure in preparation for the future trait solver.
103-
///
104-
/// Every constraint added by this method is an
105-
/// internal `IllegalUniverse` constraint.
106-
#[instrument(skip(self, universal_regions, definitions))]
107-
pub(crate) fn add_outlives_static(
108-
&mut self,
109-
universal_regions: &UniversalRegions<'tcx>,
110-
definitions: &IndexVec<RegionVid, RegionDefinition<'tcx>>,
111-
) -> ConstraintSccs {
112-
let fr_static = universal_regions.fr_static;
113-
let sccs = self.compute_sccs(fr_static, definitions);
114-
115-
// Changed to `true` if we added any constraints to `self` and need to
116-
// recompute SCCs.
117-
let mut added_constraints = false;
118-
119-
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*.
123-
if scc == sccs.scc(fr_static) {
124-
continue;
125-
}
126-
127-
let annotation = sccs.annotation(scc);
128-
129-
// If this SCC participates in a universe violation,
130-
// e.g. if it reaches a region with a universe smaller than
131-
// 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);
149-
}
150-
}
151-
152-
if added_constraints {
153-
// We changed the constraint set and so must recompute SCCs.
154-
self.compute_sccs(fr_static, definitions)
155-
} else {
156-
// If we didn't add any back-edges; no more work needs doing
157-
sccs
158-
}
70+
scc::Sccs::new_with_annotation(&region_graph, annotations)
15971
}
16072
}
16173

‎compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs

+65-54
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ use rustc_traits::{type_op_ascribe_user_type_with_span, type_op_prove_predicate_
2424
use tracing::{debug, instrument};
2525

2626
use crate::MirBorrowckCtxt;
27-
use crate::region_infer::values::RegionElement;
2827
use crate::session_diagnostics::{
2928
HigherRankedErrorCause, HigherRankedLifetimeError, HigherRankedSubtypeError,
3029
};
@@ -49,11 +48,24 @@ impl<'tcx> UniverseInfo<'tcx> {
4948
UniverseInfo::RelateTys { expected, found }
5049
}
5150

52-
pub(crate) fn report_error(
51+
/// Report an error where an element erroneously made its way into `placeholder`.
52+
pub(crate) fn report_erroneous_element(
5353
&self,
5454
mbcx: &mut MirBorrowckCtxt<'_, '_, 'tcx>,
5555
placeholder: ty::PlaceholderRegion,
56-
error_element: RegionElement,
56+
cause: ObligationCause<'tcx>,
57+
error_element: Option<ty::PlaceholderRegion>,
58+
) {
59+
if let UniverseInfo::TypeOp(ref type_op_info) = *self {
60+
type_op_info.report_erroneous_element(mbcx, placeholder, cause, error_element);
61+
} else {
62+
self.report_generic_error(mbcx, cause);
63+
}
64+
}
65+
66+
fn report_generic_error(
67+
&self,
68+
mbcx: &mut MirBorrowckCtxt<'_, '_, 'tcx>,
5769
cause: ObligationCause<'tcx>,
5870
) {
5971
match *self {
@@ -67,8 +79,8 @@ impl<'tcx> UniverseInfo<'tcx> {
6779
);
6880
mbcx.buffer_error(err);
6981
}
70-
UniverseInfo::TypeOp(ref type_op_info) => {
71-
type_op_info.report_error(mbcx, placeholder, error_element, cause);
82+
UniverseInfo::TypeOp(_) => {
83+
unreachable!("This case should already have been handled!");
7284
}
7385
UniverseInfo::Other => {
7486
// FIXME: This error message isn't great, but it doesn't show
@@ -145,57 +157,57 @@ pub(crate) trait TypeOpInfo<'tcx> {
145157
error_region: Option<ty::Region<'tcx>>,
146158
) -> Option<Diag<'infcx>>;
147159

160+
/// Turn a placeholder region into a Region with its universe adjusted by
161+
/// the base universe.
162+
fn region_with_adjusted_universe(
163+
&self,
164+
placeholder: ty::PlaceholderRegion,
165+
tcx: TyCtxt<'tcx>,
166+
) -> ty::Region<'tcx> {
167+
let Some(adjusted_universe) =
168+
placeholder.universe.as_u32().checked_sub(self.base_universe().as_u32())
169+
else {
170+
unreachable!(
171+
"Could not adjust universe {:?} of {placeholder:?} by base universe {:?}",
172+
placeholder.universe,
173+
self.base_universe()
174+
);
175+
};
176+
ty::Region::new_placeholder(
177+
tcx,
178+
ty::Placeholder { universe: adjusted_universe.into(), bound: placeholder.bound },
179+
)
180+
}
181+
182+
/// Report an error where an erroneous element reaches `placeholder`.
183+
/// The erroneous element is either another placeholder that we provide,
184+
/// or we figure out what happened later.
148185
#[instrument(level = "debug", skip(self, mbcx))]
149-
fn report_error(
186+
fn report_erroneous_element(
150187
&self,
151188
mbcx: &mut MirBorrowckCtxt<'_, '_, 'tcx>,
152189
placeholder: ty::PlaceholderRegion,
153-
error_element: RegionElement,
154190
cause: ObligationCause<'tcx>,
191+
error_element: Option<ty::PlaceholderRegion>,
155192
) {
156193
let tcx = mbcx.infcx.tcx;
157-
let base_universe = self.base_universe();
158-
debug!(?base_universe);
159-
160-
let Some(adjusted_universe) =
161-
placeholder.universe.as_u32().checked_sub(base_universe.as_u32())
162-
else {
163-
mbcx.buffer_error(self.fallback_error(tcx, cause.span));
164-
return;
165-
};
166194

167-
let placeholder_region = ty::Region::new_placeholder(
168-
tcx,
169-
ty::Placeholder { universe: adjusted_universe.into(), bound: placeholder.bound },
170-
);
171-
172-
let error_region = if let RegionElement::PlaceholderRegion(error_placeholder) =
173-
error_element
174-
{
175-
let adjusted_universe =
176-
error_placeholder.universe.as_u32().checked_sub(base_universe.as_u32());
177-
adjusted_universe.map(|adjusted| {
178-
ty::Region::new_placeholder(
179-
tcx,
180-
ty::Placeholder { universe: adjusted.into(), bound: error_placeholder.bound },
181-
)
182-
})
183-
} else {
184-
None
185-
};
195+
// FIXME: these adjusted universes are not (always) the same ones as we compute
196+
// earlier. They probably should be, but the logic downstream is complicated,
197+
// and assumes they use whatever this is.
198+
//
199+
// In fact, this function throws away a lot of interesting information that would
200+
// probably allow bypassing lots of logic downstream for a much simpler flow.
201+
let placeholder_region = self.region_with_adjusted_universe(placeholder, tcx);
202+
let error_element = error_element.map(|e| self.region_with_adjusted_universe(e, tcx));
186203

187204
debug!(?placeholder_region);
188205

189206
let span = cause.span;
190-
let nice_error = self.nice_error(mbcx, cause, placeholder_region, error_region);
207+
let nice_error = self.nice_error(mbcx, cause, placeholder_region, error_element);
191208

192209
debug!(?nice_error);
193-
194-
if let Some(nice_error) = nice_error {
195-
mbcx.buffer_error(nice_error);
196-
} else {
197-
mbcx.buffer_error(self.fallback_error(tcx, span));
198-
}
210+
mbcx.buffer_error(nice_error.unwrap_or_else(|| self.fallback_error(tcx, span)));
199211
}
200212
}
201213

@@ -450,7 +462,8 @@ fn try_extract_error_from_region_constraints<'a, 'tcx>(
450462
ty::ReVar(vid) => universe_of_region(vid),
451463
_ => ty::UniverseIndex::ROOT,
452464
};
453-
let matches =
465+
// Are the two regions the same?
466+
let regions_the_same =
454467
|a_region: Region<'tcx>, b_region: Region<'tcx>| match (a_region.kind(), b_region.kind()) {
455468
(RePlaceholder(a_p), RePlaceholder(b_p)) => a_p.bound == b_p.bound,
456469
_ => a_region == b_region,
@@ -459,7 +472,7 @@ fn try_extract_error_from_region_constraints<'a, 'tcx>(
459472
|constraint: &Constraint<'tcx>, cause: &SubregionOrigin<'tcx>, exact| match *constraint {
460473
Constraint::RegSubReg(sub, sup)
461474
if ((exact && sup == placeholder_region)
462-
|| (!exact && matches(sup, placeholder_region)))
475+
|| (!exact && regions_the_same(sup, placeholder_region)))
463476
&& sup != sub =>
464477
{
465478
Some((sub, cause.clone()))
@@ -468,23 +481,21 @@ fn try_extract_error_from_region_constraints<'a, 'tcx>(
468481
if (exact
469482
&& sup == placeholder_region
470483
&& !universe_of_region(vid).can_name(placeholder_universe))
471-
|| (!exact && matches(sup, placeholder_region)) =>
484+
|| (!exact && regions_the_same(sup, placeholder_region)) =>
472485
{
473486
Some((ty::Region::new_var(infcx.tcx, vid), cause.clone()))
474487
}
475488
_ => None,
476489
};
477-
let mut info = region_constraints
478-
.constraints
479-
.iter()
480-
.find_map(|(constraint, cause)| check(constraint, cause, true));
481-
if info.is_none() {
482-
info = region_constraints
490+
491+
let mut find_culprit = |exact_match: bool| {
492+
region_constraints
483493
.constraints
484494
.iter()
485-
.find_map(|(constraint, cause)| check(constraint, cause, false));
486-
}
487-
let (sub_region, cause) = info?;
495+
.find_map(|(constraint, cause)| check(constraint, cause, exact_match))
496+
};
497+
498+
let (sub_region, cause) = find_culprit(true).or_else(|| find_culprit(false))?;
488499

489500
debug!(?sub_region, "cause = {:#?}", cause);
490501
let error = match (error_region, *sub_region) {

‎compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
563563
let (blame_constraint, path) = self.regioncx.best_blame_constraint(
564564
borrow_region,
565565
NllRegionVariableOrigin::FreeRegion,
566-
|r| self.regioncx.provides_universal_region(r, borrow_region, outlived_region),
566+
outlived_region,
567567
);
568568
let BlameConstraint { category, from_closure, cause, .. } = blame_constraint;
569569

There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Failed to load comments.