From 7be87edcb3fbdb060d9d867c9f4fb477f5e019ed Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Thu, 11 Jun 2026 19:47:14 -0300 Subject: [PATCH 1/2] Don't ICE on non-lifetime binders under `-Zassumptions-on-binders` The region-constraint machinery for `-Zassumptions-on-binders` is region-outlives-only. A non-lifetime binder (`for`) introduces a placeholder type in the binder's universe `u`. The rewrite that pulls constraints out of `u` only folds regions (`PlaceholderReplacer` just implements `fold_region`), so an alias-outlives constraint such as `::Assoc: 'r` reaches `pull_region_outlives_constraints_out_of_universe` still in `u` and trips `assert!(max_universe < u)`. Report ambiguity for those constraints instead of asserting, matching the existing `None => Ambiguity` bail-outs in this module. The goal then surfaces as an ordinary ambiguity error rather than an ICE. --- .../rustc_type_ir/src/region_constraint.rs | 14 +++++++++++-- .../non_lifetime_binder_alias_outlives.rs | 21 +++++++++++++++++++ .../non_lifetime_binder_alias_outlives.stderr | 20 ++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 tests/ui/assumptions_on_binders/non_lifetime_binder_alias_outlives.rs create mode 100644 tests/ui/assumptions_on_binders/non_lifetime_binder_alias_outlives.stderr diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 43b1535d16809..b896d7a398178 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -654,8 +654,18 @@ fn pull_region_outlives_constraints_out_of_universe< use RegionConstraint::*; match constraint { Ambiguity | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) => { - assert!(max_universe(infcx, constraint.clone()) < u); - constraint + // With only lifetime binders the rewrite step lowers these constraints out of `u` + // (or destructures them), so we expect `max_universe < u` here. A non-lifetime + // binder (`for`) instead introduces a placeholder type in `u`, which + // `PlaceholderReplacer` (region-only, see its `fold_region`) cannot pull out, + // leaving e.g. `::Assoc: 'r` stranded in `u`. The assumptions-on-binders + // machinery is region-outlives-only and can't decide such a constraint, so report + // ambiguity rather than ICE. + if max_universe(infcx, constraint.clone()) < u { + constraint + } else { + RegionConstraint::Ambiguity + } } RegionOutlives(region_1, region_2) => { let region_1_u = max_universe(infcx, region_1); diff --git a/tests/ui/assumptions_on_binders/non_lifetime_binder_alias_outlives.rs b/tests/ui/assumptions_on_binders/non_lifetime_binder_alias_outlives.rs new file mode 100644 index 0000000000000..8178575555383 --- /dev/null +++ b/tests/ui/assumptions_on_binders/non_lifetime_binder_alias_outlives.rs @@ -0,0 +1,21 @@ +//@ compile-flags: -Znext-solver -Zassumptions-on-binders + +// Regression test for #157778. +// +// A non-lifetime binder (`for`) introduces a placeholder *type* in universe `u`. The +// resulting alias-outlives constraint `::Assoc: 'r` stays in `u` because the +// region-only rewrite (`PlaceholderReplacer` only folds regions) cannot pull a type +// placeholder out of `u`. This used to trip an `assert!(max_universe < u)` and ICE in +// `pull_region_outlives_constraints_out_of_universe`. The assumptions-on-binders machinery +// is region-outlives-only, so we now report ambiguity instead of panicking. + +#![feature(non_lifetime_binders)] + +trait Trait { + type Assoc; + type Ref //~ ERROR cannot satisfy `::Assoc: 'static` + where + for T: Trait; +} + +fn main() {} diff --git a/tests/ui/assumptions_on_binders/non_lifetime_binder_alias_outlives.stderr b/tests/ui/assumptions_on_binders/non_lifetime_binder_alias_outlives.stderr new file mode 100644 index 0000000000000..3b8c8720c6774 --- /dev/null +++ b/tests/ui/assumptions_on_binders/non_lifetime_binder_alias_outlives.stderr @@ -0,0 +1,20 @@ +error[E0284]: type annotations needed: cannot satisfy `::Assoc: 'static` + --> $DIR/non_lifetime_binder_alias_outlives.rs:16:5 + | +LL | / type Ref +LL | | where +LL | | for T: Trait; + | |________________________________________^ cannot satisfy `::Assoc: 'static` + | +note: required by a bound in `Trait::Ref` + --> $DIR/non_lifetime_binder_alias_outlives.rs:18:32 + | +LL | type Ref + | --- required by a bound in this associated type +LL | where +LL | for T: Trait; + | ^^^^^^^ required by this bound in `Trait::Ref` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0284`. From a2f80c97243067708527f1d4f5d2bb5db75e4dff Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Fri, 19 Jun 2026 17:22:18 -0300 Subject: [PATCH 2/2] Handle alias outlives ambiguity during rewrite --- .../rustc_type_ir/src/region_constraint.rs | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index b896d7a398178..ddace4b941a43 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -654,18 +654,8 @@ fn pull_region_outlives_constraints_out_of_universe< use RegionConstraint::*; match constraint { Ambiguity | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) => { - // With only lifetime binders the rewrite step lowers these constraints out of `u` - // (or destructures them), so we expect `max_universe < u` here. A non-lifetime - // binder (`for`) instead introduces a placeholder type in `u`, which - // `PlaceholderReplacer` (region-only, see its `fold_region`) cannot pull out, - // leaving e.g. `::Assoc: 'r` stranded in `u`. The assumptions-on-binders - // machinery is region-outlives-only and can't decide such a constraint, so report - // ambiguity rather than ICE. - if max_universe(infcx, constraint.clone()) < u { - constraint - } else { - RegionConstraint::Ambiguity - } + assert!(max_universe(infcx, constraint.clone()) < u); + constraint } RegionOutlives(region_1, region_2) => { let region_1_u = max_universe(infcx, region_1); @@ -850,7 +840,15 @@ fn rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling< escaping_outlives, I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), ); - candidates.push(RegionConstraint::AliasTyOutlivesViaEnv(bound_outlives)); + let candidate = RegionConstraint::AliasTyOutlivesViaEnv(bound_outlives); + if max_universe(infcx, candidate.clone()) < u { + candidates.push(candidate); + } else { + // `PlaceholderReplacer` only folds regions. A non-lifetime binder can leave + // a placeholder type in `u`, so this type-outlives constraint cannot be + // handled by the region-outlives-only eager placeholder machinery. + candidates.push(Ambiguity); + } } let assumptions = match assumptions { @@ -895,12 +893,17 @@ fn rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling< // while we did skip the binder, bound vars aren't in any universe so // this can't be an escaping bound var - candidates.extend( - regions_outliving(escaping_r, assumptions, infcx.cx()) - .filter(|r2| max_universe(infcx, *r2) < u) - .map(|r2| AliasTyOutlivesViaEnv(bound_alias.map_bound(|alias| (alias, r2)))) - .collect::>(), - ); + for r2 in regions_outliving(escaping_r, assumptions, infcx.cx()) + .filter(|r2| max_universe(infcx, *r2) < u) + { + let candidate = + AliasTyOutlivesViaEnv(bound_alias.map_bound(|alias| (alias, r2))); + if max_universe(infcx, candidate.clone()) < u { + candidates.push(candidate); + } else { + candidates.push(Ambiguity); + } + } } // I'm not convinced our handling here is *complete* so for now