diff --git a/compiler/rustc_infer/src/infer/context.rs b/compiler/rustc_infer/src/infer/context.rs index afc083aececaa..b0ea9a7f2f27b 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -63,13 +63,13 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { fn get_solver_region_constraint( &self, - ) -> rustc_type_ir::region_constraint::RegionConstraint> { + ) -> rustc_type_ir::region_constraint::CanonicalFormRegionConstraint> { self.inner.borrow().solver_region_constraint_storage.get_constraint() } fn overwrite_solver_region_constraint( &self, - constraint: rustc_type_ir::region_constraint::RegionConstraint>, + constraint: rustc_type_ir::region_constraint::CanonicalFormRegionConstraint>, ) { let mut inner = self.inner.borrow_mut(); use rustc_data_structures::undo_log::UndoLogs; @@ -330,15 +330,21 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { fn register_solver_region_constraint( &self, - c: rustc_type_ir::region_constraint::RegionConstraint>, + c: rustc_type_ir::region_constraint::CanonicalFormRegionConstraint>, ) { let mut inner = self.inner.borrow_mut(); use rustc_data_structures::undo_log::UndoLogs; + let old_constraint = inner.solver_region_constraint_storage.get_constraint(); + let new_constraint = + rustc_type_ir::region_constraint::CanonicalFormRegionConstraint::new_and( + c, + old_constraint.clone(), + ); + use crate::infer::UndoLog; - let previous_was_and = inner.solver_region_constraint_storage.is_and(); - inner.undo_log.push(UndoLog::PushSolverRegionConstraint { previous_was_and }); - inner.solver_region_constraint_storage.push(c); + inner.undo_log.push(UndoLog::OverwriteSolverRegionConstraint { old_constraint }); + inner.solver_region_constraint_storage.overwrite_solver_region_constraint(new_constraint); } fn register_ty_outlives(&self, ty: Ty<'tcx>, r: ty::Region<'tcx>, span: Span) { diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index 583fb1d7db21a..b94bdb849644a 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -1810,58 +1810,23 @@ impl<'tcx> InferCtxt<'tcx> { } type SolverRegionConstraint<'tcx> = - rustc_type_ir::region_constraint::RegionConstraint>; + rustc_type_ir::region_constraint::CanonicalFormRegionConstraint>; #[derive(Clone, Debug)] struct SolverRegionConstraintStorage<'tcx>(SolverRegionConstraint<'tcx>); impl<'tcx> SolverRegionConstraintStorage<'tcx> { fn new() -> Self { - SolverRegionConstraintStorage(SolverRegionConstraint::And(Box::new([]))) + SolverRegionConstraintStorage(SolverRegionConstraint::new_true()) } fn get_constraint(&self) -> SolverRegionConstraint<'tcx> { self.0.clone() } - fn is_and(&self) -> bool { - self.0.is_and() - } - - fn pop(&mut self, previous_was_and: bool) -> Option> { - match &mut self.0 { - SolverRegionConstraint::And(and) => { - let mut and = core::mem::take(and).into_iter().collect::>(); - let popped = and.pop()?; - if previous_was_and { - self.0 = SolverRegionConstraint::And(and.into_boxed_slice()); - } else { - assert_eq!(and.len(), 1); - self.0 = and.pop().unwrap(); - } - Some(popped) - } - _ => unreachable!(), - } - } - - #[instrument(level = "debug")] - fn push(&mut self, constraint: SolverRegionConstraint<'tcx>) { - match core::mem::replace(&mut self.0, SolverRegionConstraint::new_true()) { - SolverRegionConstraint::And(and) => { - let and = - and.into_iter().chain([constraint]).collect::>().into_boxed_slice(); - self.0 = SolverRegionConstraint::And(and); - } - previous => { - self.0 = SolverRegionConstraint::And(Box::new([previous, constraint])); - } - } - } - #[instrument(level = "debug", skip(self))] fn overwrite_solver_region_constraint(&mut self, constraint: SolverRegionConstraint<'tcx>) { - self.0 = constraint; + self.0 = constraint } } diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index 67a85dbdd741b..dd0ac1a25a91a 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -69,6 +69,7 @@ use rustc_middle::ty::{ TyCtxt, TypeVisitableExt, eager_resolve_vars, }; use rustc_span::Span; +use rustc_type_ir::region_constraint::{self, LeafRegionConstraint}; use smallvec::smallvec; use tracing::{debug, instrument}; @@ -232,7 +233,7 @@ impl<'tcx> InferCtxt<'tcx> { region_outlives: TransitiveRelation, span: Span, ) { - let assumptions = rustc_type_ir::region_constraint::Assumptions::new( + let assumptions = region_constraint::Assumptions::new( known_type_outlives.into_iter().cloned().collect(), region_outlives.maybe_map(|r| Some(Region::new_var(self.tcx, r))).unwrap(), ); @@ -254,19 +255,24 @@ impl<'tcx> InferCtxt<'tcx> { 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_root( - self, - constraint, - &assumptions, - ); + let constraint = region_constraint::destructure_type_outlives_constraints_in_root( + self, + constraint, + &assumptions, + ); debug!(?constraint); - let constraint = rustc_type_ir::region_constraint::evaluate_solver_constraint(&constraint); + let constraint = region_constraint::propagate_ambiguity(constraint); debug!(?constraint); - let mut constraints = vec![constraint]; - while let Some(c) = constraints.pop() { - use rustc_type_ir::region_constraint::RegionConstraint::*; + // FIXME(-Zassumptions-on-binders): actually implement OR as an OR + for c in constraint.and_constraint.0.into_iter().chain( + constraint + .or_constraint + .0 + .into_iter() + .flat_map(|and_constraint| and_constraint.0.into_iter()), + ) { + use LeafRegionConstraint::*; match c { Ambiguity => { @@ -281,9 +287,9 @@ impl<'tcx> InferCtxt<'tcx> { category, ); } - // FIXME(-Zassumptions-on-binders): actually implement OR as an OR - And(nested) | Or(nested) => constraints.extend(nested), - AliasTyOutlivesViaEnv(..) | PlaceholderTyOutlives(..) => unreachable!(), + AliasTyOutlivesViaEnv(..) | PlaceholderTyOutlives(..) => { + unreachable!() + } } } } diff --git a/compiler/rustc_infer/src/infer/snapshot/undo_log.rs b/compiler/rustc_infer/src/infer/snapshot/undo_log.rs index eb5b3fe7bfd41..f6694cfcc39ac 100644 --- a/compiler/rustc_infer/src/infer/snapshot/undo_log.rs +++ b/compiler/rustc_infer/src/infer/snapshot/undo_log.rs @@ -28,7 +28,6 @@ pub(crate) enum UndoLog<'tcx> { RegionUnificationTable(sv::UndoLog>>), ProjectionCache(traits::UndoLog<'tcx>), PushTypeOutlivesConstraint, - PushSolverRegionConstraint { previous_was_and: bool }, OverwriteSolverRegionConstraint { old_constraint: SolverRegionConstraint<'tcx> }, PushRegionAssumption, PushHirTypeckPotentiallyRegionDependentGoal, @@ -79,14 +78,6 @@ 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 { previous_was_and } => { - let popped = self.solver_region_constraint_storage.pop(previous_was_and); - assert_matches!( - popped, - Some(_), - "pushed solver region constraint but could not pop it" - ); - } UndoLog::OverwriteSolverRegionConstraint { old_constraint } => { self.solver_region_constraint_storage .overwrite_solver_region_constraint(old_constraint); 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 559ca0a98c58e..c6435436fd711 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 @@ -5,7 +5,7 @@ use std::ops::ControlFlow; use rustc_macros::StableHash; use rustc_type_ir::data_structures::HashSet; use rustc_type_ir::inherent::*; -use rustc_type_ir::region_constraint::{RegionConstraint, evaluate_solver_constraint}; +use rustc_type_ir::region_constraint::{self, CanonicalFormRegionConstraint}; use rustc_type_ir::relate::Relate; use rustc_type_ir::relate::solver_relating::RelateExt; use rustc_type_ir::search_graph::{CandidateHeadUsages, LowerAvailableDepth, PathKind}; @@ -1328,7 +1328,7 @@ where args } - pub(super) fn register_solver_region_constraint(&self, c: RegionConstraint) { + pub(super) fn register_solver_region_constraint(&self, c: CanonicalFormRegionConstraint) { self.delegate.register_solver_region_constraint(c); } @@ -1664,11 +1664,11 @@ where let constraint = self.delegate.get_solver_region_constraint(); debug_assert_eq!( constraint, - evaluate_solver_constraint(&constraint.clone().canonical_form()) + region_constraint::propagate_ambiguity(constraint.clone()) ); constraint } else { - RegionConstraint::new_true() + CanonicalFormRegionConstraint::new_true() }) } else { ExternalRegionConstraints::Old(if let Certainty::Yes = certainty { 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 583dd391dd4d0..45460817c1ebc 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 @@ -8,8 +8,8 @@ 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, - evaluate_solver_constraint, + And, Assumptions, LeafRegionConstraint, Or, eagerly_handle_placeholders_in_universe, + propagate_ambiguity, }; use rustc_type_ir::{ AliasTy, Binder, ClauseKind, InferCtxtLike, Interner, OutlivesClause, Region, TypeVisitable, @@ -137,8 +137,9 @@ where .fold(constraint, |constraint, u| { eagerly_handle_placeholders_in_universe(&**self.delegate, constraint, u) }); - let constraint = evaluate_solver_constraint(&constraint.canonical_form()); + let constraint = propagate_ambiguity(constraint); + debug!("final constraint={:?}", constraint); self.delegate.overwrite_solver_region_constraint(constraint.clone()); if constraint.is_false() { @@ -154,36 +155,28 @@ where /// 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: Region, - ) -> RegionConstraint { + pub(in crate::solve) fn destructure_type_outlives(&mut self, ty: I::Ty, r: Region) -> Or { 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: Region, - ) -> RegionConstraint { - RegionConstraint::And( - components.into_iter().map(|c| self.destructure_component(c, r)).collect(), - ) + fn destructure_components(&mut self, components: &[Component], r: Region) -> Or { + components + .into_iter() + .fold(Or::new_true(), |acc, c| Or::new_and(acc, self.destructure_component(c, r))) } - fn destructure_component(&mut self, c: &Component, r: Region) -> RegionConstraint { + fn destructure_component(&mut self, c: &Component, r: Region) -> Or { use Component::*; + use LeafRegionConstraint::*; match c { - Region(c_r) => RegionConstraint::RegionOutlives(*c_r, r), + Region(c_r) => Or::new_leaf(RegionOutlives(*c_r, r)), Placeholder(p) => { - RegionConstraint::PlaceholderTyOutlives(Ty::new_placeholder(self.cx(), *p), r) + Or::new_leaf(PlaceholderTyOutlives(Ty::new_placeholder(self.cx(), *p), r)) } - // The alias is either rigid or ambiguous in which case we'll return with ambiguity. Alias(_, alias) => self.destructure_alias_outlives(*alias, r), - UnresolvedInferenceVariable(_) => RegionConstraint::Ambiguity, + UnresolvedInferenceVariable(_) => Or::new_ambig(), Param(_) => panic!("Params should have been canonicalized to placeholders"), EscapingAlias(components) => self.destructure_components(components, r), } @@ -197,18 +190,15 @@ where /// 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: Region, - ) -> RegionConstraint { + fn destructure_alias_outlives(&mut self, alias: AliasTy, r: Region) -> Or { + use LeafRegionConstraint::*; + 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()); + .map(|bound| And::new([RegionOutlives(bound, r)])); + let item_bound_outlives = Or::new(item_bounds); - let where_clause_outlives = - RegionConstraint::AliasTyOutlivesViaEnv(Binder::dummy((alias, r))); + let where_clause_outlives = Or::new_leaf(AliasTyOutlivesViaEnv(Binder::dummy((alias, r)))); let mut components = Default::default(); rustc_type_ir::outlives::compute_alias_components_recursive( @@ -218,10 +208,7 @@ where ); let components_outlives = self.destructure_components(&components, r); - RegionConstraint::Or(Box::new([ - item_bound_outlives, - where_clause_outlives, - components_outlives, - ])) + let assumption_outlives = Or::new_or(item_bound_outlives, where_clause_outlives); + Or::new_or(assumption_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 3504882834268..c8950d3eed192 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -93,8 +93,12 @@ where let ty = self.normalize(GoalSource::Misc, goal.param_env, ty::Unnormalized::new_wip(ty))?; if self.cx().assumptions_on_binders() { + use rustc_type_ir::region_constraint::CanonicalFormRegionConstraint; + let constraint = self.destructure_type_outlives(ty, lt); - self.register_solver_region_constraint(constraint); + self.register_solver_region_constraint(CanonicalFormRegionConstraint::new_from_or( + constraint, + )); } else { self.register_ty_outlives(ty, lt); } @@ -119,8 +123,12 @@ where let ty::OutlivesClause(a, b) = goal.predicate; if self.cx().assumptions_on_binders() { + use rustc_type_ir::region_constraint::{ + CanonicalFormRegionConstraint, LeafRegionConstraint, + }; + let constraint = - rustc_type_ir::region_constraint::RegionConstraint::RegionOutlives(a, b); + CanonicalFormRegionConstraint::new_leaf(LeafRegionConstraint::RegionOutlives(a, b)); self.register_solver_region_constraint(constraint); } else { self.register_region_outlives(a, b, VisibleForLeakCheck::Yes); diff --git a/compiler/rustc_type_ir/src/infer_ctxt.rs b/compiler/rustc_type_ir/src/infer_ctxt.rs index 1cd070365f651..8e342fcf2aa5f 100644 --- a/compiler/rustc_type_ir/src/infer_ctxt.rs +++ b/compiler/rustc_type_ir/src/infer_ctxt.rs @@ -395,10 +395,10 @@ pub trait InferCtxtLike: Sized { ) -> Option>; fn get_solver_region_constraint( &self, - ) -> crate::region_constraint::RegionConstraint; + ) -> crate::region_constraint::CanonicalFormRegionConstraint; fn overwrite_solver_region_constraint( &self, - constraint: crate::region_constraint::RegionConstraint, + constraint: crate::region_constraint::CanonicalFormRegionConstraint, ); fn universe_of_ty(&self, ty: ty::TyVid) -> Option; @@ -519,7 +519,7 @@ pub trait InferCtxtLike: Sized { fn register_solver_region_constraint( &self, - c: crate::region_constraint::RegionConstraint, + c: crate::region_constraint::CanonicalFormRegionConstraint, ); fn register_ty_outlives( diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index d34a1ec52d153..19e9715afb32c 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -3,9 +3,10 @@ use derive_where::derive_where; use indexmap::IndexSet; #[cfg(feature = "nightly")] -use rustc_data_structures::stable_hash::{StableHash, StableHashCtxt, StableHasher}; -#[cfg(feature = "nightly")] use rustc_data_structures::transitive_relation::{TransitiveRelation, TransitiveRelationBuilder}; +#[cfg(feature = "nightly")] +use rustc_macros::StableHash_NoContext; +use rustc_type_ir_macros::{GenericTypeVisitable, TypeFoldable_Generic, TypeVisitable_Generic}; use tracing::{debug, instrument}; // Workaround for TransitiveRelation being in rustc_data_structures which isn't accessible on stable @@ -50,11 +51,9 @@ use crate::fold::TypeSuperFoldable; use crate::inherent::*; use crate::relate::{Relate, RelateResult, TypeRelation, VarianceDiagInfo}; use crate::{ - AliasTy, Binder, BoundRegion, BoundVar, BoundVariableKind, DebruijnIndex, FallibleTypeFolder, - GenericTypeVisitable, InferCtxtLike, Interner, IsRigid, OutlivesClause, Region, RegionKind, - TyKind, TypeFoldable, TypeFolder, TypeVisitable, TypeVisitor, TypingMode, UniverseIndex, - Variance, VisitorResult, max_universe, set_aliases_to_non_rigid, try_visit, - walk_visitable_list, + AliasTy, Binder, BoundRegion, BoundVar, BoundVariableKind, DebruijnIndex, InferCtxtLike, + Interner, IsRigid, OutlivesClause, Region, RegionKind, TyKind, TypeFoldable, TypeFolder, + TypingMode, UniverseIndex, Variance, max_universe, set_aliases_to_non_rigid, }; #[derive_where(Clone, Debug; I: Interner)] @@ -91,9 +90,10 @@ impl Assumptions { } } -#[derive_where(Clone, Hash, PartialEq, Debug; I: Interner)] -#[derive(GenericTypeVisitable)] -pub enum RegionConstraint { +#[derive_where(Clone, Hash, PartialEq, Eq, Debug; I: Interner)] +#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] +#[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] +pub enum LeafRegionConstraint { Ambiguity, RegionOutlives(Region, Region), /// Requirement that a (potentially higher ranked) alias outlives some (potentially higher ranked) @@ -116,309 +116,210 @@ pub enum RegionConstraint { /// 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, 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")] -impl StableHash for RegionConstraint -where - 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 stable_hash(&self, hcx: &mut CTX, hasher: &mut StableHasher) { - use RegionConstraint::*; - - std::mem::discriminant(self).stable_hash(hcx, hasher); - match self { - Ambiguity => (), - RegionOutlives(a, b) => { - a.stable_hash(hcx, hasher); - b.stable_hash(hcx, hasher); - } - AliasTyOutlivesViaEnv(outlives) => { - outlives.stable_hash(hcx, hasher); - } - PlaceholderTyOutlives(a, b) => { - a.stable_hash(hcx, hasher); - b.stable_hash(hcx, hasher); - } - And(and) => { - for a in and.iter() { - a.stable_hash(hcx, hasher); - } - } - Or(or) => { - for a in or.iter() { - a.stable_hash(hcx, hasher); - } - } +#[derive_where(Clone, Hash, PartialEq, Eq, Debug; I: Interner)] +#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] +#[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] +pub struct Or(pub Box<[And]>); +impl Or { + pub fn new_true() -> Self { + Self(Box::new([And::new([])])) + } + + pub fn is_true(&self) -> bool { + // OR([AND([])]) + if let [and] = &*self.0 + && and.0.len() == 0 + { + true + } else { + false } } -} -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)?), - AliasTyOutlivesViaEnv(outlives) => AliasTyOutlivesViaEnv(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()) - } - }) + pub fn new_false() -> Self { + Self(Box::new([])) } - 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)), - 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(); - 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()) - } - } + pub fn is_false(&self) -> bool { + // OR([]) + self.0.len() == 0 } -} -impl TypeVisitable for RegionConstraint { - fn visit_with>(&self, f: &mut F) -> F::Result { - use RegionConstraint::*; + pub fn new(i: impl IntoIterator>) -> Self { + let ands = i.into_iter().collect::>().into_boxed_slice(); + let mut new_ands: Vec> = Vec::new(); - match self { - Ambiguity => (), - RegionOutlives(a, b) => { - try_visit!(a.visit_with(f)); - try_visit!(b.visit_with(f)); - } - AliasTyOutlivesViaEnv(outlives) => { - try_visit!(outlives.visit_with(f)); - } - PlaceholderTyOutlives(a, b) => { - try_visit!(a.visit_with(f)); - try_visit!(b.visit_with(f)); - } - And(and) => { - walk_visitable_list!(f, and); - } - Or(or) => { - walk_visitable_list!(f, or); + for and in ands { + if new_ands.iter().all(|c| !c.is_and_equivalent_to(&and)) { + new_ands.push(and) } - }; + } - F::Result::output() + Self(new_ands.into_boxed_slice()) } -} -impl Default for RegionConstraint { - fn default() -> Self { - Self::new_true() + pub fn new_ambig() -> Self { + Or::new_leaf(LeafRegionConstraint::Ambiguity) } -} -impl RegionConstraint { - pub fn new_true() -> Self { - RegionConstraint::And(Box::new([])) + pub fn new_leaf(l: LeafRegionConstraint) -> Self { + Or(Box::new([And(Box::new([l]))])) } - pub fn is_true(&self) -> bool { - match self { - Self::And(and) => and.is_empty(), - _ => false, + pub fn new_and(a: Or, b: Or) -> Self { + // I think this returns false if either a or b is false? + let mut ands = Vec::new(); + for b_and in b.0 { + ands.extend( + a.0.clone() + .into_iter() + .map(|a_and| And::new(a_and.0.into_iter().chain(b_and.0.clone()))), + ); } + + Or::new(ands) } - pub fn new_false() -> Self { - RegionConstraint::Or(Box::new([])) + pub fn new_or(a: Or, b: Or) -> Self { + Or::new(a.0.into_iter().chain(b.0)) } +} - pub fn is_false(&self) -> bool { - match self { - Self::Or(or) => or.is_empty(), - _ => false, - } +#[derive_where(Clone, Hash, PartialEq, Eq, Debug; I: Interner)] +#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] +#[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] +pub struct And(pub Box<[LeafRegionConstraint]>); +impl And { + pub fn new(i: impl IntoIterator>) -> Self { + Self( + i.into_iter() + .collect::>() + .into_iter() + .collect::>() + .into_boxed_slice(), + ) } - pub fn is_or(&self) -> bool { - matches!(self, Self::Or(_)) + fn is_and_equivalent_to(&self, other: &And) -> bool { + let this = self.clone().0; + let other = other.clone().0; + + this.iter().all(|c1| other.iter().any(|c2| c1 == c2)) + && other.iter().all(|c2| this.iter().any(|c1| c1 == c2)) } +} - pub fn unwrap_or(self) -> Box<[RegionConstraint]> { - match self { - Self::Or(ors) => ors, - _ => panic!("`unwrap_or` on non-Or: {self:?}"), +#[derive_where(Clone, Hash, PartialEq, Debug; I: Interner)] +#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] +#[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] +/// CanonicalFormRegionConstraints always have constraints shared between every OR element moved +/// into the and_constraint. Additionally they are always in "OR of AND of LEAF" form instead of +/// supporting arbitrary nesting of ORs/ANDs. +/// +/// We also guarantee that there are no duplicate constraints in any of the `And` or `Or`s, though, +/// this is handled when constructing And/Ors rather than when constructing `CanonicalFormRegionConstraint`. +/// +/// It should also already be "evaluated", as in if `or_constraint` is `false` then `and_constraint` should be +/// empty. Or if an element in the `or_constraint` is `true` then it should be the only constraint. +pub struct CanonicalFormRegionConstraint { + pub and_constraint: And, + pub or_constraint: Or, +} + +impl CanonicalFormRegionConstraint { + pub fn new_from_or(or: Or) -> Self { + let Some(fst) = or.0.get(0).clone() else { + return CanonicalFormRegionConstraint::new_false(); + }; + let mut and_constraint = fst.0.to_vec(); + + for and in or.0.clone() { + and_constraint.retain(|c| and.0.iter().any(|c2| c == c2)); } - } + let and_constraint = And::new(and_constraint); + + let or_constraint = Or::new(or.0.into_iter().map(|and| { + And::new(and.0.into_iter().filter(|c| and_constraint.0.iter().all(|s_c| c != s_c))) + })); - pub fn unwrap_and(self) -> Box<[RegionConstraint]> { - match self { - Self::And(ands) => ands, - _ => panic!("`unwrap_and` on non-And: {self:?}"), + Self { + and_constraint: if or_constraint.is_false() { And::new([]) } else { and_constraint }, + or_constraint, } } - pub fn is_and(&self) -> bool { - matches!(self, Self::And(_)) + pub fn splatted_and_constraints(&self) -> Or { + Or::new(self.or_constraint.0.iter().map(|and| { + And::new(and.0.iter().cloned().chain(self.and_constraint.0.iter().cloned())) + })) } - pub fn is_ambig(&self) -> bool { - matches!(self, Self::Ambiguity) + pub fn new_and( + a: CanonicalFormRegionConstraint, + b: CanonicalFormRegionConstraint, + ) -> Self { + let and_constraint = And::new(a.and_constraint.0.into_iter().chain(b.and_constraint.0)); + let or_constraint = Or::new_and(a.or_constraint, b.or_constraint); + + Self { + and_constraint: if or_constraint.is_false() { And::new([]) } else { and_constraint }, + or_constraint, + } } - pub fn and(self, other: RegionConstraint) -> RegionConstraint { - use RegionConstraint::*; + pub fn new_true() -> Self { + Self { and_constraint: And::new([]), or_constraint: Or::new_true() } + } - 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])), - } + pub fn is_true(&self) -> bool { + self.and_constraint.0.is_empty() && self.or_constraint.is_true() } - /// 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>> { - match ors { - [] => vec![vec![]], - [or1] => { - let mut choices = vec![]; - for choice in or1 { - choices.push(vec![choice.clone()]); - } - choices - } - [or1, rest_ors @ ..] => { - let mut choices = vec![]; - for choice in or1 { - choices.extend( - permutations(rest_ors) - .into_iter() - .map(|and| std::iter::once(choice.clone()).chain(and).collect()), - ); - } - choices - } - } - } + pub fn new_false() -> Self { + Self { and_constraint: And::new([]), or_constraint: Or::new_false() } + } - let canonical = match self { - And(ands) => { - // 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); + pub fn is_false(&self) -> bool { + self.or_constraint.is_false() + } - 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) => { - // 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()) - } - _ => Or(Box::new([And(Box::new([self]))])), - }; + pub fn new_ambig() -> Self { + Self { + and_constraint: And::new([LeafRegionConstraint::Ambiguity]), + or_constraint: Or::new_true(), + } + } - assert!( - canonical.is_canonical_form(), - "non canonical form region constraint: {:?}", - canonical - ); - canonical + pub fn is_ambig(&self) -> bool { + if let [c] = &*self.and_constraint.0 + && c.is_ambig() + && self.or_constraint.is_true() + { + true + } else { + false + } } - fn is_leaf_constraint(&self) -> bool { - use RegionConstraint::*; - match self { - Ambiguity - | RegionOutlives(..) - | AliasTyOutlivesViaEnv(..) - | PlaceholderTyOutlives(..) => true, - And(..) | Or(..) => false, + pub fn new_leaf(l: LeafRegionConstraint) -> Self { + CanonicalFormRegionConstraint { + and_constraint: And(Box::new([l])), + or_constraint: Or::new_true(), } } +} - fn is_canonical_and(&self) -> bool { - if let Self::And(ands) = self { ands.iter().all(|c| c.is_leaf_constraint()) } else { false } +impl Default for CanonicalFormRegionConstraint { + fn default() -> Self { + Self::new_true() } +} - pub fn is_canonical_form(&self) -> bool { - if let Self::Or(ors) = self { ors.iter().all(|c| c.is_canonical_and()) } else { false } +impl LeafRegionConstraint { + pub fn is_ambig(&self) -> bool { + matches!(self, Self::Ambiguity) } } @@ -438,11 +339,9 @@ impl RegionConstraint { #[instrument(level = "debug", skip(infcx), ret)] pub fn eagerly_handle_placeholders_in_universe, I: Interner>( infcx: &Infcx, - constraint: RegionConstraint, + constraint: CanonicalFormRegionConstraint, u: UniverseIndex, -) -> RegionConstraint { - use RegionConstraint::*; - +) -> CanonicalFormRegionConstraint { let assumptions = infcx.get_placeholder_assumptions(u); // 1. rewrite type outlives constraints involving things from `u` into either region constraints @@ -456,29 +355,17 @@ pub fn eagerly_handle_placeholders_in_universe>() - .into_boxed_slice()); - - // 5. actually evaluate the constraint to eagerly error on false - evaluate_solver_constraint(&constraint) + let constraint = compute_new_region_constraints(infcx, constraint, u); + + // 3. rewrite region outlives constraints (potentially to false/true) + let constraint = + pull_region_outlives_constraints_out_of_universe(infcx, constraint, u, &assumptions); + + // 4. force the constraint to ambiguous if it could be `false` in future reruns + propagate_ambiguity(constraint) } /// Filter our region constraints to not include constraints between region variables from `u` and @@ -492,108 +379,117 @@ pub fn eagerly_handle_placeholders_in_universe, I: Interner>( infcx: &Infcx, - constraints: &[RegionConstraint], + constraint: CanonicalFormRegionConstraint, 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(..) | AliasTyOutlivesViaEnv(..) => { - new_constraints.push(c.clone()) - } - RegionOutlives(r1, r2) => { - regions.insert(r1); - regions.insert(r2); - region_flows_builder.add(r2, r1); +) -> CanonicalFormRegionConstraint { + use LeafRegionConstraint::*; + + let extend_from_and = |builder: &mut TransitiveRelationBuilder<_>, + regions: &mut IndexSet<_>, + constraints: &mut Vec<_>, + and: &And| { + for c in &and.0 { + match c { + Ambiguity | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) => { + constraints.push(c.clone()) + } + RegionOutlives(r1, r2) => { + regions.insert(*r1); + regions.insert(*r2); + builder.add(*r2, *r1); + } } } - } + }; + + let mut base_region_flows_builder = TransitiveRelationBuilder::default(); + let mut base_regions = IndexSet::new(); + let mut base_constraints = Vec::new(); + extend_from_and( + &mut base_region_flows_builder, + &mut base_regions, + &mut base_constraints, + &constraint.and_constraint, + ); - 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: Region| match r.kind() { - RegionKind::ReLateParam(..) - | RegionKind::ReEarlyParam(..) - | RegionKind::RePlaceholder(..) - | RegionKind::ReStatic => true, - RegionKind::ReVar(..) => max_universe(infcx, r) < u, - RegionKind::ReError(..) => false, - RegionKind::ReErased | RegionKind::ReBound(..) => unreachable!(), - }; + let mut new_ands = Vec::new(); + for and in &constraint.or_constraint.0 { + let mut region_flows_builder = base_region_flows_builder.clone(); + let mut regions = base_regions.clone(); + let mut constraints = base_constraints.clone(); + extend_from_and(&mut region_flows_builder, &mut regions, &mut constraints, and); + + 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: Region| match r.kind() { + RegionKind::ReLateParam(..) + | RegionKind::ReEarlyParam(..) + | RegionKind::RePlaceholder(..) + | RegionKind::ReStatic => true, + RegionKind::ReVar(..) => max_universe(infcx, r) < u, + RegionKind::ReError(..) => false, + RegionKind::ReErased | RegionKind::ReBound(..) => unreachable!(), + }; - if is_placeholder_like(*r) && is_placeholder_like(*ub) { - new_constraints.push(RegionOutlives(*ub, *r)); + if is_placeholder_like(r) && is_placeholder_like(ub) { + constraints.push(RegionOutlives(ub, r)); + } } } + + new_ands.push(Or::new([And::new(constraints)])) } - new_constraints + CanonicalFormRegionConstraint::new_from_or( + new_ands.into_iter().fold(Or::new_false(), |acc, c| Or::new_or(acc, c)), + ) } -/// Evaluate ANDs and ORs to true/false/ambiguous based on whether their arguments are true/false/ambiguous +/// Force the whole constraint to be ambiguous if it contains ambiguities which could +/// have caused the constraint to be `false` if they had been `false` themselves. +/// +/// For example if we have `'a: 'b AND ambig` it's possible that if we had more inference +/// information we could have produced a better region constraint than `ambig`, and that +/// constraint may then have gone on to be false, at which point we would have `'a: 'b AND false` +/// causing the whole constraint to be `false`. +/// +/// If we're not careful we can wind up returning `'a: 'b AND ambig` from passing trait solver +/// goals and then upon rerunning wind up returning `NoSolution` which would be dubious :3 +/// +/// This is inherently conservative and this method should be called as little as possible as it +/// can cause us to get ambiguities instead of `NoSolution` (for example if `'a: 'b` is `false`), +/// which can affect coherence, candidate selection, etc. +/// +/// FIXME(-Zassumptions-on-binders): this method should probably be trait-solver internal as it only +/// matters at trait solver query boundaries. We currently call it in more than just that location #[instrument(level = "debug", ret)] -pub fn evaluate_solver_constraint( - constraint: &RegionConstraint, -) -> RegionConstraint { - use RegionConstraint::*; - match constraint { - Ambiguity | RegionOutlives(..) | AliasTyOutlivesViaEnv(..) | PlaceholderTyOutlives(..) => { - constraint.clone() - } - And(and) => { - let mut and_constraints = Vec::new(); - 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() { - return RegionConstraint::new_false(); - } else if evaluated_constraint.is_ambig() { - is_ambiguous_constraint = true; - } else { - and_constraints.push(evaluated_constraint); - } - } - - if is_ambiguous_constraint { - RegionConstraint::Ambiguity - } else { - RegionConstraint::And(and_constraints.into_boxed_slice()) - } - } - Or(or) => { - let mut or_constraints = Vec::new(); - 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() { - return RegionConstraint::new_true(); - } else if evaluated_constraint.is_ambig() { - is_ambiguous_constraint = true; - } else { - or_constraints.push(evaluated_constraint); - } - } +pub fn propagate_ambiguity( + constraint: CanonicalFormRegionConstraint, +) -> CanonicalFormRegionConstraint { + if constraint.and_constraint.0.iter().any(|c| c.is_ambig()) { + return CanonicalFormRegionConstraint::new_ambig(); + } - if is_ambiguous_constraint { - RegionConstraint::Ambiguity - } else { - RegionConstraint::Or(or_constraints.into_boxed_slice()) - } + for and in constraint.or_constraint.0.iter() { + // FIXME(-Zassumptions-on-binders): This is overly conservative. If we have: + // `'a: 'b OR ambig` we don't necessarily want to propagate ambiguity here + // as we might end up with `'a: 'b` being satisfied in which case we unncessarily + // errored here. + // + // It's fine if the `ambig` wound up being `false` as that wouldn't cause a goal to + // become `NoSolution`, it would instead result in us returning the `'a: 'b` constraint + // by itself. + // + // `rust-lang/project-assumptions-on-binders#21` + if and.0.iter().any(|c| c.is_ambig()) { + return CanonicalFormRegionConstraint::new_ambig(); } } + + constraint } /// Handles converting region outlives constraints involving placeholders from `u` into OR constraints @@ -622,10 +518,10 @@ fn pull_region_outlives_constraints_out_of_universe< I: Interner, >( infcx: &Infcx, - constraint: RegionConstraint, + constraint: CanonicalFormRegionConstraint, u: UniverseIndex, assumptions: &Option>, -) -> RegionConstraint { +) -> CanonicalFormRegionConstraint { assert!(max_universe(infcx, constraint.clone()) <= u); // FIXME(-Zassumptions-on-binders): we don't lower universes of region variables when exiting `u` @@ -635,92 +531,117 @@ fn pull_region_outlives_constraints_out_of_universe< // 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(..) | AliasTyOutlivesViaEnv(..) => { - 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); + use LeafRegionConstraint::*; - if region_1_u != u && region_2_u != u { - return constraint; - } + let pull_and = |and: And| { + let mut pulled_constraints = Vec::new(); + for c in and.0 { + match c { + Ambiguity | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) => { + assert!(max_universe(infcx, c.clone()) < u); + pulled_constraints.push(Or::new_leaf(c.clone())); + } + RegionOutlives(region_1, region_2) => { + let region_1_u = max_universe(infcx, region_1); + let region_2_u = max_universe(infcx, region_2); - let assumptions = match assumptions { - Some(assumptions) => assumptions, - None => return RegionConstraint::Ambiguity, - }; + if region_1_u != u && region_2_u != u { + pulled_constraints.push(Or::new_leaf(c)); + continue; + } - let mut candidates = vec![]; - for ub in - regions_outlived_by(region_1, assumptions).filter(|r| max_universe(infcx, *r) < u) - { - // 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 assumptions = match assumptions { + Some(assumptions) => assumptions, + None => { + pulled_constraints.push(Or::new_ambig()); + continue; + } + }; + + let mut candidates = vec![]; + + for ub in regions_outlived_by(region_1, assumptions) { + // 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)); + } + } - RegionConstraint::Or(candidates.into_boxed_slice()) + pulled_constraints.push(Or::new(candidates.into_iter().map(|c| And::new([c])))); + } + }; } - And(constraints) => And(constraints - .into_iter() - .map(|constraint| { - pull_region_outlives_constraints_out_of_universe(infcx, constraint, u, assumptions) - }) - .collect()), - Or(_) => unreachable!(), - } + + pulled_constraints.into_iter().fold(Or::new_true(), |acc, c| Or::new_and(acc, c)) + }; + + let and_constraint = pull_and(constraint.and_constraint); + let or_constraint = constraint + .or_constraint + .0 + .into_iter() + .fold(Or::new_false(), |acc, c| Or::new_or(acc, pull_and(c))); + CanonicalFormRegionConstraint::new_from_or(Or::new_and(and_constraint, or_constraint)) } /// 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. +#[instrument(level = "debug", skip(infcx), ret)] pub fn destructure_type_outlives_constraints_in_root< Infcx: InferCtxtLike, I: Interner, >( infcx: &Infcx, - constraint: RegionConstraint, + constraint: CanonicalFormRegionConstraint, assumptions: &Assumptions, -) -> RegionConstraint { - use RegionConstraint::*; - - 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) +) -> CanonicalFormRegionConstraint { + use LeafRegionConstraint::*; + + let destructure_and = |and: &And| { + debug!("rewriting and: {:?}", and); + let mut destructured_constraints = Vec::new(); + for c in &and.0 { + match c { + Ambiguity | RegionOutlives(..) => { + destructured_constraints.push(Or::new_leaf(c.clone())) + } + PlaceholderTyOutlives(ty, r) => destructured_constraints.push(Or::new( + regions_outlived_by_placeholder(*ty, assumptions, infcx.cx()) + .map(move |assumption_r| And::new([RegionOutlives(assumption_r, *r)])), + )), + AliasTyOutlivesViaEnv(bound_outlives) => { + destructured_constraints.push(alias_outlives_candidates_from_assumptions( + infcx, + *bound_outlives, + assumptions, + )); + } + } } - And(constraints) => And(constraints - .into_iter() - .map(|constraint| { - destructure_type_outlives_constraints_in_root(infcx, constraint, assumptions) - }) - .collect()), - Or(constraints) => Or(constraints - .into_iter() - .map(|constraint| { - destructure_type_outlives_constraints_in_root(infcx, constraint, assumptions) - }) - .collect()), - } + debug!(?destructured_constraints); + let merged_constraints = + destructured_constraints.into_iter().fold(Or::new_true(), |acc, c| Or::new_and(acc, c)); + debug!(?merged_constraints); + merged_constraints + }; + + let and_constraint = destructure_and(&constraint.and_constraint); + let or_constraint = constraint + .or_constraint + .0 + .into_iter() + .fold(Or::new_false(), |acc, c| Or::new_or(acc, destructure_and(&c))); + + CanonicalFormRegionConstraint::new_from_or(Or::new_and(and_constraint, or_constraint)) } /// Converts type outlives constraints into either region outlives constraints, or type outlives @@ -739,10 +660,12 @@ fn rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling< I: Interner, >( infcx: &Infcx, - constraint: RegionConstraint, + constraint: CanonicalFormRegionConstraint, u: UniverseIndex, assumptions: &Option>, -) -> RegionConstraint { +) -> CanonicalFormRegionConstraint { + use LeafRegionConstraint::*; + assert!( max_universe(infcx, constraint.clone()) <= u, "constraint {:?} contains terms from a larger universe than {:?}", @@ -750,185 +673,190 @@ fn rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling< 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 region_u != u && ty_u != u { - return constraint; + let rewrite_and = |and: And| { + let mut rewritten_constraints = Vec::new(); + for c in and.0 { + match c { + Ambiguity | RegionOutlives(..) => rewritten_constraints.push(Or::new_leaf(c)), + PlaceholderTyOutlives(ty, region) => { + rewritten_constraints.push(rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder_handling(infcx, ty, region, u, assumptions)); + } + AliasTyOutlivesViaEnv(bound_outlives) => { + rewritten_constraints.push(rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handling(infcx, bound_outlives, u, assumptions)); + } } + } + rewritten_constraints.into_iter().fold(Or::new_true(), |acc, c| Or::new_and(acc, c)) + }; - let assumptions = match assumptions { - Some(assumptions) => assumptions, - None => return Ambiguity, - }; + let and_constraint = rewrite_and(constraint.and_constraint); + let or_constraint = constraint + .or_constraint + .0 + .into_iter() + .fold(Or::new_false(), |acc, c| Or::new_or(acc, rewrite_and(c))); - let mut candidates = vec![]; + CanonicalFormRegionConstraint::new_from_or(Or::new_and(and_constraint, or_constraint)) +} - // 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)), - ); +fn rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder_handling< + Infcx: InferCtxtLike, + I: Interner, +>( + infcx: &Infcx, + ty: I::Ty, + region: Region, + u: UniverseIndex, + assumptions: &Option>, +) -> Or { + use LeafRegionConstraint::*; - // 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)), - ); - } + let ty_u = max_universe(infcx, ty); + let region_u = max_universe(infcx, region); - Or(candidates.into_boxed_slice()) - } - AliasTyOutlivesViaEnv(bound_outlives) => { - let mut candidates = Vec::new(); - - // 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 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), - ); - let candidate = RegionConstraint::AliasTyOutlivesViaEnv(bound_outlives); - if max_universe(infcx, candidate.clone()) < u { - candidates.push(candidate); - } else { - // `PlaceholderReplacer` only folds regions. A non-lifetime binder can leave - // a placeholder type in `u`, so this type-outlives constraint cannot be - // handled by the region-outlives-only eager placeholder machinery. - candidates.push(Ambiguity); - } - } + if region_u != u && ty_u != u { + return Or::new_leaf(PlaceholderTyOutlives(ty, region)); + } - let assumptions = match assumptions { - Some(assumptions) => assumptions, - None => { - candidates.push(Ambiguity); - return Or(candidates.into_boxed_slice()); - } - }; + let assumptions = match assumptions { + Some(assumptions) => assumptions, + None => return Or::new_ambig(), + }; - // Actually look at the assumptions and matching our higher ranked alias outlives goal - // against potentially higher ranked type outlives assumptions. - candidates.push(alias_outlives_candidates_from_assumptions( - infcx, - bound_outlives, - assumptions, - )); - - // 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 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 - for r2 in regions_outliving(escaping_r, assumptions, infcx.cx()) - .filter(|r2| max_universe(infcx, *r2) < u) - { - let candidate = - AliasTyOutlivesViaEnv(bound_alias.map_bound(|alias| (alias, r2))); - if max_universe(infcx, candidate.clone()) < u { - candidates.push(candidate); - } else { - candidates.push(Ambiguity); - } - } - } + let mut candidates = vec![]; - // I'm not convinced our handling here is *complete* so for now - // 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::Typeck { .. } - | TypingMode::ErasedNotCoherence { .. } - | TypingMode::PostTypeckUntilBorrowck { .. } - | TypingMode::PostBorrowck { .. } - | TypingMode::Reflection - | TypingMode::PostAnalysis - | TypingMode::Codegen => (), - }; + // 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)), + ); - RegionConstraint::Or(candidates.into_boxed_slice()) + // 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)), + ); + } + + Or::new(candidates.into_iter().map(|c| And::new([c]))) +} + +fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handling< + Infcx: InferCtxtLike, + I: Interner, +>( + infcx: &Infcx, + bound_outlives: Binder, Region)>, + u: UniverseIndex, + assumptions: &Option>, +) -> Or { + use LeafRegionConstraint::*; + + let mut candidates = Vec::new(); + + // 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 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(Or::new_leaf(AliasTyOutlivesViaEnv(bound_outlives))); + } + + let assumptions = match assumptions { + Some(assumptions) => assumptions, + None => { + candidates.push(Or::new_ambig()); + return candidates.into_iter().fold(Or::new_false(), |acc, c| Or::new_or(acc, c)); } - And(constraints) => And(constraints - .into_iter() - .map(|constraint| { - rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling( - infcx, - constraint, - u, - assumptions, - ) - }) - .collect()), - Or(constraints) => Or(constraints - .into_iter() - .map(|constraint| { - rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling( - infcx, - constraint, - u, - assumptions, - ) - }) - .collect()), + }; + + // 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 max_universe(infcx, escaping_r) == u { + let mut replacer = PlaceholderReplacer { + cx: infcx.cx(), + existing_var_count: bound_outlives.bound_vars().len(), + bound_vars: IndexMap::default(), + universe: u, + current_index: DebruijnIndex::ZERO, + }; + let escaping_alias = escaping_alias.fold_with(&mut replacer); + let bound_vars = bound_outlives.bound_vars().iter().chain( + core::mem::take(&mut replacer.bound_vars) + .into_iter() + .map(|(_, bound_region)| BoundVariableKind::Region(bound_region.kind)), + ); + let bound_alias = Binder::bind_with_vars( + escaping_alias, + I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), + ); + + // while we did skip the binder, bound vars aren't in any universe so + // this can't be an escaping bound var + candidates.push(Or::new( + regions_outliving(escaping_r, assumptions, infcx.cx()) + .filter(|r2| max_universe(infcx, *r2) < u) + .map(|r2| { + And::new([AliasTyOutlivesViaEnv(bound_alias.map_bound(|alias| (alias, r2)))]) + }), + )); } + + // I'm not convinced our handling here is *complete* so for now + // let's be conservative and not let alias outlives' cause NoSolution + // in coherence + match infcx.typing_mode_raw() { + TypingMode::Coherence => candidates.push(Or::new_ambig()), + TypingMode::Typeck { .. } + | TypingMode::Reflection + | TypingMode::ErasedNotCoherence { .. } + | TypingMode::PostTypeckUntilBorrowck { .. } + | TypingMode::PostBorrowck { .. } + | TypingMode::PostAnalysis + | TypingMode::Codegen => (), + }; + + candidates.into_iter().fold(Or::new_false(), |acc, c| Or::new_or(acc, c)) } /// Returns all regions `r2` for which `r: r2` is known to hold in @@ -1020,7 +948,7 @@ fn alias_outlives_candidates_from_assumptions infcx: &Infcx, bound_outlives: Binder, Region)>, assumptions: &Assumptions, -) -> RegionConstraint { +) -> Or { let mut candidates = Vec::new(); let prev_universe = infcx.universe(); @@ -1032,7 +960,7 @@ fn alias_outlives_candidates_from_assumptions let mut relation = HigherRankedAliasMatcher { infcx, - region_constraints: vec![RegionConstraint::RegionOutlives(r2, r)], + region_constraints: vec![LeafRegionConstraint::RegionOutlives(r2, r)], }; // FIXME(#155345): Both sides should be rigid in the future. @@ -1041,28 +969,29 @@ fn alias_outlives_candidates_from_assumptions alias.to_ty(infcx.cx(), IsRigid::No), set_aliases_to_non_rigid(infcx.cx(), alias2).skip_norm_wip(), ) { - candidates - .push(RegionConstraint::And(relation.region_constraints.into_boxed_slice())); + candidates.push(And::new(relation.region_constraints)); } } }); - let constraint = RegionConstraint::Or(candidates.into_boxed_slice()); + let constraint = CanonicalFormRegionConstraint::new_from_or(Or::new(candidates)); let largest_universe = infcx.universe(); debug!(?prev_universe, ?largest_universe); - ((prev_universe.index() + 1)..=largest_universe.index()) + let canonical_constraint = ((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) - }) + }); + + canonical_constraint.splatted_and_constraints() } struct HigherRankedAliasMatcher<'a, Infcx: InferCtxtLike, I: Interner> { infcx: &'a Infcx, - region_constraints: Vec>, + region_constraints: Vec>, } impl<'a, Infcx: InferCtxtLike, I: Interner> TypeRelation @@ -1103,8 +1032,8 @@ impl<'a, Infcx: InferCtxtLike, I: Interner> TypeRelation fn regions(&mut self, a: Region, b: Region) -> RelateResult> { if a != b { - self.region_constraints.push(RegionConstraint::RegionOutlives(a, b)); - self.region_constraints.push(RegionConstraint::RegionOutlives(b, a)); + self.region_constraints.push(LeafRegionConstraint::RegionOutlives(a, b)); + self.region_constraints.push(LeafRegionConstraint::RegionOutlives(b, a)); } Ok(a) } diff --git a/compiler/rustc_type_ir/src/solve/mod.rs b/compiler/rustc_type_ir/src/solve/mod.rs index 6de031ed1bd51..9c5ffa7971b13 100644 --- a/compiler/rustc_type_ir/src/solve/mod.rs +++ b/compiler/rustc_type_ir/src/solve/mod.rs @@ -15,7 +15,7 @@ use tracing::debug; use crate::inherent::*; use crate::lang_items::SolverTraitLangItem; -use crate::region_constraint::RegionConstraint; +use crate::region_constraint::CanonicalFormRegionConstraint; use crate::search_graph::PathKind; use crate::{ self as ty, Canonical, CanonicalVarValues, CantBeErased, ConstVid, FloatVid, GenericArgKind, @@ -612,7 +612,7 @@ pub enum ExternalRegionConstraints { Old(Vec<(ty::RegionConstraint, VisibleForLeakCheck)>), /// new form of region constraints used when `-Zassumptions-on-binders` is enabled. /// supports ORs. - NextGen(RegionConstraint), + NextGen(CanonicalFormRegionConstraint), } impl ExternalRegionConstraints { @@ -639,7 +639,7 @@ 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()), + true => ExternalRegionConstraints::NextGen(CanonicalFormRegionConstraint::new_true()), false => ExternalRegionConstraints::Old(vec![]), };