From 9bc17fdcd1ed19ac586d8e25f1a47f0e7d5effcc Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Wed, 7 Oct 2026 11:50:23 +0100 Subject: [PATCH 1/2] Don't rewrite alias outlives in smaller universes --- .../rustc_type_ir/src/region_constraint.rs | 85 +++++++++++-------- .../lower_universe_alias_outlives.rs | 26 ++++++ 2 files changed, 75 insertions(+), 36 deletions(-) create mode 100644 tests/ui/assumptions_on_binders/lower_universe_alias_outlives.rs diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index be05ed2cd4d81..63d0d777e9b3c 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -861,6 +861,7 @@ fn rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling< RegionConstraint::new_from_or(Or::build_and(and_constraint, or_constraint)) } +#[instrument(level = "debug", skip(infcx), ret)] fn rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder_handling< Infcx: InferCtxtLike, I: Interner, @@ -903,6 +904,7 @@ fn rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder Or::new(candidates.into_iter().map(|c| And::new([c]))) } +#[instrument(level = "debug", skip(infcx), ret)] fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handling< Infcx: InferCtxtLike, I: Interner, @@ -914,6 +916,10 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl ) -> Or { use LeafRegionConstraint::*; + if max_universe(infcx, bound_outlives) != u { + return Or::new_leaf(AliasTyOutlivesViaEnv(bound_outlives, ())); + } + let mut candidates = Vec::new(); // given there can be higher ranked assumptions, e.g. `for<'a> >::Assoc: 'c`, that @@ -931,7 +937,7 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl // handle. // // we don't care about this when rewriting in the root universe as we know the complete set of assumptions - if max_universe(infcx, bound_outlives) == u { + { let mut replacer = PlaceholderReplacer { cx: infcx.cx(), existing_var_count: bound_outlives.bound_vars().len(), @@ -950,6 +956,7 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), ); let candidate = Or::new_leaf(AliasTyOutlivesViaEnv(bound_outlives, ())); + debug!("fully higher ranked candidate: {candidate:?}"); if max_universe(infcx, candidate.clone()) < u { candidates.push(candidate); } else { @@ -968,41 +975,47 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl // given a list of regions which outlive `'u2` // // we don't care about this when rewriting in the root universe as we know the complete set of assumptions - let (escaping_alias, escaping_r) = bound_outlives.skip_binder(); - if max_universe(infcx, escaping_r) == u { - let mut replacer = PlaceholderReplacer { - cx: infcx.cx(), - existing_var_count: bound_outlives.bound_vars().len(), - bound_vars: IndexMap::default(), - universe: u, - current_index: DebruijnIndex::ZERO, - }; - let escaping_alias = escaping_alias.fold_with(&mut replacer); - let bound_vars = bound_outlives.bound_vars().iter().chain( - core::mem::take(&mut replacer.bound_vars) - .into_iter() - .map(|(_, bound_region)| BoundVariableKind::Region(bound_region.kind)), - ); - let bound_alias = Binder::bind_with_vars( - escaping_alias, - I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), - ); - - // while we did skip the binder, bound vars aren't in any universe so - // this can't be an escaping bound var - candidates.push(Or::new( - regions_outliving(escaping_r, assumptions, infcx.cx()) - .filter(|r2| max_universe(infcx, *r2) < u) - .map(|r2| { - let candidate = - AliasTyOutlivesViaEnv(bound_alias.map_bound(|alias| (alias, r2)), ()); - if max_universe(infcx, candidate.clone()) < u { - And::new([candidate]) - } else { - And::new([Ambiguity(())]) - } - }), - )); + { + let (escaping_alias, escaping_r) = bound_outlives.skip_binder(); + let max_u = max_universe(infcx, escaping_r); + debug!(?max_u); + if max_u == u { + let mut replacer = PlaceholderReplacer { + cx: infcx.cx(), + existing_var_count: bound_outlives.bound_vars().len(), + bound_vars: IndexMap::default(), + universe: u, + current_index: DebruijnIndex::ZERO, + }; + let escaping_alias = escaping_alias.fold_with(&mut replacer); + let bound_vars = bound_outlives.bound_vars().iter().chain( + core::mem::take(&mut replacer.bound_vars) + .into_iter() + .map(|(_, bound_region)| BoundVariableKind::Region(bound_region.kind)), + ); + let bound_alias = Binder::bind_with_vars( + escaping_alias, + I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), + ); + + // while we did skip the binder, bound vars aren't in any universe so + // this can't be an escaping bound var + let candidate = Or::new( + regions_outliving(escaping_r, assumptions, infcx.cx()) + .filter(|r2| max_universe(infcx, *r2) < u) + .map(|r2| { + let candidate = + AliasTyOutlivesViaEnv(bound_alias.map_bound(|alias| (alias, r2)), ()); + if max_universe(infcx, candidate.clone()) < u { + And::new([candidate]) + } else { + And::new([Ambiguity(())]) + } + }), + ); + debug!("transitive outlived region candidate: {candidate:?}"); + candidates.push(candidate); + } } // I'm not convinced our handling here is *complete* so for now diff --git a/tests/ui/assumptions_on_binders/lower_universe_alias_outlives.rs b/tests/ui/assumptions_on_binders/lower_universe_alias_outlives.rs new file mode 100644 index 0000000000000..f547295419c4f --- /dev/null +++ b/tests/ui/assumptions_on_binders/lower_universe_alias_outlives.rs @@ -0,0 +1,26 @@ +//@ check-pass + +// test that we don't incorrectly try and eagerly handle alias outlives' from too +// low a universe + +#![feature(test_binder_constraints, generic_const_items)] + +trait AliasHaver { + type Assoc; +} + +core::test_binder_constraints! { + impl + where + T::Assoc: 'static, + { + forall<'b> { + for<> T::Assoc: 'static + } expect { + // previously this would just be `or {}` i.e. false + for<> T::Assoc: 'static + } + } +} + +fn main() {} From 7d43809ccafa351527c5439e6833e2cd05c912e3 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Wed, 7 Oct 2026 13:08:04 +0100 Subject: [PATCH 2/2] Produce less general higher ranked alias constraints --- .../rustc_type_ir/src/region_constraint.rs | 98 +++++++------------ ...lias_outlives_with_repeated_placeholder.rs | 34 +++++++ 2 files changed, 70 insertions(+), 62 deletions(-) create mode 100644 tests/ui/assumptions_on_binders/alias_outlives_with_repeated_placeholder.rs diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 63d0d777e9b3c..240e8182cc78f 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -52,6 +52,7 @@ use crate::data_structures::IndexMap; use crate::fold::TypeSuperFoldable; use crate::inherent::*; use crate::relate::{Relate, RelateResult, TypeRelation, VarianceDiagInfo}; +use crate::visit::TypeVisitableExt; use crate::{ AliasTy, Binder, BoundRegion, BoundVar, BoundVariableKind, ClauseKind, DebruijnIndex, InferCtxtLike, Interner, IsRigid, OutlivesClause, Region, RegionKind, TyKind, TypeFoldable, @@ -924,8 +925,7 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl // given there can be higher ranked assumptions, e.g. `for<'a> >::Assoc: 'c`, that // means that it's actually *always* possible for an alias outlive to be satisfied in the root universe - // which means there should *always* be atleast two candidates when destructuring alias outlives. The - // two candidates being component outlives and then a higher ranked alias outlives. + // which means there should *always* be atleast one candidate when destructuring alias outlives. // // we dont care about this for region outlives as `for<'a> 'a: 'b` can't exist as we don't elaborate // higher ranked type outlives assumptions into higher ranked region outlives assumptions. similarly, @@ -935,9 +935,9 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl // // so actually only `for<'a, 'b> Alias<'a>: 'b` and `for<'a> T: 'a` are assumptions we actually need to // handle. - // - // we don't care about this when rewriting in the root universe as we know the complete set of assumptions - { + let higher_ranked_candidate_for_outlived_region = |region| { + let bound_outlives = bound_outlives.map_bound(|(alias, _)| (alias, region)); + let mut replacer = PlaceholderReplacer { cx: infcx.cx(), existing_var_count: bound_outlives.bound_vars().len(), @@ -955,68 +955,42 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl escaping_outlives, I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), ); - let candidate = Or::new_leaf(AliasTyOutlivesViaEnv(bound_outlives, ())); - debug!("fully higher ranked candidate: {candidate:?}"); - 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(Or::new_ambig(())); + + // `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. + if max_universe(infcx, bound_outlives) == u { + return Or::new_ambig(()); } - } - // Actually look at the assumptions and matching our higher ranked alias outlives goal - // against potentially higher ranked type outlives assumptions. - candidates.push(alias_outlives_candidates_from_assumptions(infcx, bound_outlives, assumptions)); + debug!("higher ranked candidate: {bound_outlives:?}"); + Or::new_leaf(AliasTyOutlivesViaEnv(bound_outlives, ())) + }; - // we can rewrite `Alias_u1: 'u2` into `Or(Alias_u1: 'u1)` - // given a list of regions which outlive `'u2` + // We elaborate something like `Alias<'a_u2>: 'b_u2` into alias outlives involving each + // region which is known to outlive `'b_u2`. So e.g. if `'c_u2: 'b_u2` is known to hold + // would wind up looking at `Alias<'a_u2>: 'c_u2` as well as `Alias<'a_u2>: 'b_u2`. // - // we don't care about this when rewriting in the root universe as we know the complete set of assumptions - { - let (escaping_alias, escaping_r) = bound_outlives.skip_binder(); - let max_u = max_universe(infcx, escaping_r); - debug!(?max_u); - if max_u == u { - let mut replacer = PlaceholderReplacer { - cx: infcx.cx(), - existing_var_count: bound_outlives.bound_vars().len(), - bound_vars: IndexMap::default(), - universe: u, - current_index: DebruijnIndex::ZERO, - }; - let escaping_alias = escaping_alias.fold_with(&mut replacer); - let bound_vars = bound_outlives.bound_vars().iter().chain( - core::mem::take(&mut replacer.bound_vars) - .into_iter() - .map(|(_, bound_region)| BoundVariableKind::Region(bound_region.kind)), - ); - let bound_alias = Binder::bind_with_vars( - escaping_alias, - I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), - ); - - // while we did skip the binder, bound vars aren't in any universe so - // this can't be an escaping bound var - let candidate = Or::new( - regions_outliving(escaping_r, assumptions, infcx.cx()) - .filter(|r2| max_universe(infcx, *r2) < u) - .map(|r2| { - let candidate = - AliasTyOutlivesViaEnv(bound_alias.map_bound(|alias| (alias, r2)), ()); - if max_universe(infcx, candidate.clone()) < u { - And::new([candidate]) - } else { - And::new([Ambiguity(())]) - } - }), - ); - debug!("transitive outlived region candidate: {candidate:?}"); - candidates.push(candidate); + // This is important in two cases. First, if the outliving region is in a lower universe then + // we've successfully lowered the universe of one of the terms in our alias outlives constraint. + // + // Secondly, if it's a region in the current universe *and* it's a region in the components of the + // alias then we get a less general constraint after replacing with bound vars compared to if we + // had just replaced the initial region. See `assumptions_on_binders/alias_outlives_with_repeated_placeholder.rs` + let escaping_r = bound_outlives.skip_binder().1; + if max_universe(infcx, escaping_r) < u || escaping_r.has_escaping_bound_vars() { + // we don't know anything about lower universe regions, and we also don't bother + // looking at the assumptions on the `AliasTyOutlivesViaEnv` binder. + candidates.push(higher_ranked_candidate_for_outlived_region(escaping_r)) + } else { + for r in regions_outliving(escaping_r, assumptions, infcx.cx()) { + candidates.push(higher_ranked_candidate_for_outlived_region(r)); } - } + }; + + // Actually look at the assumptions and matching our higher ranked alias outlives goal + // against potentially higher ranked type outlives assumptions. + candidates.push(alias_outlives_candidates_from_assumptions(infcx, bound_outlives, assumptions)); // I'm not convinced our handling here is *complete* so for now // let's be conservative and not let alias outlives' cause NoSolution diff --git a/tests/ui/assumptions_on_binders/alias_outlives_with_repeated_placeholder.rs b/tests/ui/assumptions_on_binders/alias_outlives_with_repeated_placeholder.rs new file mode 100644 index 0000000000000..b1fa6f7f910a3 --- /dev/null +++ b/tests/ui/assumptions_on_binders/alias_outlives_with_repeated_placeholder.rs @@ -0,0 +1,34 @@ +//@ check-pass +//@ compile-flags: -Zassumptions-on-binders -Znext-solver=globally + +#![feature(test_binder_constraints, non_lifetime_binders)] +#![expect(incomplete_features)] + +trait Trait<'a> { + type Assoc; +} + +core::test_binder_constraints! { + impl + where + for<'a> T: Trait<'a>, + for<'a> >::Assoc: 'a + { + forall<'a, 'b> + where + 'a: 'b, + { + for<> >::Assoc: 'b + } expect { + or { + // this first candidate is the important one! + for<'a> >::Assoc: 'a, + // these won't wind up actually being used to prove the OR + for<'a, 'b> >::Assoc: 'b, + for<'a> >::Assoc: 'static, + } + } + } +} + +fn main() {}