From b90709acad30ea8f3e7f6c0ce8aa643eb982a92d Mon Sep 17 00:00:00 2001 From: Boxy Date: Tue, 28 Apr 2026 10:51:47 +0100 Subject: [PATCH 1/6] add higher ranked assumptions v2 flag --- compiler/rustc_infer/src/infer/context.rs | 4 ++++ compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs | 4 ++++ compiler/rustc_session/src/options.rs | 2 ++ compiler/rustc_type_ir/src/infer_ctxt.rs | 4 ++++ 4 files changed, 14 insertions(+) diff --git a/compiler/rustc_infer/src/infer/context.rs b/compiler/rustc_infer/src/infer/context.rs index fada30ff30633..39f27ec7ec6a9 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -22,6 +22,10 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { self.next_trait_solver } + fn higher_ranked_assumptions_v2(&self) -> bool { + self.tcx.sess.opts.unstable_opts.higher_ranked_assumptions_v2 + } + fn typing_mode(&self) -> ty::TypingMode<'tcx> { self.typing_mode() } 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 e224febd3c927..ac4caa2f65995 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 @@ -1092,6 +1092,10 @@ where args } + pub(super) fn higher_ranked_assumptions_v2(&self) -> bool { + self.delegate.higher_ranked_assumptions_v2() + } + pub(super) fn register_ty_outlives(&self, ty: I::Ty, lt: I::Region) { self.delegate.register_ty_outlives(ty, lt, self.origin_span); } diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 2a2d46615e2e7..2aa4b44a11059 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2355,6 +2355,8 @@ options! { help: bool = (false, parse_no_value, [UNTRACKED], "Print unstable compiler options"), higher_ranked_assumptions: bool = (false, parse_bool, [TRACKED], "allow deducing higher-ranked outlives assumptions from coroutines when proving auto traits"), + higher_ranked_assumptions_v2: bool = (false, parse_bool, [TRACKED], + "allow deducing higher-ranked outlives assumptions from all binders (`for<'a>`)"), hint_mostly_unused: bool = (false, parse_bool, [TRACKED], "hint that most of this crate will go unused, to minimize work for uncalled functions"), human_readable_cgu_names: bool = (false, parse_bool, [TRACKED], diff --git a/compiler/rustc_type_ir/src/infer_ctxt.rs b/compiler/rustc_type_ir/src/infer_ctxt.rs index 905b005cd48f8..ad93f7b382f80 100644 --- a/compiler/rustc_type_ir/src/infer_ctxt.rs +++ b/compiler/rustc_type_ir/src/infer_ctxt.rs @@ -234,6 +234,10 @@ pub trait InferCtxtLike: Sized { fn universe(&self) -> ty::UniverseIndex; fn create_next_universe(&self) -> ty::UniverseIndex; + fn higher_ranked_assumptions_v2(&self) -> bool { + false + } + fn universe_of_ty(&self, ty: ty::TyVid) -> Option; fn universe_of_lt(&self, lt: ty::RegionVid) -> Option; fn universe_of_ct(&self, ct: ty::ConstVid) -> Option; From a4649b4cbdafa07611a1f0c8487e63ba7f1a13f0 Mon Sep 17 00:00:00 2001 From: Boxy Date: Tue, 28 Apr 2026 10:02:05 +0100 Subject: [PATCH 2/6] new region constraint representation --- compiler/rustc_infer/src/infer/context.rs | 18 ++ compiler/rustc_infer/src/infer/mod.rs | 47 ++++ .../src/infer/snapshot/undo_log.rs | 9 + compiler/rustc_middle/src/traits/solve.rs | 5 + .../src/canonical/mod.rs | 9 +- .../src/solve/eval_ctxt/mod.rs | 31 ++- .../rustc_next_trait_solver/src/solve/mod.rs | 3 + compiler/rustc_type_ir/src/infer_ctxt.rs | 9 + compiler/rustc_type_ir/src/lib.rs | 1 + .../rustc_type_ir/src/region_constraint.rs | 216 ++++++++++++++++++ compiler/rustc_type_ir/src/solve/mod.rs | 15 +- 11 files changed, 351 insertions(+), 12 deletions(-) create mode 100644 compiler/rustc_type_ir/src/region_constraint.rs diff --git a/compiler/rustc_infer/src/infer/context.rs b/compiler/rustc_infer/src/infer/context.rs index 39f27ec7ec6a9..fc38022f7cb76 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -38,6 +38,12 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { self.create_next_universe() } + fn get_solve_region_constraint( + &self, + ) -> rustc_type_ir::region_constraint::RegionConstraint> { + self.inner.borrow().solver_region_constraint_storage.get_constraint() + } + fn universe_of_ty(&self, vid: ty::TyVid) -> Option { match self.try_resolve_ty_var(vid) { Err(universe) => Some(universe), @@ -276,6 +282,18 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { ); } + fn register_solver_region_constraint( + &self, + c: rustc_type_ir::region_constraint::RegionConstraint>, + ) { + let mut inner = self.inner.borrow_mut(); + use rustc_data_structures::undo_log::UndoLogs; + + use crate::infer::UndoLog; + inner.undo_log.push(UndoLog::PushSolverRegionConstraint); + inner.solver_region_constraint_storage.push(c); + } + fn register_ty_outlives(&self, ty: Ty<'tcx>, r: ty::Region<'tcx>, span: Span) { self.register_type_outlives_constraint(ty, r, &ObligationCause::dummy_with_span(span)); } diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index 26c03066c7e40..6e81fbc8f58c3 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -123,6 +123,9 @@ pub struct InferCtxtInner<'tcx> { /// region constraints would've been added. region_constraint_storage: Option>, + /// Used by the next solver when `-Zhigher-ranked-assumptions=v2` is set. + solver_region_constraint_storage: SolverRegionConstraintStorage<'tcx>, + /// A set of constraints that regionck must validate. /// /// Each constraint has the form `T:'a`, meaning "some type `T` must @@ -170,6 +173,7 @@ impl<'tcx> InferCtxtInner<'tcx> { float_unification_storage: Default::default(), float_origin_origin_storage: Default::default(), region_constraint_storage: Some(Default::default()), + solver_region_constraint_storage: SolverRegionConstraintStorage::new(), region_obligations: Default::default(), region_assumptions: Default::default(), hir_typeck_potentially_region_dependent_goals: Default::default(), @@ -1721,3 +1725,46 @@ impl<'tcx> InferCtxt<'tcx> { } } } + +type SolverRegionConstraint<'tcx> = + rustc_type_ir::region_constraint::RegionConstraint>; + +#[derive(Clone, Debug)] +struct SolverRegionConstraintStorage<'tcx>(SolverRegionConstraint<'tcx>); + +impl<'tcx> SolverRegionConstraintStorage<'tcx> { + fn new() -> Self { + SolverRegionConstraintStorage(SolverRegionConstraint::And(Box::new([]))) + } + + fn get_constraint(&self) -> SolverRegionConstraint<'tcx> { + self.0.clone() + } + + fn pop(&mut self) -> Option> { + match &mut self.0 { + SolverRegionConstraint::And(and) => { + let mut and = core::mem::take(and).into_iter().collect::>(); + let popped = and.pop()?; + self.0 = SolverRegionConstraint::And(and.into_boxed_slice()); + Some(popped) + } + _ => unreachable!(), + } + } + + #[instrument(level = "debug")] + fn push(&mut self, constraint: SolverRegionConstraint<'tcx>) { + match &mut self.0 { + SolverRegionConstraint::And(and) => { + let and = core::mem::take(and) + .into_iter() + .chain([constraint]) + .collect::>() + .into_boxed_slice(); + self.0 = SolverRegionConstraint::And(and); + } + _ => unreachable!(), + } + } +} diff --git a/compiler/rustc_infer/src/infer/snapshot/undo_log.rs b/compiler/rustc_infer/src/infer/snapshot/undo_log.rs index 19212c99ae43b..4b6e2cab316c2 100644 --- a/compiler/rustc_infer/src/infer/snapshot/undo_log.rs +++ b/compiler/rustc_infer/src/infer/snapshot/undo_log.rs @@ -28,6 +28,7 @@ pub(crate) enum UndoLog<'tcx> { RegionUnificationTable(sv::UndoLog>>), ProjectionCache(traits::UndoLog<'tcx>), PushTypeOutlivesConstraint, + PushSolverRegionConstraint, PushRegionAssumption, PushHirTypeckPotentiallyRegionDependentGoal, } @@ -77,6 +78,14 @@ impl<'tcx> Rollback> for InferCtxtInner<'tcx> { self.region_constraint_storage.as_mut().unwrap().unification_table.reverse(undo) } UndoLog::ProjectionCache(undo) => self.projection_cache.reverse(undo), + UndoLog::PushSolverRegionConstraint => { + let popped = self.solver_region_constraint_storage.pop(); + assert_matches!( + popped, + Some(_), + "pushed solver region constraint but could not pop it" + ); + } UndoLog::PushTypeOutlivesConstraint => { let popped = self.region_obligations.pop(); assert_matches!(popped, Some(_), "pushed region constraint but could not pop it"); diff --git a/compiler/rustc_middle/src/traits/solve.rs b/compiler/rustc_middle/src/traits/solve.rs index 3343f27033301..eccbc34170a0d 100644 --- a/compiler/rustc_middle/src/traits/solve.rs +++ b/compiler/rustc_middle/src/traits/solve.rs @@ -48,6 +48,10 @@ impl<'tcx> TypeFoldable> for ExternalConstraints<'tcx> { } Ok(FallibleTypeFolder::cx(folder).mk_external_constraints(ExternalConstraintsData { + solver_region_constraint: self + .solver_region_constraint + .clone() + .try_fold_with(folder)?, region_constraints: self.region_constraints.clone().try_fold_with(folder)?, opaque_types: self .opaque_types @@ -70,6 +74,7 @@ impl<'tcx> TypeFoldable> for ExternalConstraints<'tcx> { } TypeFolder::cx(folder).mk_external_constraints(ExternalConstraintsData { + solver_region_constraint: self.solver_region_constraint.clone().fold_with(folder), region_constraints: self.region_constraints.clone().fold_with(folder), opaque_types: self.opaque_types.iter().map(|opaque| opaque.fold_with(folder)).collect(), normalization_nested_goals: self.normalization_nested_goals.clone().fold_with(folder), diff --git a/compiler/rustc_next_trait_solver/src/canonical/mod.rs b/compiler/rustc_next_trait_solver/src/canonical/mod.rs index a32a693a899cf..e055f7f0d8b4d 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/mod.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/mod.rs @@ -113,9 +113,14 @@ where unify_query_var_values(delegate, param_env, &original_values, var_values, span); - let ExternalConstraintsData { region_constraints, opaque_types, normalization_nested_goals } = - &*external_constraints; + let ExternalConstraintsData { + solver_region_constraint, + region_constraints, + opaque_types, + normalization_nested_goals, + } = &*external_constraints; + delegate.register_solver_region_constraint(solver_region_constraint.clone()); register_region_constraints(delegate, region_constraints, span); register_new_opaque_types(delegate, opaque_types, span); 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 ac4caa2f65995..5c6c4dee732a6 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 @@ -1,18 +1,22 @@ use std::mem; use std::ops::ControlFlow; +use rustc_data_structures::transitive_relation::TransitiveRelationBuilder; #[cfg(feature = "nightly")] use rustc_macros::HashStable_NoContext; -use rustc_type_ir::data_structures::{HashMap, HashSet}; +use rustc_type_ir::ClauseKind::*; +use rustc_type_ir::data_structures::{HashMap, HashSet, IndexMap}; use rustc_type_ir::inherent::*; +use rustc_type_ir::outlives::Component; +use rustc_type_ir::region_constraint::RegionConstraint; use rustc_type_ir::relate::Relate; use rustc_type_ir::relate::solver_relating::RelateExt; use rustc_type_ir::search_graph::{CandidateHeadUsages, PathKind}; use rustc_type_ir::solve::OpaqueTypesJank; use rustc_type_ir::{ - self as ty, CanonicalVarValues, InferCtxtLike, Interner, TypeFoldable, TypeFolder, - TypeSuperFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, - TypingMode, + self as ty, AliasTy, Binder, CanonicalVarValues, InferCtxtLike, Interner, OutlivesPredicate, + TypeFoldable, TypeFolder, TypeSuperFoldable, TypeSuperVisitable, TypeVisitable, + TypeVisitableExt, TypeVisitor, TypingMode, UniverseIndex, }; use tracing::{debug, instrument, trace}; @@ -1096,6 +1100,10 @@ where self.delegate.higher_ranked_assumptions_v2() } + pub(super) fn register_solver_region_constraint(&self, c: RegionConstraint) { + self.delegate.register_solver_region_constraint(c); + } + pub(super) fn register_ty_outlives(&self, ty: I::Ty, lt: I::Region) { self.delegate.register_ty_outlives(ty, lt, self.origin_span); } @@ -1291,8 +1299,11 @@ where return Ok(self.make_ambiguous_response_no_constraints(cause, opaque_types_jank)); } - let external_constraints = - self.compute_external_query_constraints(certainty, normalization_nested_goals); + let external_constraints = self.compute_external_query_constraints( + certainty, + normalization_nested_goals, + RegionConstraint::new_true(), + ); let (var_values, mut external_constraints) = eager_resolve_vars(self.delegate, (self.var_values, external_constraints)); @@ -1344,6 +1355,7 @@ where &self, certainty: Certainty, normalization_nested_goals: NestedNormalizationGoals, + solver_region_constraint: RegionConstraint, ) -> ExternalConstraintsData { // We only return region constraints once the certainty is `Yes`. This // is necessary as we may drop nested goals on ambiguity, which may result @@ -1367,7 +1379,12 @@ where .delegate .clone_opaque_types_added_since(self.initial_opaque_types_storage_num_entries); - ExternalConstraintsData { region_constraints, opaque_types, normalization_nested_goals } + ExternalConstraintsData { + solver_region_constraint, + region_constraints, + opaque_types, + normalization_nested_goals, + } } } diff --git a/compiler/rustc_next_trait_solver/src/solve/mod.rs b/compiler/rustc_next_trait_solver/src/solve/mod.rs index 793d45f22d39b..6796f45b09987 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -59,11 +59,13 @@ fn has_no_inference_or_external_constraints( response: ty::Canonical>, ) -> bool { let ExternalConstraintsData { + ref solver_region_constraint, ref region_constraints, ref opaque_types, ref normalization_nested_goals, } = *response.value.external_constraints; response.value.var_values.is_identity() + && solver_region_constraint.is_true() && region_constraints.is_empty() && opaque_types.is_empty() && normalization_nested_goals.is_empty() @@ -71,6 +73,7 @@ fn has_no_inference_or_external_constraints( fn has_only_region_constraints(response: ty::Canonical>) -> bool { let ExternalConstraintsData { + solver_region_constraint: _, region_constraints: _, ref opaque_types, ref normalization_nested_goals, diff --git a/compiler/rustc_type_ir/src/infer_ctxt.rs b/compiler/rustc_type_ir/src/infer_ctxt.rs index ad93f7b382f80..97781ccc0c170 100644 --- a/compiler/rustc_type_ir/src/infer_ctxt.rs +++ b/compiler/rustc_type_ir/src/infer_ctxt.rs @@ -238,6 +238,10 @@ pub trait InferCtxtLike: Sized { false } + fn get_solve_region_constraint( + &self, + ) -> crate::region_constraint::RegionConstraint; + fn universe_of_ty(&self, ty: ty::TyVid) -> Option; fn universe_of_lt(&self, lt: ty::RegionVid) -> Option; fn universe_of_ct(&self, ct: ty::ConstVid) -> Option; @@ -337,6 +341,11 @@ pub trait InferCtxtLike: Sized { span: ::Span, ); + fn register_solver_region_constraint( + &self, + c: crate::region_constraint::RegionConstraint, + ); + fn register_ty_outlives( &self, ty: ::Ty, diff --git a/compiler/rustc_type_ir/src/lib.rs b/compiler/rustc_type_ir/src/lib.rs index 8f314ab86da24..c445a1c975014 100644 --- a/compiler/rustc_type_ir/src/lib.rs +++ b/compiler/rustc_type_ir/src/lib.rs @@ -28,6 +28,7 @@ pub mod ir_print; pub mod lang_items; pub mod lift; pub mod outlives; +pub mod region_constraint; pub mod relate; pub mod search_graph; pub mod solve; diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs new file mode 100644 index 0000000000000..41c2bf800b2b8 --- /dev/null +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -0,0 +1,216 @@ +use derive_where::derive_where; +use indexmap::IndexSet; +#[cfg(feature = "nightly")] +use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; +use rustc_data_structures::transitive_relation::{TransitiveRelation, TransitiveRelationBuilder}; +use tracing::{debug, instrument}; + +use crate::data_structures::IndexMap; +use crate::fold::TypeSuperFoldable; +use crate::inherent::*; +use crate::relate::{Relate, RelateResult, TypeRelation, VarianceDiagInfo}; +use crate::visit::TypeSuperVisitable; +use crate::{ + AliasTy, Binder, BoundRegion, BoundVar, BoundVariableKind, ConstKind, DebruijnIndex, + FallibleTypeFolder, InferCtxtLike, InferTy, Interner, OutlivesPredicate, RegionKind, TyKind, + TypeFoldable, TypeFolder, TypeVisitable, TypeVisitor, TypingMode, UniverseIndex, Variance, + VisitorResult, +}; + +#[derive_where(Clone, Hash, PartialEq, Debug; I: Interner)] +pub enum RegionConstraint { + Ambiguity, + RegionOutlives(I::Region, I::Region), + AliasTyOutlivesFromEnv(Binder, I::Region)>), + /// This is an `I::Ty` for two reasons: + /// 1. We need the type visitable impl to be able to `visit_ty` on this so canonicalization + /// knows about the placeholder + /// 2. When exiting the trait solver there may be placeholder outlives corresponding to params + /// from the root universe. These need to be changed from a `Placeholder` to the original + /// `Param`. + PlaceholderTyOutlives(I::Ty, I::Region), + + And(Box<[RegionConstraint]>), + Or(Box<[RegionConstraint]>), +} + +#[cfg(feature = "nightly")] +// This is not a derived impl because a perfect derive leads to cycle errors which +// means the trait is never actually implemented but the compiler doesn't tell you +// that so if you get a *WEIRD* error where its just telling you random types don't +// implement HashStable.... it's because of that +impl HashStable for RegionConstraint +where + I::Region: HashStable, + AliasTy: HashStable, + I::Ty: HashStable, + I::BoundVarKinds: HashStable, +{ + #[inline] + fn hash_stable(&self, hcx: &mut CTX, hasher: &mut StableHasher) { + use RegionConstraint::*; + + std::mem::discriminant(self).hash_stable(hcx, hasher); + match self { + Ambiguity => (), + RegionOutlives(a, b) => { + a.hash_stable(hcx, hasher); + b.hash_stable(hcx, hasher); + } + AliasTyOutlivesFromEnv(outlives) => { + outlives.hash_stable(hcx, hasher); + } + PlaceholderTyOutlives(a, b) => { + a.hash_stable(hcx, hasher); + b.hash_stable(hcx, hasher); + } + And(and) => { + for a in and.iter() { + a.hash_stable(hcx, hasher); + } + } + Or(or) => { + for a in or.iter() { + a.hash_stable(hcx, hasher); + } + } + } + } +} + +impl TypeFoldable for RegionConstraint { + fn try_fold_with>(self, f: &mut F) -> Result { + use RegionConstraint::*; + Ok(match self { + Ambiguity => self, + RegionOutlives(a, b) => RegionOutlives(a.try_fold_with(f)?, b.try_fold_with(f)?), + AliasTyOutlivesFromEnv(outlives) => AliasTyOutlivesFromEnv(outlives.try_fold_with(f)?), + PlaceholderTyOutlives(a, b) => { + PlaceholderTyOutlives(a.try_fold_with(f)?, b.try_fold_with(f)?) + } + And(and) => { + let mut new_and = Vec::new(); + for a in and { + new_and.push(a.try_fold_with(f)?); + } + And(new_and.into_boxed_slice()) + } + Or(or) => { + let mut new_or = Vec::new(); + for a in or { + new_or.push(a.try_fold_with(f)?); + } + Or(new_or.into_boxed_slice()) + } + }) + } + + fn fold_with>(self, f: &mut F) -> Self { + use RegionConstraint::*; + match self { + Ambiguity => self, + RegionOutlives(a, b) => RegionOutlives(a.fold_with(f), b.fold_with(f)), + AliasTyOutlivesFromEnv(outlives) => AliasTyOutlivesFromEnv(outlives.fold_with(f)), + PlaceholderTyOutlives(a, b) => PlaceholderTyOutlives(a.fold_with(f), b.fold_with(f)), + And(and) => { + let mut new_and = Vec::new(); + for a in and { + new_and.push(a.fold_with(f)); + } + And(new_and.into_boxed_slice()) + } + Or(or) => { + let mut new_or = Vec::new(); + for a in or { + new_or.push(a.fold_with(f)); + } + Or(new_or.into_boxed_slice()) + } + } + } +} + +impl TypeVisitable for RegionConstraint { + fn visit_with>(&self, f: &mut F) -> F::Result { + use core::ops::ControlFlow::*; + + use RegionConstraint::*; + + match self { + Ambiguity => (), + RegionOutlives(a, b) => { + if let b @ Break(_) = a.visit_with(f).branch() { + return F::Result::from_branch(b); + }; + if let b @ Break(_) = b.visit_with(f).branch() { + return F::Result::from_branch(b); + }; + } + AliasTyOutlivesFromEnv(outlives) => { + return outlives.visit_with(f); + } + PlaceholderTyOutlives(a, b) => { + if let b @ Break(_) = a.visit_with(f).branch() { + return F::Result::from_branch(b); + }; + if let b @ Break(_) = b.visit_with(f).branch() { + return F::Result::from_branch(b); + }; + } + And(and) => { + for a in and { + if let b @ Break(_) = a.visit_with(f).branch() { + return F::Result::from_branch(b); + }; + } + } + Or(or) => { + for a in or { + if let b @ Break(_) = a.visit_with(f).branch() { + return F::Result::from_branch(b); + }; + } + } + }; + + F::Result::output() + } +} + +impl Default for RegionConstraint { + fn default() -> Self { + Self::new_true() + } +} + +impl RegionConstraint { + pub fn new_true() -> Self { + RegionConstraint::And(Box::new([])) + } + + pub fn is_true(&self) -> bool { + match self { + Self::And(and) => and.is_empty(), + _ => false, + } + } + + pub fn new_false() -> Self { + RegionConstraint::Or(Box::new([])) + } + + pub fn is_false(&self) -> bool { + match self { + Self::Or(or) => or.is_empty(), + _ => false, + } + } + + pub fn is_or(&self) -> bool { + matches!(self, Self::Or(_)) + } + + pub fn is_ambig(&self) -> bool { + matches!(self, Self::Ambiguity) + } +} diff --git a/compiler/rustc_type_ir/src/solve/mod.rs b/compiler/rustc_type_ir/src/solve/mod.rs index fe779b66dc245..4e32d9c9fbc4b 100644 --- a/compiler/rustc_type_ir/src/solve/mod.rs +++ b/compiler/rustc_type_ir/src/solve/mod.rs @@ -10,6 +10,7 @@ use rustc_type_ir_macros::{ }; use crate::lang_items::SolverTraitLangItem; +use crate::region_constraint::RegionConstraint; use crate::search_graph::PathKind; use crate::{self as ty, Canonical, CanonicalVarValues, Interner, Upcast}; @@ -254,6 +255,7 @@ impl Eq for Response {} #[cfg_attr(feature = "nightly", derive(HashStable_NoContext))] pub struct ExternalConstraintsData { pub region_constraints: Vec>, + pub solver_region_constraint: RegionConstraint, pub opaque_types: Vec<(ty::OpaqueTypeKey, I::Ty)>, pub normalization_nested_goals: NestedNormalizationGoals, } @@ -262,9 +264,16 @@ impl Eq for ExternalConstraintsData {} impl ExternalConstraintsData { pub fn is_empty(&self) -> bool { - self.region_constraints.is_empty() - && self.opaque_types.is_empty() - && self.normalization_nested_goals.is_empty() + let ExternalConstraintsData { + solver_region_constraint, + region_constraints, + opaque_types, + normalization_nested_goals, + } = self; + solver_region_constraint.is_true() + && region_constraints.is_empty() + && opaque_types.is_empty() + && normalization_nested_goals.is_empty() } } From 2bee9293bc2af45768eac19dd20cefc4fe7bdebe Mon Sep 17 00:00:00 2001 From: Boxy Date: Tue, 28 Apr 2026 10:03:26 +0100 Subject: [PATCH 3/6] compute and track assumptions on entered binders --- compiler/rustc_infer/src/infer/at.rs | 2 + compiler/rustc_infer/src/infer/context.rs | 15 +++ compiler/rustc_infer/src/infer/mod.rs | 11 ++ .../src/solve/effect_goals.rs | 2 +- .../src/solve/eval_ctxt/mod.rs | 115 +++++++++++++++++- .../src/solve/trait_goals.rs | 57 +++++---- compiler/rustc_type_ir/src/infer_ctxt.rs | 9 ++ .../rustc_type_ir/src/region_constraint.rs | 102 ++++++++++++++++ 8 files changed, 285 insertions(+), 28 deletions(-) diff --git a/compiler/rustc_infer/src/infer/at.rs b/compiler/rustc_infer/src/infer/at.rs index 70e3d7dc9fef0..ba40af97780c7 100644 --- a/compiler/rustc_infer/src/infer/at.rs +++ b/compiler/rustc_infer/src/infer/at.rs @@ -81,6 +81,7 @@ impl<'tcx> InferCtxt<'tcx> { reported_signature_mismatch: self.reported_signature_mismatch.clone(), tainted_by_errors: self.tainted_by_errors.clone(), universe: self.universe.clone(), + universe_assumptions_for_next_solver: self.universe_assumptions_for_next_solver.clone(), next_trait_solver: self.next_trait_solver, obligation_inspector: self.obligation_inspector.clone(), } @@ -106,6 +107,7 @@ impl<'tcx> InferCtxt<'tcx> { reported_signature_mismatch: self.reported_signature_mismatch.clone(), tainted_by_errors: self.tainted_by_errors.clone(), universe: self.universe.clone(), + universe_assumptions_for_next_solver: self.universe_assumptions_for_next_solver.clone(), next_trait_solver: self.next_trait_solver, obligation_inspector: self.obligation_inspector.clone(), }; diff --git a/compiler/rustc_infer/src/infer/context.rs b/compiler/rustc_infer/src/infer/context.rs index fc38022f7cb76..d9f689cf125d2 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -38,6 +38,21 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { self.create_next_universe() } + fn insert_universe_assumptions( + &self, + u: ty::UniverseIndex, + assumptions: Option>>, + ) { + self.universe_assumptions_for_next_solver.borrow_mut().insert(u, assumptions); + } + + fn get_universe_assumptions( + &self, + u: ty::UniverseIndex, + ) -> Option>> { + self.universe_assumptions_for_next_solver.borrow().get(&u).unwrap().as_ref().cloned() + } + fn get_solve_region_constraint( &self, ) -> rustc_type_ir::region_constraint::RegionConstraint> { diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index 6e81fbc8f58c3..6f621f09f35ee 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -318,6 +318,16 @@ pub struct InferCtxt<'tcx> { /// bound. universe: Cell, + /// List of assumed wellformed types which we can derive implied + /// bounds on a `for<...>` from. Only used unstabley and by the + /// new solver. + universe_assumptions_for_next_solver: RefCell< + FxIndexMap< + ty::UniverseIndex, + Option>>, + >, + >, + next_trait_solver: bool, pub obligation_inspector: Cell>>, @@ -610,6 +620,7 @@ impl<'tcx> InferCtxtBuilder<'tcx> { reported_signature_mismatch: Default::default(), tainted_by_errors: Cell::new(None), universe: Cell::new(ty::UniverseIndex::ROOT), + universe_assumptions_for_next_solver: RefCell::new(Default::default()), next_trait_solver, obligation_inspector: Cell::new(None), } diff --git a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs index aa49997253dc2..b97dbaf204c98 100644 --- a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs @@ -257,7 +257,7 @@ where structural_traits::instantiate_constituent_tys_for_copy_clone_trait(ecx, self_ty)?; ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| { - ecx.enter_forall(constituent_tys, |ecx, tys| { + ecx.enter_forall_with_assumptions(constituent_tys, goal.param_env, |ecx, tys| { ecx.add_goals( GoalSource::ImplWhereBound, tys.into_iter().map(|ty| { 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 5c6c4dee732a6..ca31c86b75ed6 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 @@ -8,7 +8,7 @@ use rustc_type_ir::ClauseKind::*; use rustc_type_ir::data_structures::{HashMap, HashSet, IndexMap}; use rustc_type_ir::inherent::*; use rustc_type_ir::outlives::Component; -use rustc_type_ir::region_constraint::RegionConstraint; +use rustc_type_ir::region_constraint::{Assumptions, RegionConstraint}; use rustc_type_ir::relate::Relate; use rustc_type_ir::relate::solver_relating::RelateExt; use rustc_type_ir::search_graph::{CandidateHeadUsages, PathKind}; @@ -568,7 +568,7 @@ where pub(super) fn compute_goal(&mut self, goal: Goal) -> QueryResult { let Goal { param_env, predicate } = goal; let kind = predicate.kind(); - self.enter_forall(kind, |ecx, kind| match kind { + self.enter_forall_with_assumptions(kind, param_env, |ecx, kind| match kind { ty::PredicateKind::Clause(ty::ClauseKind::Trait(predicate)) => { ecx.compute_trait_goal(Goal { param_env, predicate }).map(|(r, _via)| r) } @@ -1059,14 +1059,26 @@ where self.delegate.instantiate_binder_with_infer(value) } - /// `enter_forall`, but takes `&mut self` and passes it back through the - /// callback since it can't be aliased during the call. - pub(super) fn enter_forall, U>( + /// The `param_env` is used to *compute* the assumptions of the binder, not *as* the + /// assumptions associated with the binder. + /// + /// FIXME(inherent_associated_types): fix this? + pub(super) fn enter_forall_with_assumptions, U>( &mut self, value: ty::Binder, + param_env: I::ParamEnv, f: impl FnOnce(&mut Self, T) -> U, ) -> U { - self.delegate.enter_forall(value, |value| f(self, value)) + self.delegate.enter_forall(value, |value| { + let u = self.delegate.universe(); + let assumptions = if self.higher_ranked_assumptions_v2() { + self.region_assumptions_from_term(value.clone(), u, param_env) + } else { + None + }; + self.delegate.insert_universe_assumptions(u, assumptions); + f(self, value) + }) } pub(super) fn resolve_vars_if_possible(&self, value: T) -> T @@ -1386,6 +1398,97 @@ where normalization_nested_goals, } } + + #[instrument(level = "debug", skip(self), ret)] + pub(super) fn region_assumptions_from_term( + &mut self, + t: impl TypeVisitable, + u: UniverseIndex, + param_env: I::ParamEnv, + ) -> Option> { + struct RawAssumptions<'a, 'b, D: SolverDelegate, I: Interner> { + ecx: &'a mut EvalCtxt<'b, D, I>, + param_env: I::ParamEnv, + out: Vec>, + } + + impl TypeVisitor for RawAssumptions<'_, '_, D, I> + where + I: Interner, + D: SolverDelegate, + { + fn visit_ty(&mut self, t: I::Ty) { + self.out.extend( + self.ecx + .well_formed_goals(self.param_env, t.into()) + .unwrap_or(vec![]) + .into_iter(), + ); + } + + fn visit_const(&mut self, c: I::Const) { + // FIXME: empty vec here is weird? + self.out.extend( + self.ecx + .well_formed_goals(self.param_env, c.into()) + .unwrap_or(vec![]) + .into_iter(), + ); + } + } + + let mut reqs_builder = RawAssumptions { ecx: self, param_env, out: vec![] }; + t.visit_with(&mut reqs_builder); + let reqs = reqs_builder.out; + + let mut region_outlives_builder = TransitiveRelationBuilder::default(); + let mut inverse_region_outlives_builder = TransitiveRelationBuilder::default(); + let mut type_outlives = vec![]; + + // If there are inference variables in type outlives then we may not be able + // to elaborate to the full set of implied bounds right now. To avoid incorrectly + // NoSolution'ing when lifting constraints to a lower universe due to no usable + // assumptions, we just bail here. + // + // This is somewhat imprecise as if both the infer var and the outlived region are + // in a lower universe than the binder we're computing assumptions for then it doesn't + // really matter as we wouldn't use those outlives as assumptions anyway. + if reqs.iter().any(|goal| { + // We don't care about region infers as they can't be further destructured + goal.predicate.has_non_region_infer() + }) { + return None; + } + + // FIXME(-Zhigher-ranked-assumptions-v2): we need to normalize here/somewhere + // as we assume the type outlives assumptions only have rigid types :> + let clauses = rustc_type_ir::elaborate::elaborate( + self.cx(), + reqs.into_iter().filter_map(|goal| goal.predicate.as_clause()), + ); + + clauses + .filter(move |clause| { + rustc_type_ir::region_constraint::max_universe(&**self.delegate, *clause) == u + }) + .for_each(|clause| match clause.kind().skip_binder() { + RegionOutlives(OutlivesPredicate(r1, r2)) => { + assert!(clause.kind().no_bound_vars().is_some()); + region_outlives_builder.add(r1, r2); + inverse_region_outlives_builder.add(r2, r1); + } + TypeOutlives(p) => { + type_outlives.push(clause.kind().map_bound(|_| p)); + } + _ => (), + }); + + Some(Assumptions { + type_outlives, + region_outlives: region_outlives_builder.freeze(), + inverse_region_outlives: inverse_region_outlives_builder.freeze(), + }) + } } /// Eagerly replace aliases with inference variables, emitting `AliasRelate` diff --git a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs index ffe90da4cb0fe..bfe01e678e55f 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -1063,12 +1063,16 @@ where && ecx .probe(|_| ProbeKind::ProjectionCompatibility) .enter(|ecx| -> Result<_, NoSolution> { - ecx.enter_forall(target_projection, |ecx, target_projection| { - let source_projection = - ecx.instantiate_binder_with_infer(source_projection); - ecx.eq(param_env, source_projection, target_projection)?; - ecx.try_evaluate_added_goals() - }) + ecx.enter_forall_with_assumptions( + target_projection, + param_env, + |ecx, target_projection| { + let source_projection = + ecx.instantiate_binder_with_infer(source_projection); + ecx.eq(param_env, source_projection, target_projection)?; + ecx.try_evaluate_added_goals() + }, + ) }) .is_ok() }; @@ -1081,12 +1085,16 @@ where ty::ExistentialPredicate::Trait(target_principal) => { let source_principal = upcast_principal.unwrap(); let target_principal = bound.rebind(target_principal); - ecx.enter_forall(target_principal, |ecx, target_principal| { - let source_principal = - ecx.instantiate_binder_with_infer(source_principal); - ecx.eq(param_env, source_principal, target_principal)?; - ecx.try_evaluate_added_goals() - })?; + ecx.enter_forall_with_assumptions( + target_principal, + param_env, + |ecx, target_principal| { + let source_principal = + ecx.instantiate_binder_with_infer(source_principal); + ecx.eq(param_env, source_principal, target_principal)?; + ecx.try_evaluate_added_goals() + }, + )?; } // Check that b_ty's projection is satisfied by exactly one of // a_ty's projections. First, we look through the list to see if @@ -1107,12 +1115,16 @@ where Certainty::AMBIGUOUS, ); } - ecx.enter_forall(target_projection, |ecx, target_projection| { - let source_projection = - ecx.instantiate_binder_with_infer(source_projection); - ecx.eq(param_env, source_projection, target_projection)?; - ecx.try_evaluate_added_goals() - })?; + ecx.enter_forall_with_assumptions( + target_projection, + param_env, + |ecx, target_projection| { + let source_projection = + ecx.instantiate_binder_with_infer(source_projection); + ecx.eq(param_env, source_projection, target_projection)?; + ecx.try_evaluate_added_goals() + }, + )?; } // Check that b_ty's auto traits are present in a_ty's bounds. ty::ExistentialPredicate::AutoTrait(def_id) => { @@ -1337,14 +1349,17 @@ where ) -> Result>, NoSolution>, ) -> Result, NoSolution> { self.probe_trait_candidate(source).enter(|ecx| { - let goals = - ecx.enter_forall(constituent_tys(ecx, goal.predicate.self_ty())?, |ecx, tys| { + let goals = ecx.enter_forall_with_assumptions( + constituent_tys(ecx, goal.predicate.self_ty())?, + goal.param_env, + |ecx, tys| { tys.into_iter() .map(|ty| { goal.with(ecx.cx(), goal.predicate.with_replaced_self_ty(ecx.cx(), ty)) }) .collect::>() - }); + }, + ); ecx.add_goals(GoalSource::ImplWhereBound, goals); ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) }) diff --git a/compiler/rustc_type_ir/src/infer_ctxt.rs b/compiler/rustc_type_ir/src/infer_ctxt.rs index 97781ccc0c170..a4cff0414df93 100644 --- a/compiler/rustc_type_ir/src/infer_ctxt.rs +++ b/compiler/rustc_type_ir/src/infer_ctxt.rs @@ -238,6 +238,15 @@ pub trait InferCtxtLike: Sized { false } + fn insert_universe_assumptions( + &self, + u: ty::UniverseIndex, + assumptions: Option>, + ); + fn get_universe_assumptions( + &self, + u: ty::UniverseIndex, + ) -> Option>; fn get_solve_region_constraint( &self, ) -> crate::region_constraint::RegionConstraint; diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 41c2bf800b2b8..e5d1dd9de1bd9 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -17,6 +17,40 @@ use crate::{ VisitorResult, }; +#[derive_where(Clone, Debug; I: Interner)] +pub struct Assumptions { + pub type_outlives: Vec>>, + pub region_outlives: TransitiveRelation, + pub inverse_region_outlives: TransitiveRelation, +} + +impl Assumptions { + pub fn empty() -> Self { + Self { + type_outlives: Vec::new(), + region_outlives: TransitiveRelationBuilder::default().freeze(), + inverse_region_outlives: TransitiveRelationBuilder::default().freeze(), + } + } + + pub fn new( + type_outlives: Vec>>, + region_outlives: TransitiveRelation, + ) -> Self { + Self { + inverse_region_outlives: { + let mut builder = TransitiveRelationBuilder::default(); + for (r1, r2) in region_outlives.base_edges() { + builder.add(r2, r1); + } + builder.freeze() + }, + type_outlives, + region_outlives, + } + } +} + #[derive_where(Clone, Hash, PartialEq, Debug; I: Interner)] pub enum RegionConstraint { Ambiguity, @@ -214,3 +248,71 @@ impl RegionConstraint { matches!(self, Self::Ambiguity) } } + +pub fn max_universe, I: Interner, T: TypeVisitable>( + infcx: &Infcx, + t: T, +) -> UniverseIndex { + let mut visitor = MaxUniverse::new(infcx); + t.visit_with(&mut visitor); + visitor.max_universe() +} + +struct MaxUniverse<'a, Infcx: InferCtxtLike> { + max_universe: UniverseIndex, + infcx: &'a Infcx, +} + +impl<'a, Infcx: InferCtxtLike> MaxUniverse<'a, Infcx> { + fn new(infcx: &'a Infcx) -> Self { + MaxUniverse { infcx, max_universe: UniverseIndex::ROOT } + } + + fn max_universe(self) -> UniverseIndex { + self.max_universe + } +} + +impl<'a, Infcx: InferCtxtLike, I: Interner> TypeVisitor + for MaxUniverse<'a, Infcx> +{ + fn visit_ty(&mut self, t: I::Ty) { + if let TyKind::Placeholder(placeholder) = t.kind() { + self.max_universe = self.max_universe.max(placeholder.universe); + } + + if let TyKind::Infer(InferTy::TyVar(inf)) = t.kind() { + let u = self.infcx.universe_of_ty(inf).unwrap(); + debug!("var {inf:?} in universe {u:?}"); + self.max_universe = self.max_universe.max(u) + } + + t.super_visit_with(self) + } + + fn visit_const(&mut self, c: I::Const) { + if let ConstKind::Placeholder(placeholder) = c.kind() { + self.max_universe = self.max_universe.max(placeholder.universe); + } + + if let ConstKind::Infer(rustc_type_ir::InferConst::Var(inf)) = c.kind() { + let u = self.infcx.universe_of_ct(inf).unwrap(); + debug!("var {inf:?} in universe {u:?}"); + self.max_universe = self.max_universe.max(u) + } + + c.super_visit_with(self) + } + + fn visit_region(&mut self, r: I::Region) { + if let RegionKind::RePlaceholder(placeholder) = r.kind() { + self.max_universe = self.max_universe.max(placeholder.universe); + } + + if let RegionKind::ReVar(var) = r.kind() { + let u = self.infcx.universe_of_lt(var).unwrap(); + debug!("var {var:?} in universe {u:?}"); + self.max_universe = self.max_universe.max(u) + } + } +} From 48a09661836432aacbbdbc7c7018e284eb9330e2 Mon Sep 17 00:00:00 2001 From: Boxy Date: Tue, 28 Apr 2026 10:51:58 +0100 Subject: [PATCH 4/6] produce new region constraints --- .../src/infer/outlives/obligations.rs | 4 +- .../rustc_infer/src/infer/outlives/verify.rs | 50 +---- .../src/placeholder.rs | 104 ++++++++++ .../src/solve/eval_ctxt/mod.rs | 143 +++++++++++++- .../rustc_next_trait_solver/src/solve/mod.rs | 182 +++++++++++++++++- .../src/solve/delegate.rs | 10 +- compiler/rustc_type_ir/src/inherent.rs | 12 ++ compiler/rustc_type_ir/src/outlives.rs | 37 +++- 8 files changed, 481 insertions(+), 61 deletions(-) diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index e98bee3b12cde..606d8c6b165f0 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -142,6 +142,8 @@ impl<'tcx> InferCtxt<'tcx> { sub_region: Region<'tcx>, cause: &ObligationCause<'tcx>, ) { + assert!(!self.tcx.sess.opts.unstable_opts.higher_ranked_assumptions_v2); + // `is_global` means the type has no params, infer, placeholder, or non-`'static` // free regions. If the type has none of these things, then we can skip registering // this outlives obligation since it has no components which affect lifetime @@ -455,7 +457,7 @@ where // These are guaranteed to apply, no matter the inference // results. let trait_bounds: Vec<_> = - self.verify_bound.declared_bounds_from_definition(alias_ty).collect(); + rustc_type_ir::outlives::declared_bounds_from_definition(self.tcx, alias_ty).collect(); debug!(?trait_bounds); diff --git a/compiler/rustc_infer/src/infer/outlives/verify.rs b/compiler/rustc_infer/src/infer/outlives/verify.rs index d0d1df6f04462..8484b7c222955 100644 --- a/compiler/rustc_infer/src/infer/outlives/verify.rs +++ b/compiler/rustc_infer/src/infer/outlives/verify.rs @@ -1,9 +1,9 @@ use std::assert_matches; use rustc_middle::ty::outlives::{Component, compute_alias_components_recursive}; -use rustc_middle::ty::{self, OutlivesPredicate, Ty, TyCtxt, Unnormalized}; +use rustc_middle::ty::{self, OutlivesPredicate, Ty, TyCtxt}; use smallvec::smallvec; -use tracing::{debug, instrument, trace}; +use tracing::{debug, instrument}; use crate::infer::outlives::env::RegionBoundPairs; use crate::infer::region_constraints::VerifyIfEq; @@ -121,7 +121,8 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> { // Extend with bounds that we can find from the definition. let definition_bounds = - self.declared_bounds_from_definition(alias_ty).map(|r| VerifyBound::OutlivedBy(r)); + rustc_type_ir::outlives::declared_bounds_from_definition(self.tcx, alias_ty) + .map(|r| VerifyBound::OutlivedBy(r)); // see the extensive comment in projection_must_outlive let recursive_bound = { @@ -247,47 +248,4 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> { bounds } - - /// Given a projection like `>::Bar`, returns any bounds - /// declared in the trait definition. For example, if the trait were - /// - /// ```rust - /// trait Foo<'a> { - /// type Bar: 'a; - /// } - /// ``` - /// - /// If we were given the `DefId` of `Foo::Bar`, we would return - /// `'a`. You could then apply the instantiations from the - /// projection to convert this into your namespace. This also - /// works if the user writes `where >::Bar: 'a` on - /// the trait. In fact, it works by searching for just such a - /// where-clause. - /// - /// It will not, however, work for higher-ranked bounds like: - /// - /// ```ignore(this does compile today, previously was marked as `compile_fail,E0311`) - /// trait Foo<'a, 'b> - /// where for<'x> >::Bar: 'x - /// { - /// type Bar; - /// } - /// ``` - /// - /// This is for simplicity, and because we are not really smart - /// enough to cope with such bounds anywhere. - pub(crate) fn declared_bounds_from_definition( - &self, - alias_ty: ty::AliasTy<'tcx>, - ) -> impl Iterator> { - let tcx = self.tcx; - let bounds = tcx.item_self_bounds(alias_ty.kind.def_id()); - trace!("{:#?}", bounds.skip_binder()); - bounds - .iter_instantiated(tcx, alias_ty.args) - .map(Unnormalized::skip_norm_wip) - .filter_map(|p| p.as_type_outlives_clause()) - .filter_map(|p| p.no_bound_vars()) - .map(|OutlivesPredicate(_, r)| r) - } } diff --git a/compiler/rustc_next_trait_solver/src/placeholder.rs b/compiler/rustc_next_trait_solver/src/placeholder.rs index 83f3bdf01dc83..b70c073b53c39 100644 --- a/compiler/rustc_next_trait_solver/src/placeholder.rs +++ b/compiler/rustc_next_trait_solver/src/placeholder.rs @@ -164,3 +164,107 @@ where if p.has_vars_bound_at_or_above(self.current_index) { p.super_fold_with(self) } else { p } } } + +/// The inverse of [`BoundVarReplacer`]: replaces placeholders with the bound vars from which +/// they came. +pub struct PlaceholderReplacer<'a, I: Interner> { + cx: I, + mapped_regions: IndexMap, ty::BoundRegion>, + mapped_types: IndexMap, ty::BoundTy>, + mapped_consts: IndexMap, ty::BoundConst>, + universe_indices: &'a [Option], + current_index: ty::DebruijnIndex, +} + +impl<'a, I: Interner> PlaceholderReplacer<'a, I> { + pub fn replace_placeholders>( + cx: I, + mapped_regions: IndexMap, ty::BoundRegion>, + mapped_types: IndexMap, ty::BoundTy>, + mapped_consts: IndexMap, ty::BoundConst>, + universe_indices: &'a [Option], + value: T, + ) -> T { + let mut replacer = PlaceholderReplacer { + cx, + mapped_regions, + mapped_types, + mapped_consts, + universe_indices, + current_index: ty::INNERMOST, + }; + value.fold_with(&mut replacer) + } + + fn debruijn_for_universe(&self, universe: ty::UniverseIndex) -> ty::DebruijnIndex { + let index = self + .universe_indices + .iter() + .position(|u| matches!(u, Some(u_idx) if *u_idx == universe)) + .unwrap_or_else(|| panic!("unexpected placeholder universe {universe:?}")); + + ty::DebruijnIndex::from_usize( + self.universe_indices.len() - index + self.current_index.as_usize() - 1, + ) + } +} + +impl TypeFolder for PlaceholderReplacer<'_, I> { + fn cx(&self) -> I { + self.cx + } + + fn fold_binder>(&mut self, t: ty::Binder) -> ty::Binder { + if !t.has_placeholders() && !t.has_infer() { + return t; + } + + self.current_index.shift_in(1); + let t = t.super_fold_with(self); + self.current_index.shift_out(1); + t + } + + fn fold_region(&mut self, r: I::Region) -> I::Region { + if let ty::RePlaceholder(p) = r.kind() { + if let Some(replace_var) = self.mapped_regions.get(&p) { + let db = self.debruijn_for_universe(p.universe()); + return Region::new_bound(self.cx(), db, *replace_var); + } + } + + r + } + + fn fold_ty(&mut self, ty: I::Ty) -> I::Ty { + if let ty::Placeholder(p) = ty.kind() { + match self.mapped_types.get(&p) { + Some(replace_var) => { + let db = self.debruijn_for_universe(p.universe()); + Ty::new_bound(self.cx(), db, *replace_var) + } + None => ty, + } + } else if ty.has_placeholders() || ty.has_infer() { + ty.super_fold_with(self) + } else { + ty + } + } + + fn fold_const(&mut self, ct: I::Const) -> I::Const { + if let ty::ConstKind::Placeholder(p) = ct.kind() { + match self.mapped_consts.get(&p) { + Some(replace_var) => { + let db = self.debruijn_for_universe(p.universe()); + Const::new_bound(self.cx(), db, *replace_var) + } + None => ct, + } + } else if ct.has_placeholders() || ct.has_infer() { + ct.super_fold_with(self) + } else { + ct + } + } +} 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 ca31c86b75ed6..334bfa0f6e283 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 @@ -1092,6 +1092,10 @@ where self.delegate.shallow_resolve(ty) } + pub(super) fn shallow_resolve_const(&self, ct: I::Const) -> I::Const { + self.delegate.shallow_resolve_const(ct) + } + pub(super) fn eager_resolve_region(&self, r: I::Region) -> I::Region { if let ty::ReVar(vid) = r.kind() { self.delegate.opportunistic_resolve_lt_var(vid) @@ -1209,6 +1213,19 @@ where BoundVarReplacer::replace_bound_vars(&**self.delegate, universes, t).0 } + pub(super) fn replace_escaping_bound_vars>( + &self, + value: T, + universes: &mut Vec>, + ) -> ( + T, + IndexMap, ty::BoundRegion>, + IndexMap, ty::BoundTy>, + IndexMap, ty::BoundConst>, + ) { + BoundVarReplacer::replace_bound_vars(&**self.delegate, universes, value) + } + pub(super) fn may_use_unstable_feature( &self, param_env: I::ParamEnv, @@ -1256,12 +1273,24 @@ where previous call to `try_evaluate_added_goals!`" ); - // We only check for leaks from universes which were entered inside - // of the query. - self.delegate.leak_check(self.max_input_universe).map_err(|NoSolution| { - trace!("failed the leak check"); - NoSolution - })?; + let (solver_region_constraint, goals_certainty) = + match self.delegate.higher_ranked_assumptions_v2() { + true => { + let (constraint, certainty) = self + .eagerly_handle_placeholders(self.delegate.get_solve_region_constraint())?; + (constraint, certainty.and(goals_certainty)) + } + false => { + // We only check for leaks from universes which were entered inside + // of the query. + self.delegate.leak_check(self.max_input_universe).map_err(|NoSolution| { + trace!("failed the leak check"); + NoSolution + })?; + + (RegionConstraint::new_true(), goals_certainty) + } + }; let (certainty, normalization_nested_goals) = match (self.current_goal_kind, shallow_certainty) { @@ -1314,7 +1343,7 @@ where let external_constraints = self.compute_external_query_constraints( certainty, normalization_nested_goals, - RegionConstraint::new_true(), + solver_region_constraint, ); let (var_values, mut external_constraints) = eager_resolve_vars(self.delegate, (self.var_values, external_constraints)); @@ -1378,7 +1407,10 @@ where // `tests/ui/higher-ranked/leak-check/leak-check-in-selection-5-ambig.rs` and // `tests/ui/higher-ranked/leak-check/leak-check-in-selection-6-ambig-unify.rs`. let region_constraints = if certainty == Certainty::Yes { - self.delegate.make_deduplicated_region_constraints() + match self.higher_ranked_assumptions_v2() { + true => Vec::new(), + false => self.delegate.make_deduplicated_region_constraints(), + } } else { Default::default() }; @@ -1489,6 +1521,101 @@ where inverse_region_outlives: inverse_region_outlives_builder.freeze(), }) } + + #[instrument(level = "debug", skip(self), ret)] + fn eagerly_handle_placeholders( + &mut self, + constraint: RegionConstraint, + ) -> Result<(RegionConstraint, Certainty), NoSolution> { + let smallest_universe = self.max_input_universe.index(); + let largest_universe = self.delegate.universe().index(); + debug!(?smallest_universe, largest_universe); + + let constraint = ((smallest_universe + 1)..=largest_universe) + .map(|u| UniverseIndex::from_usize(u)) + .rev() + .fold(constraint, |constraint, u| { + // rustc_type_ir::region_constraint::eagerly_handle_placeholders_in_universe( + // &**self.delegate, + // constraint, + // u, + // ) + todo!() as RegionConstraint:: + }); + + if constraint.is_false() { + Err(NoSolution) + } else { + let certainty = + if constraint.is_ambig() { Certainty::AMBIGUOUS } else { Certainty::Yes }; + + Ok((constraint, certainty)) + } + } + + #[instrument(level = "debug", skip(self), ret)] + pub(super) fn destructure_type_outlives( + &mut self, + ty: I::Ty, + r: I::Region, + ) -> RegionConstraint { + let mut components = Default::default(); + rustc_type_ir::outlives::push_outlives_components(self.cx(), ty, &mut components); + self.destructure_components(&components, r) + } + + fn destructure_components( + &mut self, + components: &[Component], + r: I::Region, + ) -> RegionConstraint { + RegionConstraint::And( + components.into_iter().map(|c| self.destructure_component(c, r)).collect(), + ) + } + + fn destructure_component(&mut self, c: &Component, r: I::Region) -> RegionConstraint { + use Component::*; + match c { + Region(c_r) => RegionConstraint::RegionOutlives(*c_r, r), + Placeholder(p) => { + RegionConstraint::PlaceholderTyOutlives(Ty::new_placeholder(self.cx(), *p), r) + } + Alias(alias) => self.destructure_alias_outlives(*alias, r), + UnresolvedInferenceVariable(_) => RegionConstraint::Ambiguity, + Param(_) => panic!("Params should have been canonicalized to placeholders"), + EscapingAlias(components) => self.destructure_components(components, r), + } + } + + #[instrument(level = "debug", skip(self), ret)] + fn destructure_alias_outlives( + &mut self, + alias: AliasTy, + r: I::Region, + ) -> RegionConstraint { + let item_bounds = + rustc_type_ir::outlives::declared_bounds_from_definition(self.cx(), alias) + .map(|bound| RegionConstraint::RegionOutlives(bound, r)); + let item_bound_outlives = RegionConstraint::Or(item_bounds.collect()); + + let where_clause_outlives = + RegionConstraint::AliasTyOutlivesFromEnv(Binder::dummy((alias, r))); + + let mut components = Default::default(); + rustc_type_ir::outlives::compute_alias_components_recursive( + self.cx(), + alias, + &mut components, + ); + let components_outlives = self.destructure_components(&components, r); + + RegionConstraint::Or(Box::new([ + item_bound_outlives, + where_clause_outlives, + components_outlives, + ])) + } } /// Eagerly replace aliases with inference variables, emitting `AliasRelate` diff --git a/compiler/rustc_next_trait_solver/src/solve/mod.rs b/compiler/rustc_next_trait_solver/src/solve/mod.rs index 6796f45b09987..ea0930b40686c 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -22,9 +22,13 @@ mod search_graph; mod trait_goals; use derive_where::derive_where; +use rustc_type_ir::data_structures::ensure_sufficient_stack; use rustc_type_ir::inherent::*; pub use rustc_type_ir::solve::*; -use rustc_type_ir::{self as ty, Interner, TyVid, TypingMode}; +use rustc_type_ir::{ + self as ty, FallibleTypeFolder, Interner, TermKind, TyVid, TypeFoldable, TypeSuperFoldable, + TypeVisitableExt, TypingMode, +}; use tracing::instrument; pub use self::eval_ctxt::{ @@ -32,6 +36,7 @@ pub use self::eval_ctxt::{ evaluate_root_goal_for_proof_tree_raw_provider, }; use crate::delegate::SolverDelegate; +use crate::placeholder::PlaceholderReplacer; use crate::solve::assembly::Candidate; /// How many fixpoint iterations we should attempt inside of the solver before bailing @@ -94,7 +99,24 @@ where goal: Goal>, ) -> QueryResult { let ty::OutlivesPredicate(ty, lt) = goal.predicate; - self.register_ty_outlives(ty, lt); + + if self.higher_ranked_assumptions_v2() { + let ty = match self.deeply_normalize_for_outlives(goal.param_env, ty) { + Ok(ty) => ty, + Err(Ok(cause)) => { + return self.evaluate_added_goals_and_make_canonical_response( + Certainty::Maybe { cause, opaque_types_jank: OpaqueTypesJank::AllGood }, + ); + } + Err(Err(e)) => return Err(e), + }; + + let constraint = self.destructure_type_outlives(ty, lt); + self.register_solver_region_constraint(constraint); + } else { + self.register_ty_outlives(ty, lt); + } + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } @@ -104,7 +126,15 @@ where goal: Goal>, ) -> QueryResult { let ty::OutlivesPredicate(a, b) = goal.predicate; - self.register_region_outlives(a, b); + + if self.higher_ranked_assumptions_v2() { + let constraint = + rustc_type_ir::region_constraint::RegionConstraint::RegionOutlives(a, b); + self.register_solver_region_constraint(constraint); + } else { + self.register_region_outlives(a, b); + } + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } @@ -368,6 +398,152 @@ where } } + // ripped from #152270 + fn deeply_normalize_for_outlives( + &mut self, + param_env: I::ParamEnv, + ty: I::Ty, + ) -> Result> { + let ty = self.shallow_resolve(ty); + if !ty.has_aliases() { + return Ok(ty); + } + + struct DeepNormalizer<'ecx, 'a, D, I> + where + D: SolverDelegate, + I: Interner, + { + ecx: &'ecx mut EvalCtxt<'a, D, I>, + param_env: I::ParamEnv, + depth: usize, + universes: Vec>, + } + + impl DeepNormalizer<'_, '_, D, I> + where + D: SolverDelegate, + I: Interner, + { + fn normalize_alias_term( + &mut self, + alias_term: I::Term, + has_escaping_bound_vars: bool, + ) -> Result> { + debug_assert!(alias_term.to_alias_term(self.ecx.cx()).is_some()); + + // Avoid getting stuck on self-referential normalization. + if self.depth >= self.ecx.cx().recursion_limit() { + return Err(Ok(MaybeCause::Overflow { + suggest_increasing_limit: true, + keep_constraints: false, + })); + } + + self.depth += 1; + let result = (|| { + let (alias_term, mapped_bound_vars) = if has_escaping_bound_vars { + let (term, mapped_regions, mapped_types, mapped_consts) = + self.ecx.replace_escaping_bound_vars(alias_term, &mut self.universes); + (term, Some((mapped_regions, mapped_types, mapped_consts))) + } else { + (alias_term, None) + }; + + let normalized_term = self + .ecx + .structurally_normalize_term(self.param_env, alias_term) + .map_err(Err)?; + let normalized_term = match normalized_term.kind() { + TermKind::Ty(ty) => ty.try_super_fold_with(self)?.into(), + TermKind::Const(ct) => ct.try_super_fold_with(self)?.into(), + }; + + if let Some((mapped_regions, mapped_types, mapped_consts)) = mapped_bound_vars { + Ok(PlaceholderReplacer::replace_placeholders( + self.ecx.cx(), + mapped_regions, + mapped_types, + mapped_consts, + &self.universes, + normalized_term, + )) + } else { + Ok(normalized_term) + } + })(); + self.depth -= 1; + result + } + } + + impl FallibleTypeFolder for DeepNormalizer<'_, '_, D, I> + where + D: SolverDelegate, + I: Interner, + { + type Error = Result; + + fn cx(&self) -> I { + self.ecx.cx() + } + + fn try_fold_binder>( + &mut self, + t: ty::Binder, + ) -> Result, Self::Error> { + self.universes.push(None); + let t = t.try_super_fold_with(self)?; + self.universes.pop(); + Ok(t) + } + + #[instrument(level = "trace", skip(self), ret)] + fn try_fold_ty(&mut self, ty: I::Ty) -> Result { + let ty = self.ecx.shallow_resolve(ty); + if !ty.has_aliases() && !ty.has_non_region_infer() { + return Ok(ty); + } + match ty.kind() { + ty::Infer(_) => Err(Ok(MaybeCause::Ambiguity)), + ty::Alias(..) => { + let term = ensure_sufficient_stack(|| { + self.normalize_alias_term(ty.into(), ty.has_escaping_bound_vars()) + })?; + Ok(term.expect_ty()) + } + _ => ty.try_super_fold_with(self), + } + } + + #[instrument(level = "trace", skip(self), ret)] + fn try_fold_const(&mut self, ct: I::Const) -> Result { + let ct = self.ecx.shallow_resolve_const(ct); + if !ct.has_aliases() && !ct.has_non_region_infer() { + return Ok(ct); + } + match ct.kind() { + ty::ConstKind::Infer(_) => Err(Ok(MaybeCause::Ambiguity)), + ty::ConstKind::Unevaluated(..) => { + let term = ensure_sufficient_stack(|| { + self.normalize_alias_term(ct.into(), ct.has_escaping_bound_vars()) + })?; + Ok(term.expect_const()) + } + _ => ct.try_super_fold_with(self), + } + } + } + + let ty = ty.try_fold_with(&mut DeepNormalizer { + ecx: self, + param_env, + depth: 0, + universes: vec![], + })?; + Ok(ty) + } + fn opaque_type_is_rigid(&self, def_id: I::DefId) -> bool { match self.typing_mode() { // Opaques are never rigid outside of analysis mode. diff --git a/compiler/rustc_trait_selection/src/solve/delegate.rs b/compiler/rustc_trait_selection/src/solve/delegate.rs index 05ecc4725a7b6..fa18a53789fe2 100644 --- a/compiler/rustc_trait_selection/src/solve/delegate.rs +++ b/compiler/rustc_trait_selection/src/solve/delegate.rs @@ -102,12 +102,16 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< } } + let higher_ranked_assumptions_v2 = + self.tcx.sess.opts.unstable_opts.higher_ranked_assumptions_v2; let pred = goal.predicate.kind(); match pred.no_bound_vars()? { ty::PredicateKind::DynCompatible(def_id) if self.0.tcx.is_dyn_compatible(def_id) => { Some(Certainty::Yes) } - ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(outlives)) => { + ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(outlives)) + if !higher_ranked_assumptions_v2 => + { self.0.sub_regions( SubregionOrigin::RelateRegionParamBound(span, None), outlives.1, @@ -115,7 +119,9 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< ); Some(Certainty::Yes) } - ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(outlives)) => { + ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(outlives)) + if !higher_ranked_assumptions_v2 => + { self.0.register_type_outlives_constraint( outlives.0, outlives.1, diff --git a/compiler/rustc_type_ir/src/inherent.rs b/compiler/rustc_type_ir/src/inherent.rs index a8757e045d170..9badb556b9e4a 100644 --- a/compiler/rustc_type_ir/src/inherent.rs +++ b/compiler/rustc_type_ir/src/inherent.rs @@ -563,6 +563,18 @@ pub trait Clause>: { fn as_predicate(self) -> I::Predicate; + fn as_type_outlives_clause(self) -> Option>> { + self.kind() + .map_bound(|clause| { + if let ty::ClauseKind::TypeOutlives(outlives) = clause { + Some(outlives) + } else { + None + } + }) + .transpose() + } + fn as_trait_clause(self) -> Option>> { self.kind() .map_bound(|clause| if let ty::ClauseKind::Trait(t) = clause { Some(t) } else { None }) diff --git a/compiler/rustc_type_ir/src/outlives.rs b/compiler/rustc_type_ir/src/outlives.rs index 56f40d3f78288..452c78e314e4d 100644 --- a/compiler/rustc_type_ir/src/outlives.rs +++ b/compiler/rustc_type_ir/src/outlives.rs @@ -8,7 +8,7 @@ use smallvec::{SmallVec, smallvec}; use crate::data_structures::SsoHashSet; use crate::inherent::*; use crate::visit::{TypeSuperVisitable, TypeVisitable, TypeVisitableExt as _, TypeVisitor}; -use crate::{self as ty, Interner}; +use crate::{self as ty, AliasTy, Interner, OutlivesPredicate, Unnormalized}; #[derive_where(Debug; I: Interner)] pub enum Component { @@ -237,3 +237,38 @@ pub fn compute_alias_components_recursive( child.visit_with(&mut visitor); } } + +/// Given a projection like `>::Bar`, returns any bounds +/// declared in the trait definition. For example, if the trait were +/// +/// ```rust +/// trait Foo<'a> { +/// type Bar: 'a; +/// } +/// ``` +/// +/// If we were given >::Bar`, we would return +/// `'b`. This doesn't work for higher-ranked bounds such as: +/// +/// ```ignore(this does compile today, previously was marked as `compile_fail,E0311`) +/// trait Foo<'a, 'b> +/// where for<'x> >::Bar: 'x +/// { +/// type Bar; +/// } +/// ``` +/// +/// This is for simplicity, and because we are not really smart +/// enough to cope with such bounds anywhere. +pub fn declared_bounds_from_definition( + cx: I, + alias_ty: AliasTy, +) -> impl Iterator { + let bounds = cx.item_self_bounds(alias_ty.kind.def_id()); + bounds + .iter_instantiated(cx, alias_ty.args) + .map(Unnormalized::skip_norm_wip) + .filter_map(|p| p.as_type_outlives_clause()) + .filter_map(|p| p.no_bound_vars()) + .map(|OutlivesPredicate(_, r)| r) +} From 045c85f91576ffbf6dcde39eb59173c224940d87 Mon Sep 17 00:00:00 2001 From: Boxy Date: Mon, 27 Apr 2026 17:52:09 +0100 Subject: [PATCH 5/6] destructure in root universe --- .../src/type_check/free_region_relations.rs | 2 +- compiler/rustc_borrowck/src/type_check/mod.rs | 16 ++++ .../src/infer/outlives/obligations.rs | 85 ++++++++++++++++++- 3 files changed, 101 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/free_region_relations.rs b/compiler/rustc_borrowck/src/type_check/free_region_relations.rs index 41eb5f0302f4f..6c7beb85f1e8c 100644 --- a/compiler/rustc_borrowck/src/type_check/free_region_relations.rs +++ b/compiler/rustc_borrowck/src/type_check/free_region_relations.rs @@ -26,7 +26,7 @@ pub(crate) struct UniversalRegionRelations<'tcx> { /// Stores the outlives relations that are known to hold from the /// implied bounds, in-scope where-clauses, and that sort of /// thing. - outlives: TransitiveRelation, + pub(crate) outlives: TransitiveRelation, /// This is the `<=` relation; that is, if `a: b`, then `b <= a`, /// and we store that here. This is useful when figuring out how diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 59874e82e920f..2b08d880a3e64 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -173,6 +173,22 @@ pub(crate) fn type_check<'tcx>( let polonius_context = typeck.polonius_context; + let mut converter = constraint_conversion::ConstraintConversion::new( + typeck.infcx, + typeck.universal_regions, + typeck.region_bound_pairs, + typeck.known_type_outlives_obligations, + Locations::All(rustc_span::DUMMY_SP), + rustc_span::DUMMY_SP, + ConstraintCategory::Boring, + typeck.constraints, + ); + typeck.infcx.destructure_solver_region_constraints_for_borrowck( + &mut converter, + typeck.known_type_outlives_obligations, + universal_region_relations.outlives.clone(), + ); + // In case type check encountered an error region, we suppress unhelpful extra // errors in by clearing out all outlives bounds that we may end up checking. if let Some(guar) = universal_region_relations.universal_regions.encountered_re_error() { diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index 606d8c6b165f0..f3c6a4e424d09 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -59,15 +59,17 @@ //! might later infer `?U` to something like `&'b u32`, which would //! imply that `'b: 'a`. +use rustc_data_structures::transitive_relation::TransitiveRelation; use rustc_data_structures::undo_log::UndoLogs; use rustc_middle::bug; use rustc_middle::mir::ConstraintCategory; use rustc_middle::traits::query::NoSolution; use rustc_middle::ty::outlives::{Component, push_outlives_components}; use rustc_middle::ty::{ - self, GenericArgKind, GenericArgsRef, PolyTypeOutlivesPredicate, Region, Ty, TyCtxt, + self, GenericArgKind, GenericArgsRef, PolyTypeOutlivesPredicate, Region, RegionVid, Ty, TyCtxt, TypeFoldable as _, TypeVisitableExt, }; +use rustc_span::DUMMY_SP; use smallvec::smallvec; use tracing::{debug, instrument}; @@ -198,6 +200,85 @@ impl<'tcx> InferCtxt<'tcx> { std::mem::take(&mut self.inner.borrow_mut().region_assumptions) } + pub fn destructure_solver_region_constraints_for_regionck( + &self, + outlives_env: &OutlivesEnvironment<'tcx>, + ) { + let assumptions = rustc_type_ir::region_constraint::Assumptions::new( + outlives_env.known_type_outlives().into_iter().cloned().collect(), + outlives_env.free_region_map().relation.clone(), + ); + self.destructure_solve_region_constraints(assumptions, self); + } + + pub fn destructure_solver_region_constraints_for_borrowck( + &self, + // this is always ConstraintConversion but lol + conversion: impl TypeOutlivesDelegate<'tcx>, + known_type_outlives: &[PolyTypeOutlivesPredicate<'tcx>], + region_outlives: TransitiveRelation, + ) { + let assumptions = rustc_type_ir::region_constraint::Assumptions::new( + known_type_outlives.into_iter().cloned().collect(), + region_outlives.maybe_map(|r| Some(Region::new_var(self.tcx, r))).unwrap(), + ); + self.destructure_solve_region_constraints(assumptions, conversion); + } + + #[instrument(level = "debug", skip(self, conversion))] + pub fn destructure_solve_region_constraints( + &self, + assumptions: rustc_type_ir::region_constraint::Assumptions>, + mut conversion: impl TypeOutlivesDelegate<'tcx>, + ) { + if !self.tcx.sess.opts.unstable_opts.higher_ranked_assumptions_v2 { + return; + } + + assert!(self.next_trait_solver()); + + // FIXME(-Zhigher-ranked-assumptions-v2): Implement diagnostics + let origin = SubregionOrigin::Reborrow(DUMMY_SP); + let category = origin.to_constraint_category(); + + 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_universe( + self, + constraint, + None, + &Some(assumptions), + ); + debug!(?constraint); + let constraint = rustc_type_ir::region_constraint::evaluate_solver_constraint(&constraint); + debug!(?constraint); + + let mut constraints = vec![constraint]; + while let Some(c) = constraints.pop() { + use rustc_type_ir::region_constraint::RegionConstraint::*; + + match c { + Ambiguity => { + self.dcx().err("unable to satisfy constraints involving placeholders due to unknown implied bounds"); + } + RegionOutlives(a, b) => { + conversion.push_sub_region_constraint( + origin.clone(), + // we flip these because regionck is silly :> + b, + a, + category, + ); + } + // FIXME(-Zhigher-ranked-assumptions-v2): actually implement OR as an OR + And(nested) | Or(nested) => constraints.extend(nested), + AliasTyOutlivesFromEnv(..) => unreachable!(), + PlaceholderTyOutlives(..) => unreachable!(), + } + } + } + /// Process the region obligations that must be proven (during /// `regionck`) for the given `body_id`, given information about /// the region bounds in scope and so forth. @@ -219,6 +300,8 @@ impl<'tcx> InferCtxt<'tcx> { ) -> Result<(), (PolyTypeOutlivesPredicate<'tcx>, SubregionOrigin<'tcx>)> { assert!(!self.in_snapshot(), "cannot process registered region obligations in a snapshot"); + self.destructure_solver_region_constraints_for_regionck(outlives_env); + // Must loop since the process of normalizing may itself register region obligations. for iteration in 0.. { let my_region_obligations = self.take_registered_region_obligations(); From 02a859deb41436f35271810e2e192e262112a329 Mon Sep 17 00:00:00 2001 From: Boxy Date: Tue, 28 Apr 2026 10:12:42 +0100 Subject: [PATCH 6/6] rewrite region constraints to smaller universes --- compiler/rustc_middle/src/ty/list.rs | 11 + .../src/solve/eval_ctxt/mod.rs | 11 +- compiler/rustc_type_ir/src/inherent.rs | 14 +- compiler/rustc_type_ir/src/interner.rs | 7 +- .../rustc_type_ir/src/region_constraint.rs | 787 ++++++++++++++++++ 5 files changed, 817 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_middle/src/ty/list.rs b/compiler/rustc_middle/src/ty/list.rs index ed5a48b094f24..5336413119939 100644 --- a/compiler/rustc_middle/src/ty/list.rs +++ b/compiler/rustc_middle/src/ty/list.rs @@ -153,6 +153,17 @@ impl<'a, H, T: Copy> rustc_type_ir::inherent::SliceLike for &'a RawList { } } +impl<'tcx> rustc_type_ir::inherent::BoundVarKinds> + for &'tcx RawList<(), crate::ty::BoundVariableKind<'tcx>> +{ + fn from_vars( + tcx: TyCtxt<'tcx>, + iter: impl IntoIterator>, + ) -> Self { + tcx.mk_bound_variable_kinds_from_iter(iter.into_iter()) + } +} + macro_rules! impl_list_empty { ($header_ty:ty, $header_init:expr) => { impl RawList<$header_ty, T> { 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 334bfa0f6e283..8966eb341c110 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 @@ -1535,12 +1535,11 @@ where .map(|u| UniverseIndex::from_usize(u)) .rev() .fold(constraint, |constraint, u| { - // rustc_type_ir::region_constraint::eagerly_handle_placeholders_in_universe( - // &**self.delegate, - // constraint, - // u, - // ) - todo!() as RegionConstraint:: + rustc_type_ir::region_constraint::eagerly_handle_placeholders_in_universe( + &**self.delegate, + constraint, + u, + ) }); if constraint.is_false() { diff --git a/compiler/rustc_type_ir/src/inherent.rs b/compiler/rustc_type_ir/src/inherent.rs index 9badb556b9e4a..5584ac2441194 100644 --- a/compiler/rustc_type_ir/src/inherent.rs +++ b/compiler/rustc_type_ir/src/inherent.rs @@ -400,7 +400,13 @@ pub trait GenericArg>: #[rust_analyzer::prefer_underscore_import] pub trait Term>: - Copy + Debug + Hash + Eq + IntoKind> + TypeFoldable + Relate + Copy + + Debug + + Hash + + Eq + + IntoKind> + + TypeFoldable + + Relate { fn as_type(&self) -> Option { if let ty::TermKind::Ty(ty) = self.kind() { Some(ty) } else { None } @@ -727,6 +733,12 @@ pub trait OpaqueTypeStorageEntries: Debug + Copy + Default { fn needs_reevaluation(self, canonicalized: usize) -> bool; } +pub trait BoundVarKinds: + Copy + Debug + Hash + Eq + SliceLike> + Default +{ + fn from_vars(cx: I, iter: impl IntoIterator>) -> Self; +} + pub trait SliceLike: Sized + Copy { type Item: Copy; type IntoIter: Iterator + DoubleEndedIterator; diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index f350e5da9654c..1738bdced0f0a 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -61,12 +61,7 @@ pub trait Interner: type GenericArg: GenericArg; type Term: Term; - type BoundVarKinds: Copy - + Debug - + Hash - + Eq - + SliceLike> - + Default; + type BoundVarKinds: BoundVarKinds; type PredefinedOpaques: Copy + Debug diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index e5d1dd9de1bd9..870089829a856 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -247,6 +247,640 @@ impl RegionConstraint { pub fn is_ambig(&self) -> bool { matches!(self, Self::Ambiguity) } + + pub fn and(self, other: RegionConstraint) -> RegionConstraint { + use RegionConstraint::*; + + match (self, other) { + (And(a_ands), And(b_ands)) => And(a_ands + .into_iter() + .chain(b_ands.into_iter()) + .collect::>() + .into_boxed_slice()), + (And(ands), other) | (other, And(ands)) => { + And(ands.into_iter().chain([other]).collect::>().into_boxed_slice()) + } + (this, other) => And(Box::new([this, other])), + } + } + + #[instrument(level = "debug", ret)] + pub fn canonical_form(self) -> Self { + use RegionConstraint::*; + + fn permutations(ors: &[Vec>]) -> Vec> { + match ors { + [] => vec![], + [or] => or.clone(), + [or1, or2] => { + let mut permutations = vec![]; + for c1 in or1 { + for c2 in or2 { + permutations.push(c1.clone().and(c2.clone())); + } + } + + permutations + } + [rest @ .., or1, or2] => { + let combined_or = permutations(&[or1.clone(), or2.clone()]); + + let mut input = vec![]; + input.push(combined_or); + input.extend(rest.to_vec()); + permutations(&input) + } + } + } + + let canonical = match self { + And(ands) => { + let mut un_ored = vec![]; + let mut ors = vec![]; + + let mut temp_ands: Vec<_> = ands.into(); + while let Some(c) = temp_ands.pop() { + let c = c.canonical_form(); + + if let Or(c_ors) = c { + ors.push(c_ors.into()); + } else if let And(ands) = c { + temp_ands.extend(ands); + } else { + un_ored.push(c); + } + } + + let mut or_combinations = permutations(&ors); + match or_combinations.len() { + 0 => And(un_ored.into_boxed_slice()), + 1 => And(un_ored.into_boxed_slice()).and(or_combinations.pop().unwrap()), + _ => Or(or_combinations + .into_iter() + .map(|c| And(un_ored.clone().into_boxed_slice()).and(c)) + .collect::>() + .into_boxed_slice()), + } + } + Or(ors) => { + let mut constraints = vec![]; + + let mut temp_ors: Vec<_> = ors.into(); + while let Some(c) = temp_ors.pop() { + let c = c.canonical_form(); + if let Or(c_ors) = c { + temp_ors.extend(c_ors); + } else { + constraints.push(c); + } + } + + if constraints.len() == 1 { + constraints.pop().unwrap() + } else { + Or(constraints.into_boxed_slice()) + } + } + _ => self, + }; + + assert!(canonical.is_canonical_form()); + canonical + } + + fn is_leaf_constraint(&self) -> bool { + use RegionConstraint::*; + match self { + Ambiguity + | RegionOutlives(..) + | AliasTyOutlivesFromEnv(..) + | PlaceholderTyOutlives(..) => true, + And(..) | Or(..) => false, + } + } + + fn is_and_of_leaf_constraints(&self) -> bool { + if let Self::And(ands) = self { ands.iter().all(|c| c.is_leaf_constraint()) } else { false } + } + + fn is_or_of_and_of_leaf_constraints(&self) -> bool { + if let Self::Or(ors) = self { + ors.iter().all(|c| c.is_leaf_constraint() || c.is_and_of_leaf_constraints()) + } else { + false + } + } + + pub fn is_canonical_form(&self) -> bool { + self.is_leaf_constraint() + || self.is_and_of_leaf_constraints() + || self.is_or_of_and_of_leaf_constraints() + } +} + +impl From for RegionConstraint { + fn from(b: bool) -> Self { + match b { + true => Self::new_true(), + false => Self::new_false(), + } + } +} + +#[instrument(level = "debug", skip(infcx), ret)] +pub fn eagerly_handle_placeholders_in_universe, I: Interner>( + infcx: &Infcx, + constraint: RegionConstraint, + u: UniverseIndex, +) -> RegionConstraint { + use RegionConstraint::*; + + let assumptions = infcx.get_universe_assumptions(u); + + // 1. rewrite type outlives constraints + let constraint = + destructure_type_outlives_constraints_in_universe(infcx, constraint, Some(u), &assumptions); + + // 2. rewrite the constraint into a canonical ORs of ANDs form + let constraint = constraint.canonical_form(); + + // 3. compute transitive region outlives and get a new set of region outlives constraints by + // looking for every region which either a placeholder_u flows into it, or it flows into + // the placeholder. + // + // do this for each element in the top level OR + let constraint = match constraint { + Or(ors) => { + let new_ors = ors.into_iter().map(|c| match c { + And(ands) => { + And(compute_new_region_constraints(infcx, &ands, u).into_boxed_slice()) + } + Or(_) => unreachable!(), + _ => { + let mut constraints = compute_new_region_constraints(infcx, &[c], u); + assert!(constraints.len() == 1); + constraints.pop().unwrap() + } + }); + Or(new_ors.collect::>().into_boxed_slice()) + } + And(ands) => And(compute_new_region_constraints(infcx, &ands, u).into_boxed_slice()), + _ => { + let mut constraints = compute_new_region_constraints(infcx, &[constraint], u); + assert!(constraints.len() == 1); + constraints.pop().unwrap() + } + }; + + // 4. rewrite region outlives constraints (potentially to false/true) + let constraint = pull_region_constraint_out_of_universe(infcx, constraint, u, &assumptions); + + // 5. actually evalaute the constraint to eagerly error on false + evaluate_solver_constraint(&constraint) +} + +#[instrument(level = "debug", skip(infcx), ret)] +fn compute_new_region_constraints, I: Interner>( + infcx: &Infcx, + constraints: &[RegionConstraint], + u: UniverseIndex, +) -> Vec> { + use RegionConstraint::*; + + let mut new_constraints = vec![]; + + let mut region_flows_builder = TransitiveRelationBuilder::default(); + let mut regions = IndexSet::new(); + for c in constraints { + match c { + And(..) | Or(..) => unreachable!(), + Ambiguity | PlaceholderTyOutlives(..) | AliasTyOutlivesFromEnv(..) => { + new_constraints.push(c.clone()) + } + RegionOutlives(r1, r2) => { + regions.insert(r1); + regions.insert(r2); + region_flows_builder.add(r2, r1); + } + } + } + + let region_flow = region_flows_builder.freeze(); + for r in regions.into_iter() { + for ub in region_flow.reachable_from(r) { + // we want to retain any region constraints between two "placeholder-likes" where for our + // purposes a placeholder-like is either a placeholder or variable in a lower universe + let is_placeholder_like = |r: I::Region| match r.kind() { + RegionKind::ReLateParam(..) + | RegionKind::ReEarlyParam(..) + | RegionKind::RePlaceholder(..) + | RegionKind::ReStatic => true, + RegionKind::ReVar(..) => max_universe(infcx, r) < u, + RegionKind::ReError(..) | RegionKind::ReErased => false, + RegionKind::ReBound(..) => unreachable!(), + }; + + if is_placeholder_like(*r) && is_placeholder_like(*ub) { + new_constraints.push(RegionOutlives(*ub, *r)); + } + } + } + + new_constraints +} + +#[derive(Copy, Clone, Debug)] +enum Certainty { + Yes, + Ambig, +} + +#[instrument(level = "debug", ret)] +pub fn evaluate_solver_constraint( + constraint: &RegionConstraint, +) -> RegionConstraint { + use RegionConstraint::*; + match constraint { + Ambiguity | RegionOutlives(..) | AliasTyOutlivesFromEnv(..) | PlaceholderTyOutlives(..) => { + constraint.clone() + } + And(and) => { + let mut and_constraints = Vec::new(); + let mut certainty = Certainty::Yes; + for c in and.iter() { + let evaluated_constraint = evaluate_solver_constraint(c); + if evaluated_constraint.is_true() { + // - do nothing + } else if evaluated_constraint.is_false() { + and_constraints = vec![RegionConstraint::new_false()]; + certainty = Certainty::Yes; + break; + } else { + if evaluated_constraint.is_ambig() { + certainty = Certainty::Ambig; + } + and_constraints.push(evaluated_constraint); + } + } + + if let Certainty::Ambig = certainty { + RegionConstraint::Ambiguity + } else if and_constraints.len() == 1 { + and_constraints.pop().unwrap() + } else { + RegionConstraint::And(and_constraints.into_boxed_slice()) + } + } + Or(or) => { + let mut or_constraints = Vec::new(); + let mut certainty = Certainty::Yes; + for c in or.iter() { + let evaluated_constraint = evaluate_solver_constraint(c); + if evaluated_constraint.is_false() { + // do nothing + } else if evaluated_constraint.is_true() { + or_constraints = vec![RegionConstraint::new_true()]; + certainty = Certainty::Yes; + break; + } else { + if evaluated_constraint.is_ambig() { + certainty = Certainty::Ambig; + } + or_constraints.push(evaluated_constraint); + } + } + + if let Certainty::Ambig = certainty { + RegionConstraint::Ambiguity + } else if or_constraints.len() == 1 { + or_constraints.pop().unwrap() + } else { + RegionConstraint::Or(or_constraints.into_boxed_slice()) + } + } + } +} + +#[instrument(level = "debug", skip(infcx), ret)] +fn pull_region_constraint_out_of_universe, I: Interner>( + infcx: &Infcx, + constraint: RegionConstraint, + u: UniverseIndex, + assumptions: &Option>, +) -> RegionConstraint { + assert!(max_universe(infcx, constraint.clone()) <= u); + + // FIXME(-Zhigher-ranked-assumptions-v2): we don't lower universes of region variables when exiting `u` + // this seems dubious/potentially wrong? we can't just blindly do this though as if we had something + // like `!T_u -> ?x_u -> !U_u` then lowering `?x` to `u-1` when exiting `u` would be wrong. + + use RegionConstraint::*; + match constraint { + Ambiguity | PlaceholderTyOutlives(..) | AliasTyOutlivesFromEnv(..) => { + assert!(max_universe(infcx, constraint.clone()) < u); + constraint + } + RegionOutlives(region_1, region_2) => { + let region_1_u = max_universe(infcx, region_1); + let region_2_u = max_universe(infcx, region_2); + + if region_1_u != u && region_2_u != u { + return constraint; + } + + let assumptions = match assumptions { + Some(assumptions) => assumptions, + None => return RegionConstraint::Ambiguity, + }; + + if regions_outliving::(region_2, assumptions, infcx.cx()) + .find(|r| *r == region_1) + .is_some() + { + return RegionConstraint::new_true(); + } + + let mut constraints = vec![RegionOutlives::(region_1, region_2)]; + // `'r1_Uu: x` + if region_1_u == u { + // all regions `'y` for which `'r1_Um: 'y_Un` where `n < m` + constraints = regions_outlived_by::(region_1, assumptions) + .filter(|r| max_universe(infcx, *r) < region_1_u) + .map(|r| RegionOutlives(r, region_2)) + .collect(); + } + + // `'x: 'r2_Uu` + if region_2_u == u { + constraints = constraints + .into_iter() + .flat_map(|constraint| { + let RegionOutlives(region_1, _) = constraint else { unreachable!() }; + // all regions `'y` for which `'y_Un: 'r2_Um` where `n < m` + regions_outliving::(region_2, assumptions, infcx.cx()) + .filter(|r| max_universe(infcx, *r) < region_2_u) + .map(move |r| RegionOutlives::(region_1, r)) + }) + .collect(); + } + + RegionConstraint::Or(constraints.into_boxed_slice()) + } + And(constraints) => And(constraints + .into_iter() + .map(|constraint| { + pull_region_constraint_out_of_universe(infcx, constraint, u, assumptions) + }) + .collect()), + Or(constraints) => Or(constraints + .into_iter() + .map(|constraint| { + pull_region_constraint_out_of_universe(infcx, constraint, u, assumptions) + }) + .collect()), + } +} + +#[instrument(level = "debug", skip(infcx), ret)] +pub fn destructure_type_outlives_constraints_in_universe< + Infcx: InferCtxtLike, + I: Interner, +>( + infcx: &Infcx, + constraint: RegionConstraint, + u: Option, + assumptions: &Option>, +) -> RegionConstraint { + if let Some(u) = u { + assert!( + max_universe(infcx, constraint.clone()) <= u, + "constraint {:?} contains terms from a larger universe than {:?}", + constraint.clone(), + u + ); + } + + use RegionConstraint::*; + match constraint { + Ambiguity | RegionOutlives(..) => constraint, + PlaceholderTyOutlives(ty, region) => { + let ty_u = max_universe(infcx, ty); + let region_u = max_universe(infcx, region); + + if u.is_some_and(|u| region_u != u && ty_u != u) { + return constraint; + } + + let assumptions = match assumptions { + Some(assumptions) => assumptions, + None => return RegionConstraint::Ambiguity, + }; + + // FIXME(-Zhigher-ranked-assumptions-v2): things are slightly wrong here, if we know `!T_u1: '!a_u1` and + // `'!a_u1: '!b_u1` and are rewriting `!T: '!b_u1` then that should probably succeed, but we don't handle + // that here. IOW type outlives assumptions aren't treated transitively but they should be + + if regions_outlived_by_placeholder::(ty, assumptions, infcx.cx()) + .find(|r| *r == region) + .is_some() + { + debug!("matched assumption for ty outlives"); + return RegionConstraint::new_true(); + } + + let mut candidates = vec![PlaceholderTyOutlives(ty, region)]; + + // transitive outlives involving the region, e.g. `!T: 'r_Uu` can be rewritten to `!T: 'x_Uu-1` if `'x_Uu-1: 'r_Uu` is known + // we don't really care about this if `u` is `None` because we just want a big OR constraint of outlives between all assumptions + if u.is_some_and(|u| region_u == u) { + // all regions `'y` for which `'y_Un: 'r_Uu` where `n < u` + candidates = regions_outliving::(region, assumptions, infcx.cx()) + .filter(|r| max_universe(infcx, *r) < region_u) + .map(move |r| PlaceholderTyOutlives::(ty, r)) + .collect(); + } + + // assumptions on `!T`, e.g. `!T: 'x_Uu-1` should result in a `'r_Uu: 'x_Uu-1` constraint + if u.is_none_or(|u| ty_u == u) { + candidates = candidates + .into_iter() + .flat_map(|constraint| { + let PlaceholderTyOutlives(ty, region) = constraint else { unreachable!() }; + + regions_outlived_by_placeholder::(ty, assumptions, infcx.cx()) + .filter(|r| u.is_none_or(|u| max_universe(infcx, *r) < u)) + .map(move |r| RegionOutlives(r, region)) + }) + .collect(); + } + + RegionConstraint::Or(candidates.into_boxed_slice()) + } + AliasTyOutlivesFromEnv(bound_outlives) => { + let mut candidates = Vec::new(); + + // Actually look at the assumptions and matching our higher ranked alias outlives goal + // against potentially higher ranked type outlives assumptions. + match assumptions { + opt_assumptions @ Some(assumptions) => { + let requirements = + alias_outlives_candidate_requirement(infcx, bound_outlives, assumptions); + let rewritten_requiurements = destructure_type_outlives_constraints_in_universe( + infcx, + requirements, + u, + opt_assumptions, + ); + candidates.push(rewritten_requiurements); + } + None => candidates.push(RegionConstraint::Ambiguity), + }; + + // 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. + // + // 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, + // we don't care about `for<'a> Foo<'a>: 'b` as we always destructure adts into their components and if + // we dont equivalently elaborate the assumption into assumptions on the adt's components we just drop the + // assumptions + // + // 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 + if let Some(u) = u + && max_universe(infcx, bound_outlives) == 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_outlives = bound_outlives.skip_binder().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_outlives = Binder::bind_with_vars( + escaping_outlives, + I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), + ); + candidates.push(RegionConstraint::AliasTyOutlivesFromEnv(bound_outlives)); + } + + // we can rewrite `Alias_u1: 'u2` into `Or(Alias_u1: 'u1)` + // 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 let Some(u) = u + && max_universe(infcx, escaping_r) == u + { + match assumptions { + Some(assumptions) => { + 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.extend( + regions_outliving(escaping_r, assumptions, infcx.cx()) + .filter(|r2| max_universe(infcx, *r2) < u) + .map(|r2| { + AliasTyOutlivesFromEnv( + bound_alias.map_bound(|alias| (alias, r2)), + ) + }) + .collect::>(), + ); + } + None => candidates.push(RegionConstraint::Ambiguity), + }; + } + + // I'm not convinced our handling here is *complete* so for now + // let's be conservative and not let alias outlives' cause leak check + // errors in coherence + match infcx.typing_mode() { + TypingMode::Coherence => candidates.push(RegionConstraint::Ambiguity), + TypingMode::Analysis { .. } + | TypingMode::Borrowck { .. } + | TypingMode::PostBorrowckAnalysis { .. } + | TypingMode::PostAnalysis => (), + }; + + RegionConstraint::Or(candidates.into_boxed_slice()) + } + And(constraints) => And(constraints + .into_iter() + .map(|constraint| { + destructure_type_outlives_constraints_in_universe(infcx, constraint, u, assumptions) + }) + .collect()), + Or(constraints) => Or(constraints + .into_iter() + .map(|constraint| { + destructure_type_outlives_constraints_in_universe(infcx, constraint, u, assumptions) + }) + .collect()), + } +} + +pub fn regions_outlived_by( + r: I::Region, + assumptions: &Assumptions, +) -> impl Iterator { + assumptions.region_outlives.reachable_from(r).into_iter() +} + +pub fn regions_outliving( + r: I::Region, + assumptions: &Assumptions, + cx: I, +) -> impl Iterator { + // FIXME: 'static may have been an input region canonicalized to something else is that important? + assumptions + .inverse_region_outlives + .reachable_from(r) + .into_iter() + .chain([I::Region::new_static(cx)]) +} + +pub fn regions_outlived_by_placeholder( + t: I::Ty, + assumptions: &Assumptions, + cx: I, +) -> impl Iterator { + match t.kind() { + TyKind::Placeholder(..) | TyKind::Param(..) => (), + _ => unreachable!("non-placeholder in `regions_outlived_by_placeholder`: {t:?}"), + } + + assumptions.type_outlives.iter().flat_map(move |binder| match binder.no_bound_vars() { + Some(OutlivesPredicate(ty, r)) => (ty == t).then_some(r), + None => Some(I::Region::new_static(cx)), + }) } pub fn max_universe, I: Interner, T: TypeVisitable>( @@ -316,3 +950,156 @@ impl<'a, Infcx: InferCtxtLike, I: Interner> TypeVisitor } } } + +pub struct PlaceholderReplacer { + cx: I, + existing_var_count: usize, + bound_vars: IndexMap>, + universe: UniverseIndex, + current_index: DebruijnIndex, +} + +impl TypeFolder for PlaceholderReplacer { + fn cx(&self) -> I { + self.cx + } + + fn fold_region(&mut self, r: I::Region) -> I::Region { + match r.kind() { + RegionKind::RePlaceholder(p) if p.universe == self.universe => { + let bound_vars_len = self.bound_vars.len(); + let mapped_var = self.bound_vars.entry(p.bound.var).or_insert(BoundRegion { + var: BoundVar::from_usize(self.existing_var_count + bound_vars_len), + kind: p.bound.kind, + }); + I::Region::new_bound(self.cx, self.current_index, *mapped_var) + } + _ => r, + } + } + + fn fold_binder>(&mut self, b: Binder) -> Binder { + self.current_index.shift_in(1); + let b = b.super_fold_with(self); + self.current_index.shift_out(1); + b + } +} + +#[instrument(level = "debug", skip(infcx), ret)] +pub fn alias_outlives_candidate_requirement, I: Interner>( + infcx: &Infcx, + bound_outlives: Binder, I::Region)>, + assumptions: &Assumptions, +) -> RegionConstraint { + let mut candidates = Vec::new(); + + let prev_universe = infcx.universe(); + + infcx.enter_forall(bound_outlives, |(alias, r)| { + let u = infcx.universe(); + infcx.insert_universe_assumptions(u, Some(Assumptions::empty())); + + for bound_type_outlives in assumptions.type_outlives.iter() { + let OutlivesPredicate(alias2, r2) = + infcx.instantiate_binder_with_infer(*bound_type_outlives); + + let mut relation = HigherRankedAliasMatcher { + infcx, + region_constraints: vec![RegionConstraint::RegionOutlives(r2, r)], + }; + + if let Ok(_) = relation.relate(alias.to_ty(infcx.cx()), alias2) { + candidates + .push(RegionConstraint::And(relation.region_constraints.into_boxed_slice())); + } + } + }); + + let constraint = RegionConstraint::Or(candidates.into_boxed_slice()); + + let largest_universe = infcx.universe(); + debug!(?prev_universe, ?largest_universe); + + ((prev_universe.index() + 1)..=largest_universe.index()) + .map(|u| UniverseIndex::from_usize(u)) + .rev() + .fold(constraint, |constraint, u| { + eagerly_handle_placeholders_in_universe(infcx, constraint, u) + }) +} + +struct HigherRankedAliasMatcher<'a, Infcx: InferCtxtLike, I: Interner> { + infcx: &'a Infcx, + region_constraints: Vec>, +} + +impl<'a, Infcx: InferCtxtLike, I: Interner> TypeRelation + for HigherRankedAliasMatcher<'a, Infcx, I> +{ + fn cx(&self) -> I { + self.infcx.cx() + } + + fn relate_ty_args( + &mut self, + a_ty: I::Ty, + _b_ty: I::Ty, + _ty_def_id: I::DefId, + a_args: I::GenericArgs, + b_args: I::GenericArgs, + _mk: impl FnOnce(I::GenericArgs) -> I::Ty, + ) -> RelateResult { + rustc_type_ir::relate::relate_args_invariantly(self, a_args, b_args)?; + Ok(a_ty) + } + + fn relate_with_variance>( + &mut self, + _variance: Variance, + _info: VarianceDiagInfo, + a: T, + b: T, + ) -> RelateResult { + // FIXME(-Zhigher-ranked-assumptions-v2): bivariance is important for opaque type args so + // we should actually handle variance in some way here. + self.relate(a, b) + } + + fn tys(&mut self, a: I::Ty, b: I::Ty) -> RelateResult { + rustc_type_ir::relate::structurally_relate_tys(self, a, b) + } + + fn regions(&mut self, a: I::Region, b: I::Region) -> RelateResult { + if a != b { + self.region_constraints.push(RegionConstraint::RegionOutlives(a, b)); + self.region_constraints.push(RegionConstraint::RegionOutlives(b, a)); + } + Ok(a) + } + + fn consts(&mut self, a: I::Const, b: I::Const) -> RelateResult { + rustc_type_ir::relate::structurally_relate_consts(self, a, b) + } + + fn binders(&mut self, a: Binder, b: Binder) -> RelateResult> + where + T: Relate, + { + self.infcx.enter_forall(a, |a| { + let u = self.infcx.universe(); + self.infcx.insert_universe_assumptions(u, Some(Assumptions::empty())); + let b = self.infcx.instantiate_binder_with_infer(b); + self.relate(a, b) + })?; + + self.infcx.enter_forall(b, |b| { + let u = self.infcx.universe(); + self.infcx.insert_universe_assumptions(u, Some(Assumptions::empty())); + let a = self.infcx.instantiate_binder_with_infer(a); + self.relate(a, b) + })?; + + Ok(a) + } +}