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/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..5525b54b19c9a 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -173,6 +173,23 @@ 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(), + infcx.tcx.def_span(infcx.root_def_id), + ); + // 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_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 70e3d7dc9fef0..d2ead01748e38 100644 --- a/compiler/rustc_infer/src/infer/at.rs +++ b/compiler/rustc_infer/src/infer/at.rs @@ -81,6 +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(), + 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(), } @@ -106,6 +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(), + 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 8107d91ba4109..2c94f866c08b4 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 assumptions_on_binders(&self) -> bool { + self.tcx.sess.opts.unstable_opts.assumptions_on_binders + } + fn universe(&self) -> ty::UniverseIndex { self.universe() } @@ -38,6 +42,40 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { self.create_next_universe() } + fn insert_placeholder_assumptions( + &self, + u: ty::UniverseIndex, + assumptions: Option>>, + ) { + self.placeholder_assumptions_for_next_solver.borrow_mut().insert(u, assumptions); + } + + fn get_placeholder_assumptions( + &self, + u: ty::UniverseIndex, + ) -> Option>> { + self.placeholder_assumptions_for_next_solver.borrow().get(&u).unwrap().as_ref().cloned() + } + + 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), @@ -290,6 +328,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..cdc5021aaf71d 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(), @@ -315,6 +319,19 @@ 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. + // + // 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>>, + >, + >, + next_trait_solver: bool, pub obligation_inspector: Cell>>, @@ -397,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. @@ -408,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, } } @@ -607,6 +629,7 @@ impl<'tcx> InferCtxtBuilder<'tcx> { reported_signature_mismatch: Default::default(), tainted_by_errors: Cell::new(None), universe: Cell::new(ty::UniverseIndex::ROOT), + placeholder_assumptions_for_next_solver: RefCell::new(Default::default()), next_trait_solver, obligation_inspector: Cell::new(None), } @@ -1661,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, } } @@ -1750,3 +1774,55 @@ 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!(), + } + } + + #[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 d0234cc2621a9..b73ed50c1ea16 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::Span; use smallvec::smallvec; use tracing::{debug, instrument}; @@ -145,6 +147,8 @@ impl<'tcx> InferCtxt<'tcx> { sub_region: Region<'tcx>, cause: &ObligationCause<'tcx>, ) { + 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 // this outlives obligation since it has no components which affect lifetime @@ -199,6 +203,86 @@ 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>, + 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_solver_region_constraints(assumptions, self, span); + } + + 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, + 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_solver_region_constraints(assumptions, conversion, span); + } + + #[instrument(level = "debug", skip(self, conversion))] + 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.assumptions_on_binders { + return; + } + + assert!(self.next_trait_solver()); + + 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_root( + self, + constraint, + &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(-Zassumptions-on-binders): actually implement OR as an OR + And(nested) | Or(nested) => constraints.extend(nested), + AliasTyOutlivesViaEnv(..) => 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. @@ -217,9 +301,12 @@ 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, span); + // 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(); @@ -458,7 +545,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_infer/src/infer/snapshot/undo_log.rs b/compiler/rustc_infer/src/infer/snapshot/undo_log.rs index a10026f2f77c7..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> { @@ -28,6 +28,8 @@ pub(crate) enum UndoLog<'tcx> { RegionUnificationTable(sv::UndoLog>>), ProjectionCache(traits::UndoLog<'tcx>), PushTypeOutlivesConstraint, + PushSolverRegionConstraint, + OverwriteSolverRegionConstraint { old_constraint: SolverRegionConstraint<'tcx> }, PushRegionAssumption, PushHirTypeckPotentiallyRegionDependentGoal, } @@ -77,6 +79,18 @@ 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::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 f61088d2c5bf9..ceecb26d242dd 100644 --- a/compiler/rustc_middle/src/traits/solve.rs +++ b/compiler/rustc_middle/src/traits/solve.rs @@ -81,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_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/canonical/mod.rs b/compiler/rustc_next_trait_solver/src/canonical/mod.rs index 4eea0f2c2198d..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; @@ -117,11 +118,16 @@ where let ExternalConstraintsData { region_constraints, opaque_types, normalization_nested_goals } = &*external_constraints; - 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) @@ -375,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/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 227d091b5c2f7..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 @@ -5,13 +5,14 @@ use std::ops::ControlFlow; use rustc_macros::StableHash; use rustc_type_ir::data_structures::{HashMap, HashSet}; use rustc_type_ir::inherent::*; +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, CanonicalVarValues, ClauseKind, InferCtxtLike, Interner, MayBeErased, @@ -39,6 +40,7 @@ use crate::solve::{ }; mod probe; +mod solver_region_constraints; /// The kind of goal we're currently proving. /// @@ -823,7 +825,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)? @@ -1327,12 +1329,27 @@ where /// `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.assumptions_on_binders() { + self.region_assumptions_for_placeholders_in_universe(value.clone(), u, param_env) + } else { + None + }; + self.delegate.insert_placeholder_assumptions(u, assumptions); + f(self, value) + }) } pub(super) fn resolve_vars_if_possible(&self, value: T) -> T @@ -1362,6 +1379,14 @@ where args } + pub(super) fn assumptions_on_binders(&self) -> bool { + self.delegate.assumptions_on_binders() + } + + 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); } @@ -1553,12 +1578,22 @@ 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 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) { @@ -1618,9 +1653,9 @@ where // 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, @@ -1672,10 +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 { - self.delegate.make_deduplicated_region_constraints() - } else { - Default::default() + let region_constraints = match self.assumptions_on_binders() { + true if let Certainty::Yes = certainty => { + ExternalRegionConstraints::NextGen(self.delegate.get_solver_region_constraint()) + } + 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. 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..2d663bde31a8d --- /dev/null +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs @@ -0,0 +1,222 @@ +//! 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, +}; +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, + { + type Result = (); + + 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 be2c44cdca89f..695da28c1aa72 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -91,7 +91,15 @@ where goal: Goal>, ) -> QueryResultOrRerunNonErased { let ty::OutlivesPredicate(ty, lt) = goal.predicate; - self.register_ty_outlives(ty, lt); + + 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 { + self.register_ty_outlives(ty, lt); + } + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } @@ -101,7 +109,15 @@ where goal: Goal>, ) -> QueryResultOrRerunNonErased { let ty::OutlivesPredicate(a, b) = goal.predicate; - self.register_region_outlives(a, b, VisibleForLeakCheck::Yes); + + if self.assumptions_on_binders() { + 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_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_session/src/options.rs b/compiler/rustc_session/src/options.rs index 8734dad6df503..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: 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 e9c68784ccd36..f66fba02c414a 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 { + // 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)) => { @@ -122,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)) => { + ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(outlives)) + if !assumptions_on_binders => + { if outlives.has_escaping_bound_vars() { return None; } @@ -135,7 +139,9 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< ); Some(Certainty::Yes) } - ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(outlives)) => { + ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(outlives)) + if !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 093969704e54e..9b33b940ec8e8 100644 --- a/compiler/rustc_type_ir/src/infer_ctxt.rs +++ b/compiler/rustc_type_ir/src/infer_ctxt.rs @@ -338,6 +338,27 @@ pub trait InferCtxtLike: Sized { fn universe(&self) -> ty::UniverseIndex; fn create_next_universe(&self) -> ty::UniverseIndex; + fn assumptions_on_binders(&self) -> bool { + false + } + + fn insert_placeholder_assumptions( + &self, + u: ty::UniverseIndex, + assumptions: Option>, + ); + fn get_placeholder_assumptions( + &self, + u: ty::UniverseIndex, + ) -> Option>; + 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; fn universe_of_ct(&self, ct: ty::ConstVid) -> Option; @@ -439,6 +460,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/inherent.rs b/compiler/rustc_type_ir/src/inherent.rs index 7ff447a81a284..8859ab7c037b0 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 }) @@ -690,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..09def0212a153 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 @@ -290,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/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/outlives.rs b/compiler/rustc_type_ir/src/outlives.rs index 56f40d3f78288..2a1cbc3575d85 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) +} 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..1e8533471c8ed --- /dev/null +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -0,0 +1,1205 @@ +//! 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::{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::*; +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, 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, + RegionOutlives(I::Region, 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")] +impl StableHash for RegionConstraint +where + 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 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); + } + } + } + } +} + +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()) + } + }) + } + + 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()) + } + } + } +} + +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); + }; + } + AliasTyOutlivesViaEnv(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 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) + } + + 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])), + } + } + + /// 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(|mut and| { + and.push(choice.clone()); + and + })); + } + choices + } + } + } + + 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); + + 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]))])), + }; + + assert!( + canonical.is_canonical_form(), + "non canonical form region constraint: {:?}", + canonical + ); + canonical + } + + fn is_leaf_constraint(&self) -> bool { + use RegionConstraint::*; + match self { + Ambiguity + | RegionOutlives(..) + | AliasTyOutlivesViaEnv(..) + | PlaceholderTyOutlives(..) => true, + And(..) | Or(..) => false, + } + } + + fn is_canonical_and(&self) -> bool { + if let Self::And(ands) = self { ands.iter().all(|c| c.is_leaf_constraint()) } else { false } + } + + pub fn is_canonical_form(&self) -> bool { + 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, + constraint: RegionConstraint, + u: UniverseIndex, +) -> RegionConstraint { + use RegionConstraint::*; + + let assumptions = infcx.get_placeholder_assumptions(u); + + // 1. rewrite type outlives constraints involving things from `u` into either region constraints + // involving things from `u` or type outlives constraints not involving things from `u` + // + // 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(); + + // 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 = 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) + pull_region_outlives_constraints_out_of_universe(infcx, and, u, &assumptions) + }) + .collect::>() + .into_boxed_slice()); + + // 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, + 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(..) | AliasTyOutlivesViaEnv(..) => { + 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(..) => false, + RegionKind::ReErased | RegionKind::ReBound(..) => unreachable!(), + }; + + if is_placeholder_like(*r) && is_placeholder_like(*ub) { + new_constraints.push(RegionOutlives(*ub, *r)); + } + } + } + + new_constraints +} + +/// 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(..) | 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); + } + } + + if is_ambiguous_constraint { + RegionConstraint::Ambiguity + } else { + RegionConstraint::Or(or_constraints.into_boxed_slice()) + } + } + } +} + +/// 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_outlives_constraints_out_of_universe< + Infcx: InferCtxtLike, + I: Interner, +>( + infcx: &Infcx, + constraint: RegionConstraint, + u: UniverseIndex, + assumptions: &Option>, +) -> RegionConstraint { + assert!(max_universe(infcx, constraint.clone()) <= 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(..) | 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); + + if region_1_u != u && region_2_u != u { + return constraint; + } + + let assumptions = match assumptions { + Some(assumptions) => assumptions, + None => return RegionConstraint::Ambiguity, + }; + + 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)); + } + } + + 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!(), + } +} + +/// 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::*; + + 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| { + 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()), + } +} + +/// 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)] +fn rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling< + Infcx: InferCtxtLike, + I: Interner, +>( + infcx: &Infcx, + constraint: RegionConstraint, + u: UniverseIndex, + assumptions: &Option>, +) -> RegionConstraint { + 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 region_u != u && ty_u != u { + return constraint; + } + + let assumptions = match assumptions { + Some(assumptions) => assumptions, + None => return Ambiguity, + }; + + 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)), + ); + } + + 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), + ); + 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 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 NoSolution + // in coherence + match infcx.typing_mode_raw() { + TypingMode::Coherence => candidates.push(RegionConstraint::Ambiguity), + TypingMode::Analysis { .. } + | TypingMode::ErasedNotCoherence { .. } + | TypingMode::Borrowck { .. } + | TypingMode::PostBorrowckAnalysis { .. } + | TypingMode::PostAnalysis => (), + }; + + RegionConstraint::Or(candidates.into_boxed_slice()) + } + 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()), + } +} + +/// 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 { + // 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 { + assumptions + .inverse_region_outlives + .reachable_from(r) + .into_iter() + // 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, + 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)), + }) +} + +/// The largest universe a variable or placeholder was from in `t` +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() +} + +// 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, +} + +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> +{ + type Result = (); + + 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), + } + } + + fn visit_const(&mut self, c: I::Const) { + 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), + } + } + + fn visit_region(&mut self, r: I::Region) { + 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); + } + _ => (), + } + } +} + +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) + } + // FIXME(-Zassumptions-on-binders): We should be handling region variables here somehow + _ => 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 + } +} + +/// 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)] +fn alias_outlives_candidates_from_assumptions, I: Interner>( + infcx: &Infcx, + bound_outlives: Binder, I::Region)>, + assumptions: &Assumptions, +) -> RegionConstraint { + let mut candidates = Vec::new(); + + 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_placeholder_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(-Zassumptions-on-binders): 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_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_placeholder_assumptions(u, Some(Assumptions::empty())); + let a = self.infcx.instantiate_binder_with_infer(a); + self.relate(a, b) + })?; + + Ok(a) + } +} diff --git a/compiler/rustc_type_ir/src/solve/mod.rs b/compiler/rustc_type_ir/src/solve/mod.rs index b136dec792f6a..f2ea2e5a729e8 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, @@ -578,12 +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 region_constraints: ExternalRegionConstraints, pub opaque_types: Vec<(ty::OpaqueTypeKey, I::Ty)>, pub normalization_nested_goals: NestedNormalizationGoals, } @@ -591,10 +612,28 @@ 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 { - self.region_constraints.is_empty() - && self.opaque_types.is_empty() - && self.normalization_nested_goals.is_empty() + let ExternalConstraintsData { + region_constraints, + opaque_types, + normalization_nested_goals, + } = self; + 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() {}