From e72868d9a077191bb2b8cbf2d5e8f826194d65ac Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Tue, 16 Jun 2026 15:37:46 -0300 Subject: [PATCH 01/15] Capture binder region constraints while relating --- .../src/canonical/mod.rs | 27 +++- .../src/solve/eval_ctxt/mod.rs | 108 ++++++++++++++- .../src/relate/solver_relating.rs | 128 +++++++++++++++++- ...principal-upcast-region-eq-issue-157859.rs | 19 +++ ...cipal-upcast-region-eq-issue-157859.stderr | 13 ++ .../trait-upcast-projection-region-eq.rs | 23 ++++ .../trait-upcast-projection-region-eq.stderr | 13 ++ 7 files changed, 321 insertions(+), 10 deletions(-) create mode 100644 tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.rs create mode 100644 tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.stderr create mode 100644 tests/ui/assumptions_on_binders/trait-upcast-projection-region-eq.rs create mode 100644 tests/ui/assumptions_on_binders/trait-upcast-projection-region-eq.stderr diff --git a/compiler/rustc_next_trait_solver/src/canonical/mod.rs b/compiler/rustc_next_trait_solver/src/canonical/mod.rs index 0d8620c3614a2..35b2b26ad105c 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/mod.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/mod.rs @@ -296,6 +296,7 @@ where struct ResponseRelating<'infcx, Infcx, I: Interner> { infcx: &'infcx Infcx, span: I::Span, + region_constraints: Option>>, } impl<'infcx, Infcx, I> ResponseRelating<'infcx, Infcx, I> @@ -303,8 +304,12 @@ where Infcx: InferCtxtLike, I: Interner, { - fn new(infcx: &'infcx Infcx, span: I::Span) -> Self { - ResponseRelating { infcx, span } + fn new(infcx: &'infcx Infcx, span: I::Span, collect_region_constraints: bool) -> Self { + ResponseRelating { + infcx, + span, + region_constraints: collect_region_constraints.then(Vec::new), + } } } @@ -416,7 +421,14 @@ where #[instrument(skip(self), level = "trace")] fn regions(&mut self, a: Region, b: Region) -> RelateResult> { - self.infcx.equate_regions(a, b, VisibleForLeakCheck::Yes, self.span); + if let Some(region_constraints) = &mut self.region_constraints { + if a != b { + region_constraints.push(ty::RegionConstraint::RegionOutlives(a, b)); + region_constraints.push(ty::RegionConstraint::RegionOutlives(b, a)); + } + } else { + self.infcx.equate_regions(a, b, VisibleForLeakCheck::Yes, self.span); + } Ok(a) } @@ -501,8 +513,15 @@ fn unify_query_var_values( assert_eq!(original_values.len(), var_values.len()); for (&orig, response) in iter::zip(original_values, var_values.var_values.iter()) { - let mut must_eq = ResponseRelating::new(&**delegate, span); + let collect_region_constraints = delegate.cx().assumptions_on_binders(); + let mut must_eq = + ResponseRelating::new(&**delegate, span, collect_region_constraints); must_eq.relate(orig, response).unwrap(); + if let Some(region_constraints) = must_eq.region_constraints { + delegate.register_solver_region_constraint(ty::RegionConstraint::And( + region_constraints.into_boxed_slice(), + )); + } } } diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index 60813f00dd4c9..15b2a15540247 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -1218,6 +1218,78 @@ where self.relate(param_env, lhs, ty::Variance::Invariant, rhs) } + /// This should be used when relating a rigid alias with another type. + /// + /// Normally we emit a nested `AliasRelate` when equating an inference + /// variable and an alias. This causes us to instead constrain the inference + /// variable to the alias without emitting a nested alias relate goals. + #[instrument(level = "trace", skip(self, param_env), ret)] + pub(super) fn relate_rigid_alias_non_alias( + &mut self, + param_env: I::ParamEnv, + alias: ty::AliasTerm, + variance: ty::Variance, + term: I::Term, + ) -> Result<(), NoSolutionOrRerunNonErased> { + // NOTE: this check is purely an optimization, the structural eq would + // always fail if the term is not an inference variable. + if term.is_infer() { + let cx = self.cx(); + // We need to relate `alias` to `term` treating only the outermost + // constructor as rigid, relating any contained generic arguments as + // normal. We do this by first structurally equating the `term` + // with the alias constructor instantiated with unconstrained infer vars, + // and then relate this with the whole `alias`. + // + // Alternatively we could modify `Equate` for this case by adding another + // variant to `StructurallyRelateAliases`. + let def_id = match alias.kind { + ty::AliasTermKind::ProjectionTy { def_id } => def_id.into(), + ty::AliasTermKind::InherentTy { def_id } => def_id.into(), + ty::AliasTermKind::OpaqueTy { def_id } => def_id.into(), + ty::AliasTermKind::FreeTy { def_id } => def_id.into(), + ty::AliasTermKind::AnonConst { def_id } => def_id.into(), + ty::AliasTermKind::ProjectionConst { def_id } => def_id.into(), + ty::AliasTermKind::FreeConst { def_id } => def_id.into(), + ty::AliasTermKind::InherentConst { def_id } => def_id.into(), + }; + let identity_args = self.fresh_args_for_item(def_id); + let rigid_ctor = alias.with_args(cx, identity_args); + let ctor_term = rigid_ctor.to_term(cx); + self.eq_structurally_relating_aliases(param_env, term, ctor_term)?; + self.relate(param_env, alias, variance, rigid_ctor) + } else { + Err(NoSolution.into()) + } + } + + /// This should only be used when we're either instantiating a previously + /// unconstrained "return value" or when we're sure that all aliases in + /// the types are rigid. + #[instrument(level = "trace", skip(self, param_env), ret)] + pub(super) fn eq_structurally_relating_aliases>( + &mut self, + param_env: I::ParamEnv, + lhs: T, + rhs: T, + ) -> Result<(), NoSolutionOrRerunNonErased> { + let result = if self.cx().assumptions_on_binders() { + let (goals, region_constraints) = + self.delegate.eq_structurally_relating_aliases_with_region_constraints( + param_env, + lhs, + rhs, + self.origin_span, + )?; + self.register_solver_region_constraint(region_constraints); + goals + } else { + self.delegate.eq_structurally_relating_aliases(param_env, lhs, rhs, self.origin_span)? + }; + assert_eq!(result, vec![]); + Ok(()) + } + #[instrument(level = "trace", skip(self, param_env), ret)] pub(super) fn sub>( &mut self, @@ -1236,7 +1308,19 @@ where variance: ty::Variance, rhs: T, ) -> Result<(), NoSolutionOrRerunNonErased> { - let goals = self.delegate.relate(param_env, lhs, variance, rhs, self.origin_span)?; + let goals = if self.cx().assumptions_on_binders() { + let (goals, region_constraints) = self.delegate.relate_with_region_constraints( + param_env, + lhs, + variance, + rhs, + self.origin_span, + )?; + self.register_solver_region_constraint(region_constraints); + goals + } else { + self.delegate.relate(param_env, lhs, variance, rhs, self.origin_span)? + }; for &goal in goals.iter() { let source = match goal.predicate.kind().skip_binder() { ty::PredicateKind::Subtype { .. } @@ -1259,12 +1343,30 @@ where /// goals correctly. #[instrument(level = "trace", skip(self, param_env), ret)] pub(super) fn eq_and_get_goals>( - &self, + &mut self, param_env: I::ParamEnv, lhs: T, rhs: T, ) -> Result>, NoSolution> { - Ok(self.delegate.relate(param_env, lhs, ty::Variance::Invariant, rhs, self.origin_span)?) + if self.cx().assumptions_on_binders() { + let (goals, region_constraints) = self.delegate.relate_with_region_constraints( + param_env, + lhs, + ty::Variance::Invariant, + rhs, + self.origin_span, + )?; + self.register_solver_region_constraint(region_constraints); + Ok(goals) + } else { + Ok(self.delegate.relate( + param_env, + lhs, + ty::Variance::Invariant, + rhs, + self.origin_span, + )?) + } } pub(super) fn instantiate_binder_with_infer + Copy>( diff --git a/compiler/rustc_type_ir/src/relate/solver_relating.rs b/compiler/rustc_type_ir/src/relate/solver_relating.rs index 1e8ff77e4d395..1e9328aa129dc 100644 --- a/compiler/rustc_type_ir/src/relate/solver_relating.rs +++ b/compiler/rustc_type_ir/src/relate/solver_relating.rs @@ -2,6 +2,7 @@ use tracing::{debug, instrument}; use self::combine::{PredicateEmittingRelation, super_combine_consts, super_combine_tys}; use crate::data_structures::DelayedSet; +use crate::region_constraint::RegionConstraint; use crate::relate::combine::combine_ty_args; pub use crate::relate::*; use crate::solve::{Goal, VisibleForLeakCheck}; @@ -19,6 +20,53 @@ pub trait RelateExt: InferCtxtLike { Vec::Predicate>>, TypeError, >; + fn relate_with_region_constraints>( + &self, + param_env: ::ParamEnv, + lhs: T, + variance: ty::Variance, + rhs: T, + span: ::Span, + ) -> Result< + ( + Vec::Predicate>>, + RegionConstraint, + ), + TypeError, + >; +} + +fn relate_with_options( + infcx: &Infcx, + param_env: ::ParamEnv, + lhs: T, + variance: ty::Variance, + rhs: T, + span: ::Span, + collect_region_constraints: bool, +) -> Result< + ( + Vec::Predicate>>, + Option>, + ), + TypeError, +> +where + Infcx: InferCtxtLike, + T: Relate, +{ + let mut relate = SolverRelating::build( + infcx, + variance, + param_env, + span, + collect_region_constraints.then(Vec::new), + ); + relate.relate(lhs, rhs)?; + Ok(( + relate.goals, + relate.region_constraints.map(|c| RegionConstraint::And(c.into_boxed_slice())), + )) } impl RelateExt for Infcx { @@ -33,9 +81,42 @@ impl RelateExt for Infcx { Vec::Predicate>>, TypeError, > { - let mut relate = SolverRelating::new(self, variance, param_env, span); - relate.relate(lhs, rhs)?; - Ok(relate.goals) + let (goals, _) = relate_with_options( + self, + param_env, + lhs, + variance, + rhs, + span, + false, + )?; + Ok(goals) + } + + fn relate_with_region_constraints>( + &self, + param_env: ::ParamEnv, + lhs: T, + variance: ty::Variance, + rhs: T, + span: ::Span, + ) -> Result< + ( + Vec::Predicate>>, + RegionConstraint, + ), + TypeError, + > { + let (goals, region_constraints) = relate_with_options( + self, + param_env, + lhs, + variance, + rhs, + span, + true, + )?; + Ok((goals, region_constraints.unwrap())) } } @@ -48,6 +129,7 @@ pub struct SolverRelating<'infcx, Infcx, I: Interner> { // Mutable fields. ambient_variance: ty::Variance, goals: Vec>, + region_constraints: Option>>, /// The cache only tracks the `ambient_variance` as it's the /// only field which is mutable and which meaningfully changes /// the result when relating types. @@ -83,6 +165,16 @@ where ambient_variance: ty::Variance, param_env: I::ParamEnv, span: I::Span, + ) -> Self { + Self::build(infcx, ambient_variance, param_env, span, None) + } + + fn build( + infcx: &'infcx Infcx, + ambient_variance: ty::Variance, + param_env: I::ParamEnv, + span: I::Span, + region_constraints: Option>>, ) -> Self { SolverRelating { infcx, @@ -90,6 +182,7 @@ where ambient_variance, param_env, goals: vec![], + region_constraints, cache: Default::default(), } } @@ -249,6 +342,35 @@ where } } + let resolve_region = |r: I::Region| match r.kind() { + ty::ReVar(vid) => self.infcx.opportunistic_resolve_lt_var(vid), + _ => r, + }; + let a = resolve_region(a); + let b = resolve_region(b); + + if let Some(region_constraints) = &mut self.region_constraints { + if a == b { + return Ok(a); + } else { + match self.ambient_variance { + ty::Covariant => { + region_constraints.push(RegionConstraint::RegionOutlives(a, b)); + } + ty::Contravariant => { + region_constraints.push(RegionConstraint::RegionOutlives(b, a)); + } + ty::Invariant => { + region_constraints.push(RegionConstraint::RegionOutlives(a, b)); + region_constraints.push(RegionConstraint::RegionOutlives(b, a)); + } + ty::Bivariant => { + unreachable!("Expected bivariance to be handled in relate_with_variance") + } + } + } + } + Ok(a) } diff --git a/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.rs b/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.rs new file mode 100644 index 0000000000000..52b90750e01a9 --- /dev/null +++ b/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.rs @@ -0,0 +1,19 @@ +//@compile-flags: -Zassumptions-on-binders -Znext-solver=globally +//@ dont-require-annotations: ERROR + +trait Super { + fn a(&self) { + let a: &dyn Sub = &(); + let b: &dyn Super fn(&'a ())> = a; + } +} + +impl Super for () {} + +trait Sub: Super {} + +impl Sub for () {} + +fn main() { + let a: &dyn Sub = &(); +} diff --git a/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.stderr b/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.stderr new file mode 100644 index 0000000000000..5ed8cb2dce80a --- /dev/null +++ b/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.stderr @@ -0,0 +1,13 @@ +error[E0277]: the trait bound `&dyn Sub: CoerceUnsized<&dyn Super fn(&'a ())>>` is not satisfied + --> $DIR/principal-upcast-region-eq-issue-157859.rs:7:49 + | +LL | let b: &dyn Super fn(&'a ())> = a; + | ^ the nightly-only, unstable trait `Unsize fn(&'a ())>>` is not implemented for `dyn Sub` + | + = note: all implementations of `Unsize` are provided automatically by the compiler, see for more information + = note: required for `&dyn Sub` to implement `CoerceUnsized<&dyn Super fn(&'a ())>>` + = note: required for the cast from `&dyn Sub` to `&dyn Super fn(&'a ())>` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/assumptions_on_binders/trait-upcast-projection-region-eq.rs b/tests/ui/assumptions_on_binders/trait-upcast-projection-region-eq.rs new file mode 100644 index 0000000000000..d32cdd33e6e8f --- /dev/null +++ b/tests/ui/assumptions_on_binders/trait-upcast-projection-region-eq.rs @@ -0,0 +1,23 @@ +//@ compile-flags: -Zassumptions-on-binders -Znext-solver=globally + +trait Super { + type Assoc; + + fn a(&self) { + let a: &dyn Sub = &(); + let b: &dyn Super fn(&'a ())> = a; + //~^ ERROR the trait bound `&dyn Sub: CoerceUnsized<&dyn Super fn(&'a ())>>` is not satisfied + } +} + +impl Super for () { + type Assoc = fn(&'static ()); +} + +trait Sub: Super {} + +impl Sub for () {} + +fn main() { + let a: &dyn Sub = &(); +} diff --git a/tests/ui/assumptions_on_binders/trait-upcast-projection-region-eq.stderr b/tests/ui/assumptions_on_binders/trait-upcast-projection-region-eq.stderr new file mode 100644 index 0000000000000..fb55bab0ad20c --- /dev/null +++ b/tests/ui/assumptions_on_binders/trait-upcast-projection-region-eq.stderr @@ -0,0 +1,13 @@ +error[E0277]: the trait bound `&dyn Sub: CoerceUnsized<&dyn Super fn(&'a ())>>` is not satisfied + --> $DIR/trait-upcast-projection-region-eq.rs:8:57 + | +LL | let b: &dyn Super fn(&'a ())> = a; + | ^ the nightly-only, unstable trait `Unsize fn(&'a ())>>` is not implemented for `dyn Sub` + | + = note: all implementations of `Unsize` are provided automatically by the compiler, see for more information + = note: required for `&dyn Sub` to implement `CoerceUnsized<&dyn Super fn(&'a ())>>` + = note: required for the cast from `&dyn Sub` to `&dyn Super fn(&'a ())>` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. From 6047bc2eff2b6b74124114807b5247a1d2a5d1e6 Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Sat, 20 Jun 2026 14:52:59 -0300 Subject: [PATCH 02/15] Register solver region constraints while relating --- .../src/canonical/mod.rs | 26 +-- .../src/solve/eval_ctxt/mod.rs | 55 +----- .../src/relate/solver_relating.rs | 163 ++++-------------- 3 files changed, 51 insertions(+), 193 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/canonical/mod.rs b/compiler/rustc_next_trait_solver/src/canonical/mod.rs index 35b2b26ad105c..44b7bdc754e7c 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/mod.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/mod.rs @@ -296,7 +296,6 @@ where struct ResponseRelating<'infcx, Infcx, I: Interner> { infcx: &'infcx Infcx, span: I::Span, - region_constraints: Option>>, } impl<'infcx, Infcx, I> ResponseRelating<'infcx, Infcx, I> @@ -304,12 +303,8 @@ where Infcx: InferCtxtLike, I: Interner, { - fn new(infcx: &'infcx Infcx, span: I::Span, collect_region_constraints: bool) -> Self { - ResponseRelating { - infcx, - span, - region_constraints: collect_region_constraints.then(Vec::new), - } + fn new(infcx: &'infcx Infcx, span: I::Span) -> Self { + ResponseRelating { infcx, span } } } @@ -421,10 +416,12 @@ where #[instrument(skip(self), level = "trace")] fn regions(&mut self, a: Region, b: Region) -> RelateResult> { - if let Some(region_constraints) = &mut self.region_constraints { + if self.cx().assumptions_on_binders() { if a != b { - region_constraints.push(ty::RegionConstraint::RegionOutlives(a, b)); - region_constraints.push(ty::RegionConstraint::RegionOutlives(b, a)); + self.infcx + .register_solver_region_constraint(ty::RegionConstraint::RegionOutlives(a, b)); + self.infcx + .register_solver_region_constraint(ty::RegionConstraint::RegionOutlives(b, a)); } } else { self.infcx.equate_regions(a, b, VisibleForLeakCheck::Yes, self.span); @@ -513,15 +510,8 @@ fn unify_query_var_values( assert_eq!(original_values.len(), var_values.len()); for (&orig, response) in iter::zip(original_values, var_values.var_values.iter()) { - let collect_region_constraints = delegate.cx().assumptions_on_binders(); - let mut must_eq = - ResponseRelating::new(&**delegate, span, collect_region_constraints); + let mut must_eq = ResponseRelating::new(&**delegate, span); must_eq.relate(orig, response).unwrap(); - if let Some(region_constraints) = must_eq.region_constraints { - delegate.register_solver_region_constraint(ty::RegionConstraint::And( - region_constraints.into_boxed_slice(), - )); - } } } diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index 15b2a15540247..d89f75323a6f7 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -1273,19 +1273,12 @@ where lhs: T, rhs: T, ) -> Result<(), NoSolutionOrRerunNonErased> { - let result = if self.cx().assumptions_on_binders() { - let (goals, region_constraints) = - self.delegate.eq_structurally_relating_aliases_with_region_constraints( - param_env, - lhs, - rhs, - self.origin_span, - )?; - self.register_solver_region_constraint(region_constraints); - goals - } else { - self.delegate.eq_structurally_relating_aliases(param_env, lhs, rhs, self.origin_span)? - }; + let result = self.delegate.eq_structurally_relating_aliases( + param_env, + lhs, + rhs, + self.origin_span, + )?; assert_eq!(result, vec![]); Ok(()) } @@ -1308,19 +1301,7 @@ where variance: ty::Variance, rhs: T, ) -> Result<(), NoSolutionOrRerunNonErased> { - let goals = if self.cx().assumptions_on_binders() { - let (goals, region_constraints) = self.delegate.relate_with_region_constraints( - param_env, - lhs, - variance, - rhs, - self.origin_span, - )?; - self.register_solver_region_constraint(region_constraints); - goals - } else { - self.delegate.relate(param_env, lhs, variance, rhs, self.origin_span)? - }; + let goals = self.delegate.relate(param_env, lhs, variance, rhs, self.origin_span)?; for &goal in goals.iter() { let source = match goal.predicate.kind().skip_binder() { ty::PredicateKind::Subtype { .. } @@ -1343,30 +1324,12 @@ where /// goals correctly. #[instrument(level = "trace", skip(self, param_env), ret)] pub(super) fn eq_and_get_goals>( - &mut self, + &self, param_env: I::ParamEnv, lhs: T, rhs: T, ) -> Result>, NoSolution> { - if self.cx().assumptions_on_binders() { - let (goals, region_constraints) = self.delegate.relate_with_region_constraints( - param_env, - lhs, - ty::Variance::Invariant, - rhs, - self.origin_span, - )?; - self.register_solver_region_constraint(region_constraints); - Ok(goals) - } else { - Ok(self.delegate.relate( - param_env, - lhs, - ty::Variance::Invariant, - rhs, - self.origin_span, - )?) - } + Ok(self.delegate.relate(param_env, lhs, ty::Variance::Invariant, rhs, self.origin_span)?) } pub(super) fn instantiate_binder_with_infer + Copy>( diff --git a/compiler/rustc_type_ir/src/relate/solver_relating.rs b/compiler/rustc_type_ir/src/relate/solver_relating.rs index 1e9328aa129dc..47a2ddfb269ab 100644 --- a/compiler/rustc_type_ir/src/relate/solver_relating.rs +++ b/compiler/rustc_type_ir/src/relate/solver_relating.rs @@ -20,53 +20,6 @@ pub trait RelateExt: InferCtxtLike { Vec::Predicate>>, TypeError, >; - fn relate_with_region_constraints>( - &self, - param_env: ::ParamEnv, - lhs: T, - variance: ty::Variance, - rhs: T, - span: ::Span, - ) -> Result< - ( - Vec::Predicate>>, - RegionConstraint, - ), - TypeError, - >; -} - -fn relate_with_options( - infcx: &Infcx, - param_env: ::ParamEnv, - lhs: T, - variance: ty::Variance, - rhs: T, - span: ::Span, - collect_region_constraints: bool, -) -> Result< - ( - Vec::Predicate>>, - Option>, - ), - TypeError, -> -where - Infcx: InferCtxtLike, - T: Relate, -{ - let mut relate = SolverRelating::build( - infcx, - variance, - param_env, - span, - collect_region_constraints.then(Vec::new), - ); - relate.relate(lhs, rhs)?; - Ok(( - relate.goals, - relate.region_constraints.map(|c| RegionConstraint::And(c.into_boxed_slice())), - )) } impl RelateExt for Infcx { @@ -81,42 +34,9 @@ impl RelateExt for Infcx { Vec::Predicate>>, TypeError, > { - let (goals, _) = relate_with_options( - self, - param_env, - lhs, - variance, - rhs, - span, - false, - )?; - Ok(goals) - } - - fn relate_with_region_constraints>( - &self, - param_env: ::ParamEnv, - lhs: T, - variance: ty::Variance, - rhs: T, - span: ::Span, - ) -> Result< - ( - Vec::Predicate>>, - RegionConstraint, - ), - TypeError, - > { - let (goals, region_constraints) = relate_with_options( - self, - param_env, - lhs, - variance, - rhs, - span, - true, - )?; - Ok((goals, region_constraints.unwrap())) + let mut relate = SolverRelating::new(self, variance, param_env, span); + relate.relate(lhs, rhs)?; + Ok(relate.goals) } } @@ -129,7 +49,6 @@ pub struct SolverRelating<'infcx, Infcx, I: Interner> { // Mutable fields. ambient_variance: ty::Variance, goals: Vec>, - region_constraints: Option>>, /// The cache only tracks the `ambient_variance` as it's the /// only field which is mutable and which meaningfully changes /// the result when relating types. @@ -165,16 +84,6 @@ where ambient_variance: ty::Variance, param_env: I::ParamEnv, span: I::Span, - ) -> Self { - Self::build(infcx, ambient_variance, param_env, span, None) - } - - fn build( - infcx: &'infcx Infcx, - ambient_variance: ty::Variance, - param_env: I::ParamEnv, - span: I::Span, - region_constraints: Option>>, ) -> Self { SolverRelating { infcx, @@ -182,7 +91,6 @@ where ambient_variance, param_env, goals: vec![], - region_constraints, cache: Default::default(), } } @@ -331,42 +239,39 @@ where #[instrument(skip(self), level = "trace")] fn regions(&mut self, a: Region, b: Region) -> RelateResult> { - match self.ambient_variance { - // Subtype(&'a u8, &'b u8) => Outlives('a: 'b) => SubRegion('b, 'a) - ty::Covariant => self.infcx.sub_regions(b, a, VisibleForLeakCheck::Yes, self.span), - // Suptype(&'a u8, &'b u8) => Outlives('b: 'a) => SubRegion('a, 'b) - ty::Contravariant => self.infcx.sub_regions(a, b, VisibleForLeakCheck::Yes, self.span), - ty::Invariant => self.infcx.equate_regions(a, b, VisibleForLeakCheck::Yes, self.span), - ty::Bivariant => { - unreachable!("Expected bivariance to be handled in relate_with_variance") - } - } - - let resolve_region = |r: I::Region| match r.kind() { - ty::ReVar(vid) => self.infcx.opportunistic_resolve_lt_var(vid), - _ => r, - }; - let a = resolve_region(a); - let b = resolve_region(b); - - if let Some(region_constraints) = &mut self.region_constraints { + if self.cx().assumptions_on_binders() { if a == b { return Ok(a); - } else { - match self.ambient_variance { - ty::Covariant => { - region_constraints.push(RegionConstraint::RegionOutlives(a, b)); - } - ty::Contravariant => { - region_constraints.push(RegionConstraint::RegionOutlives(b, a)); - } - ty::Invariant => { - region_constraints.push(RegionConstraint::RegionOutlives(a, b)); - region_constraints.push(RegionConstraint::RegionOutlives(b, a)); - } - ty::Bivariant => { - unreachable!("Expected bivariance to be handled in relate_with_variance") - } + } + + match self.ambient_variance { + ty::Covariant => self + .infcx + .register_solver_region_constraint(RegionConstraint::RegionOutlives(a, b)), + ty::Contravariant => self + .infcx + .register_solver_region_constraint(RegionConstraint::RegionOutlives(b, a)), + ty::Invariant => { + self.infcx + .register_solver_region_constraint(RegionConstraint::RegionOutlives(a, b)); + self.infcx + .register_solver_region_constraint(RegionConstraint::RegionOutlives(b, a)); + } + ty::Bivariant => { + unreachable!("Expected bivariance to be handled in relate_with_variance") + } + } + } else { + match self.ambient_variance { + ty::Covariant => self.infcx.sub_regions(b, a, VisibleForLeakCheck::Yes, self.span), + ty::Contravariant => { + self.infcx.sub_regions(a, b, VisibleForLeakCheck::Yes, self.span) + } + ty::Invariant => { + self.infcx.equate_regions(a, b, VisibleForLeakCheck::Yes, self.span) + } + ty::Bivariant => { + unreachable!("Expected bivariance to be handled in relate_with_variance") } } } From a0d18dc4e99910c989806e4e869b8959e015d688 Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Tue, 23 Jun 2026 14:10:30 -0300 Subject: [PATCH 03/15] Reproduce direct solver region registration failures --- .../src/solve/eval_ctxt/mod.rs | 65 ------------------- 1 file changed, 65 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index d89f75323a6f7..60813f00dd4c9 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -1218,71 +1218,6 @@ where self.relate(param_env, lhs, ty::Variance::Invariant, rhs) } - /// This should be used when relating a rigid alias with another type. - /// - /// Normally we emit a nested `AliasRelate` when equating an inference - /// variable and an alias. This causes us to instead constrain the inference - /// variable to the alias without emitting a nested alias relate goals. - #[instrument(level = "trace", skip(self, param_env), ret)] - pub(super) fn relate_rigid_alias_non_alias( - &mut self, - param_env: I::ParamEnv, - alias: ty::AliasTerm, - variance: ty::Variance, - term: I::Term, - ) -> Result<(), NoSolutionOrRerunNonErased> { - // NOTE: this check is purely an optimization, the structural eq would - // always fail if the term is not an inference variable. - if term.is_infer() { - let cx = self.cx(); - // We need to relate `alias` to `term` treating only the outermost - // constructor as rigid, relating any contained generic arguments as - // normal. We do this by first structurally equating the `term` - // with the alias constructor instantiated with unconstrained infer vars, - // and then relate this with the whole `alias`. - // - // Alternatively we could modify `Equate` for this case by adding another - // variant to `StructurallyRelateAliases`. - let def_id = match alias.kind { - ty::AliasTermKind::ProjectionTy { def_id } => def_id.into(), - ty::AliasTermKind::InherentTy { def_id } => def_id.into(), - ty::AliasTermKind::OpaqueTy { def_id } => def_id.into(), - ty::AliasTermKind::FreeTy { def_id } => def_id.into(), - ty::AliasTermKind::AnonConst { def_id } => def_id.into(), - ty::AliasTermKind::ProjectionConst { def_id } => def_id.into(), - ty::AliasTermKind::FreeConst { def_id } => def_id.into(), - ty::AliasTermKind::InherentConst { def_id } => def_id.into(), - }; - let identity_args = self.fresh_args_for_item(def_id); - let rigid_ctor = alias.with_args(cx, identity_args); - let ctor_term = rigid_ctor.to_term(cx); - self.eq_structurally_relating_aliases(param_env, term, ctor_term)?; - self.relate(param_env, alias, variance, rigid_ctor) - } else { - Err(NoSolution.into()) - } - } - - /// This should only be used when we're either instantiating a previously - /// unconstrained "return value" or when we're sure that all aliases in - /// the types are rigid. - #[instrument(level = "trace", skip(self, param_env), ret)] - pub(super) fn eq_structurally_relating_aliases>( - &mut self, - param_env: I::ParamEnv, - lhs: T, - rhs: T, - ) -> Result<(), NoSolutionOrRerunNonErased> { - let result = self.delegate.eq_structurally_relating_aliases( - param_env, - lhs, - rhs, - self.origin_span, - )?; - assert_eq!(result, vec![]); - Ok(()) - } - #[instrument(level = "trace", skip(self, param_env), ret)] pub(super) fn sub>( &mut self, From 250e1af820e3a2642bc8e6761c6d9ad633845ac3 Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Fri, 10 Jul 2026 14:50:44 -0300 Subject: [PATCH 04/15] Handle reflexive solver region constraints --- compiler/rustc_type_ir/src/region_constraint.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 9a643b538d93f..c39277602b699 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -645,6 +645,11 @@ fn pull_region_outlives_constraints_out_of_universe< pulled_constraints.push(Or::new_leaf(c.clone())); } RegionOutlives(region_1, region_2, ()) => { + if region_1 == region_2 { + // Reflexive constraints are always satisfied, even if the region is + // from `u`, so there's nothing left to pull out of the universe. + continue; + } let region_1_u = max_universe(infcx, region_1); let region_2_u = max_universe(infcx, region_2); From 8190f2392187c6aba1b818911e45d1f9424af64a Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Fri, 10 Jul 2026 14:51:48 -0300 Subject: [PATCH 05/15] Normalize equated region vars in solver constraints --- .../rustc_type_ir/src/region_constraint.rs | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index c39277602b699..bc47950c9472e 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -446,6 +446,10 @@ pub fn eagerly_handle_placeholders_in_universe RegionConstraint { let assumptions = infcx.get_placeholder_assumptions(u); + // Do this before rewriting type outlives constraints: alias/env matching below needs to + // see placeholders equated with current-universe region variables in the same conjunction. + let constraint = normalize_equated_region_vars(infcx, constraint, u); + // 1. rewrite type outlives constraints involving things from `u` into either region constraints // involving things from `u` or type outlives constraints not involving things from `u` // @@ -470,6 +474,115 @@ pub fn eagerly_handle_placeholders_in_universe, I: Interner>( + infcx: &Infcx, + constraint: RegionConstraint, + u: UniverseIndex, +) -> RegionConstraint { + use RegionConstraint::*; + + match constraint { + Ambiguity | RegionOutlives(..) | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) => { + constraint + } + Or(constraints) => Or(constraints + .into_iter() + .map(|constraint| normalize_equated_region_vars(infcx, constraint, u)) + .collect()), + And(constraints) => { + let constraint = And(constraints + .into_iter() + .map(|constraint| normalize_equated_region_vars(infcx, constraint, u)) + .collect()); + + let mut region_outlives = vec![]; + collect_conjunctive_region_outlives(&constraint, &mut region_outlives); + + let mut replacements = vec![]; + for (r1, r2) in region_outlives.iter().copied() { + if let Some(partner) = equated_non_var_partner(infcx, ®ion_outlives, r1, r2, u) { + replacements.push((r1, partner)); + } + + if let Some(partner) = equated_non_var_partner(infcx, ®ion_outlives, r2, r1, u) { + replacements.push((r2, partner)); + } + } + + if replacements.is_empty() { + constraint + } else { + constraint.fold_with(&mut EquatedRegionVarReplacer { cx: infcx.cx(), replacements }) + } + } + } +} + +fn equated_non_var_partner, I: Interner>( + infcx: &Infcx, + region_outlives: &[(I::Region, I::Region)], + candidate: I::Region, + partner: I::Region, + u: UniverseIndex, +) -> Option { + if is_current_universe_region_var(infcx, candidate, u) + && !is_region_var::(partner) + && region_outlives + .iter() + .any(|(outlives, outlived)| *outlives == partner && *outlived == candidate) + { + Some(partner) + } else { + None + } +} + +fn collect_conjunctive_region_outlives( + constraint: &RegionConstraint, + out: &mut Vec<(I::Region, I::Region)>, +) { + use RegionConstraint::*; + + match constraint { + RegionOutlives(r1, r2) => out.push((*r1, *r2)), + And(constraints) => { + for constraint in constraints.iter() { + collect_conjunctive_region_outlives(constraint, out); + } + } + Ambiguity | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) | Or(..) => {} + } +} + +fn is_current_universe_region_var, I: Interner>( + infcx: &Infcx, + region: I::Region, + u: UniverseIndex, +) -> bool { + is_region_var::(region) && max_universe(infcx, region) == u +} + +fn is_region_var(region: I::Region) -> bool { + matches!(region.kind(), RegionKind::ReVar(_)) +} + +struct EquatedRegionVarReplacer { + cx: I, + replacements: Vec<(I::Region, I::Region)>, +} + +impl TypeFolder for EquatedRegionVarReplacer { + fn cx(&self) -> I { + self.cx + } + + fn fold_region(&mut self, r: I::Region) -> I::Region { + // If a region variable has multiple non-var partners, the remaining folded + // constraints still relate those partners, so first-match only affects representation. + self.replacements.iter().find_map(|(from, to)| (*from == r).then_some(*to)).unwrap_or(r) + } +} + /// Filter our region constraints to not include constraints between region variables from `u` and /// other regions as those are always satisfied. This requires some care to handle correctly for example: /// `'!a_u1: '?x_u1: '!b_u1` should result in us requiring `'!a_u1: '!b_u1` rather than dropping the two From bcfe9a4f639ae5c7ab662eea813664ef099f2611 Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Sat, 11 Jul 2026 11:01:03 -0300 Subject: [PATCH 06/15] Normalize transitive equated region variables --- .../rustc_type_ir/src/region_constraint.rs | 109 ++++++++++++++---- 1 file changed, 85 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index bc47950c9472e..c432a3e1c6d9b 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -497,17 +497,7 @@ fn normalize_equated_region_vars, I: Interner let mut region_outlives = vec![]; collect_conjunctive_region_outlives(&constraint, &mut region_outlives); - - let mut replacements = vec![]; - for (r1, r2) in region_outlives.iter().copied() { - if let Some(partner) = equated_non_var_partner(infcx, ®ion_outlives, r1, r2, u) { - replacements.push((r1, partner)); - } - - if let Some(partner) = equated_non_var_partner(infcx, ®ion_outlives, r2, r1, u) { - replacements.push((r2, partner)); - } - } + let replacements = compute_equated_region_var_replacements(infcx, ®ion_outlives, u); if replacements.is_empty() { constraint @@ -518,23 +508,67 @@ fn normalize_equated_region_vars, I: Interner } } -fn equated_non_var_partner, I: Interner>( +fn compute_equated_region_var_replacements, I: Interner>( infcx: &Infcx, region_outlives: &[(I::Region, I::Region)], - candidate: I::Region, - partner: I::Region, u: UniverseIndex, -) -> Option { - if is_current_universe_region_var(infcx, candidate, u) - && !is_region_var::(partner) - && region_outlives - .iter() - .any(|(outlives, outlived)| *outlives == partner && *outlived == candidate) - { - Some(partner) - } else { - None +) -> Vec<(I::Region, I::Region)> { + compute_equated_region_var_replacements_from( + region_outlives, + |r| is_current_universe_region_var(infcx, r, u), + is_region_var::, + ) +} + +fn compute_equated_region_var_replacements_from( + region_outlives: &[(R, R)], + mut is_current_universe_region_var: impl FnMut(R) -> bool, + mut is_region_var: impl FnMut(R) -> bool, +) -> Vec<(R, R)> +where + R: Copy + Eq + std::hash::Hash, +{ + let mut equated_regions_builder = TransitiveRelationBuilder::default(); + let mut has_equated_regions = false; + for (r1, r2) in region_outlives.iter().copied() { + // Paired outlives constraints represent region equality. Build a transitive relation so + // current-universe variables equated through other variables still find a non-var partner. + if has_reverse_region_outlives_edge(region_outlives, r1, r2) { + equated_regions_builder.add(r1, r2); + equated_regions_builder.add(r2, r1); + has_equated_regions = true; + } } + + if !has_equated_regions { + return vec![]; + } + + let equated_regions = equated_regions_builder.freeze(); + let mut candidates = IndexSet::new(); + for (r1, r2) in region_outlives.iter().copied() { + if is_current_universe_region_var(r1) { + candidates.insert(r1); + } + + if is_current_universe_region_var(r2) { + candidates.insert(r2); + } + } + + candidates + .into_iter() + .filter_map(|candidate| { + std::iter::once(candidate) + .chain(equated_regions.reachable_from(candidate)) + .find(|r| !is_region_var(*r)) + .map(|partner| (candidate, partner)) + }) + .collect() +} + +fn has_reverse_region_outlives_edge(region_outlives: &[(R, R)], r1: R, r2: R) -> bool { + region_outlives.iter().any(|(outlives, outlived)| outlives == &r2 && outlived == &r1) } fn collect_conjunctive_region_outlives( @@ -583,6 +617,33 @@ impl TypeFolder for EquatedRegionVarReplacer { } } +#[cfg(test)] +mod tests { + use super::compute_equated_region_var_replacements_from; + + #[test] + fn equated_region_var_replacements_follow_transitive_region_var_chains() { + const REVAR_1: u8 = 1; + const REVAR_2: u8 = 2; + const PLACEHOLDER: u8 = 3; + + let region_outlives = [ + (REVAR_1, REVAR_2), + (REVAR_2, REVAR_1), + (REVAR_2, PLACEHOLDER), + (PLACEHOLDER, REVAR_2), + ]; + + let replacements = compute_equated_region_var_replacements_from( + ®ion_outlives, + |r| matches!(r, REVAR_1 | REVAR_2), + |r| matches!(r, REVAR_1 | REVAR_2), + ); + + assert_eq!(replacements, vec![(REVAR_1, PLACEHOLDER), (REVAR_2, PLACEHOLDER)]); + } +} + /// Filter our region constraints to not include constraints between region variables from `u` and /// other regions as those are always satisfied. This requires some care to handle correctly for example: /// `'!a_u1: '?x_u1: '!b_u1` should result in us requiring `'!a_u1: '!b_u1` rather than dropping the two From 0c2c2a39b6f92129ee2389ea17e1bdb520d85db2 Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Sat, 11 Jul 2026 11:12:20 -0300 Subject: [PATCH 07/15] Move region constraint tests into test module --- .../rustc_type_ir/src/region_constraint.rs | 26 +------------------ .../src/region_constraint/tests.rs | 19 ++++++++++++++ 2 files changed, 20 insertions(+), 25 deletions(-) create mode 100644 compiler/rustc_type_ir/src/region_constraint/tests.rs diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index c432a3e1c6d9b..66d5f9e4c1137 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -618,31 +618,7 @@ impl TypeFolder for EquatedRegionVarReplacer { } #[cfg(test)] -mod tests { - use super::compute_equated_region_var_replacements_from; - - #[test] - fn equated_region_var_replacements_follow_transitive_region_var_chains() { - const REVAR_1: u8 = 1; - const REVAR_2: u8 = 2; - const PLACEHOLDER: u8 = 3; - - let region_outlives = [ - (REVAR_1, REVAR_2), - (REVAR_2, REVAR_1), - (REVAR_2, PLACEHOLDER), - (PLACEHOLDER, REVAR_2), - ]; - - let replacements = compute_equated_region_var_replacements_from( - ®ion_outlives, - |r| matches!(r, REVAR_1 | REVAR_2), - |r| matches!(r, REVAR_1 | REVAR_2), - ); - - assert_eq!(replacements, vec![(REVAR_1, PLACEHOLDER), (REVAR_2, PLACEHOLDER)]); - } -} +mod tests; /// Filter our region constraints to not include constraints between region variables from `u` and /// other regions as those are always satisfied. This requires some care to handle correctly for example: diff --git a/compiler/rustc_type_ir/src/region_constraint/tests.rs b/compiler/rustc_type_ir/src/region_constraint/tests.rs new file mode 100644 index 0000000000000..b77884683c9f4 --- /dev/null +++ b/compiler/rustc_type_ir/src/region_constraint/tests.rs @@ -0,0 +1,19 @@ +use super::compute_equated_region_var_replacements_from; + +#[test] +fn equated_region_var_replacements_follow_transitive_region_var_chains() { + const REVAR_1: u8 = 1; + const REVAR_2: u8 = 2; + const PLACEHOLDER: u8 = 3; + + let region_outlives = + [(REVAR_1, REVAR_2), (REVAR_2, REVAR_1), (REVAR_2, PLACEHOLDER), (PLACEHOLDER, REVAR_2)]; + + let replacements = compute_equated_region_var_replacements_from( + ®ion_outlives, + |r| matches!(r, REVAR_1 | REVAR_2), + |r| matches!(r, REVAR_1 | REVAR_2), + ); + + assert_eq!(replacements, vec![(REVAR_1, PLACEHOLDER), (REVAR_2, PLACEHOLDER)]); +} From 4a75d1907b1b321d2e4d3da594c45686867e019f Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Tue, 21 Jul 2026 20:46:26 -0300 Subject: [PATCH 08/15] Adapt region constraints to upstream APIs --- .../rustc_next_trait_solver/src/canonical/mod.rs | 11 +++++++---- compiler/rustc_type_ir/src/region_constraint.rs | 14 +++++++------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/canonical/mod.rs b/compiler/rustc_next_trait_solver/src/canonical/mod.rs index 44b7bdc754e7c..6fc741eab6849 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/mod.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/mod.rs @@ -14,6 +14,7 @@ use std::iter; use canonicalizer::Canonicalizer; use rustc_index::IndexVec; use rustc_type_ir::inherent::*; +use rustc_type_ir::region_constraint::RegionConstraint as SolverRegionConstraint; use rustc_type_ir::relate::{ self, Relate, RelateResult, TypeRelation, VarianceDiagInfo, relate_args_invariantly, }; @@ -418,10 +419,12 @@ where fn regions(&mut self, a: Region, b: Region) -> RelateResult> { if self.cx().assumptions_on_binders() { if a != b { - self.infcx - .register_solver_region_constraint(ty::RegionConstraint::RegionOutlives(a, b)); - self.infcx - .register_solver_region_constraint(ty::RegionConstraint::RegionOutlives(b, a)); + self.infcx.register_solver_region_constraint( + SolverRegionConstraint::RegionOutlives(a, b), + ); + self.infcx.register_solver_region_constraint( + SolverRegionConstraint::RegionOutlives(b, a), + ); } } else { self.infcx.equate_regions(a, b, VisibleForLeakCheck::Yes, self.span); diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 66d5f9e4c1137..478f67376a85b 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -510,9 +510,9 @@ fn normalize_equated_region_vars, I: Interner fn compute_equated_region_var_replacements, I: Interner>( infcx: &Infcx, - region_outlives: &[(I::Region, I::Region)], + region_outlives: &[(Region, Region)], u: UniverseIndex, -) -> Vec<(I::Region, I::Region)> { +) -> Vec<(Region, Region)> { compute_equated_region_var_replacements_from( region_outlives, |r| is_current_universe_region_var(infcx, r, u), @@ -573,7 +573,7 @@ fn has_reverse_region_outlives_edge(region_outlives: &[(R, R)], r1: R, r2 fn collect_conjunctive_region_outlives( constraint: &RegionConstraint, - out: &mut Vec<(I::Region, I::Region)>, + out: &mut Vec<(Region, Region)>, ) { use RegionConstraint::*; @@ -590,19 +590,19 @@ fn collect_conjunctive_region_outlives( fn is_current_universe_region_var, I: Interner>( infcx: &Infcx, - region: I::Region, + region: Region, u: UniverseIndex, ) -> bool { is_region_var::(region) && max_universe(infcx, region) == u } -fn is_region_var(region: I::Region) -> bool { +fn is_region_var(region: Region) -> bool { matches!(region.kind(), RegionKind::ReVar(_)) } struct EquatedRegionVarReplacer { cx: I, - replacements: Vec<(I::Region, I::Region)>, + replacements: Vec<(Region, Region)>, } impl TypeFolder for EquatedRegionVarReplacer { @@ -610,7 +610,7 @@ impl TypeFolder for EquatedRegionVarReplacer { self.cx } - fn fold_region(&mut self, r: I::Region) -> I::Region { + fn fold_region(&mut self, r: Region) -> Region { // If a region variable has multiple non-var partners, the remaining folded // constraints still relate those partners, so first-match only affects representation. self.replacements.iter().find_map(|(from, to)| (*from == r).then_some(*to)).unwrap_or(r) From ebe0f2511fcd56a9f34836d91033d07f5ca9f590 Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Tue, 21 Jul 2026 20:46:42 -0300 Subject: [PATCH 09/15] Clarify region constraint branch terminology --- compiler/rustc_type_ir/src/region_constraint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 478f67376a85b..573674ed573b4 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -447,7 +447,7 @@ pub fn eagerly_handle_placeholders_in_universe Date: Wed, 19 Aug 2026 22:47:52 -0300 Subject: [PATCH 10/15] Match bivariance before region equality --- compiler/rustc_type_ir/src/relate/solver_relating.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_type_ir/src/relate/solver_relating.rs b/compiler/rustc_type_ir/src/relate/solver_relating.rs index 47a2ddfb269ab..573a5523f3750 100644 --- a/compiler/rustc_type_ir/src/relate/solver_relating.rs +++ b/compiler/rustc_type_ir/src/relate/solver_relating.rs @@ -240,11 +240,11 @@ where #[instrument(skip(self), level = "trace")] fn regions(&mut self, a: Region, b: Region) -> RelateResult> { if self.cx().assumptions_on_binders() { - if a == b { - return Ok(a); - } - match self.ambient_variance { + ty::Bivariant => { + unreachable!("Expected bivariance to be handled in relate_with_variance") + } + _ if a == b => return Ok(a), ty::Covariant => self .infcx .register_solver_region_constraint(RegionConstraint::RegionOutlives(a, b)), @@ -257,9 +257,6 @@ where self.infcx .register_solver_region_constraint(RegionConstraint::RegionOutlives(b, a)); } - ty::Bivariant => { - unreachable!("Expected bivariance to be handled in relate_with_variance") - } } } else { match self.ambient_variance { From 11482c13515d99f972f100bae65fec23e3db3572 Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Wed, 19 Aug 2026 22:47:57 -0300 Subject: [PATCH 11/15] Annotate principal upcast coerce error --- .../principal-upcast-region-eq-issue-157859.rs | 4 ++-- .../principal-upcast-region-eq-issue-157859.stderr | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.rs b/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.rs index 52b90750e01a9..f3050fe336eb1 100644 --- a/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.rs +++ b/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.rs @@ -1,10 +1,10 @@ -//@compile-flags: -Zassumptions-on-binders -Znext-solver=globally -//@ dont-require-annotations: ERROR +//@ compile-flags: -Zassumptions-on-binders -Znext-solver=globally trait Super { fn a(&self) { let a: &dyn Sub = &(); let b: &dyn Super fn(&'a ())> = a; + //~^ ERROR the trait bound `&dyn Sub: CoerceUnsized<&dyn Super fn(&'a ())>>` is not satisfied } } diff --git a/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.stderr b/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.stderr index 5ed8cb2dce80a..afc1f56491503 100644 --- a/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.stderr +++ b/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.stderr @@ -1,5 +1,5 @@ error[E0277]: the trait bound `&dyn Sub: CoerceUnsized<&dyn Super fn(&'a ())>>` is not satisfied - --> $DIR/principal-upcast-region-eq-issue-157859.rs:7:49 + --> $DIR/principal-upcast-region-eq-issue-157859.rs:6:49 | LL | let b: &dyn Super fn(&'a ())> = a; | ^ the nightly-only, unstable trait `Unsize fn(&'a ())>>` is not implemented for `dyn Sub` From f1dc7a36500795c6ee221c341a0cb842f5293786 Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Wed, 19 Aug 2026 22:48:23 -0300 Subject: [PATCH 12/15] Store equated region replacements in a map --- .../rustc_type_ir/src/region_constraint.rs | 61 +++++++++---------- .../src/region_constraint/tests.rs | 18 ++++-- 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 573674ed573b4..3109470f1a449 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -46,7 +46,7 @@ impl Default for TransitiveRelationBuilder { } } -use crate::data_structures::IndexMap; +use crate::data_structures::{HashMap, HashSet, IndexMap}; use crate::fold::TypeSuperFoldable; use crate::inherent::*; use crate::relate::{Relate, RelateResult, TypeRelation, VarianceDiagInfo}; @@ -512,7 +512,7 @@ fn compute_equated_region_var_replacements, I infcx: &Infcx, region_outlives: &[(Region, Region)], u: UniverseIndex, -) -> Vec<(Region, Region)> { +) -> HashMap, Region> { compute_equated_region_var_replacements_from( region_outlives, |r| is_current_universe_region_var(infcx, r, u), @@ -524,16 +524,18 @@ fn compute_equated_region_var_replacements_from( region_outlives: &[(R, R)], mut is_current_universe_region_var: impl FnMut(R) -> bool, mut is_region_var: impl FnMut(R) -> bool, -) -> Vec<(R, R)> +) -> HashMap where R: Copy + Eq + std::hash::Hash, { + let edges: HashSet<(R, R)> = region_outlives.iter().copied().collect(); + let mut equated_regions_builder = TransitiveRelationBuilder::default(); let mut has_equated_regions = false; for (r1, r2) in region_outlives.iter().copied() { // Paired outlives constraints represent region equality. Build a transitive relation so // current-universe variables equated through other variables still find a non-var partner. - if has_reverse_region_outlives_edge(region_outlives, r1, r2) { + if edges.contains(&(r2, r1)) { equated_regions_builder.add(r1, r2); equated_regions_builder.add(r2, r1); has_equated_regions = true; @@ -541,34 +543,31 @@ where } if !has_equated_regions { - return vec![]; + return HashMap::default(); } let equated_regions = equated_regions_builder.freeze(); - let mut candidates = IndexSet::new(); + let mut seen = HashSet::default(); + let mut replacements = HashMap::default(); for (r1, r2) in region_outlives.iter().copied() { - if is_current_universe_region_var(r1) { - candidates.insert(r1); - } + for candidate in [r1, r2] { + if !seen.insert(candidate) || !is_current_universe_region_var(candidate) { + continue; + } - if is_current_universe_region_var(r2) { - candidates.insert(r2); + // `reachable_from` already includes `candidate` when both equality edges exist. + // Candidates are always revars, so the partner has to come from that closure. + // If a var has several non-var partners, `find` just picks one; the remaining + // folded constraints still relate those partners, so first-match only affects + // representation. + if let Some(partner) = + equated_regions.reachable_from(candidate).into_iter().find(|r| !is_region_var(*r)) + { + replacements.insert(candidate, partner); + } } } - - candidates - .into_iter() - .filter_map(|candidate| { - std::iter::once(candidate) - .chain(equated_regions.reachable_from(candidate)) - .find(|r| !is_region_var(*r)) - .map(|partner| (candidate, partner)) - }) - .collect() -} - -fn has_reverse_region_outlives_edge(region_outlives: &[(R, R)], r1: R, r2: R) -> bool { - region_outlives.iter().any(|(outlives, outlived)| outlives == &r2 && outlived == &r1) + replacements } fn collect_conjunctive_region_outlives( @@ -602,7 +601,7 @@ fn is_region_var(region: Region) -> bool { struct EquatedRegionVarReplacer { cx: I, - replacements: Vec<(Region, Region)>, + replacements: HashMap, Region>, } impl TypeFolder for EquatedRegionVarReplacer { @@ -611,15 +610,10 @@ impl TypeFolder for EquatedRegionVarReplacer { } fn fold_region(&mut self, r: Region) -> Region { - // If a region variable has multiple non-var partners, the remaining folded - // constraints still relate those partners, so first-match only affects representation. - self.replacements.iter().find_map(|(from, to)| (*from == r).then_some(*to)).unwrap_or(r) + self.replacements.get(&r).copied().unwrap_or(r) } } -#[cfg(test)] -mod tests; - /// Filter our region constraints to not include constraints between region variables from `u` and /// other regions as those are always satisfied. This requires some care to handle correctly for example: /// `'!a_u1: '?x_u1: '!b_u1` should result in us requiring `'!a_u1: '!b_u1` rather than dropping the two @@ -1345,3 +1339,6 @@ impl<'a, Infcx: InferCtxtLike, I: Interner> TypeRelation Ok(a) } } + +#[cfg(all(test, feature = "nightly"))] +mod tests; diff --git a/compiler/rustc_type_ir/src/region_constraint/tests.rs b/compiler/rustc_type_ir/src/region_constraint/tests.rs index b77884683c9f4..831d98f590162 100644 --- a/compiler/rustc_type_ir/src/region_constraint/tests.rs +++ b/compiler/rustc_type_ir/src/region_constraint/tests.rs @@ -5,15 +5,25 @@ fn equated_region_var_replacements_follow_transitive_region_var_chains() { const REVAR_1: u8 = 1; const REVAR_2: u8 = 2; const PLACEHOLDER: u8 = 3; + // Equated with REVAR_1, but not a current-universe candidate and not a valid partner. + const OTHER_REVAR: u8 = 4; - let region_outlives = - [(REVAR_1, REVAR_2), (REVAR_2, REVAR_1), (REVAR_2, PLACEHOLDER), (PLACEHOLDER, REVAR_2)]; + let region_outlives = [ + (REVAR_1, REVAR_2), + (REVAR_2, REVAR_1), + (REVAR_2, PLACEHOLDER), + (PLACEHOLDER, REVAR_2), + (REVAR_1, OTHER_REVAR), + (OTHER_REVAR, REVAR_1), + ]; let replacements = compute_equated_region_var_replacements_from( ®ion_outlives, |r| matches!(r, REVAR_1 | REVAR_2), - |r| matches!(r, REVAR_1 | REVAR_2), + |r| matches!(r, REVAR_1 | REVAR_2 | OTHER_REVAR), ); - assert_eq!(replacements, vec![(REVAR_1, PLACEHOLDER), (REVAR_2, PLACEHOLDER)]); + assert_eq!(replacements.len(), 2); + assert_eq!(replacements.get(&REVAR_1), Some(&PLACEHOLDER)); + assert_eq!(replacements.get(&REVAR_2), Some(&PLACEHOLDER)); } From 710a4ebc9f573d7cf250e97d9c5a6d4a145e7af0 Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Wed, 19 Aug 2026 22:48:54 -0300 Subject: [PATCH 13/15] Clarify normalize and reflexive outlives comments --- compiler/rustc_type_ir/src/region_constraint.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 3109470f1a449..5ddb23abfd71c 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -446,8 +446,10 @@ pub fn eagerly_handle_placeholders_in_universe RegionConstraint { let assumptions = infcx.get_placeholder_assumptions(u); - // Do this before rewriting type outlives constraints: alias/env matching below needs to - // see placeholders equated with current-universe region variables in the same `And` branch. + // Replace current-universe `'?x` with a non-var it's equated with in this `And` + // (`'?x: '!a` and `'!a: '?x` → `'!a`). Alias/env matching has to see that shape + // or `alias_outlives.rs` / `implied_higher_ranked_alias_outlives_assumption.rs` + // go ambiguous. let constraint = normalize_equated_region_vars(infcx, constraint, u); // 1. rewrite type outlives constraints involving things from `u` into either region constraints @@ -790,8 +792,10 @@ fn pull_region_outlives_constraints_out_of_universe< } RegionOutlives(region_1, region_2, ()) => { if region_1 == region_2 { - // Reflexive constraints are always satisfied, even if the region is - // from `u`, so there's nothing left to pull out of the universe. + // `'r: 'r` is always true, including for current-universe regions, so + // there's nothing left to pull out of the universe. Relating a region to + // itself, component destructure, and normalize rewriting `'?x: '!a` + + // `'!a: '?x` into `'!a: '!a` can all produce this. continue; } let region_1_u = max_universe(infcx, region_1); From d8ac09489ded0c6c5a54fdf99765850e6f457f14 Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Wed, 19 Aug 2026 23:52:07 -0300 Subject: [PATCH 14/15] Update placeholder assumption diagnostic --- .../placeholder-assumptions-issue-157840.stderr | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/ui/assumptions_on_binders/placeholder-assumptions-issue-157840.stderr b/tests/ui/assumptions_on_binders/placeholder-assumptions-issue-157840.stderr index 5e8e131addd28..3ebb61b92c5ad 100644 --- a/tests/ui/assumptions_on_binders/placeholder-assumptions-issue-157840.stderr +++ b/tests/ui/assumptions_on_binders/placeholder-assumptions-issue-157840.stderr @@ -4,10 +4,11 @@ error[E0277]: the trait bound `(): Trait fn(>::Assoc))> LL | (): Trait<>::Assoc>, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Trait fn(>::Assoc))>` is not implemented for `()` | -help: consider extending the `where` clause, but there might be an alternative better way to express this requirement +help: this trait has no implementations, consider adding one + --> $DIR/placeholder-assumptions-issue-157840.rs:3:1 | -LL | (): Trait<>::Assoc>, (): Trait fn(>::Assoc))> - | +++++++++++++++++++++++++++++++++++++++++++++++++ +LL | trait Trait {} + | ^^^^^^^^^^^^^^ error: aborting due to 1 previous error From e34e0cded9b2d9ef7281ae96858a39706faa17f8 Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Sat, 22 Aug 2026 10:59:04 -0300 Subject: [PATCH 15/15] Adapt region constraints to current upstream APIs --- .../src/canonical/mod.rs | 15 ++-- .../rustc_type_ir/src/region_constraint.rs | 85 ++++++++++--------- .../src/relate/solver_relating.rs | 23 ++--- 3 files changed, 67 insertions(+), 56 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/canonical/mod.rs b/compiler/rustc_next_trait_solver/src/canonical/mod.rs index 6fc741eab6849..986bdaa7ddbd9 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/mod.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/mod.rs @@ -14,7 +14,9 @@ use std::iter; use canonicalizer::Canonicalizer; use rustc_index::IndexVec; use rustc_type_ir::inherent::*; -use rustc_type_ir::region_constraint::RegionConstraint as SolverRegionConstraint; +use rustc_type_ir::region_constraint::{ + LeafRegionConstraint, RegionConstraint as SolverRegionConstraint, +}; use rustc_type_ir::relate::{ self, Relate, RelateResult, TypeRelation, VarianceDiagInfo, relate_args_invariantly, }; @@ -419,12 +421,11 @@ where fn regions(&mut self, a: Region, b: Region) -> RelateResult> { if self.cx().assumptions_on_binders() { if a != b { - self.infcx.register_solver_region_constraint( - SolverRegionConstraint::RegionOutlives(a, b), - ); - self.infcx.register_solver_region_constraint( - SolverRegionConstraint::RegionOutlives(b, a), - ); + let region_outlives = |a, b| { + SolverRegionConstraint::new_leaf(LeafRegionConstraint::RegionOutlives(a, b, ())) + }; + self.infcx.register_solver_region_constraint(region_outlives(a, b), self.span); + self.infcx.register_solver_region_constraint(region_outlives(b, a), self.span); } } else { self.infcx.equate_regions(a, b, VisibleForLeakCheck::Yes, self.span); diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 5ddb23abfd71c..0ece6ceacff67 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -481,32 +481,46 @@ fn normalize_equated_region_vars, I: Interner constraint: RegionConstraint, u: UniverseIndex, ) -> RegionConstraint { - use RegionConstraint::*; + // Every `And` in the `Or` is a separate branch, so regions equated inside one of + // them may only be replaced within that branch. + let or_constraint = Or(constraint + .or_constraint + .0 + .into_iter() + .map(|and| normalize_equated_region_vars_in_and(infcx, and, u)) + .collect()); - match constraint { - Ambiguity | RegionOutlives(..) | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) => { - constraint - } - Or(constraints) => Or(constraints - .into_iter() - .map(|constraint| normalize_equated_region_vars(infcx, constraint, u)) - .collect()), - And(constraints) => { - let constraint = And(constraints - .into_iter() - .map(|constraint| normalize_equated_region_vars(infcx, constraint, u)) - .collect()); + // The outer `And` is conjoined with every branch, so its replacements apply to the + // whole constraint. + let replacements = compute_equated_region_var_replacements( + infcx, + &conjunctive_region_outlives(&constraint.and_constraint), + u, + ); + let constraint = RegionConstraint { and_constraint: constraint.and_constraint, or_constraint }; + let constraint = if replacements.is_empty() { + constraint + } else { + constraint.fold_with(&mut EquatedRegionVarReplacer { cx: infcx.cx(), replacements }) + }; - let mut region_outlives = vec![]; - collect_conjunctive_region_outlives(&constraint, &mut region_outlives); - let replacements = compute_equated_region_var_replacements(infcx, ®ion_outlives, u); + // Replacing regions can make previously distinct leaves equal, so rebuild the + // canonical form instead of handing back a constraint with duplicates in it. + RegionConstraint::new_from_or(constraint.splatted_and_constraints()) +} - if replacements.is_empty() { - constraint - } else { - constraint.fold_with(&mut EquatedRegionVarReplacer { cx: infcx.cx(), replacements }) - } - } +fn normalize_equated_region_vars_in_and, I: Interner>( + infcx: &Infcx, + and: And, + u: UniverseIndex, +) -> And { + let replacements = + compute_equated_region_var_replacements(infcx, &conjunctive_region_outlives(&and), u); + + if replacements.is_empty() { + and + } else { + And::new(and.fold_with(&mut EquatedRegionVarReplacer { cx: infcx.cx(), replacements }).0) } } @@ -572,21 +586,16 @@ where replacements } -fn collect_conjunctive_region_outlives( - constraint: &RegionConstraint, - out: &mut Vec<(Region, Region)>, -) { - use RegionConstraint::*; - - match constraint { - RegionOutlives(r1, r2) => out.push((*r1, *r2)), - And(constraints) => { - for constraint in constraints.iter() { - collect_conjunctive_region_outlives(constraint, out); - } - } - Ambiguity | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) | Or(..) => {} - } +fn conjunctive_region_outlives(and: &And) -> Vec<(Region, Region)> { + use LeafRegionConstraint::*; + + and.0 + .iter() + .filter_map(|c| match c { + RegionOutlives(r1, r2, ()) => Some((*r1, *r2)), + Ambiguity(_) | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) => None, + }) + .collect() } fn is_current_universe_region_var, I: Interner>( diff --git a/compiler/rustc_type_ir/src/relate/solver_relating.rs b/compiler/rustc_type_ir/src/relate/solver_relating.rs index 573a5523f3750..ddb94d6483f31 100644 --- a/compiler/rustc_type_ir/src/relate/solver_relating.rs +++ b/compiler/rustc_type_ir/src/relate/solver_relating.rs @@ -2,7 +2,7 @@ use tracing::{debug, instrument}; use self::combine::{PredicateEmittingRelation, super_combine_consts, super_combine_tys}; use crate::data_structures::DelayedSet; -use crate::region_constraint::RegionConstraint; +use crate::region_constraint::{LeafRegionConstraint, RegionConstraint}; use crate::relate::combine::combine_ty_args; pub use crate::relate::*; use crate::solve::{Goal, VisibleForLeakCheck}; @@ -240,22 +240,23 @@ where #[instrument(skip(self), level = "trace")] fn regions(&mut self, a: Region, b: Region) -> RelateResult> { if self.cx().assumptions_on_binders() { + let region_outlives = + |a, b| RegionConstraint::new_leaf(LeafRegionConstraint::RegionOutlives(a, b, ())); + match self.ambient_variance { ty::Bivariant => { unreachable!("Expected bivariance to be handled in relate_with_variance") } _ if a == b => return Ok(a), - ty::Covariant => self - .infcx - .register_solver_region_constraint(RegionConstraint::RegionOutlives(a, b)), - ty::Contravariant => self - .infcx - .register_solver_region_constraint(RegionConstraint::RegionOutlives(b, a)), + ty::Covariant => { + self.infcx.register_solver_region_constraint(region_outlives(a, b), self.span) + } + ty::Contravariant => { + self.infcx.register_solver_region_constraint(region_outlives(b, a), self.span) + } ty::Invariant => { - self.infcx - .register_solver_region_constraint(RegionConstraint::RegionOutlives(a, b)); - self.infcx - .register_solver_region_constraint(RegionConstraint::RegionOutlives(b, a)); + self.infcx.register_solver_region_constraint(region_outlives(a, b), self.span); + self.infcx.register_solver_region_constraint(region_outlives(b, a), self.span); } } } else {