Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
282bc18
split `rewrite_type_outlives_constraints_in_universe` into multiple fns
BoxyUwU Aug 21, 2026
580a5fc
restructure `rewrite_type_outlives_constraints_in_universe`
BoxyUwU Aug 21, 2026
221137a
restructure `compute_new_region_constraints`
BoxyUwU Aug 21, 2026
15b08b4
restructure `pull_region_outlives_constraints_out_of_universe`
BoxyUwU Aug 21, 2026
392edef
restructure `destructure_type_outlives_constraints_in_root`
BoxyUwU Aug 21, 2026
4d35ce9
remove big manual derives
BoxyUwU Aug 21, 2026
58e60ab
stub out `evaluate_solver_constraint`
BoxyUwU Aug 21, 2026
6cf17d2
always canonical form
BoxyUwU Jul 24, 2026
ec38877
add `new_leaf` functions
BoxyUwU Aug 21, 2026
cb42b8d
propagate ambiguity not evaluate
BoxyUwU Aug 21, 2026
9897fe9
unncessarily typo
BoxyUwU Aug 27, 2026
f96c64f
`Or::new_and` nit
BoxyUwU Aug 27, 2026
47be0ac
`And::new` nit
BoxyUwU Aug 27, 2026
7748294
s/CanonicalFormRegionConstraint/RegionConstraint
BoxyUwU Aug 27, 2026
518ebc9
import whoopsies
BoxyUwU Aug 28, 2026
666fbdb
use reduce
BoxyUwU Aug 28, 2026
c523918
empty or is falsey :3
BoxyUwU Aug 28, 2026
1482fea
add perf fixme for region constraint undolog
BoxyUwU Aug 28, 2026
1058ab7
do type annotation better :3
BoxyUwU Aug 28, 2026
6eebdec
dont clone for no reason
BoxyUwU Aug 28, 2026
8794cae
better docs on region constraints
BoxyUwU Aug 28, 2026
ba7c1dd
split first!
BoxyUwU Aug 28, 2026
b93ea27
rename `Or::new_and/or` and `RegionConstraint::new_and/or`
BoxyUwU Aug 28, 2026
be61456
whoops everything is span sensitive now
BoxyUwU Aug 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 88 additions & 79 deletions compiler/rustc_hir_analysis/src/check/wfcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use rustc_lint_defs::builtin::{REDUNDANT_LIFETIMES, SHADOWING_SUPERTRAIT_ITEMS};
use rustc_macros::{Diagnostic, TypeFoldable, TypeVisitable};
use rustc_middle::mir::interpret::ErrorHandled;
use rustc_middle::traits::solve::NoSolution;
use rustc_middle::ty::region_constraint::{And, LeafRegionConstraint, Or};
use rustc_middle::ty::trait_def::TraitSpecializationKind;
use rustc_middle::ty::{
self, GenericArgKind, GenericArgs, GenericParamDefKind, RegionExt, Ty, TyCtxt, TypeFlags,
Expand Down Expand Up @@ -2335,7 +2336,7 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> {
pub(super) fn check_test_binder_body(&self, body: TestBinderBody<'tcx>) {
let constraints = match validate(self.tcx(), &body.constraints) {
Ok(()) => body.constraints,
Err(_guar) => ty::region_constraint::RegionConstraint::And(Box::new([])),
Err(_guar) => ty::region_constraint::RegionConstraint::new_true(),
};

self.infcx.register_solver_region_constraint(constraints);
Expand All @@ -2351,40 +2352,39 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> {
tcx: TyCtxt<'tcx>,
constraint: &SolverRegionConstraint<'tcx>,
) -> Result<(), ErrorGuaranteed> {
match constraint {
ty::region_constraint::RegionConstraint::Ambiguity(_) => Ok(()),
ty::region_constraint::RegionConstraint::RegionOutlives(..) => Ok(()),
ty::region_constraint::RegionConstraint::AliasTyOutlivesViaEnv(..) => Ok(()),
ty::region_constraint::RegionConstraint::PlaceholderTyOutlives(ty, _, span) => {
// we can't check this during lowering, because the ty is a ty::Bound that gets
// instantiated with a placeholder when entering the containing forall.
if let ty::Placeholder(_) | ty::Param(_) = ty.kind() {
Ok(())
} else {
let mut err = tcx.dcx().struct_span_err(
*span,
"the lhs of a ty outlives must be a placeholder",
);
err.note(format!("it is a {ty}"));
err.note(format!("and here it is `Debug`ged :3 {ty:?}"));
Err(err.emit())
}
}
ty::region_constraint::RegionConstraint::And(constraints) => {
let mut res = Ok(());
for constraint in constraints {
res = res.and(validate(tcx, constraint));
}
res
}
ty::region_constraint::RegionConstraint::Or(constraints) => {
let mut res = Ok(());
for constraint in constraints {
res = res.and(validate(tcx, constraint));
let mut r = Ok(());

let mut validate_and = |and: &And<TyCtxt<'_>, _>| {
for c in and.0.iter() {
match c {
LeafRegionConstraint::Ambiguity(_)
| LeafRegionConstraint::RegionOutlives(..)
| LeafRegionConstraint::AliasTyOutlivesViaEnv(..) => (), // OK
LeafRegionConstraint::PlaceholderTyOutlives(ty, _, span) => {
// we can't check this during lowering, because the ty is a ty::Bound that gets
// instantiated with a placeholder when entering the containing forall.
if let ty::Placeholder(_) | ty::Param(_) = ty.kind() {
// all OK
} else {
let mut err = tcx.dcx().struct_span_err(
*span,
"the lhs of a ty outlives must be a placeholder",
);
err.note(format!("it is a {ty}"));
err.note(format!("and here it is `Debug`ged :3 {ty:?}"));
r = Err(err.emit());
}
}
}
res
}
};

validate_and(&constraint.and_constraint);
for and in constraint.or_constraint.0.iter() {
validate_and(and);
}

r
}
}

Expand All @@ -2406,13 +2406,9 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> {
solver_region_constraint.without_spans(),
u,
)
.with_span(forall.span);
if let Some(assert_on_exit) = forall.assert_on_exit {
self.check_test_binder_region_constraints(
forall.span,
&assert_on_exit.clone().canonical_form(),
&constraint.clone().canonical_form(),
);
.with_spans(forall.span);
if let Some(assert_on_exit) = &forall.assert_on_exit {
self.check_test_binder_region_constraints(forall.span, assert_on_exit, &constraint);
}
self.infcx.overwrite_solver_region_constraint(constraint);
});
Expand All @@ -2425,58 +2421,71 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> {
expected: &SolverRegionConstraint<'tcx>,
actual: &SolverRegionConstraint<'tcx>,
) {
fn span_of<'tcx>(constraint: &SolverRegionConstraint<'tcx>) -> Option<Span> {
match constraint {
SolverRegionConstraint::Ambiguity(sp)
| SolverRegionConstraint::RegionOutlives(_, _, sp)
| SolverRegionConstraint::AliasTyOutlivesViaEnv(_, sp)
| ty::region_constraint::RegionConstraint::PlaceholderTyOutlives(_, _, sp) => {
Some(*sp)
}
SolverRegionConstraint::And(constraints)
| SolverRegionConstraint::Or(constraints) => constraints
.iter()
.map(span_of)
.flatten()
.fold(None, |l, r| Some(l.map_or(r, |l| l.to(r)))),
}
}
fn err<'tcx>(
tcx: TyCtxt<'tcx>,
fallback_span: Span,
expected: &SolverRegionConstraint<'tcx>,
actual: &SolverRegionConstraint<'tcx>,
expected_span: Span,
expected: impl std::fmt::Debug,
actual_span: Option<Span>,
actual: impl std::fmt::Debug,
) {
let mut err = tcx.dcx().struct_span_err(
span_of(expected).unwrap_or(fallback_span),
"forall expect clause failed",
);
if let Some(actual_span) = span_of(actual) {
let mut err = tcx.dcx().struct_span_err(expected_span, "forall expect clause failed");
if let Some(actual_span) = actual_span {
err.span_note(actual_span, "constraint from here");
}
err.note(format!("expected: {expected:?}"));
err.note(format!("actual: {actual:?}"));
err.emit();
}
match (expected, actual) {
(
SolverRegionConstraint::And(expected_arr),
SolverRegionConstraint::And(actual_arr),
)
| (SolverRegionConstraint::Or(expected_arr), SolverRegionConstraint::Or(actual_arr)) => {
if expected_arr.len() != actual_arr.len() {
err(self.tcx(), fallback_span, expected, actual);
} else {
for (expected, actual) in expected_arr.iter().zip(actual_arr) {
self.check_test_binder_region_constraints(fallback_span, expected, actual);
}

let span_of_and = |c: &And<_, _>| {
c.0.iter().map(|leaf| leaf.span()).reduce(|span: Span, acc| acc.to(span))
};

let span_of_or = |c: &Or<_, _>| {
c.0.iter().flat_map(|and| span_of_and(and)).reduce(|span, acc| acc.to(span))
};

let check_leaf_constraint =
|expected: LeafRegionConstraint<_, _>, actual: LeafRegionConstraint<_, _>| {
if expected.clone().without_span() != actual.clone().without_span() {
err(self.tcx(), expected.span(), expected, Some(actual.span()), actual);
}
};

let check_and_constraint = |expected: And<_, _>, actual: And<_, _>| {
if expected.0.len() != actual.0.len() {
err(
self.tcx(),
span_of_and(&expected).unwrap_or(fallback_span),
expected,
span_of_and(&actual),
actual,
)
} else {
for (expected, actual) in expected.0.into_iter().zip(actual.0.into_iter()) {
check_leaf_constraint(expected, actual);
}
}
_ if expected.clone().without_spans() != actual.clone().without_spans() => {
err(self.tcx(), fallback_span, expected, actual);
};

let check_or_constraint = |expected: Or<_, _>, actual: Or<_, _>| {
if expected.0.len() != actual.0.len() {
err(
self.tcx(),
span_of_or(&expected).unwrap_or(fallback_span),
expected,
span_of_or(&actual),
actual,
)
} else {
for (expected, actual) in expected.0.into_iter().zip(actual.0.into_iter()) {
check_and_constraint(expected, actual);
}
}
_ => (),
}
};

check_or_constraint(expected.or_constraint.clone(), actual.or_constraint.clone());
check_and_constraint(expected.and_constraint.clone(), actual.and_constraint.clone());
}

#[instrument(level = "debug", skip(self))]
Expand Down
27 changes: 17 additions & 10 deletions compiler/rustc_hir_analysis/src/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ use rustc_trait_selection::traits::{
FulfillmentError, ObligationCtxt, hir_ty_lowering_dyn_compatibility_violations,
};
use tracing::{debug, instrument};
use ty::region_constraint::LeafRegionConstraint;

use crate::check::wfcheck::{TestBinderBody, TestBinderExists, TestBinderForall};
use crate::diagnostics::{self, ElidedLifetimesAreNotAllowedInDelegations};
Expand Down Expand Up @@ -432,19 +433,23 @@ impl<'tcx> ItemCtxt<'tcx> {
constraint: &hir::TestBinderConstraint<'tcx>,
) -> SolverRegionConstraint<'tcx> {
match constraint {
hir::TestBinderConstraint::And { items } => {
ty::region_constraint::RegionConstraint::And(
items.iter().map(|i| self.lower_test_binder_constraint(i)).collect(),
)
}
hir::TestBinderConstraint::Or { items } => ty::region_constraint::RegionConstraint::Or(
items.iter().map(|i| self.lower_test_binder_constraint(i)).collect(),
),
hir::TestBinderConstraint::And { items } => items
.into_iter()
.map(|item| self.lower_test_binder_constraint(item))
.reduce(SolverRegionConstraint::build_and)
.unwrap_or(SolverRegionConstraint::new_true()),
hir::TestBinderConstraint::Or { items } => items
.into_iter()
.map(|item| self.lower_test_binder_constraint(item))
.reduce(SolverRegionConstraint::build_or)
.unwrap_or(SolverRegionConstraint::new_false()),
hir::TestBinderConstraint::Lifetime { lhs, rhs } => {
let span = lhs.ident.span.to(rhs.ident.span);
let lhs = self.lowerer().lower_lifetime(lhs, RegionInferReason::RegionPredicate);
let rhs = self.lowerer().lower_lifetime(rhs, RegionInferReason::RegionPredicate);
ty::region_constraint::RegionConstraint::RegionOutlives(lhs, rhs, span)
SolverRegionConstraint::new_leaf(LeafRegionConstraint::RegionOutlives(
lhs, rhs, span,
))
}
hir::TestBinderConstraint::Type { lhs, rhs } => {
let span = lhs.span.to(rhs.ident.span);
Expand All @@ -453,7 +458,9 @@ impl<'tcx> ItemCtxt<'tcx> {
// note that we cannot check that lhs is a placeholder at this moment, as at this
// point it is a bound variable that is not yet instantiated with a placeholder.
// instead, we check it when we emit the region constraint.
ty::region_constraint::RegionConstraint::PlaceholderTyOutlives(lhs, rhs, span)
SolverRegionConstraint::new_leaf(LeafRegionConstraint::PlaceholderTyOutlives(
lhs, rhs, span,
))
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_infer/src/infer/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> {
constraint: rustc_type_ir::region_constraint::RegionConstraint<TyCtxt<'tcx>>,
span: Span,
) {
self.overwrite_solver_region_constraint(constraint.with_span(span));
self.overwrite_solver_region_constraint(constraint.with_spans(span));
}

fn universe_of_ty(&self, vid: ty::TyVid) -> Option<ty::UniverseIndex> {
Expand Down Expand Up @@ -328,7 +328,7 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> {
c: rustc_type_ir::region_constraint::RegionConstraint<TyCtxt<'tcx>>,
span: Span,
) {
self.register_solver_region_constraint(c.with_span(span));
self.register_solver_region_constraint(c.with_spans(span));
}

fn register_ty_outlives(&self, ty: Ty<'tcx>, r: ty::Region<'tcx>, span: Span) {
Expand Down
49 changes: 32 additions & 17 deletions compiler/rustc_infer/src/infer/outlives/obligations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ use rustc_middle::ty::{
TyCtxt, TypeVisitableExt, eager_resolve_vars,
};
use rustc_span::Span;
use rustc_type_ir::region_constraint::{self, LeafRegionConstraint};
use smallvec::smallvec;
use tracing::{debug, instrument};

Expand Down Expand Up @@ -142,9 +143,18 @@ impl<'tcx> InferCtxt<'tcx> {

pub fn register_solver_region_constraint(&self, c: SolverRegionConstraint<'tcx>) {
let mut inner = self.inner.borrow_mut();
let previous_was_and = inner.solver_region_constraint_storage.is_and();
inner.undo_log.push(UndoLog::PushSolverRegionConstraint { previous_was_and });
inner.solver_region_constraint_storage.push(c);

let old_constraint = inner.solver_region_constraint_storage.get_constraint();
let new_constraint = rustc_type_ir::region_constraint::RegionConstraint::build_and(
c,
old_constraint.clone(),
);

// FIXME(-Zassumptions-on-binders): This is pretty bad for perf, we don't make incremental
// changes to the region constraints, instead we just rewrite the entire thing every time
// and store the old version.
inner.undo_log.push(UndoLog::OverwriteSolverRegionConstraint { old_constraint });
Comment thread
BoxyUwU marked this conversation as resolved.
inner.solver_region_constraint_storage.overwrite(new_constraint);
}

pub fn register_type_outlives_constraint(
Expand Down Expand Up @@ -238,7 +248,7 @@ impl<'tcx> InferCtxt<'tcx> {
known_type_outlives: &[PolyTypeOutlivesClause<'tcx>],
region_outlives: TransitiveRelation<RegionVid>,
) {
let assumptions = rustc_type_ir::region_constraint::Assumptions::new(
let assumptions = region_constraint::Assumptions::new(
known_type_outlives.into_iter().cloned().collect(),
region_outlives.maybe_map(|r| Some(Region::new_var(self.tcx, r))).unwrap(),
);
Expand All @@ -256,19 +266,24 @@ impl<'tcx> InferCtxt<'tcx> {

let constraint = self.inner.borrow().solver_region_constraint_storage.get_constraint();
debug!(?constraint);
let constraint =
rustc_type_ir::region_constraint::destructure_type_outlives_constraints_in_root(
self,
constraint,
&assumptions,
);
let constraint = region_constraint::destructure_type_outlives_constraints_in_root(
self,
constraint,
&assumptions,
);
debug!(?constraint);
let constraint = rustc_type_ir::region_constraint::evaluate_solver_constraint(&constraint);
let constraint = region_constraint::propagate_ambiguity(constraint);
debug!(?constraint);

let mut constraints = vec![constraint];
while let Some(c) = constraints.pop() {
use rustc_type_ir::region_constraint::RegionConstraint::*;
// FIXME(-Zassumptions-on-binders): actually implement OR as an OR
for c in constraint.and_constraint.0.into_iter().chain(
constraint
.or_constraint
.0
.into_iter()
.flat_map(|and_constraint| and_constraint.0.into_iter()),
) {
use LeafRegionConstraint::*;

match c {
Ambiguity(span) => {
Expand All @@ -287,9 +302,9 @@ impl<'tcx> InferCtxt<'tcx> {
b, a, category,
);
}
// FIXME(-Zassumptions-on-binders): actually implement OR as an OR
And(nested) | Or(nested) => constraints.extend(nested),
AliasTyOutlivesViaEnv(..) | PlaceholderTyOutlives(..) => unreachable!(),
AliasTyOutlivesViaEnv(..) | PlaceholderTyOutlives(..) => {
unreachable!()
}
}
}
}
Expand Down
9 changes: 0 additions & 9 deletions compiler/rustc_infer/src/infer/snapshot/undo_log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ pub(crate) enum UndoLog<'tcx> {
RegionUnificationTable(sv::UndoLog<ut::Delegate<RegionVidKey<'tcx>>>),
ProjectionCache(traits::UndoLog<'tcx>),
PushTypeOutlivesConstraint,
PushSolverRegionConstraint { previous_was_and: bool },
OverwriteSolverRegionConstraint { old_constraint: SolverRegionConstraint<'tcx> },
PushRegionAssumption,
PushHirTypeckPotentiallyRegionDependentGoal,
Expand Down Expand Up @@ -79,14 +78,6 @@ impl<'tcx> Rollback<UndoLog<'tcx>> for InferCtxtInner<'tcx> {
self.region_constraint_storage.as_mut().unwrap().unification_table.reverse(undo)
}
UndoLog::ProjectionCache(undo) => self.projection_cache.reverse(undo),
UndoLog::PushSolverRegionConstraint { previous_was_and } => {
let popped = self.solver_region_constraint_storage.pop(previous_was_and);
assert_matches!(
popped,
Some(_),
"pushed solver region constraint but could not pop it"
);
}
UndoLog::OverwriteSolverRegionConstraint { old_constraint } => {
self.solver_region_constraint_storage.overwrite(old_constraint);
}
Expand Down
Loading
Loading