Skip to content

Commit 29b7590

Browse files
committed
Auto merge of #155887 - BoxyUwU:higher_ranked_assumptions_v2, r=lcnr
-Zassumptions-on-binders r? lcnr cc https://rust-lang.github.io/rust-project-goals/2026/assumptions_on_binders.html I would cc a tracking issue but the project goals haven't been finalized yet ^^' Implements `-Zassumptions-on-binders`. This has a few main components: 1. We introduce a new form of region constraints for use by the trait solver which supports ORs 2. When entering binders universally inside of the trait solver we walk the bound thing and compute a list of region assumptions. We then track in the `InferCtxt` all the region outlives and type outlives mentioning a placeholder from the binder for use when handling constraints involving placeholders 3. Ideally when exiting a binder, but currently actually when computing a response inside the solver, we look through all of region constraints involving placeholders and eagerly handle these region constraints instead of returning them to the caller This is very much a first-draft impl (though it is vaguely functional), there's a lot we need to change going forwards: - We should really be using this new form of region constraint everywhere/more generally we shouldnt have two kinds of region constraints - We shouldn't be computing implied bounds when entering binders, instead they should be explicit everywhere and actually checked when instantiating binders. As-is `-Zassumptions-on-binders` probably widens existing soundness holes around implied bounds due to having significantly more implied bounds - We should be eagerly handling placeholders *everywhere* not just inside of the trait solver. Right now there will still be missing assumptions for placeholders when we do higher ranked type relations during type checking outside of the trait solver. - I'm not normalizing our assumptions or our constraints and we should be doing both ✨ - Handling of alias outlives' involving placeholders is incomplete in a number of ways - We should handle placeholders when leaving binders not when computing responses IMO - Actually support OR region constraints in borrow checking and region checking ✨ right now all our OR constraints are converted into normal ANDed region constraints in root contexts. - Right now diagnostics just point to the whole item which the unsatisfied constraints came from. this is suboptimal! fix this! - Move universe information into InferCtxtInner so it can be rolled back by probes - we should make some kind of test suite helper so we can directly write universal/existential quantifiers and assumptions rather than having to go through rust syntax :') How do we actually eagerly handle constraints? The general idea is that we have some function (`eagerly_handle_placeholders_in_universe`) which takes: - A set of region constraints - The universe which we want to eagerly handle constraints in - A set of outlives assumptions associated with that universe/binder This function will rewrite all of the region constraints which involve placeholders from the passed in universe to be in terms of variables from smaller universes (or drop the constraints if we know them to be satisfied). For example: ```rust for<'a> where('a: 'b) { prove ('a: 'c) } ``` when exiting `for<'a>` we want to handle the `'!a: 'c` constraint *somehow* and we can do that by requiring that *any* of the lifetimes which `'!a` outlives, themselves outlive `'c`. In this case we can require `Or('b: 'c)` and instead of `'!a: 'c` which gives us a constraint that makes sense after exiting the forall. some more examples: ```rust for<'a> where('a: 'b, 'a: 'c) { prove('a: 'd) } // rewritten to Or('b: 'd, 'c: 'd) for<'a> where('b: 'a) { prove(T: 'a) } // rewritten to Or(T: 'b) ``` The tricky thing here is that we want/need to avoid the trait solver knowing about *all* type outlives/region outlives assumptions. So this algorithm is implemented with only knowing about the assumptions coming from the binder that is being exited. We want to avoid passing all outlives assumptions through the trait solver for two main reasons. The first is just perf, augmenting the `ParamEnv` with significant amounts of outlives assumptions could easily mess up caching. The second is that it's Not Possible™️ to implement. Type checking requires trait solving inside of a closure which still has an uninferred signature, this means that there are some set of implied bounds that we just don't know about yet because we only know some inference variable is well formed and nothing else. --- Long term we should be able to wholly rip out placeholder handling from borrow checking and we won't ever encounter placeholders outside of the binders they were produced from.
2 parents aa31d6d + df7bb96 commit 29b7590

40 files changed

Lines changed: 2154 additions & 143 deletions

File tree

compiler/rustc_borrowck/src/constraints/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ impl<'tcx> fmt::Debug for OutlivesConstraint<'tcx> {
9696
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
9797
write!(
9898
formatter,
99-
"({:?}: {:?}) due to {:?} ({:?}) ({:?})",
100-
self.sup, self.sub, self.locations, self.variance_info, self.category,
99+
"({:?}: {:?}) due to {:?} ({:?}) ({:?}) (span: {:?})",
100+
self.sup, self.sub, self.locations, self.variance_info, self.category, self.span,
101101
)
102102
}
103103
}

compiler/rustc_borrowck/src/diagnostics/region_errors.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ impl<'tcx> ConstraintDescription for ConstraintCategory<'tcx> {
5757
ConstraintCategory::OpaqueType => "opaque type ",
5858
ConstraintCategory::ClosureUpvar(_) => "closure capture ",
5959
ConstraintCategory::Usage => "this usage ",
60-
ConstraintCategory::Predicate(_)
60+
ConstraintCategory::SolverRegionConstraint(_)
61+
| ConstraintCategory::Predicate(_)
6162
| ConstraintCategory::Boring
6263
| ConstraintCategory::BoringNoLocation
6364
| ConstraintCategory::Internal
@@ -473,6 +474,14 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
473474
let errci = ErrorConstraintInfo { fr, outlived_fr, category, span: cause.span };
474475

475476
let mut diag = match (category, fr_is_local, outlived_fr_is_local) {
477+
(ConstraintCategory::SolverRegionConstraint(span), _, _) => {
478+
let mut d = self.dcx().struct_span_err(
479+
span,
480+
"unsatisfied lifetime constraint from -Zassumptions-on-binders :3",
481+
);
482+
d.note("meoow :c");
483+
d
484+
}
476485
(ConstraintCategory::Return(kind), true, false) if self.is_closure_fn_mut(fr) => {
477486
self.report_fnmut_error(&errci, kind)
478487
}

compiler/rustc_borrowck/src/region_infer/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1798,6 +1798,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
17981798
// the `'region: 'static` constraints introduced by placeholder outlives.
17991799
ConstraintCategory::Internal => 7,
18001800
ConstraintCategory::OutlivesUnnameablePlaceholder(_) => 8,
1801+
ConstraintCategory::SolverRegionConstraint(_) => 9,
18011802
};
18021803

18031804
debug!("constraint {constraint:?} category: {category:?}, interest: {interest:?}");

compiler/rustc_borrowck/src/type_check/free_region_relations.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub(crate) struct UniversalRegionRelations<'tcx> {
2626
/// Stores the outlives relations that are known to hold from the
2727
/// implied bounds, in-scope where-clauses, and that sort of
2828
/// thing.
29-
outlives: TransitiveRelation<RegionVid>,
29+
pub(crate) outlives: TransitiveRelation<RegionVid>,
3030

3131
/// This is the `<=` relation; that is, if `a: b`, then `b <= a`,
3232
/// and we store that here. This is useful when figuring out how

compiler/rustc_borrowck/src/type_check/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,25 @@ pub(crate) fn type_check<'tcx>(
173173

174174
let polonius_context = typeck.polonius_context;
175175

176+
if infcx.tcx.assumptions_on_binders() {
177+
let mut converter = constraint_conversion::ConstraintConversion::new(
178+
typeck.infcx,
179+
typeck.universal_regions,
180+
typeck.region_bound_pairs,
181+
typeck.known_type_outlives_obligations,
182+
Locations::All(rustc_span::DUMMY_SP),
183+
rustc_span::DUMMY_SP,
184+
ConstraintCategory::Boring,
185+
typeck.constraints,
186+
);
187+
typeck.infcx.destructure_solver_region_constraints_for_borrowck(
188+
&mut converter,
189+
typeck.known_type_outlives_obligations,
190+
universal_region_relations.outlives.clone(),
191+
infcx.tcx.def_span(infcx.root_def_id),
192+
);
193+
}
194+
176195
// In case type check encountered an error region, we suppress unhelpful extra
177196
// errors in by clearing out all outlives bounds that we may end up checking.
178197
if let Some(guar) = universal_region_relations.universal_regions.encountered_re_error() {

compiler/rustc_hir_analysis/src/check/wfcheck.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ where
197197

198198
lint_redundant_lifetimes(tcx, body_def_id, &outlives_env);
199199

200-
let errors = infcx.resolve_regions_with_outlives_env(&outlives_env);
200+
let errors = infcx.resolve_regions_with_outlives_env(&outlives_env, tcx.def_span(body_def_id));
201201
if errors.is_empty() {
202202
return Ok(());
203203
}
@@ -211,7 +211,8 @@ where
211211
// the implied bounds hack if this contains `bevy_ecs`'s `ParamSet` type.
212212
false,
213213
);
214-
let errors_compat = infcx_compat.resolve_regions_with_outlives_env(&outlives_env);
214+
let errors_compat =
215+
infcx_compat.resolve_regions_with_outlives_env(&outlives_env, tcx.def_span(body_def_id));
215216
if errors_compat.is_empty() {
216217
// FIXME: Once we fix bevy, this would be the place to insert a warning
217218
// to upgrade bevy.

compiler/rustc_infer/src/infer/at.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ impl<'tcx> InferCtxt<'tcx> {
8181
reported_signature_mismatch: self.reported_signature_mismatch.clone(),
8282
tainted_by_errors: self.tainted_by_errors.clone(),
8383
universe: self.universe.clone(),
84+
placeholder_assumptions_for_next_solver: self
85+
.placeholder_assumptions_for_next_solver
86+
.clone(),
8487
next_trait_solver: self.next_trait_solver,
8588
obligation_inspector: self.obligation_inspector.clone(),
8689
}
@@ -106,6 +109,9 @@ impl<'tcx> InferCtxt<'tcx> {
106109
reported_signature_mismatch: self.reported_signature_mismatch.clone(),
107110
tainted_by_errors: self.tainted_by_errors.clone(),
108111
universe: self.universe.clone(),
112+
placeholder_assumptions_for_next_solver: self
113+
.placeholder_assumptions_for_next_solver
114+
.clone(),
109115
next_trait_solver: self.next_trait_solver,
110116
obligation_inspector: self.obligation_inspector.clone(),
111117
};

compiler/rustc_infer/src/infer/context.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,40 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> {
3838
self.create_next_universe()
3939
}
4040

41+
fn insert_placeholder_assumptions(
42+
&self,
43+
u: ty::UniverseIndex,
44+
assumptions: Option<rustc_type_ir::region_constraint::Assumptions<TyCtxt<'tcx>>>,
45+
) {
46+
self.placeholder_assumptions_for_next_solver.borrow_mut().insert(u, assumptions);
47+
}
48+
49+
fn get_placeholder_assumptions(
50+
&self,
51+
u: ty::UniverseIndex,
52+
) -> Option<rustc_type_ir::region_constraint::Assumptions<TyCtxt<'tcx>>> {
53+
self.placeholder_assumptions_for_next_solver.borrow().get(&u).unwrap().as_ref().cloned()
54+
}
55+
56+
fn get_solver_region_constraint(
57+
&self,
58+
) -> rustc_type_ir::region_constraint::RegionConstraint<TyCtxt<'tcx>> {
59+
self.inner.borrow().solver_region_constraint_storage.get_constraint()
60+
}
61+
62+
fn overwrite_solver_region_constraint(
63+
&self,
64+
constraint: rustc_type_ir::region_constraint::RegionConstraint<TyCtxt<'tcx>>,
65+
) {
66+
let mut inner = self.inner.borrow_mut();
67+
use rustc_data_structures::undo_log::UndoLogs;
68+
69+
use crate::infer::UndoLog;
70+
let old_constraint = inner.solver_region_constraint_storage.get_constraint();
71+
inner.undo_log.push(UndoLog::OverwriteSolverRegionConstraint { old_constraint });
72+
inner.solver_region_constraint_storage.overwrite_solver_region_constraint(constraint);
73+
}
74+
4175
fn universe_of_ty(&self, vid: ty::TyVid) -> Option<ty::UniverseIndex> {
4276
match self.try_resolve_ty_var(vid) {
4377
Err(universe) => Some(universe),
@@ -290,6 +324,18 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> {
290324
);
291325
}
292326

327+
fn register_solver_region_constraint(
328+
&self,
329+
c: rustc_type_ir::region_constraint::RegionConstraint<TyCtxt<'tcx>>,
330+
) {
331+
let mut inner = self.inner.borrow_mut();
332+
use rustc_data_structures::undo_log::UndoLogs;
333+
334+
use crate::infer::UndoLog;
335+
inner.undo_log.push(UndoLog::PushSolverRegionConstraint);
336+
inner.solver_region_constraint_storage.push(c);
337+
}
338+
293339
fn register_ty_outlives(&self, ty: Ty<'tcx>, r: ty::Region<'tcx>, span: Span) {
294340
self.register_type_outlives_constraint(ty, r, &ObligationCause::dummy_with_span(span));
295341
}

compiler/rustc_infer/src/infer/mod.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ pub struct InferCtxtInner<'tcx> {
124124
/// region constraints would've been added.
125125
region_constraint_storage: Option<RegionConstraintStorage<'tcx>>,
126126

127+
/// Used by the next solver when `-Zassumptions-on-binders` is set.
128+
solver_region_constraint_storage: SolverRegionConstraintStorage<'tcx>,
129+
127130
/// A set of constraints that regionck must validate.
128131
///
129132
/// Each constraint has the form `T:'a`, meaning "some type `T` must
@@ -171,6 +174,7 @@ impl<'tcx> InferCtxtInner<'tcx> {
171174
float_unification_storage: Default::default(),
172175
float_origin_origin_storage: Default::default(),
173176
region_constraint_storage: Some(Default::default()),
177+
solver_region_constraint_storage: SolverRegionConstraintStorage::new(),
174178
region_obligations: Default::default(),
175179
region_assumptions: Default::default(),
176180
hir_typeck_potentially_region_dependent_goals: Default::default(),
@@ -315,6 +319,19 @@ pub struct InferCtxt<'tcx> {
315319
/// bound.
316320
universe: Cell<ty::UniverseIndex>,
317321

322+
/// List of assumed wellformed types which we can derive implied
323+
/// bounds on a `for<...>` from. Only used unstabley and by the
324+
/// new solver.
325+
//
326+
// FIXME(-Zassumptions-on-binders): This and `universe` should probably be
327+
// in `InferCtxtInner` so they can participate in rollbacks and whatnot
328+
placeholder_assumptions_for_next_solver: RefCell<
329+
FxIndexMap<
330+
ty::UniverseIndex,
331+
Option<rustc_type_ir::region_constraint::Assumptions<TyCtxt<'tcx>>>,
332+
>,
333+
>,
334+
318335
next_trait_solver: bool,
319336

320337
pub obligation_inspector: Cell<Option<ObligationInspector<'tcx>>>,
@@ -426,6 +443,10 @@ pub enum SubregionOrigin<'tcx> {
426443
},
427444

428445
AscribeUserTypeProvePredicate(Span),
446+
447+
// FIXME(-Zassumptions-on-binders): this is a temporary hack until we support
448+
// proper diagnostics for solver region constraints.
449+
SolverRegionConstraint(Span),
429450
}
430451

431452
// `SubregionOrigin` is used a lot. Make sure it doesn't unintentionally get bigger.
@@ -437,6 +458,7 @@ impl<'tcx> SubregionOrigin<'tcx> {
437458
match self {
438459
Self::Subtype(type_trace) => type_trace.cause.to_constraint_category(),
439460
Self::AscribeUserTypeProvePredicate(span) => ConstraintCategory::Predicate(*span),
461+
Self::SolverRegionConstraint(span) => ConstraintCategory::SolverRegionConstraint(*span),
440462
_ => ConstraintCategory::BoringNoLocation,
441463
}
442464
}
@@ -636,6 +658,7 @@ impl<'tcx> InferCtxtBuilder<'tcx> {
636658
reported_signature_mismatch: Default::default(),
637659
tainted_by_errors: Cell::new(None),
638660
universe: Cell::new(ty::UniverseIndex::ROOT),
661+
placeholder_assumptions_for_next_solver: RefCell::new(Default::default()),
639662
next_trait_solver,
640663
obligation_inspector: Cell::new(None),
641664
}
@@ -1690,6 +1713,7 @@ impl<'tcx> SubregionOrigin<'tcx> {
16901713
SubregionOrigin::CompareImplItemObligation { span, .. } => span,
16911714
SubregionOrigin::AscribeUserTypeProvePredicate(span) => span,
16921715
SubregionOrigin::CheckAssociatedTypeBounds { ref parent, .. } => parent.span(),
1716+
SubregionOrigin::SolverRegionConstraint(a) => a,
16931717
}
16941718
}
16951719

@@ -1779,3 +1803,55 @@ impl<'tcx> InferCtxt<'tcx> {
17791803
}
17801804
}
17811805
}
1806+
1807+
type SolverRegionConstraint<'tcx> =
1808+
rustc_type_ir::region_constraint::RegionConstraint<TyCtxt<'tcx>>;
1809+
1810+
#[derive(Clone, Debug)]
1811+
struct SolverRegionConstraintStorage<'tcx>(SolverRegionConstraint<'tcx>);
1812+
1813+
impl<'tcx> SolverRegionConstraintStorage<'tcx> {
1814+
fn new() -> Self {
1815+
SolverRegionConstraintStorage(SolverRegionConstraint::And(Box::new([])))
1816+
}
1817+
1818+
fn get_constraint(&self) -> SolverRegionConstraint<'tcx> {
1819+
self.0.clone()
1820+
}
1821+
1822+
fn pop(&mut self) -> Option<SolverRegionConstraint<'tcx>> {
1823+
match &mut self.0 {
1824+
SolverRegionConstraint::And(and) => {
1825+
let mut and = core::mem::take(and).into_iter().collect::<Vec<_>>();
1826+
let popped = and.pop()?;
1827+
self.0 = SolverRegionConstraint::And(and.into_boxed_slice());
1828+
Some(popped)
1829+
}
1830+
_ => unreachable!(),
1831+
}
1832+
}
1833+
1834+
#[instrument(level = "debug")]
1835+
fn push(&mut self, constraint: SolverRegionConstraint<'tcx>) {
1836+
match &mut self.0 {
1837+
SolverRegionConstraint::And(and) => {
1838+
let and = core::mem::take(and)
1839+
.into_iter()
1840+
.chain([constraint])
1841+
.collect::<Vec<_>>()
1842+
.into_boxed_slice();
1843+
self.0 = SolverRegionConstraint::And(and);
1844+
}
1845+
_ => unreachable!(),
1846+
}
1847+
}
1848+
1849+
#[instrument(level = "debug", skip(self))]
1850+
fn overwrite_solver_region_constraint(&mut self, constraint: SolverRegionConstraint<'tcx>) {
1851+
if !constraint.is_and() {
1852+
self.0 = SolverRegionConstraint::And(vec![constraint].into_boxed_slice())
1853+
} else {
1854+
self.0 = constraint;
1855+
}
1856+
}
1857+
}

compiler/rustc_infer/src/infer/outlives/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::iter;
55
use rustc_data_structures::undo_log::UndoLogs;
66
use rustc_middle::traits::query::{NoSolution, OutlivesBound};
77
use rustc_middle::ty;
8+
use rustc_span::Span;
89
use tracing::instrument;
910

1011
use self::env::OutlivesEnvironment;
@@ -49,8 +50,9 @@ impl<'tcx> InferCtxt<'tcx> {
4950
ty::PolyTypeOutlivesPredicate<'tcx>,
5051
SubregionOrigin<'tcx>,
5152
) -> Result<ty::PolyTypeOutlivesPredicate<'tcx>, NoSolution>,
53+
span: Span,
5254
) -> Vec<RegionResolutionError<'tcx>> {
53-
match self.process_registered_region_obligations(outlives_env, deeply_normalize_ty) {
55+
match self.process_registered_region_obligations(outlives_env, deeply_normalize_ty, span) {
5456
Ok(()) => {}
5557
Err((clause, origin)) => {
5658
return vec![RegionResolutionError::CannotNormalize(clause, origin)];

0 commit comments

Comments
 (0)