From d5b926cf77eeefe68a76b8327c9d8d129198f017 Mon Sep 17 00:00:00 2001 From: Boxy Date: Tue, 28 Apr 2026 10:51:47 +0100 Subject: [PATCH 1/9] 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 8107d91ba4109..bff8d67cb20ce 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -30,6 +30,10 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { self.typing_mode_raw() } + fn higher_ranked_assumptions_v2(&self) -> bool { + self.tcx.sess.opts.unstable_opts.higher_ranked_assumptions_v2 + } + fn universe(&self) -> ty::UniverseIndex { self.universe() } 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 227d091b5c2f7..0e537ceff73d3 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 @@ -1362,6 +1362,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 8734dad6df503..b6653f673574a 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 093969704e54e..3cf6b70c766a0 100644 --- a/compiler/rustc_type_ir/src/infer_ctxt.rs +++ b/compiler/rustc_type_ir/src/infer_ctxt.rs @@ -338,6 +338,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 25e61c366f79765b23ea9f71f07b0fbf7a73c00d Mon Sep 17 00:00:00 2001 From: Boxy Date: Tue, 28 Apr 2026 10:02:05 +0100 Subject: [PATCH 2/9] 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 bff8d67cb20ce..5c9dc38c80748 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -42,6 +42,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), @@ -294,6 +300,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 b802c3b2e8d40..a5bebe971fd4e 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -124,6 +124,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 @@ -171,6 +174,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(), @@ -1750,3 +1754,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 a10026f2f77c7..0589fac269a44 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 f61088d2c5bf9..0fb6cc73b418e 100644 --- a/compiler/rustc_middle/src/traits/solve.rs +++ b/compiler/rustc_middle/src/traits/solve.rs @@ -50,6 +50,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 @@ -72,6 +76,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 4eea0f2c2198d..2d705eac83ddb 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/mod.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/mod.rs @@ -114,9 +114,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.iter().map(|(c, vis)| (*c, vis.and(visible_for_leak_check))), 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 0e537ceff73d3..faeb021a4b520 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,10 +1,14 @@ use std::mem; use std::ops::ControlFlow; +use rustc_data_structures::transitive_relation::TransitiveRelationBuilder; #[cfg(feature = "nightly")] use rustc_macros::StableHash; -use rustc_type_ir::data_structures::{HashMap, HashSet}; +use rustc_type_ir::data_structures::{HashMap, HashSet, IndexMap}; +use rustc_type_ir::ClauseKind::*; 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}; @@ -14,9 +18,9 @@ use rustc_type_ir::solve::{ RerunResultExt, SmallCopyList, }; use rustc_type_ir::{ - self as ty, CanonicalVarValues, ClauseKind, InferCtxtLike, Interner, MayBeErased, - OpaqueTypeKey, PredicateKind, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeSuperVisitable, - TypeVisitable, TypeVisitableExt, TypeVisitor, TypingMode, + self as ty, AliasTy, Binder, CanonicalVarValues, ClauseKind, InferCtxtLike, Interner, MayBeErased, + OpaqueTypeKey, OutlivesPredicate, PredicateKind, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeSuperVisitable, + TypeVisitable, TypeVisitableExt, TypeVisitor, TypingMode, UniverseIndex }; use tracing::{Level, debug, instrument, trace, warn}; @@ -1366,6 +1370,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); } @@ -1615,8 +1623,11 @@ where return Ok(self.make_ambiguous_response_no_constraints(maybe_info)); } - 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)); @@ -1667,6 +1678,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 @@ -1694,7 +1706,12 @@ where assert!(opaque_types.is_empty()); } - 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 be2c44cdca89f..b9e4712bfbd51 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 3cf6b70c766a0..8233c0692fbf9 100644 --- a/compiler/rustc_type_ir/src/infer_ctxt.rs +++ b/compiler/rustc_type_ir/src/infer_ctxt.rs @@ -342,6 +342,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; @@ -443,6 +447,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 09b8d0eaf6d80..d9906795dfba6 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 b136dec792f6a..3f33ca45bc502 100644 --- a/compiler/rustc_type_ir/src/solve/mod.rs +++ b/compiler/rustc_type_ir/src/solve/mod.rs @@ -13,6 +13,7 @@ use rustc_type_ir_macros::{ use tracing::debug; use crate::lang_items::SolverTraitLangItem; +use crate::region_constraint::RegionConstraint; use crate::search_graph::PathKind; use crate::{ self as ty, Canonical, CanonicalVarValues, CantBeErased, Interner, TypingMode, Upcast, @@ -584,6 +585,7 @@ impl Eq for Response {} #[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] pub struct ExternalConstraintsData { pub region_constraints: Vec<(ty::RegionConstraint, VisibleForLeakCheck)>, + pub solver_region_constraint: RegionConstraint, pub opaque_types: Vec<(ty::OpaqueTypeKey, I::Ty)>, pub normalization_nested_goals: NestedNormalizationGoals, } @@ -592,9 +594,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 d9b944bc824f87be99556830d25f0214fd795e86 Mon Sep 17 00:00:00 2001 From: Boxy Date: Tue, 28 Apr 2026 10:03:26 +0100 Subject: [PATCH 3/9] 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 | 124 ++++++++++++++++-- .../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, 290 insertions(+), 32 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 5c9dc38c80748..1cffd01c2f997 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -42,6 +42,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 a5bebe971fd4e..cd05efb3fe354 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -319,6 +319,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>>, @@ -611,6 +621,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 ccd5c649d4c9e..0a260f97e5164 100644 --- a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs @@ -259,7 +259,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 faeb021a4b520..77413a6865696 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 @@ -4,11 +4,11 @@ use std::ops::ControlFlow; use rustc_data_structures::transitive_relation::TransitiveRelationBuilder; #[cfg(feature = "nightly")] use rustc_macros::StableHash; -use rustc_type_ir::data_structures::{HashMap, HashSet, IndexMap}; 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}; @@ -18,9 +18,10 @@ use rustc_type_ir::solve::{ RerunResultExt, SmallCopyList, }; use rustc_type_ir::{ - self as ty, AliasTy, Binder, CanonicalVarValues, ClauseKind, InferCtxtLike, Interner, MayBeErased, - OpaqueTypeKey, OutlivesPredicate, PredicateKind, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeSuperVisitable, - TypeVisitable, TypeVisitableExt, TypeVisitor, TypingMode, UniverseIndex + self as ty, AliasTy, Binder, CanonicalVarValues, ClauseKind, InferCtxtLike, Interner, + MayBeErased, OpaqueTypeKey, OutlivesPredicate, PredicateKind, TypeFoldable, TypeFolder, + TypeSuperFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, + TypingMode, UniverseIndex, }; use tracing::{Level, debug, instrument, trace, warn}; @@ -827,7 +828,7 @@ where ) -> QueryResultOrRerunNonErased { let Goal { param_env, predicate } = goal; let kind = predicate.kind(); - self.enter_forall(kind, |ecx, kind| { + self.enter_forall_with_assumptions(kind, param_env, |ecx, kind| { Ok(match kind { ty::PredicateKind::Clause(ty::ClauseKind::Trait(predicate)) => { ecx.compute_trait_goal(Goal { param_env, predicate }).map(|(r, _via)| r)? @@ -1329,14 +1330,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 @@ -1713,6 +1726,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 7e56dcb02591d..f3ace5a70c69c 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -1072,12 +1072,16 @@ where && ecx .probe(|_| ProbeKind::ProjectionCompatibility) .enter(|ecx| { - 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() + }, + ) .map_err(Into::into) }) .is_ok() @@ -1091,12 +1095,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 @@ -1119,12 +1127,16 @@ where ) .map_err(Into::into); } - 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) => { @@ -1349,14 +1361,17 @@ where ) -> Result>, NoSolution>, ) -> Result, NoSolutionOrRerunNonErased> { 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 8233c0692fbf9..ad0cbe51f37ea 100644 --- a/compiler/rustc_type_ir/src/infer_ctxt.rs +++ b/compiler/rustc_type_ir/src/infer_ctxt.rs @@ -342,6 +342,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 b253fcf4489941c8708734cc9f073b6cbd87048e Mon Sep 17 00:00:00 2001 From: Boxy Date: Tue, 28 Apr 2026 10:51:58 +0100 Subject: [PATCH 4/9] produce new region constraints --- .../src/infer/outlives/obligations.rs | 4 +- .../rustc_infer/src/infer/outlives/verify.rs | 50 +----- .../src/solve/eval_ctxt/mod.rs | 143 +++++++++++++++++- .../rustc_next_trait_solver/src/solve/mod.rs | 36 ++++- .../src/solve/delegate.rs | 6 +- compiler/rustc_type_ir/src/inherent.rs | 12 ++ compiler/rustc_type_ir/src/outlives.rs | 37 ++++- 7 files changed, 227 insertions(+), 61 deletions(-) diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index d0234cc2621a9..734f220184c35 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -145,6 +145,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 @@ -458,7 +460,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/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index 77413a6865696..7d919c6899e38 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 @@ -1363,6 +1363,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) @@ -1527,6 +1531,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( &mut self, param_env: I::ParamEnv, @@ -1578,12 +1595,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) { @@ -1639,7 +1668,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)); @@ -1702,7 +1731,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() }; @@ -1817,6 +1849,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 b9e4712bfbd51..aa06dd20c237f 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>, ) -> QueryResultOrRerunNonErased { 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>, ) -> QueryResultOrRerunNonErased { let ty::OutlivesPredicate(a, b) = goal.predicate; - self.register_region_outlives(a, b, VisibleForLeakCheck::Yes); + + 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, VisibleForLeakCheck::Yes); + } + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } diff --git a/compiler/rustc_trait_selection/src/solve/delegate.rs b/compiler/rustc_trait_selection/src/solve/delegate.rs index e9c68784ccd36..ac3226b5a068f 100644 --- a/compiler/rustc_trait_selection/src/solve/delegate.rs +++ b/compiler/rustc_trait_selection/src/solve/delegate.rs @@ -73,6 +73,8 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< goal: Goal<'tcx, ty::Predicate<'tcx>>, span: Span, ) -> Option { + let higher_ranked_assumptions_v2 = + self.tcx.sess.opts.unstable_opts.higher_ranked_assumptions_v2; let pred = goal.predicate.kind(); match pred.skip_binder() { ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred)) => { @@ -122,7 +124,7 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< 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 => { if outlives.has_escaping_bound_vars() { return None; } @@ -135,7 +137,7 @@ 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 => { if outlives.has_escaping_bound_vars() { return None; } diff --git a/compiler/rustc_type_ir/src/inherent.rs b/compiler/rustc_type_ir/src/inherent.rs index 7ff447a81a284..b29593528ee3b 100644 --- a/compiler/rustc_type_ir/src/inherent.rs +++ b/compiler/rustc_type_ir/src/inherent.rs @@ -531,6 +531,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 2ddd2dca7cb3a3490fee65fe29f4b9f05b31940a Mon Sep 17 00:00:00 2001 From: Boxy Date: Mon, 27 Apr 2026 17:52:09 +0100 Subject: [PATCH 5/9] 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 48389d9354998..0ba5214a06580 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 734f220184c35..3d0169b8060cf 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}; @@ -201,6 +203,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. @@ -222,6 +303,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 adbfd3f3d9f588b739443bd33a9ade52f9a5720d Mon Sep 17 00:00:00 2001 From: Boxy Date: Tue, 28 Apr 2026 10:12:42 +0100 Subject: [PATCH 6/9] 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 | 6 + compiler/rustc_type_ir/src/interner.rs | 7 +- .../rustc_type_ir/src/region_constraint.rs | 787 ++++++++++++++++++ 5 files changed, 810 insertions(+), 12 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 7d919c6899e38..8e95e2ea453b5 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 @@ -1863,12 +1863,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 b29593528ee3b..8859ab7c037b0 100644 --- a/compiler/rustc_type_ir/src/inherent.rs +++ b/compiler/rustc_type_ir/src/inherent.rs @@ -702,6 +702,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 7cdd82fe1a807..0e182518faa41 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -96,12 +96,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) + } +} From c42e7ea4fe4a66bdd2a806bb5e50831737046650 Mon Sep 17 00:00:00 2001 From: Boxy Date: Fri, 1 May 2026 15:08:41 +0100 Subject: [PATCH 7/9] new stuff --- .../rustc_borrowck/src/constraints/mod.rs | 4 +- .../src/diagnostics/region_errors.rs | 11 +- .../rustc_borrowck/src/region_infer/mod.rs | 1 + compiler/rustc_borrowck/src/type_check/mod.rs | 1 + .../rustc_hir_analysis/src/check/wfcheck.rs | 5 +- compiler/rustc_infer/src/infer/at.rs | 8 +- compiler/rustc_infer/src/infer/context.rs | 27 +- compiler/rustc_infer/src/infer/mod.rs | 22 +- .../rustc_infer/src/infer/outlives/mod.rs | 4 +- .../src/infer/outlives/obligations.rs | 30 +- .../src/infer/snapshot/undo_log.rs | 7 +- compiler/rustc_middle/src/mir/query.rs | 4 + compiler/rustc_middle/src/traits/solve.rs | 17 +- .../src/ty/context/impl_interner.rs | 4 + .../src/canonical/mod.rs | 31 +- .../src/solve/eval_ctxt/mod.rs | 305 ++----- .../eval_ctxt/solver_region_constraints.rs | 217 +++++ .../rustc_next_trait_solver/src/solve/mod.rs | 25 +- compiler/rustc_session/src/options.rs | 4 +- .../src/error_reporting/infer/region.rs | 15 + compiler/rustc_trait_selection/src/regions.rs | 46 +- .../src/solve/delegate.rs | 12 +- .../src/traits/auto_trait.rs | 4 +- compiler/rustc_type_ir/src/infer_ctxt.rs | 12 +- compiler/rustc_type_ir/src/interner.rs | 2 + compiler/rustc_type_ir/src/outlives.rs | 4 +- .../rustc_type_ir/src/region_constraint.rs | 758 ++++++++++-------- compiler/rustc_type_ir/src/solve/mod.rs | 42 +- tests/ui/README.md | 4 + .../assumptions_on_binders/alias_outlives.rs | 42 + .../alias_outlives.stderr | 21 + ...higher_ranked_alias_outlives_assumption.rs | 53 ++ 32 files changed, 1020 insertions(+), 722 deletions(-) create mode 100644 compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs create mode 100644 tests/ui/assumptions_on_binders/alias_outlives.rs create mode 100644 tests/ui/assumptions_on_binders/alias_outlives.stderr create mode 100644 tests/ui/assumptions_on_binders/implied_higher_ranked_alias_outlives_assumption.rs diff --git a/compiler/rustc_borrowck/src/constraints/mod.rs b/compiler/rustc_borrowck/src/constraints/mod.rs index 99ddccabd15fe..98f418f12ef2a 100644 --- a/compiler/rustc_borrowck/src/constraints/mod.rs +++ b/compiler/rustc_borrowck/src/constraints/mod.rs @@ -96,8 +96,8 @@ impl<'tcx> fmt::Debug for OutlivesConstraint<'tcx> { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { write!( formatter, - "({:?}: {:?}) due to {:?} ({:?}) ({:?})", - self.sup, self.sub, self.locations, self.variance_info, self.category, + "({:?}: {:?}) due to {:?} ({:?}) ({:?}) (span: {:?})", + self.sup, self.sub, self.locations, self.variance_info, self.category, self.span, ) } } diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs index f9c91c3371516..bac3661ec51ad 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs @@ -57,7 +57,8 @@ impl<'tcx> ConstraintDescription for ConstraintCategory<'tcx> { ConstraintCategory::OpaqueType => "opaque type ", ConstraintCategory::ClosureUpvar(_) => "closure capture ", ConstraintCategory::Usage => "this usage ", - ConstraintCategory::Predicate(_) + ConstraintCategory::SolverRegionConstraint(_) + | ConstraintCategory::Predicate(_) | ConstraintCategory::Boring | ConstraintCategory::BoringNoLocation | ConstraintCategory::Internal @@ -473,6 +474,14 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { let errci = ErrorConstraintInfo { fr, outlived_fr, category, span: cause.span }; let mut diag = match (category, fr_is_local, outlived_fr_is_local) { + (ConstraintCategory::SolverRegionConstraint(span), _, _) => { + let mut d = self.dcx().struct_span_err( + span, + "unsatisfied lifetime constraint from -Zassumptions-on-binders :3", + ); + d.note("meoow :c"); + d + } (ConstraintCategory::Return(kind), true, false) if self.is_closure_fn_mut(fr) => { self.report_fnmut_error(&errci, kind) } diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 2a759387788a7..254b0df3e5a30 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -1798,6 +1798,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // the `'region: 'static` constraints introduced by placeholder outlives. ConstraintCategory::Internal => 7, ConstraintCategory::OutlivesUnnameablePlaceholder(_) => 8, + ConstraintCategory::SolverRegionConstraint(_) => 9, }; debug!("constraint {constraint:?} category: {category:?}, interest: {interest:?}"); diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 0ba5214a06580..5525b54b19c9a 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -187,6 +187,7 @@ pub(crate) fn type_check<'tcx>( &mut converter, typeck.known_type_outlives_obligations, universal_region_relations.outlives.clone(), + infcx.tcx.def_span(infcx.root_def_id), ); // In case type check encountered an error region, we suppress unhelpful extra diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 63753aee383a0..9eff23ef8622a 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -197,7 +197,7 @@ where lint_redundant_lifetimes(tcx, body_def_id, &outlives_env); - let errors = infcx.resolve_regions_with_outlives_env(&outlives_env); + let errors = infcx.resolve_regions_with_outlives_env(&outlives_env, tcx.def_span(body_def_id)); if errors.is_empty() { return Ok(()); } @@ -211,7 +211,8 @@ where // the implied bounds hack if this contains `bevy_ecs`'s `ParamSet` type. false, ); - let errors_compat = infcx_compat.resolve_regions_with_outlives_env(&outlives_env); + let errors_compat = + infcx_compat.resolve_regions_with_outlives_env(&outlives_env, tcx.def_span(body_def_id)); if errors_compat.is_empty() { // FIXME: Once we fix bevy, this would be the place to insert a warning // to upgrade bevy. diff --git a/compiler/rustc_infer/src/infer/at.rs b/compiler/rustc_infer/src/infer/at.rs index ba40af97780c7..d2ead01748e38 100644 --- a/compiler/rustc_infer/src/infer/at.rs +++ b/compiler/rustc_infer/src/infer/at.rs @@ -81,7 +81,9 @@ 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(), + placeholder_assumptions_for_next_solver: self + .placeholder_assumptions_for_next_solver + .clone(), next_trait_solver: self.next_trait_solver, obligation_inspector: self.obligation_inspector.clone(), } @@ -107,7 +109,9 @@ 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(), + placeholder_assumptions_for_next_solver: self + .placeholder_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 1cffd01c2f997..2c94f866c08b4 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -30,8 +30,8 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { self.typing_mode_raw() } - fn higher_ranked_assumptions_v2(&self) -> bool { - self.tcx.sess.opts.unstable_opts.higher_ranked_assumptions_v2 + fn assumptions_on_binders(&self) -> bool { + self.tcx.sess.opts.unstable_opts.assumptions_on_binders } fn universe(&self) -> ty::UniverseIndex { @@ -42,27 +42,40 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { self.create_next_universe() } - fn insert_universe_assumptions( + fn insert_placeholder_assumptions( &self, u: ty::UniverseIndex, assumptions: Option>>, ) { - self.universe_assumptions_for_next_solver.borrow_mut().insert(u, assumptions); + self.placeholder_assumptions_for_next_solver.borrow_mut().insert(u, assumptions); } - fn get_universe_assumptions( + fn get_placeholder_assumptions( &self, u: ty::UniverseIndex, ) -> Option>> { - self.universe_assumptions_for_next_solver.borrow().get(&u).unwrap().as_ref().cloned() + self.placeholder_assumptions_for_next_solver.borrow().get(&u).unwrap().as_ref().cloned() } - fn get_solve_region_constraint( + fn get_solver_region_constraint( &self, ) -> rustc_type_ir::region_constraint::RegionConstraint> { self.inner.borrow().solver_region_constraint_storage.get_constraint() } + fn overwrite_solver_region_constraint( + &self, + constraint: rustc_type_ir::region_constraint::RegionConstraint>, + ) { + let mut inner = self.inner.borrow_mut(); + use rustc_data_structures::undo_log::UndoLogs; + + use crate::infer::UndoLog; + let old_constraint = inner.solver_region_constraint_storage.get_constraint(); + inner.undo_log.push(UndoLog::OverwriteSolverRegionConstraint { old_constraint }); + inner.solver_region_constraint_storage.overwrite_solver_region_constraint(constraint); + } + fn universe_of_ty(&self, vid: ty::TyVid) -> Option { match self.try_resolve_ty_var(vid) { Err(universe) => Some(universe), diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index cd05efb3fe354..cdc5021aaf71d 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -322,7 +322,10 @@ pub struct InferCtxt<'tcx> { /// 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< + // + // FIXME(-Zassumptions-on-binders): This and `universe` should probably be + // in `InferCtxtInner` so they can participate in rollbacks and whatnot + placeholder_assumptions_for_next_solver: RefCell< FxIndexMap< ty::UniverseIndex, Option>>, @@ -411,6 +414,10 @@ pub enum SubregionOrigin<'tcx> { }, AscribeUserTypeProvePredicate(Span), + + // FIXME(-Zassumptions-on-binders): this is a temporary hack until we support + // proper diagnostics for solver region constraints. + SolverRegionConstraint(Span), } // `SubregionOrigin` is used a lot. Make sure it doesn't unintentionally get bigger. @@ -422,6 +429,7 @@ impl<'tcx> SubregionOrigin<'tcx> { match self { Self::Subtype(type_trace) => type_trace.cause.to_constraint_category(), Self::AscribeUserTypeProvePredicate(span) => ConstraintCategory::Predicate(*span), + Self::SolverRegionConstraint(span) => ConstraintCategory::SolverRegionConstraint(*span), _ => ConstraintCategory::BoringNoLocation, } } @@ -621,7 +629,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()), + placeholder_assumptions_for_next_solver: RefCell::new(Default::default()), next_trait_solver, obligation_inspector: Cell::new(None), } @@ -1676,6 +1684,7 @@ impl<'tcx> SubregionOrigin<'tcx> { SubregionOrigin::CompareImplItemObligation { span, .. } => span, SubregionOrigin::AscribeUserTypeProvePredicate(span) => span, SubregionOrigin::CheckAssociatedTypeBounds { ref parent, .. } => parent.span(), + SubregionOrigin::SolverRegionConstraint(a) => a, } } @@ -1807,4 +1816,13 @@ impl<'tcx> SolverRegionConstraintStorage<'tcx> { _ => unreachable!(), } } + + #[instrument(level = "debug", skip(self))] + fn overwrite_solver_region_constraint(&mut self, constraint: SolverRegionConstraint<'tcx>) { + if !constraint.is_and() { + self.0 = SolverRegionConstraint::And(vec![constraint].into_boxed_slice()) + } else { + self.0 = constraint; + } + } } diff --git a/compiler/rustc_infer/src/infer/outlives/mod.rs b/compiler/rustc_infer/src/infer/outlives/mod.rs index 2538df46575e4..92b47295ade88 100644 --- a/compiler/rustc_infer/src/infer/outlives/mod.rs +++ b/compiler/rustc_infer/src/infer/outlives/mod.rs @@ -5,6 +5,7 @@ use std::iter; use rustc_data_structures::undo_log::UndoLogs; use rustc_middle::traits::query::{NoSolution, OutlivesBound}; use rustc_middle::ty; +use rustc_span::Span; use tracing::instrument; use self::env::OutlivesEnvironment; @@ -49,8 +50,9 @@ impl<'tcx> InferCtxt<'tcx> { ty::PolyTypeOutlivesPredicate<'tcx>, SubregionOrigin<'tcx>, ) -> Result, NoSolution>, + span: Span, ) -> Vec> { - match self.process_registered_region_obligations(outlives_env, deeply_normalize_ty) { + match self.process_registered_region_obligations(outlives_env, deeply_normalize_ty, span) { Ok(()) => {} Err((clause, origin)) => { return vec![RegionResolutionError::CannotNormalize(clause, origin)]; diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index 3d0169b8060cf..b73ed50c1ea16 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -69,7 +69,7 @@ use rustc_middle::ty::{ self, GenericArgKind, GenericArgsRef, PolyTypeOutlivesPredicate, Region, RegionVid, Ty, TyCtxt, TypeFoldable as _, TypeVisitableExt, }; -use rustc_span::DUMMY_SP; +use rustc_span::Span; use smallvec::smallvec; use tracing::{debug, instrument}; @@ -147,7 +147,7 @@ impl<'tcx> InferCtxt<'tcx> { sub_region: Region<'tcx>, cause: &ObligationCause<'tcx>, ) { - assert!(!self.tcx.sess.opts.unstable_opts.higher_ranked_assumptions_v2); + assert!(!self.tcx.sess.opts.unstable_opts.assumptions_on_binders); // `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 @@ -206,12 +206,13 @@ impl<'tcx> InferCtxt<'tcx> { pub fn destructure_solver_region_constraints_for_regionck( &self, outlives_env: &OutlivesEnvironment<'tcx>, + span: Span, ) { 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); + self.destructure_solver_region_constraints(assumptions, self, span); } pub fn destructure_solver_region_constraints_for_borrowck( @@ -220,38 +221,38 @@ impl<'tcx> InferCtxt<'tcx> { conversion: impl TypeOutlivesDelegate<'tcx>, known_type_outlives: &[PolyTypeOutlivesPredicate<'tcx>], region_outlives: TransitiveRelation, + span: Span, ) { 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); + self.destructure_solver_region_constraints(assumptions, conversion, span); } #[instrument(level = "debug", skip(self, conversion))] - pub fn destructure_solve_region_constraints( + pub fn destructure_solver_region_constraints( &self, assumptions: rustc_type_ir::region_constraint::Assumptions>, mut conversion: impl TypeOutlivesDelegate<'tcx>, + span: Span, ) { - if !self.tcx.sess.opts.unstable_opts.higher_ranked_assumptions_v2 { + if !self.tcx.sess.opts.unstable_opts.assumptions_on_binders { return; } assert!(self.next_trait_solver()); - // FIXME(-Zhigher-ranked-assumptions-v2): Implement diagnostics - let origin = SubregionOrigin::Reborrow(DUMMY_SP); + let origin = SubregionOrigin::SolverRegionConstraint(span); 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( + rustc_type_ir::region_constraint::destructure_type_outlives_constraints_in_root( self, constraint, - None, - &Some(assumptions), + &assumptions, ); debug!(?constraint); let constraint = rustc_type_ir::region_constraint::evaluate_solver_constraint(&constraint); @@ -274,9 +275,9 @@ impl<'tcx> InferCtxt<'tcx> { category, ); } - // FIXME(-Zhigher-ranked-assumptions-v2): actually implement OR as an OR + // FIXME(-Zassumptions-on-binders): actually implement OR as an OR And(nested) | Or(nested) => constraints.extend(nested), - AliasTyOutlivesFromEnv(..) => unreachable!(), + AliasTyOutlivesViaEnv(..) => unreachable!(), PlaceholderTyOutlives(..) => unreachable!(), } } @@ -300,10 +301,11 @@ impl<'tcx> InferCtxt<'tcx> { SubregionOrigin<'tcx>, ) -> Result, NoSolution>, + span: Span, ) -> 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); + self.destructure_solver_region_constraints_for_regionck(outlives_env, span); // Must loop since the process of normalizing may itself register region obligations. for iteration in 0.. { diff --git a/compiler/rustc_infer/src/infer/snapshot/undo_log.rs b/compiler/rustc_infer/src/infer/snapshot/undo_log.rs index 0589fac269a44..09d8eb3bf9232 100644 --- a/compiler/rustc_infer/src/infer/snapshot/undo_log.rs +++ b/compiler/rustc_infer/src/infer/snapshot/undo_log.rs @@ -7,7 +7,7 @@ use rustc_middle::ty::{self, OpaqueTypeKey, ProvisionalHiddenType}; use tracing::debug; use crate::infer::unify_key::{ConstVidKey, RegionVidKey}; -use crate::infer::{InferCtxtInner, region_constraints, type_variable}; +use crate::infer::{InferCtxtInner, SolverRegionConstraint, region_constraints, type_variable}; use crate::traits; pub struct Snapshot<'tcx> { @@ -29,6 +29,7 @@ pub(crate) enum UndoLog<'tcx> { ProjectionCache(traits::UndoLog<'tcx>), PushTypeOutlivesConstraint, PushSolverRegionConstraint, + OverwriteSolverRegionConstraint { old_constraint: SolverRegionConstraint<'tcx> }, PushRegionAssumption, PushHirTypeckPotentiallyRegionDependentGoal, } @@ -86,6 +87,10 @@ impl<'tcx> Rollback> for InferCtxtInner<'tcx> { "pushed solver region constraint but could not pop it" ); } + UndoLog::OverwriteSolverRegionConstraint { old_constraint } => { + self.solver_region_constraint_storage + .overwrite_solver_region_constraint(old_constraint); + } 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/mir/query.rs b/compiler/rustc_middle/src/mir/query.rs index bd029278e7584..f8607cafcfa6e 100644 --- a/compiler/rustc_middle/src/mir/query.rs +++ b/compiler/rustc_middle/src/mir/query.rs @@ -151,6 +151,10 @@ pub enum ConstraintCategory<'tcx> { #[type_visitable(ignore)] ty::RegionVid, ), + + // FIXME(-Zassumptions-on-binders): this is a temporary hack until we support + // proper diagnostics for solver region constraints. + SolverRegionConstraint(Span), } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] diff --git a/compiler/rustc_middle/src/traits/solve.rs b/compiler/rustc_middle/src/traits/solve.rs index 0fb6cc73b418e..ceecb26d242dd 100644 --- a/compiler/rustc_middle/src/traits/solve.rs +++ b/compiler/rustc_middle/src/traits/solve.rs @@ -50,10 +50,6 @@ 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 @@ -76,7 +72,6 @@ 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), @@ -86,8 +81,14 @@ impl<'tcx> TypeFoldable> for ExternalConstraints<'tcx> { impl<'tcx> TypeVisitable> for ExternalConstraints<'tcx> { fn visit_with>>(&self, visitor: &mut V) -> V::Result { - try_visit!(self.region_constraints.visit_with(visitor)); - try_visit!(self.opaque_types.visit_with(visitor)); - self.normalization_nested_goals.visit_with(visitor) + let ExternalConstraintsData { + region_constraints, + opaque_types, + normalization_nested_goals, + } = &**self; + + try_visit!(region_constraints.visit_with(visitor)); + try_visit!(opaque_types.visit_with(visitor)); + normalization_nested_goals.visit_with(visitor) } } diff --git a/compiler/rustc_middle/src/ty/context/impl_interner.rs b/compiler/rustc_middle/src/ty/context/impl_interner.rs index 19668a4c0f20e..6a644d4ae4d91 100644 --- a/compiler/rustc_middle/src/ty/context/impl_interner.rs +++ b/compiler/rustc_middle/src/ty/context/impl_interner.rs @@ -327,6 +327,10 @@ impl<'tcx> Interner for TyCtxt<'tcx> { self.features() } + fn assumptions_on_binders(self) -> bool { + self.sess.opts.unstable_opts.assumptions_on_binders + } + fn coroutine_hidden_types( self, def_id: DefId, diff --git a/compiler/rustc_next_trait_solver/src/canonical/mod.rs b/compiler/rustc_next_trait_solver/src/canonical/mod.rs index 2d705eac83ddb..6c6ead109e20e 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/mod.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/mod.rs @@ -24,8 +24,9 @@ use tracing::instrument; use crate::delegate::SolverDelegate; use crate::resolve::eager_resolve_vars; use crate::solve::{ - CanonicalInput, CanonicalResponse, Certainty, ExternalConstraintsData, Goal, - NestedNormalizationGoals, QueryInput, Response, VisibleForLeakCheck, inspect, + CanonicalInput, CanonicalResponse, Certainty, ExternalConstraintsData, + ExternalRegionConstraints, Goal, NestedNormalizationGoals, QueryInput, Response, + VisibleForLeakCheck, inspect, }; pub mod canonicalizer; @@ -114,19 +115,19 @@ where unify_query_var_values(delegate, param_env, &original_values, var_values, span); - let ExternalConstraintsData { - solver_region_constraint, - region_constraints, - opaque_types, - normalization_nested_goals, - } = &*external_constraints; + let ExternalConstraintsData { region_constraints, opaque_types, normalization_nested_goals } = + &*external_constraints; - delegate.register_solver_region_constraint(solver_region_constraint.clone()); - register_region_constraints( - delegate, - region_constraints.iter().map(|(c, vis)| (*c, vis.and(visible_for_leak_check))), - span, - ); + match region_constraints { + ExternalRegionConstraints::Old(r) => register_region_constraints( + delegate, + r.iter().map(|(c, vis)| (*c, vis.and(visible_for_leak_check))), + span, + ), + ExternalRegionConstraints::NextGen(r) => { + delegate.register_solver_region_constraint(r.clone()) + } + }; register_new_opaque_types(delegate, opaque_types, span); (normalization_nested_goals.clone(), certainty) @@ -380,7 +381,7 @@ pub fn response_no_constraints_raw( var_values: ty::CanonicalVarValues::make_identity(cx, var_kinds), // FIXME: maybe we should store the "no response" version in cx, like // we do for cx.types and stuff. - external_constraints: cx.mk_external_constraints(ExternalConstraintsData::default()), + external_constraints: cx.mk_external_constraints(ExternalConstraintsData::new(cx)), certainty, }, } 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 8e95e2ea453b5..5eaffdb4648a6 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,27 +1,23 @@ use std::mem; use std::ops::ControlFlow; -use rustc_data_structures::transitive_relation::TransitiveRelationBuilder; #[cfg(feature = "nightly")] use rustc_macros::StableHash; -use rustc_type_ir::ClauseKind::*; -use rustc_type_ir::data_structures::{HashMap, HashSet, IndexMap}; +use rustc_type_ir::data_structures::{HashMap, HashSet}; use rustc_type_ir::inherent::*; -use rustc_type_ir::outlives::Component; -use rustc_type_ir::region_constraint::{Assumptions, RegionConstraint}; +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::{ - AccessedOpaques, FetchEligibleAssocItemResponse, MaybeInfo, NoSolutionOrRerunNonErased, - OpaqueTypesJank, QueryResultOrRerunNonErased, RerunCondition, RerunNonErased, RerunReason, - RerunResultExt, SmallCopyList, + AccessedOpaques, ExternalRegionConstraints, FetchEligibleAssocItemResponse, MaybeInfo, + NoSolutionOrRerunNonErased, OpaqueTypesJank, QueryResultOrRerunNonErased, RerunCondition, + RerunNonErased, RerunReason, RerunResultExt, SmallCopyList, }; use rustc_type_ir::{ - self as ty, AliasTy, Binder, CanonicalVarValues, ClauseKind, InferCtxtLike, Interner, - MayBeErased, OpaqueTypeKey, OutlivesPredicate, PredicateKind, TypeFoldable, TypeFolder, - TypeSuperFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, - TypingMode, UniverseIndex, + self as ty, CanonicalVarValues, ClauseKind, InferCtxtLike, Interner, MayBeErased, + OpaqueTypeKey, PredicateKind, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeSuperVisitable, + TypeVisitable, TypeVisitableExt, TypeVisitor, TypingMode, }; use tracing::{Level, debug, instrument, trace, warn}; @@ -44,6 +40,7 @@ use crate::solve::{ }; mod probe; +mod solver_region_constraints; /// The kind of goal we're currently proving. /// @@ -1330,6 +1327,9 @@ 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. + /// /// The `param_env` is used to *compute* the assumptions of the binder, not *as* the /// assumptions associated with the binder. /// @@ -1342,12 +1342,12 @@ where ) -> U { 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) + let assumptions = if self.assumptions_on_binders() { + self.region_assumptions_for_placeholders_in_universe(value.clone(), u, param_env) } else { None }; - self.delegate.insert_universe_assumptions(u, assumptions); + self.delegate.insert_placeholder_assumptions(u, assumptions); f(self, value) }) } @@ -1363,10 +1363,6 @@ 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) @@ -1383,8 +1379,8 @@ where args } - pub(super) fn higher_ranked_assumptions_v2(&self) -> bool { - self.delegate.higher_ranked_assumptions_v2() + pub(super) fn assumptions_on_binders(&self) -> bool { + self.delegate.assumptions_on_binders() } pub(super) fn register_solver_region_constraint(&self, c: RegionConstraint) { @@ -1531,19 +1527,6 @@ 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( &mut self, param_env: I::ParamEnv, @@ -1595,24 +1578,22 @@ where previous call to `try_evaluate_added_goals!`" ); - 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 goals_certainty = match self.delegate.assumptions_on_binders() { + true => { + let certainty = self.eagerly_handle_placeholders()?; + 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 + })?; + + goals_certainty + } + }; let (certainty, normalization_nested_goals) = match (self.current_goal_kind, shallow_certainty) { @@ -1665,19 +1646,16 @@ where return Ok(self.make_ambiguous_response_no_constraints(maybe_info)); } - let external_constraints = self.compute_external_query_constraints( - certainty, - normalization_nested_goals, - solver_region_constraint, - ); + let external_constraints = + self.compute_external_query_constraints(certainty, normalization_nested_goals); let (var_values, mut external_constraints) = eager_resolve_vars(self.delegate, (self.var_values, external_constraints)); // Remove any trivial or duplicated region constraints once we've resolved regions let mut unique = HashSet::default(); - external_constraints - .region_constraints - .retain(|(outlives, _)| !outlives.is_trivial() && unique.insert(*outlives)); + if let ExternalRegionConstraints::Old(r) = &mut external_constraints.region_constraints { + r.retain(|(outlives, _)| !outlives.is_trivial() && unique.insert(*outlives)); + } let canonical = canonicalize_response( self.delegate, @@ -1720,7 +1698,6 @@ 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 @@ -1730,13 +1707,15 @@ where // region constraints from an ambiguous nested goal. This is tested in both // `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 { - match self.higher_ranked_assumptions_v2() { - true => Vec::new(), - false => self.delegate.make_deduplicated_region_constraints(), + let region_constraints = match self.assumptions_on_binders() { + true if let Certainty::Yes = certainty => { + ExternalRegionConstraints::NextGen(self.delegate.get_solver_region_constraint()) } - } else { - Default::default() + true => ExternalRegionConstraints::NextGen(RegionConstraint::new_true()), + false if let Certainty::Yes = certainty => { + ExternalRegionConstraints::Old(self.delegate.make_deduplicated_region_constraints()) + } + false => ExternalRegionConstraints::Old(vec![]), }; // We only return *newly defined* opaque types from canonical queries. @@ -1751,197 +1730,7 @@ where assert!(opaque_types.is_empty()); } - ExternalConstraintsData { - solver_region_constraint, - region_constraints, - opaque_types, - 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(), - }) - } - - #[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, - ) - }); - - 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, - ])) + ExternalConstraintsData { region_constraints, opaque_types, normalization_nested_goals } } } diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs new file mode 100644 index 0000000000000..b662fd61be18e --- /dev/null +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs @@ -0,0 +1,217 @@ +//! Logic for `-Zassumptions-on-binders` stuff + +use rustc_data_structures::transitive_relation::TransitiveRelationBuilder; +use rustc_type_ir::ClauseKind::*; +use rustc_type_ir::inherent::*; +use rustc_type_ir::outlives::{Component, push_outlives_components}; +use rustc_type_ir::region_constraint::{ + Assumptions, RegionConstraint, eagerly_handle_placeholders_in_universe, max_universe, +}; +use rustc_type_ir::{ + AliasTy, Binder, ClauseKind, InferCtxtLike, Interner, OutlivesPredicate, TypeVisitable, + TypeVisitableExt, TypeVisitor, UniverseIndex, +}; +use tracing::{debug, instrument}; + +use crate::delegate::SolverDelegate; +use crate::solve::{Certainty, EvalCtxt, Goal, NoSolution}; + +/// Logic for `-Zassumptions-on-binders` stuff +impl<'a, D, I> EvalCtxt<'a, D> +where + D: SolverDelegate, + I: Interner, +{ + /// Computes the assumptions associated with a binder for use in eagerly handling placeholders when + /// exiting the binder. Though, right now we do not actually handle placeholders when exiting binders, + /// instead we handle placeholders when computing the final response for the goal being computed. + #[instrument(level = "debug", skip(self), ret)] + pub(super) fn region_assumptions_for_placeholders_in_universe( + &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![Goal::new( + self.ecx.cx(), + self.param_env, + ClauseKind::WellFormed(t.into()), + )]) + .into_iter(), + ); + } + + fn visit_const(&mut self, c: I::Const) { + self.out.extend( + self.ecx + .well_formed_goals(self.param_env, c.into()) + .unwrap_or(vec![Goal::new( + self.ecx.cx(), + self.param_env, + ClauseKind::WellFormed(c.into()), + )]) + .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 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(-Zassumptions-on-binders): 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| 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); + } + TypeOutlives(p) => { + type_outlives.push(clause.kind().map_bound(|_| p)); + } + _ => (), + }, + ); + + Some(Assumptions::new(type_outlives, region_outlives_builder.freeze())) + } + + #[instrument(level = "debug", skip(self), ret)] + pub(super) fn eagerly_handle_placeholders(&mut self) -> Result { + let constraint = self.delegate.get_solver_region_constraint(); + + 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| { + eagerly_handle_placeholders_in_universe(&**self.delegate, constraint, u) + }); + + self.delegate.overwrite_solver_region_constraint(constraint.clone()); + + if constraint.is_false() { + Err(NoSolution) + } else if constraint.is_ambig() { + Ok(Certainty::AMBIGUOUS) + } else { + Ok(Certainty::Yes) + } + } + + /// Convert a type outlives constraint into a set of region outlives constraints and + /// type outlives constraints between the "components" of the type. E.g. `Foo: 'b` + /// will be turned into `T: 'b, 'a: 'b` + #[instrument(level = "debug", skip(self), ret)] + pub(in crate::solve) fn destructure_type_outlives( + &mut self, + ty: I::Ty, + r: I::Region, + ) -> RegionConstraint { + let mut components = Default::default(); + 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), + } + } + + /// Convert an alias outlives constraint into an OR constraint of any number of three + /// separate classes of candidates: + /// 1. component outlives. we turn `Alias: 'b` into `T: 'b, 'a: 'b`. + /// 2. item bounds. we turn `Alias: 'b` into `'c: 'b` if `Alias` is + /// defined as `type Alias: 'c` + /// 3. env assumptions. we defer handling `Alias: 'b` via where clauses until + /// when exiting the current binder. See [`RegionConstraint::AliasTyOutlivesViaEnv`]. + #[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::AliasTyOutlivesViaEnv(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, + ])) + } +} diff --git a/compiler/rustc_next_trait_solver/src/solve/mod.rs b/compiler/rustc_next_trait_solver/src/solve/mod.rs index aa06dd20c237f..695da28c1aa72 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -22,13 +22,9 @@ 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, FallibleTypeFolder, Interner, TermKind, TyVid, TypeFoldable, TypeSuperFoldable, - TypeVisitableExt, TypingMode, -}; +use rustc_type_ir::{self as ty, Interner, TyVid, TypingMode}; use tracing::instrument; pub use self::eval_ctxt::{ @@ -36,7 +32,6 @@ 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 @@ -64,13 +59,11 @@ 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() @@ -78,7 +71,6 @@ 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, @@ -100,17 +92,8 @@ where ) -> QueryResultOrRerunNonErased { let ty::OutlivesPredicate(ty, lt) = goal.predicate; - 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), - }; - + if self.assumptions_on_binders() { + // FIXME(-Zassumptions-on-binders): we need to normalize `ty` let constraint = self.destructure_type_outlives(ty, lt); self.register_solver_region_constraint(constraint); } else { @@ -127,7 +110,7 @@ where ) -> QueryResultOrRerunNonErased { let ty::OutlivesPredicate(a, b) = goal.predicate; - if self.higher_ranked_assumptions_v2() { + if self.assumptions_on_binders() { let constraint = rustc_type_ir::region_constraint::RegionConstraint::RegionOutlives(a, b); self.register_solver_region_constraint(constraint); diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index b6653f673574a..b30e79532969a 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2203,6 +2203,8 @@ options! { either `loaded` or `not-loaded`."), assume_incomplete_release: bool = (false, parse_bool, [TRACKED], "make cfg(version) treat the current version as incomplete (default: no)"), + assumptions_on_binders: bool = (false, parse_bool, [TRACKED], + "allow deducing higher-ranked outlives assumptions from all binders (`for<'a>`)"), autodiff: Vec = (Vec::new(), parse_autodiff, [TRACKED], "a list of autodiff flags to enable Mandatory setting: @@ -2355,8 +2357,6 @@ 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_trait_selection/src/error_reporting/infer/region.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/region.rs index 2e9dcae700d2a..902afd7c6c4bf 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/region.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/region.rs @@ -292,6 +292,13 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { RegionOriginNote::Plain { span, msg: msg!("...so that the where clause holds") } .add_to_diag(err); } + SubregionOrigin::SolverRegionConstraint(span) => { + RegionOriginNote::Plain { + span, + msg: msg!("this diagnostic is currently WIP while -Zassumptions-on-binders is incomplete"), + } + .add_to_diag(err); + } } } @@ -560,6 +567,14 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { notes: instantiated.into_iter().chain(must_outlive).collect(), }) } + SubregionOrigin::SolverRegionConstraint(span) => { + let mut d = self.dcx().struct_span_err( + span, + "unsatisfied lifetime constraint from -Zassumptions-on-binders :3", + ); + d.note("meoow :c"); + d + } }; if sub.is_error() || sup.is_error() { err.downgrade_to_delayed_bug(); diff --git a/compiler/rustc_trait_selection/src/regions.rs b/compiler/rustc_trait_selection/src/regions.rs index e2123ace5a95d..866be1e532661 100644 --- a/compiler/rustc_trait_selection/src/regions.rs +++ b/compiler/rustc_trait_selection/src/regions.rs @@ -5,6 +5,7 @@ use rustc_macros::extension; use rustc_middle::traits::ObligationCause; use rustc_middle::traits::query::NoSolution; use rustc_middle::ty::{self, Ty, Unnormalized, elaborate}; +use rustc_span::Span; use crate::traits::ScrubbedTraitError; use crate::traits::outlives_bounds::InferCtxtExt; @@ -85,34 +86,37 @@ impl<'tcx> InferCtxt<'tcx> { param_env: ty::ParamEnv<'tcx>, assumed_wf_tys: impl IntoIterator>, ) -> Vec> { - self.resolve_regions_with_outlives_env(&OutlivesEnvironment::new( - self, - body_id, - param_env, - assumed_wf_tys, - )) + self.resolve_regions_with_outlives_env( + &OutlivesEnvironment::new(self, body_id, param_env, assumed_wf_tys), + self.tcx.def_span(body_id), + ) } /// Don't call this directly unless you know what you're doing. fn resolve_regions_with_outlives_env( &self, outlives_env: &OutlivesEnvironment<'tcx>, + span: Span, ) -> Vec> { - self.resolve_regions_with_normalize(&outlives_env, |ty, origin| { - let ty = self.resolve_vars_if_possible(ty); + self.resolve_regions_with_normalize( + &outlives_env, + |ty, origin| { + let ty = self.resolve_vars_if_possible(ty); - if self.next_trait_solver() { - crate::solve::deeply_normalize( - self.at( - &ObligationCause::dummy_with_span(origin.span()), - outlives_env.param_env, - ), - Unnormalized::new_wip(ty), - ) - .map_err(|_: Vec>| NoSolution) - } else { - Ok(ty) - } - }) + if self.next_trait_solver() { + crate::solve::deeply_normalize( + self.at( + &ObligationCause::dummy_with_span(origin.span()), + outlives_env.param_env, + ), + Unnormalized::new_wip(ty), + ) + .map_err(|_: Vec>| NoSolution) + } else { + Ok(ty) + } + }, + span, + ) } } diff --git a/compiler/rustc_trait_selection/src/solve/delegate.rs b/compiler/rustc_trait_selection/src/solve/delegate.rs index ac3226b5a068f..f66fba02c414a 100644 --- a/compiler/rustc_trait_selection/src/solve/delegate.rs +++ b/compiler/rustc_trait_selection/src/solve/delegate.rs @@ -73,8 +73,8 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< goal: Goal<'tcx, ty::Predicate<'tcx>>, span: Span, ) -> Option { - let higher_ranked_assumptions_v2 = - self.tcx.sess.opts.unstable_opts.higher_ranked_assumptions_v2; + // FIXME(-Zassumptions-on-binders): actually handle fast path + let assumptions_on_binders = self.tcx.sess.opts.unstable_opts.assumptions_on_binders; let pred = goal.predicate.kind(); match pred.skip_binder() { ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred)) => { @@ -124,7 +124,9 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< ty::PredicateKind::DynCompatible(def_id) if self.0.tcx.is_dyn_compatible(def_id) => { Some(Certainty::Yes) } - ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(outlives)) if !higher_ranked_assumptions_v2 => { + ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(outlives)) + if !assumptions_on_binders => + { if outlives.has_escaping_bound_vars() { return None; } @@ -137,7 +139,9 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< ); Some(Certainty::Yes) } - ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(outlives)) if !higher_ranked_assumptions_v2 => { + ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(outlives)) + if !assumptions_on_binders => + { if outlives.has_escaping_bound_vars() { return None; } diff --git a/compiler/rustc_trait_selection/src/traits/auto_trait.rs b/compiler/rustc_trait_selection/src/traits/auto_trait.rs index ef99b7772d6be..646945990a1d5 100644 --- a/compiler/rustc_trait_selection/src/traits/auto_trait.rs +++ b/compiler/rustc_trait_selection/src/traits/auto_trait.rs @@ -9,6 +9,7 @@ use rustc_data_structures::unord::UnordSet; use rustc_hir::def_id::CRATE_DEF_ID; use rustc_infer::infer::DefineOpaqueTypes; use rustc_middle::ty::{Region, RegionVid}; +use rustc_span::DUMMY_SP; use tracing::debug; use super::*; @@ -162,7 +163,8 @@ impl<'tcx> AutoTraitFinder<'tcx> { } let outlives_env = OutlivesEnvironment::new(&infcx, CRATE_DEF_ID, full_env, []); - let _ = infcx.process_registered_region_obligations(&outlives_env, |ty, _| Ok(ty)); + let _ = + infcx.process_registered_region_obligations(&outlives_env, |ty, _| Ok(ty), DUMMY_SP); let region_data = infcx.inner.borrow_mut().unwrap_region_constraints().data().clone(); diff --git a/compiler/rustc_type_ir/src/infer_ctxt.rs b/compiler/rustc_type_ir/src/infer_ctxt.rs index ad0cbe51f37ea..9b33b940ec8e8 100644 --- a/compiler/rustc_type_ir/src/infer_ctxt.rs +++ b/compiler/rustc_type_ir/src/infer_ctxt.rs @@ -338,22 +338,26 @@ pub trait InferCtxtLike: Sized { fn universe(&self) -> ty::UniverseIndex; fn create_next_universe(&self) -> ty::UniverseIndex; - fn higher_ranked_assumptions_v2(&self) -> bool { + fn assumptions_on_binders(&self) -> bool { false } - fn insert_universe_assumptions( + fn insert_placeholder_assumptions( &self, u: ty::UniverseIndex, assumptions: Option>, ); - fn get_universe_assumptions( + fn get_placeholder_assumptions( &self, u: ty::UniverseIndex, ) -> Option>; - fn get_solve_region_constraint( + fn get_solver_region_constraint( &self, ) -> crate::region_constraint::RegionConstraint; + fn overwrite_solver_region_constraint( + &self, + constraint: crate::region_constraint::RegionConstraint, + ); fn universe_of_ty(&self, ty: ty::TyVid) -> Option; fn universe_of_lt(&self, lt: ty::RegionVid) -> Option; diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index 0e182518faa41..09def0212a153 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -285,6 +285,8 @@ pub trait Interner: type Features: Features; fn features(self) -> Self::Features; + fn assumptions_on_binders(self) -> bool; + fn coroutine_hidden_types( self, def_id: Self::CoroutineId, diff --git a/compiler/rustc_type_ir/src/outlives.rs b/compiler/rustc_type_ir/src/outlives.rs index 452c78e314e4d..2a1cbc3575d85 100644 --- a/compiler/rustc_type_ir/src/outlives.rs +++ b/compiler/rustc_type_ir/src/outlives.rs @@ -247,10 +247,10 @@ pub fn compute_alias_components_recursive( /// } /// ``` /// -/// If we were given >::Bar`, we would return +/// 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`) +/// ```ignore (this does compile today, previously was marked as compile_fail,E0311) /// trait Foo<'a, 'b> /// where for<'x> >::Bar: 'x /// { diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 870089829a856..2c5127d5c11f9 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -1,7 +1,9 @@ +//! The bulk of the logic for implementing `-Zassumptions-on-binders` + use derive_where::derive_where; use indexmap::IndexSet; #[cfg(feature = "nightly")] -use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; +use rustc_data_structures::stable_hasher::{StableHash, StableHashCtxt, StableHasher}; use rustc_data_structures::transitive_relation::{TransitiveRelation, TransitiveRelationBuilder}; use tracing::{debug, instrument}; @@ -55,57 +57,71 @@ impl Assumptions { pub enum RegionConstraint { Ambiguity, RegionOutlives(I::Region, I::Region), - AliasTyOutlivesFromEnv(Binder, I::Region)>), + /// Requirement that a (potentially higher ranked) alias outlives some (potentially higher ranked) + /// region due to an assumption in the environment. This cannot be satisfied via component outlives + /// or item bounds. + /// + /// We cannot eagerly look at assumptions as we are usually working with an incomplete set of assumptions + /// and there may wind up being assumptions we can use to prove this when we're in a smaller universe. + /// + /// We eagerly destructure alias outlives requirements into region outlives requirements corresponding to + /// component outlives & item bound outlives rules, leaving only param env candidates. + AliasTyOutlivesViaEnv(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`. + /// + /// We cannot eagerly look at assumptions as we are usually working with an incomplete set of assumptions + /// and there may wind up being assumptions we can use to prove this when we're in a smaller universe. PlaceholderTyOutlives(I::Ty, I::Region), And(Box<[RegionConstraint]>), Or(Box<[RegionConstraint]>), } +// This is not a derived impl because a perfect derive leads to inductive +// cycle causing the trait to never actually be implemented #[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 +impl StableHash for RegionConstraint where - I::Region: HashStable, - AliasTy: HashStable, - I::Ty: HashStable, - I::BoundVarKinds: HashStable, + I::Region: StableHash, + I::Ty: StableHash, + I::GenericArgs: StableHash, + I::TraitAssocTyId: StableHash, + I::InherentAssocTyId: StableHash, + I::OpaqueTyId: StableHash, + I::FreeTyAliasId: StableHash, + I::BoundVarKinds: StableHash, { #[inline] - fn hash_stable(&self, hcx: &mut CTX, hasher: &mut StableHasher) { + fn stable_hash(&self, hcx: &mut CTX, hasher: &mut StableHasher) { use RegionConstraint::*; - std::mem::discriminant(self).hash_stable(hcx, hasher); + std::mem::discriminant(self).stable_hash(hcx, hasher); match self { Ambiguity => (), RegionOutlives(a, b) => { - a.hash_stable(hcx, hasher); - b.hash_stable(hcx, hasher); + a.stable_hash(hcx, hasher); + b.stable_hash(hcx, hasher); } - AliasTyOutlivesFromEnv(outlives) => { - outlives.hash_stable(hcx, hasher); + AliasTyOutlivesViaEnv(outlives) => { + outlives.stable_hash(hcx, hasher); } PlaceholderTyOutlives(a, b) => { - a.hash_stable(hcx, hasher); - b.hash_stable(hcx, hasher); + a.stable_hash(hcx, hasher); + b.stable_hash(hcx, hasher); } And(and) => { for a in and.iter() { - a.hash_stable(hcx, hasher); + a.stable_hash(hcx, hasher); } } Or(or) => { for a in or.iter() { - a.hash_stable(hcx, hasher); + a.stable_hash(hcx, hasher); } } } @@ -118,7 +134,7 @@ impl TypeFoldable for 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)?), + AliasTyOutlivesViaEnv(outlives) => AliasTyOutlivesViaEnv(outlives.try_fold_with(f)?), PlaceholderTyOutlives(a, b) => { PlaceholderTyOutlives(a.try_fold_with(f)?, b.try_fold_with(f)?) } @@ -144,7 +160,7 @@ impl TypeFoldable for RegionConstraint { match self { Ambiguity => self, RegionOutlives(a, b) => RegionOutlives(a.fold_with(f), b.fold_with(f)), - AliasTyOutlivesFromEnv(outlives) => AliasTyOutlivesFromEnv(outlives.fold_with(f)), + AliasTyOutlivesViaEnv(outlives) => AliasTyOutlivesViaEnv(outlives.fold_with(f)), PlaceholderTyOutlives(a, b) => PlaceholderTyOutlives(a.fold_with(f), b.fold_with(f)), And(and) => { let mut new_and = Vec::new(); @@ -180,7 +196,7 @@ impl TypeVisitable for RegionConstraint { return F::Result::from_branch(b); }; } - AliasTyOutlivesFromEnv(outlives) => { + AliasTyOutlivesViaEnv(outlives) => { return outlives.visit_with(f); } PlaceholderTyOutlives(a, b) => { @@ -244,6 +260,24 @@ impl RegionConstraint { matches!(self, Self::Or(_)) } + pub fn unwrap_or(self) -> Box<[RegionConstraint]> { + match self { + Self::Or(ors) => ors, + _ => panic!("`unwrap_or` on non-Or: {self:?}"), + } + } + + pub fn unwrap_and(self) -> Box<[RegionConstraint]> { + match self { + Self::And(ands) => ands, + _ => panic!("`unwrap_and` on non-And: {self:?}"), + } + } + + pub fn is_and(&self) -> bool { + matches!(self, Self::And(_)) + } + pub fn is_ambig(&self) -> bool { matches!(self, Self::Ambiguity) } @@ -264,87 +298,85 @@ impl RegionConstraint { } } + /// Converts the region constraint into an ORs of ANDs of "leaf" constraints. Where + /// a leaf constraint is a non-or/and constraint. #[instrument(level = "debug", ret)] pub fn canonical_form(self) -> Self { use RegionConstraint::*; - fn permutations(ors: &[Vec>]) -> Vec> { + 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())); - } + [] => vec![vec![]], + [or1] => { + let mut choices = vec![]; + for choice in or1 { + choices.push(vec![choice.clone()]); } - - permutations + choices } - [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) + [or1, rest_ors @ ..] => { + let mut choices = vec![]; + for choice in or1 { + choices.extend(permutations(rest_ors).into_iter().map(|mut and| { + and.push(choice.clone()); + and + })); + } + choices } } } 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); - } - } + // AND of OR of AND of LEAFs + // + // We can turn `AND of OR of X` into `OR of AND of X` by enumerating every set of choices + // for the list of ORs. For example if we have `AND ( OR(A, B), OR(C, D) )` we can convert this into + // `OR ( AND (A, C), AND (A, D), AND (B, C), AND (B, D ))` + // + // if A/B/C/D are all in canonical forms then we wind up with an `OR of AND of AND of LEAFs` which + // is trivially canonicalizeable by flattening the multiple layers of AND into one. + let ors = ands + .into_iter() + .map(|c| c.canonical_form().unwrap_or().to_vec()) + .collect::>(); + debug!(?ors); + let or_permutations = permutations(&ors); + debug!(?or_permutations); - 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(or_permutations + .into_iter() + .map(|c| { + And(c + .into_iter() + .flat_map(|c2| c2.unwrap_and().into_iter()) + .collect::>() + .into_boxed_slice()) + }) + .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()) - } + // OR of OR of AND of LEAFs + // + // trivially canonicalizeable by concatenating all of the ORs into one big OR + Or(ors + .into_iter() + .flat_map(|c| c.canonical_form().unwrap_or().into_iter()) + .collect::>() + .into_boxed_slice()) } - _ => self, + _ => Or(Box::new([And(Box::new([self]))])), }; - assert!(canonical.is_canonical_form()); + assert!( + canonical.is_canonical_form(), + "non canonical form region constraint: {:?}", + canonical + ); canonical } @@ -353,40 +385,34 @@ impl RegionConstraint { match self { Ambiguity | RegionOutlives(..) - | AliasTyOutlivesFromEnv(..) + | AliasTyOutlivesViaEnv(..) | PlaceholderTyOutlives(..) => true, And(..) | Or(..) => false, } } - fn is_and_of_leaf_constraints(&self) -> bool { + fn is_canonical_and(&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(), - } + if let Self::Or(ors) = self { ors.iter().all(|c| c.is_canonical_and()) } else { false } } } +/// Takes any constraints involving placeholders from the current universe and eagerly checks them. +/// This can be done a few ways: +/// - There's an assumption on the binder introducing the placeholder which means the constraint is satisfied (true) +/// - There's assumptions on the binder introducing the placeholder which allow us to rewrite the constraint in +/// terms of lower universe variables. For example given `for<'a> where('b: 'a) { prove(T: '!a_u1) }` we can +/// convert this constraint to `T: 'b` which no longer references anything from `u1`. +/// - There are no relevant assumptions so we can neither rewrite the constraint nor consider it satisfied (false) +/// - We failed to compute the full set of assumptions when entering the binder corresponding to `u`. (ambiguity) +/// +/// After handling all of the region constraints in `u` we then evaluate the entire constraint as much as possible, +/// propagating true/false/ambiguity as close to the root of the constraint as we can. The returned constraint should +/// be checked for whether it is true/false/ambiguous as that should affect the result of whatever operation required +/// entering the binder corresponding to `u`. #[instrument(level = "debug", skip(infcx), ret)] pub fn eagerly_handle_placeholders_in_universe, I: Interner>( infcx: &Infcx, @@ -395,11 +421,18 @@ pub fn eagerly_handle_placeholders_in_universe RegionConstraint { use RegionConstraint::*; - let assumptions = infcx.get_universe_assumptions(u); + let assumptions = infcx.get_placeholder_assumptions(u); - // 1. rewrite type outlives constraints - let constraint = - destructure_type_outlives_constraints_in_universe(infcx, constraint, Some(u), &assumptions); + // 1. rewrite type outlives constraints involving things from `u` into either region constraints + // involving things from `u` or type outlives constraints not involving things from `u` + // + // IOW, we only want to encounter things from `u` as part of region out lives constraints. + let constraint = rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling( + infcx, + constraint, + u, + &assumptions, + ); // 2. rewrite the constraint into a canonical ORs of ANDs form let constraint = constraint.canonical_form(); @@ -409,36 +442,31 @@ pub fn eagerly_handle_placeholders_in_universe { - 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() - } - }; + let constraint = Or(constraint + .unwrap_or() + .into_iter() + .map(|c| { + let and = + And(compute_new_region_constraints(infcx, &c.unwrap_and(), u).into_boxed_slice()); - // 4. rewrite region outlives constraints (potentially to false/true) - let constraint = pull_region_constraint_out_of_universe(infcx, constraint, u, &assumptions); + // 4. rewrite region outlives constraints (potentially to false/true) + pull_region_outlives_constraints_out_of_universe(infcx, and, u, &assumptions) + }) + .collect::>() + .into_boxed_slice()); - // 5. actually evalaute the constraint to eagerly error on false + // 5. actually evaluate the constraint to eagerly error on false evaluate_solver_constraint(&constraint) } +/// Filter our region constraints to not include constraints between region variables from `u` and +/// other regions as those are always satisfied. This requires some care to handle correctly for example: +/// `'!a_u1: '?x_u1: '!b_u1` should result in us requiring `'!a_u1: '!b_u1` rather than dropping the two +/// constraints entirely. +/// +/// The only constraints involving things from `u` should be region outlives constraints at this point. Type +/// outlives constraints should have been handled already either by destructuring into region outlives or by +/// being rewritten in terms of smaller universe variables. #[instrument(level = "debug", skip(infcx), ret)] fn compute_new_region_constraints, I: Interner>( infcx: &Infcx, @@ -454,7 +482,7 @@ fn compute_new_region_constraints, I: Interne for c in constraints { match c { And(..) | Or(..) => unreachable!(), - Ambiguity | PlaceholderTyOutlives(..) | AliasTyOutlivesFromEnv(..) => { + Ambiguity | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) => { new_constraints.push(c.clone()) } RegionOutlives(r1, r2) => { @@ -476,8 +504,8 @@ fn compute_new_region_constraints, I: Interne | RegionKind::RePlaceholder(..) | RegionKind::ReStatic => true, RegionKind::ReVar(..) => max_universe(infcx, r) < u, - RegionKind::ReError(..) | RegionKind::ReErased => false, - RegionKind::ReBound(..) => unreachable!(), + RegionKind::ReError(..) => false, + RegionKind::ReErased | RegionKind::ReBound(..) => unreachable!(), }; if is_placeholder_like(*r) && is_placeholder_like(*ub) { @@ -489,71 +517,56 @@ fn compute_new_region_constraints, I: Interne new_constraints } -#[derive(Copy, Clone, Debug)] -enum Certainty { - Yes, - Ambig, -} - +/// Evaluate ANDs and ORs to true/false/ambiguous based on whether their arguments are true/false/ambiguous #[instrument(level = "debug", ret)] pub fn evaluate_solver_constraint( constraint: &RegionConstraint, ) -> RegionConstraint { use RegionConstraint::*; match constraint { - Ambiguity | RegionOutlives(..) | AliasTyOutlivesFromEnv(..) | PlaceholderTyOutlives(..) => { + Ambiguity | RegionOutlives(..) | AliasTyOutlivesViaEnv(..) | PlaceholderTyOutlives(..) => { constraint.clone() } And(and) => { let mut and_constraints = Vec::new(); - let mut certainty = Certainty::Yes; + let mut is_ambiguous_constraint = false; 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; + return RegionConstraint::new_false(); + } else if evaluated_constraint.is_ambig() { + is_ambiguous_constraint = true; } else { - if evaluated_constraint.is_ambig() { - certainty = Certainty::Ambig; - } and_constraints.push(evaluated_constraint); } } - if let Certainty::Ambig = certainty { + if is_ambiguous_constraint { 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; + let mut is_ambiguous_constraint = false; 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; + return RegionConstraint::new_true(); + } else if evaluated_constraint.is_ambig() { + is_ambiguous_constraint = true; } else { - if evaluated_constraint.is_ambig() { - certainty = Certainty::Ambig; - } or_constraints.push(evaluated_constraint); } } - if let Certainty::Ambig = certainty { + if is_ambiguous_constraint { RegionConstraint::Ambiguity - } else if or_constraints.len() == 1 { - or_constraints.pop().unwrap() } else { RegionConstraint::Or(or_constraints.into_boxed_slice()) } @@ -561,8 +574,31 @@ pub fn evaluate_solver_constraint( } } +/// Handles converting region outlives constraints involving placeholders from `u` into OR constraints +/// involving regions from smaller universes with known relationships to the placeholder. For example: +/// ```ignore (not rust) +/// for<'a, 'b> where( +/// 'c: 'b, 'd: 'b, +/// 'a: 'e, 'a: 'f, +/// ) { +/// 'a_u1: 'b_u1 +/// } +/// ``` +/// will get converted to: +/// ```ignore (not rust) +/// OR( +/// 'e: 'c, +/// 'e: 'd, +/// 'f: 'c, +/// 'f: 'd, +/// ) +/// ``` +/// if we are handling constraints in `u1`. #[instrument(level = "debug", skip(infcx), ret)] -fn pull_region_constraint_out_of_universe, I: Interner>( +fn pull_region_outlives_constraints_out_of_universe< + Infcx: InferCtxtLike, + I: Interner, +>( infcx: &Infcx, constraint: RegionConstraint, u: UniverseIndex, @@ -570,13 +606,16 @@ fn pull_region_constraint_out_of_universe, I: ) -> RegionConstraint { assert!(max_universe(infcx, constraint.clone()) <= u); - // FIXME(-Zhigher-ranked-assumptions-v2): we don't lower universes of region variables when exiting `u` + // FIXME(-Zassumptions-on-binders): 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. + // + // I'm not even sure this would be necessary given we filter out region constraints involving regions# + // from the current universe and only retain those between placeholders. use RegionConstraint::*; match constraint { - Ambiguity | PlaceholderTyOutlives(..) | AliasTyOutlivesFromEnv(..) => { + Ambiguity | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) => { assert!(max_universe(infcx, constraint.clone()) < u); constraint } @@ -593,72 +632,101 @@ fn pull_region_constraint_out_of_universe, I: None => return RegionConstraint::Ambiguity, }; - if regions_outliving::(region_2, assumptions, infcx.cx()) - .find(|r| *r == region_1) - .is_some() + let mut candidates = vec![]; + for ub in + regions_outlived_by(region_1, assumptions).filter(|r| max_universe(infcx, *r) < u) { - return RegionConstraint::new_true(); + // FIXME(-Zassumptions-on-binders): if `region_2` is in a smaller universe there'll be both + // `'region_2` and `'static` as lower bounds which seems... unfortunate and may cause us to + // add a bunch of duplicate `'ub: 'static` candidates the more binders we leave. + for lb in regions_outliving(region_2, assumptions, infcx.cx()) + .filter(|r| max_universe(infcx, *r) < u) + { + // As long as any region outlived by `region_1` outlives any region region which + // `region_2` outlives, we know that `region_1: region_2` holds. In other words, + // there exists some set of 4 regions for which `'r1: 'i1` `'i1: 'i2` `'i2: 'r2` + candidates.push(RegionOutlives(ub, lb)); + } } - 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(); - } + RegionConstraint::Or(candidates.into_boxed_slice()) + } + And(constraints) => And(constraints + .into_iter() + .map(|constraint| { + pull_region_outlives_constraints_out_of_universe(infcx, constraint, u, assumptions) + }) + .collect()), + Or(_) => unreachable!(), + } +} - // `'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(); - } +/// Converts type outlives constraints into region outlives constraints. This assumes the *complete* set of +/// assumptions are known. This should not be called until the end of type checking. +/// +/// The returned region constraint will not have *any* PlaceholderTyOutlives or AliasTyOutlivesViaEnv constraints. +pub fn destructure_type_outlives_constraints_in_root< + Infcx: InferCtxtLike, + I: Interner, +>( + infcx: &Infcx, + constraint: RegionConstraint, + assumptions: &Assumptions, +) -> RegionConstraint { + use RegionConstraint::*; - RegionConstraint::Or(constraints.into_boxed_slice()) + match constraint { + Ambiguity | RegionOutlives(..) => constraint, + PlaceholderTyOutlives(ty, r) => { + Or(regions_outlived_by_placeholder(ty, assumptions, infcx.cx()) + .map(move |assumption_r| RegionOutlives(assumption_r, r)) + .collect::>() + .into_boxed_slice()) + } + AliasTyOutlivesViaEnv(bound_outlives) => { + alias_outlives_candidates_from_assumptions(infcx, bound_outlives, assumptions) } And(constraints) => And(constraints .into_iter() .map(|constraint| { - pull_region_constraint_out_of_universe(infcx, constraint, u, assumptions) + destructure_type_outlives_constraints_in_root(infcx, constraint, assumptions) }) .collect()), Or(constraints) => Or(constraints .into_iter() .map(|constraint| { - pull_region_constraint_out_of_universe(infcx, constraint, u, assumptions) + destructure_type_outlives_constraints_in_root(infcx, constraint, assumptions) }) .collect()), } } +/// Converts type outlives constraints into either region outlives constraints, or type outlives +/// constraints which do not contain anything from `u`. +/// +/// This only works off assumptions associated with the binder corresponding to `u` both for +/// perf reasons and because the full set of region assumptions is not known during type checking +/// due to closure signature inference. +/// +/// This only really causes problems for higher-ranked outlives assumptions, for example if we have +/// `where for<'a> >::Assoc: 'b` then we can't use that to prove `>::Assoc: 'b` +/// until we are in the root context. See comments inside this function for more detail. #[instrument(level = "debug", skip(infcx), ret)] -pub fn destructure_type_outlives_constraints_in_universe< +fn rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling< Infcx: InferCtxtLike, I: Interner, >( infcx: &Infcx, constraint: RegionConstraint, - u: Option, + u: UniverseIndex, 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 - ); - } + assert!( + max_universe(infcx, constraint.clone()) <= u, + "constraint {:?} contains terms from a larger universe than {:?}", + constraint.clone(), + u + ); use RegionConstraint::*; match constraint { @@ -667,75 +735,40 @@ pub fn destructure_type_outlives_constraints_in_universe< 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) { + if region_u != u && ty_u != u { return constraint; } let assumptions = match assumptions { Some(assumptions) => assumptions, - None => return RegionConstraint::Ambiguity, + None => return 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(); + let mut candidates = vec![]; + + // There could be `!T: 'region` assumptions in the env even if `!T` is in a + // smaller universe + candidates.extend( + regions_outlived_by_placeholder(ty, assumptions, infcx.cx()) + .map(move |assumption_r| RegionOutlives(assumption_r, region)), + ); + + // We can express `!T: 'region` as `!T: 'r` where `'r: 'region`. This is only necessary + // if the placeholder type is in a smaller universe as otherwise we know all regions which + // the placeholder outlives and can just destructure into an OR of RegionOutlives. + if region_u == u && ty_u < u { + candidates.extend( + regions_outliving::(region, assumptions, infcx.cx()) + .filter(|r| max_universe(infcx, *r) < u) + .map(|r| PlaceholderTyOutlives(ty, r)), + ); } - RegionConstraint::Or(candidates.into_boxed_slice()) + Or(candidates.into_boxed_slice()) } - AliasTyOutlivesFromEnv(bound_outlives) => { + AliasTyOutlivesViaEnv(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 @@ -751,9 +784,7 @@ pub fn destructure_type_outlives_constraints_in_universe< // 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 - { + if max_universe(infcx, bound_outlives) == u { let mut replacer = PlaceholderReplacer { cx: infcx.cx(), existing_var_count: bound_outlives.bound_vars().len(), @@ -771,60 +802,66 @@ pub fn destructure_type_outlives_constraints_in_universe< escaping_outlives, I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), ); - candidates.push(RegionConstraint::AliasTyOutlivesFromEnv(bound_outlives)); + candidates.push(RegionConstraint::AliasTyOutlivesViaEnv(bound_outlives)); } + let assumptions = match assumptions { + Some(assumptions) => assumptions, + None => { + candidates.push(Ambiguity); + return Or(candidates.into_boxed_slice()); + } + }; + + // Actually look at the assumptions and matching our higher ranked alias outlives goal + // against potentially higher ranked type outlives assumptions. + candidates.push(alias_outlives_candidates_from_assumptions( + infcx, + bound_outlives, + assumptions, + )); + // 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), + if max_universe(infcx, escaping_r) == u { + let mut replacer = PlaceholderReplacer { + cx: infcx.cx(), + existing_var_count: bound_outlives.bound_vars().len(), + bound_vars: IndexMap::default(), + universe: u, + current_index: DebruijnIndex::ZERO, }; + let escaping_alias = escaping_alias.fold_with(&mut replacer); + let bound_vars = bound_outlives.bound_vars().iter().chain( + core::mem::take(&mut replacer.bound_vars) + .into_iter() + .map(|(_, bound_region)| BoundVariableKind::Region(bound_region.kind)), + ); + let bound_alias = Binder::bind_with_vars( + escaping_alias, + I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), + ); + + // while we did skip the binder, bound vars aren't in any universe so + // this can't be an escaping bound var + candidates.extend( + regions_outliving(escaping_r, assumptions, infcx.cx()) + .filter(|r2| max_universe(infcx, *r2) < u) + .map(|r2| AliasTyOutlivesViaEnv(bound_alias.map_bound(|alias| (alias, r2)))) + .collect::>(), + ); } // 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() { + // let's be conservative and not let alias outlives' cause NoSolution + // in coherence + match infcx.typing_mode_raw() { TypingMode::Coherence => candidates.push(RegionConstraint::Ambiguity), TypingMode::Analysis { .. } + | TypingMode::ErasedNotCoherence { .. } | TypingMode::Borrowck { .. } | TypingMode::PostBorrowckAnalysis { .. } | TypingMode::PostAnalysis => (), @@ -835,38 +872,56 @@ pub fn destructure_type_outlives_constraints_in_universe< And(constraints) => And(constraints .into_iter() .map(|constraint| { - destructure_type_outlives_constraints_in_universe(infcx, constraint, u, assumptions) + rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling( + infcx, + constraint, + u, + assumptions, + ) }) .collect()), Or(constraints) => Or(constraints .into_iter() .map(|constraint| { - destructure_type_outlives_constraints_in_universe(infcx, constraint, u, assumptions) + rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling( + infcx, + constraint, + u, + assumptions, + ) }) .collect()), } } +/// Returns all regions `r2` for which `r: r2` is known to hold in +/// the universe associated with `assumptions` pub fn regions_outlived_by( r: I::Region, assumptions: &Assumptions, ) -> impl Iterator { - assumptions.region_outlives.reachable_from(r).into_iter() + // FIXME(-Zassumptions-on-binders): do we need to be adding the reflexive edge here? + assumptions.region_outlives.reachable_from(r).into_iter().chain([r]) } +/// Returns all regions `r2` for which `r2: r` is known to hold in +/// the universe associated with `assumptions` 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)]) + // FIXME(-Zassumptions-on-binders): 'static may have been an input region canonicalized to something else is that important? + // FIXME(-Zassumptions-on-binders): do we need to adding the reflexive edge here? + .chain([r, I::Region::new_static(cx)]) } +/// Returns all regions `r` for which `!t: r` is known to hold in +/// the universe associated with `assumptions` pub fn regions_outlived_by_placeholder( t: I::Ty, assumptions: &Assumptions, @@ -883,6 +938,7 @@ pub fn regions_outlived_by_placeholder( }) } +/// The largest universe a variable or placeholder was from in `t` pub fn max_universe, I: Interner, T: TypeVisitable>( infcx: &Infcx, t: T, @@ -892,6 +948,8 @@ pub fn max_universe, I: Interner, T: TypeVisi visitor.max_universe() } +// FIXME(-Zassumptions-on-binders): Share this with the visitor used by generalization. We currently don't +// as generalization does not look at universes of inference variables but we do struct MaxUniverse<'a, Infcx: InferCtxtLike> { max_universe: UniverseIndex, infcx: &'a Infcx, @@ -910,43 +968,41 @@ impl<'a, Infcx: InferCtxtLike> MaxUniverse<'a, Infcx> { 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); - } + type Result = (); - 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) + fn visit_ty(&mut self, t: I::Ty) { + match t.kind() { + TyKind::Placeholder(p) => self.max_universe = self.max_universe.max(p.universe), + TyKind::Infer(InferTy::TyVar(inf)) => { + 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), } - - 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) + match c.kind() { + ConstKind::Placeholder(p) => self.max_universe = self.max_universe.max(p.universe), + ConstKind::Infer(rustc_type_ir::InferConst::Var(inf)) => { + 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), } - - 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) + match r.kind() { + RegionKind::RePlaceholder(p) => self.max_universe = self.max_universe.max(p.universe), + RegionKind::ReVar(var) => { + let u = self.infcx.universe_of_lt(var).unwrap(); + debug!("var {var:?} in universe {u:?}"); + self.max_universe = self.max_universe.max(u); + } + _ => (), } } } @@ -974,6 +1030,7 @@ impl TypeFolder for PlaceholderReplacer { }); I::Region::new_bound(self.cx, self.current_index, *mapped_var) } + // FIXME(-Zassumptions-on-binders): We should be handling region variables here somehow _ => r, } } @@ -986,8 +1043,12 @@ impl TypeFolder for PlaceholderReplacer { } } +/// Converts an `AliasTyOutlivesViaEnv` constraint into an OR of region outlives constraints by +/// matching the alias against any `Alias: 'a` assumptions. This is somewhat tricky as we have a +/// potentially higher ranked alias being equated with a potentially higher ranked assumption and +/// we don't handle it correctly right now (though it is a somewhat reasonable halfway step). #[instrument(level = "debug", skip(infcx), ret)] -pub fn alias_outlives_candidate_requirement, I: Interner>( +fn alias_outlives_candidates_from_assumptions, I: Interner>( infcx: &Infcx, bound_outlives: Binder, I::Region)>, assumptions: &Assumptions, @@ -996,9 +1057,10 @@ pub fn alias_outlives_candidate_requirement, let prev_universe = infcx.universe(); + // FIXME(-Zassumptions-on-binders): Handle the assumptions on this binder infcx.enter_forall(bound_outlives, |(alias, r)| { let u = infcx.universe(); - infcx.insert_universe_assumptions(u, Some(Assumptions::empty())); + infcx.insert_placeholder_assumptions(u, Some(Assumptions::empty())); for bound_type_outlives in assumptions.type_outlives.iter() { let OutlivesPredicate(alias2, r2) = @@ -1061,7 +1123,7 @@ impl<'a, Infcx: InferCtxtLike, I: Interner> TypeRelation a: T, b: T, ) -> RelateResult { - // FIXME(-Zhigher-ranked-assumptions-v2): bivariance is important for opaque type args so + // FIXME(-Zassumptions-on-binders): bivariance is important for opaque type args so // we should actually handle variance in some way here. self.relate(a, b) } @@ -1088,14 +1150,14 @@ impl<'a, Infcx: InferCtxtLike, I: Interner> TypeRelation { self.infcx.enter_forall(a, |a| { let u = self.infcx.universe(); - self.infcx.insert_universe_assumptions(u, Some(Assumptions::empty())); + self.infcx.insert_placeholder_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())); + self.infcx.insert_placeholder_assumptions(u, Some(Assumptions::empty())); let a = self.infcx.instantiate_binder_with_infer(a); self.relate(a, b) })?; diff --git a/compiler/rustc_type_ir/src/solve/mod.rs b/compiler/rustc_type_ir/src/solve/mod.rs index 3f33ca45bc502..f2ea2e5a729e8 100644 --- a/compiler/rustc_type_ir/src/solve/mod.rs +++ b/compiler/rustc_type_ir/src/solve/mod.rs @@ -579,13 +579,32 @@ pub struct Response { impl Eq for Response {} +#[derive_where(Clone, Hash, PartialEq, Debug; I: Interner)] +#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] +#[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] +pub enum ExternalRegionConstraints { + /// normal region constraints used on stable/when -Znext-solver is used by itself + Old(Vec<(ty::RegionConstraint, VisibleForLeakCheck)>), + /// new form of region constraints used when `-Zassumptions-on-binders` is enabled. + /// supports ORs. + NextGen(RegionConstraint), +} + +impl ExternalRegionConstraints { + pub fn is_empty(&self) -> bool { + match self { + Self::Old(r) => r.is_empty(), + Self::NextGen(r) => r.is_true(), + } + } +} + /// Additional constraints returned on success. -#[derive_where(Clone, Hash, PartialEq, Debug, Default; I: Interner)] +#[derive_where(Clone, Hash, PartialEq, Debug; I: Interner)] #[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] #[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] pub struct ExternalConstraintsData { - pub region_constraints: Vec<(ty::RegionConstraint, VisibleForLeakCheck)>, - pub solver_region_constraint: RegionConstraint, + pub region_constraints: ExternalRegionConstraints, pub opaque_types: Vec<(ty::OpaqueTypeKey, I::Ty)>, pub normalization_nested_goals: NestedNormalizationGoals, } @@ -593,15 +612,26 @@ pub struct ExternalConstraintsData { impl Eq for ExternalConstraintsData {} impl ExternalConstraintsData { + pub fn new(cx: I) -> Self { + let region_constraints = match cx.assumptions_on_binders() { + true => ExternalRegionConstraints::NextGen(RegionConstraint::new_true()), + false => ExternalRegionConstraints::Old(vec![]), + }; + + Self { + region_constraints, + opaque_types: vec![], + normalization_nested_goals: NestedNormalizationGoals::default(), + } + } + pub fn is_empty(&self) -> bool { let ExternalConstraintsData { - solver_region_constraint, region_constraints, opaque_types, normalization_nested_goals, } = self; - solver_region_constraint.is_true() - && region_constraints.is_empty() + region_constraints.is_empty() && opaque_types.is_empty() && normalization_nested_goals.is_empty() } diff --git a/tests/ui/README.md b/tests/ui/README.md index 2fe1657e7ecf2..cb3e9d260bf9f 100644 --- a/tests/ui/README.md +++ b/tests/ui/README.md @@ -109,6 +109,10 @@ Tests focused on associated types. If the associated type is not in a trait defi See [Associated Types | Reference](https://doc.rust-lang.org/reference/items/associated-items.html#associated-types). +## `tests/ui/assumptions_on_binders`: -Zassumptions-on-binders + +Tests focused on the -Zassumptions-on-binders flag. + ## `tests/ui/async-await`: Async/Await Tests for the async/await related features. E.g. async functions, await expressions, and their interaction with other language features. diff --git a/tests/ui/assumptions_on_binders/alias_outlives.rs b/tests/ui/assumptions_on_binders/alias_outlives.rs new file mode 100644 index 0000000000000..c234773fb10d8 --- /dev/null +++ b/tests/ui/assumptions_on_binders/alias_outlives.rs @@ -0,0 +1,42 @@ +//@ compile-flags: -Znext-solver -Zassumptions-on-binders + +// test that a `::Assoc: '!a_u1` constraint is considered to be satisfied +// if there's a `T::Assoc: 'static` assumption in the root universe and if not that it is +// an error :) + +#![feature(generic_const_items)] + +trait AliasHaver { + type Assoc; +} + +trait Trait<'a> {} +impl<'a, T: 'a> Trait<'a> for T {} + +struct ReqTrait Trait<'a>>(T); + +fn borrowck_env_pass<'a, T: AliasHaver>() +where + ::Assoc: 'static, +{ + let _: ReqTrait; +} + +fn borrowck_env_fail<'a, T: AliasHaver>() +//~^ ERROR: unsatisfied lifetime constraint from -Zassumptions-on-binders +where + ::Assoc: 'a, +{ + let _: ReqTrait; +} + +const REGIONCK_ENV_PASS<'a, T: AliasHaver>: ReqTrait = todo!() +where + ::Assoc: 'static; + +const REGIONCK_ENV_FAIL<'a, T: AliasHaver>: ReqTrait = todo!() +//~^ ERROR: unsatisfied lifetime constraint from -Zassumptions-on-binders +where + ::Assoc: 'a; + +fn main() {} diff --git a/tests/ui/assumptions_on_binders/alias_outlives.stderr b/tests/ui/assumptions_on_binders/alias_outlives.stderr new file mode 100644 index 0000000000000..bd3819798737d --- /dev/null +++ b/tests/ui/assumptions_on_binders/alias_outlives.stderr @@ -0,0 +1,21 @@ +error: unsatisfied lifetime constraint from -Zassumptions-on-binders :3 + --> $DIR/alias_outlives.rs:37:1 + | +LL | const REGIONCK_ENV_FAIL<'a, T: AliasHaver>: ReqTrait = todo!() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: meoow :c + +error: unsatisfied lifetime constraint from -Zassumptions-on-binders :3 + --> $DIR/alias_outlives.rs:25:1 + | +LL | / fn borrowck_env_fail<'a, T: AliasHaver>() +LL | | +LL | | where +LL | | ::Assoc: 'a, + | |_________________________________^ + | + = note: meoow :c + +error: aborting due to 2 previous errors + diff --git a/tests/ui/assumptions_on_binders/implied_higher_ranked_alias_outlives_assumption.rs b/tests/ui/assumptions_on_binders/implied_higher_ranked_alias_outlives_assumption.rs new file mode 100644 index 0000000000000..c0cb6ce416aed --- /dev/null +++ b/tests/ui/assumptions_on_binders/implied_higher_ranked_alias_outlives_assumption.rs @@ -0,0 +1,53 @@ +//@ compile-flags: -Znext-solver -Zassumptions-on-binders +//@ check-pass + +#![feature(generic_const_items)] + +// sorry for writing this +// - boxy + +// for<'a> where(for<'d> ::Assoc: 'c) { +// for<'b> { +// >::Assoc: 'c +// } +// +// rewritten to: for<'b> >::Assoc: 'c +// } +// rewritten to: true (via assumption) +// rewritting to `for<'a, 'b> >::Assoc: 'c` would be wrong + +trait Trait<'a, 'b> { + type Assoc; +} + +struct ImpliedBound<'a, 'c, T: for<'b> Trait<'a, 'b>>(T, &'a (), &'c ()) +where + for<'b> >::Assoc: 'c,; + +trait InnerBinder<'a, 'b, 'c> {} +impl<'a, 'b, 'c, S> InnerBinder<'a, 'b, 'c> for S +where + S: Trait<'a, 'b>, + >::Assoc: 'c {} + +trait OuterBinder<'a, 'c, T0> {} +impl<'a, 'c, T0, S> OuterBinder<'a, 'c, T0> for S +where + for<'b> S: InnerBinder<'a, 'b, 'c>, {} + +struct ReqTrait<'c, T>(&'c (), T) +where + for<'a> T: OuterBinder<'a, 'c, ImpliedBound<'a, 'c, T>>,; + +fn borrowck_env<'c, T>() +where + T: for<'a, 'b> Trait<'a, 'b> +{ + let _: ReqTrait<'c, T>; +} + +const REGIONCK_ENV<'c, T>: ReqTrait<'c, T> = todo!() +where + T: for<'a, 'b> Trait<'a, 'b>; + +fn main() {} From 00502c67417ae798a06d4fe1d6d8bc287268a21c Mon Sep 17 00:00:00 2001 From: Boxy Date: Fri, 8 May 2026 21:31:15 +0100 Subject: [PATCH 8/9] break on stable --- .../eval_ctxt/solver_region_constraints.rs | 3 ++ .../rustc_type_ir/src/region_constraint.rs | 38 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs index b662fd61be18e..b5f3f0a82c16e 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs @@ -1,9 +1,12 @@ //! Logic for `-Zassumptions-on-binders` stuff +#[cfg(feature = "nightly")] use rustc_data_structures::transitive_relation::TransitiveRelationBuilder; use rustc_type_ir::ClauseKind::*; use rustc_type_ir::inherent::*; use rustc_type_ir::outlives::{Component, push_outlives_components}; +#[cfg(not(feature = "nightly"))] +use rustc_type_ir::region_constraint::TransitiveRelationBuilder; use rustc_type_ir::region_constraint::{ Assumptions, RegionConstraint, eagerly_handle_placeholders_in_universe, max_universe, }; diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 2c5127d5c11f9..1e8533471c8ed 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -4,9 +4,47 @@ use derive_where::derive_where; use indexmap::IndexSet; #[cfg(feature = "nightly")] use rustc_data_structures::stable_hasher::{StableHash, StableHashCtxt, StableHasher}; +#[cfg(feature = "nightly")] use rustc_data_structures::transitive_relation::{TransitiveRelation, TransitiveRelationBuilder}; use tracing::{debug, instrument}; +// Workaround for TransitiveRelation being in rustc_data_structures which isn't accessible on stable +#[cfg(not(feature = "nightly"))] +#[derive(Default, Clone, Debug)] +pub struct TransitiveRelation(T); +#[cfg(not(feature = "nightly"))] +impl TransitiveRelation { + pub fn reachable_from(&self, _data: T) -> Vec { + unreachable!("-Zassumptions-on-binders is not supported for r-a") + } + + pub fn base_edges(&self) -> impl Iterator { + unreachable!("-Zassumptions-on-binders is not supported for r-a"); + + #[allow(unreachable_code)] + [].into_iter() + } +} +#[derive(Clone, Debug)] +#[cfg(not(feature = "nightly"))] +pub struct TransitiveRelationBuilder(T); +#[cfg(not(feature = "nightly"))] +impl TransitiveRelationBuilder { + pub fn freeze(self) -> TransitiveRelation { + unreachable!("-Zassumptions-on-binders is not supported for r-a") + } + + pub fn add(&mut self, _: T, _: T) { + unreachable!("-Zassumptions-on-binders is not supported for r-a") + } +} +#[cfg(not(feature = "nightly"))] +impl Default for TransitiveRelationBuilder { + fn default() -> Self { + unreachable!("-Zassumptions-on-binders is not supported for r-a") + } +} + use crate::data_structures::IndexMap; use crate::fold::TypeSuperFoldable; use crate::inherent::*; From 29c1906c46b7a69abf562559a25c360a8ede0240 Mon Sep 17 00:00:00 2001 From: Boxy Date: Fri, 8 May 2026 23:43:37 +0100 Subject: [PATCH 9/9] w --- .../src/solve/eval_ctxt/solver_region_constraints.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs index b5f3f0a82c16e..2d663bde31a8d 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs @@ -46,6 +46,8 @@ where I: Interner, D: SolverDelegate, { + type Result = (); + fn visit_ty(&mut self, t: I::Ty) { self.out.extend( self.ecx