diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 9f23a0d5ab631..4599c3b8e9fcf 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -173,7 +173,7 @@ pub(crate) fn type_check<'tcx>( let polonius_context = typeck.polonius_context; - if infcx.tcx.assumptions_on_binders() { + if infcx.tcx.assumptions_on_binders_any() { let mut converter = constraint_conversion::ConstraintConversion::new( typeck.infcx, typeck.universal_regions, diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 0dee9690737df..10596caccc94c 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -2369,6 +2369,7 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { match c { LeafRegionConstraint::Ambiguity(_) | LeafRegionConstraint::RegionOutlives(..) + | LeafRegionConstraint::TypeOutlives(..) | LeafRegionConstraint::AliasTyOutlivesViaEnv(..) => (), // OK LeafRegionConstraint::PlaceholderTyOutlives(ty, _, span) => { // we can't check this during lowering, because the ty is a ty::Bound that gets diff --git a/compiler/rustc_infer/src/infer/context.rs b/compiler/rustc_infer/src/infer/context.rs index f9b08efad88cf..08d7b287ab21e 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -190,9 +190,11 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { ) -> U { self.enter_forall(value, |value| { let u = self.universe(); - self.placeholder_assumptions_for_next_solver - .borrow_mut() - .insert(u, Some(rustc_type_ir::region_constraint::Assumptions::empty())); + let assumptions = self + .tcx + .assumptions_on_binders_full() + .then(rustc_type_ir::region_constraint::Assumptions::empty); + self.placeholder_assumptions_for_next_solver.borrow_mut().insert(u, assumptions); f(value) }) } diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index cbbf5e3c91c42..b52bd86624679 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -163,7 +163,7 @@ impl<'tcx> InferCtxt<'tcx> { sub_region: Region<'tcx>, cause: &ObligationCause<'tcx>, ) { - assert!(!self.tcx.assumptions_on_binders()); + assert!(!self.tcx.assumptions_on_binders_any()); // `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 @@ -261,7 +261,7 @@ impl<'tcx> InferCtxt<'tcx> { assumptions: rustc_type_ir::region_constraint::Assumptions>, mut conversion: impl TypeOutlivesDelegate<'tcx>, ) { - assert!(self.tcx.assumptions_on_binders()); + assert!(self.tcx.assumptions_on_binders_any()); assert!(self.next_trait_solver()); let constraint = self.inner.borrow().solver_region_constraint_storage.get_constraint(); @@ -302,7 +302,7 @@ impl<'tcx> InferCtxt<'tcx> { b, a, category, ); } - AliasTyOutlivesViaEnv(..) | PlaceholderTyOutlives(..) => { + TypeOutlives(..) | AliasTyOutlivesViaEnv(..) | PlaceholderTyOutlives(..) => { unreachable!() } } @@ -326,7 +326,7 @@ impl<'tcx> InferCtxt<'tcx> { ) { assert!(!self.in_snapshot(), "cannot process registered region obligations in a snapshot"); - if self.tcx.assumptions_on_binders() { + if self.tcx.assumptions_on_binders_any() { self.destructure_solver_region_constraints_for_regionck(outlives_env); } diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 41ce4fb759dac..6f80c09bd76ed 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -10,14 +10,15 @@ use rustc_errors::ColorConfig; use rustc_errors::emitter::HumanReadableErrorType; use rustc_lint_defs::Level; use rustc_session::config::{ - AnnotateMoves, AutoDiff, BranchProtection, CFGuard, Cfg, CodegenRetagOptions, CoverageLevel, - CoverageOptions, DebugInfo, DumpMonoStatsFormat, ErrorOutputType, ExternEntry, ExternLocation, - Externs, FmtDebug, FunctionReturn, IncrementalStateAssertion, InliningThreshold, Input, - InstrumentCoverage, InstrumentMcount, InstrumentMcountOpts, InstrumentXRay, LinkSelfContained, - LinkerPluginLto, LocationDetail, LtoCli, MirIncludeSpans, NextSolverConfig, Offload, Options, - OutFileName, OutputType, OutputTypes, PAuthKey, PacRet, Passes, PatchableFunctionEntry, - Polonius, ProcMacroExecutionStrategy, Strip, SwitchWithOptPath, SymbolManglingVersion, - WasiExecModel, build_session_options, rustc_optgroups, + AnnotateMoves, AssumptionsOnBinders, AutoDiff, BranchProtection, CFGuard, Cfg, + CodegenRetagOptions, CoverageLevel, CoverageOptions, DebugInfo, DumpMonoStatsFormat, + ErrorOutputType, ExternEntry, ExternLocation, Externs, FmtDebug, FunctionReturn, + IncrementalStateAssertion, InliningThreshold, Input, InstrumentCoverage, InstrumentMcount, + InstrumentMcountOpts, InstrumentXRay, LinkSelfContained, LinkerPluginLto, LocationDetail, + LtoCli, MirIncludeSpans, NextSolverConfig, Offload, Options, OutFileName, OutputType, + OutputTypes, PAuthKey, PacRet, Passes, PatchableFunctionEntry, Polonius, + ProcMacroExecutionStrategy, Strip, SwitchWithOptPath, SymbolManglingVersion, WasiExecModel, + build_session_options, rustc_optgroups, }; use rustc_session::search_paths::SearchPath; use rustc_session::utils::{CanonicalizedPath, NativeLib}; @@ -950,7 +951,14 @@ fn test_assumptions_on_binders_enables_next_solver_globally() { // `-Zassumptions-on-binders` alone enables the next solver globally. let matches = optgroups().parse(&["-Zassumptions-on-binders".to_string()]).unwrap(); let opts = build_session_options(&mut early_dcx, &matches); - assert!(opts.unstable_opts.assumptions_on_binders); + assert_eq!(opts.unstable_opts.assumptions_on_binders, AssumptionsOnBinders::All); + assert_eq!(opts.unstable_opts.next_solver, globally); + + // The minimal coroutine mode also requires the next solver globally. + let matches = + optgroups().parse(&["-Zassumptions-on-binders=min_coroutines".to_string()]).unwrap(); + let opts = build_session_options(&mut early_dcx, &matches); + assert_eq!(opts.unstable_opts.assumptions_on_binders, AssumptionsOnBinders::MinCoroutines); assert_eq!(opts.unstable_opts.next_solver, globally); // Flag order must not matter when both `-Zassumptions-on-binders` and `-Znext-solver` @@ -961,7 +969,7 @@ fn test_assumptions_on_binders_enables_next_solver_globally() { ] { let matches = optgroups().parse(&args).unwrap(); let opts = build_session_options(&mut early_dcx, &matches); - assert!(opts.unstable_opts.assumptions_on_binders); + assert_eq!(opts.unstable_opts.assumptions_on_binders, AssumptionsOnBinders::All); assert_eq!(opts.unstable_opts.next_solver, globally); } @@ -974,7 +982,7 @@ fn test_assumptions_on_binders_enables_next_solver_globally() { ] { let matches = optgroups().parse(&args).unwrap(); let opts = build_session_options(&mut early_dcx, &matches); - assert!(opts.unstable_opts.assumptions_on_binders); + assert_eq!(opts.unstable_opts.assumptions_on_binders, AssumptionsOnBinders::All); assert_eq!(opts.unstable_opts.next_solver, globally); } } diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 5ff5c05de734a..75622a8284d96 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -2827,8 +2827,22 @@ impl<'tcx> TyCtxt<'tcx> { || self.sess.opts.unstable_opts.typing_mode_post_typeck_until_borrowck } - pub fn assumptions_on_binders(self) -> bool { - self.sess.opts.unstable_opts.assumptions_on_binders + /// Whether any `-Zassumptions-on-binders` mode is enabled. Use this to gate + /// the shared machinery, e.g. tracking region constraints in the solver. + pub fn assumptions_on_binders_any(self) -> bool { + self.sess.opts.unstable_opts.assumptions_on_binders.any_is_enabled() + } + + /// Whether the full `-Zassumptions-on-binders` mode is enabled, deducing + /// assumptions from every binder. + pub fn assumptions_on_binders_full(self) -> bool { + self.sess.opts.unstable_opts.assumptions_on_binders.is_full() + } + + /// Whether `-Zassumptions-on-binders=min_coroutines` is enabled, deducing + /// assumptions only from coroutine-witness binders. + pub fn assumptions_on_binders_min_coroutines(self) -> bool { + self.sess.opts.unstable_opts.assumptions_on_binders.is_min_coroutines() } pub fn is_impl_trait_in_trait(self, def_id: DefId) -> bool { diff --git a/compiler/rustc_middle/src/ty/context/impl_interner.rs b/compiler/rustc_middle/src/ty/context/impl_interner.rs index 202991d3f0ada..56d16d9e75043 100644 --- a/compiler/rustc_middle/src/ty/context/impl_interner.rs +++ b/compiler/rustc_middle/src/ty/context/impl_interner.rs @@ -379,8 +379,16 @@ impl<'tcx> Interner for TyCtxt<'tcx> { self.features() } - fn assumptions_on_binders(self) -> bool { - self.assumptions_on_binders() + fn assumptions_on_binders_any(self) -> bool { + self.assumptions_on_binders_any() + } + + fn assumptions_on_binders_full(self) -> bool { + self.assumptions_on_binders_full() + } + + fn assumptions_on_binders_min_coroutines(self) -> bool { + self.assumptions_on_binders_min_coroutines() } fn renormalize_rigid_aliases(self) -> bool { diff --git a/compiler/rustc_next_trait_solver/src/canonical/mod.rs b/compiler/rustc_next_trait_solver/src/canonical/mod.rs index 0d8620c3614a2..2c8c784be76a4 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/mod.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/mod.rs @@ -163,20 +163,7 @@ where let prev_universe = delegate.universe(); let universes_created_in_query = response.max_universe.index(); for _ in 0..universes_created_in_query { - let new_universe = delegate.create_next_universe(); - if delegate.cx().assumptions_on_binders() { - // FIXME(-Zassumptions-on-binders): Remove this temporary workaround once - // opaque types no longer escape query responses with query-created placeholders. - // Region constraints involving query-created placeholders were handled inside - // the query. However, the placeholders can still escape in other response - // fields, such as opaque type constraints. To avoid triggering - // assertions, we explicitly insert empty assumptions for the - // recreated universes here. - delegate.insert_placeholder_assumptions( - new_universe, - Some(rustc_type_ir::region_constraint::Assumptions::empty()), - ); - } + create_next_universe_with_placeholder_assumptions(delegate); } compute_query_response_instantiation_values_in_universe( @@ -188,6 +175,25 @@ where ) } +fn create_next_universe_with_placeholder_assumptions(delegate: &D) +where + D: SolverDelegate, + I: Interner, +{ + let new_universe = delegate.create_next_universe(); + if delegate.cx().assumptions_on_binders_any() { + // FIXME(-Zassumptions-on-binders): Remove this temporary workaround once opaque types no + // longer escape query responses with query-created placeholders. Region constraints + // involving query-created placeholders were handled inside the query, but placeholders can + // still escape in other response fields. These contextless universes use empty assumptions: + // they cannot discharge constraints, but allow them to propagate back to their source. + delegate.insert_placeholder_assumptions( + new_universe, + Some(rustc_type_ir::region_constraint::Assumptions::empty()), + ); + } +} + fn compute_query_response_instantiation_values_in_universe( delegate: &D, original_values: &[I::GenericArg], @@ -591,7 +597,7 @@ where // and the previous instantiation, extend `orig_values` for it. let max_universe = prev_universe + state.max_universe.index(); while delegate.universe() < max_universe { - delegate.create_next_universe(); + create_next_universe_with_placeholder_assumptions(delegate); } orig_values.extend( state.value.var_values.var_values.as_slice()[orig_values.len()..] diff --git a/compiler/rustc_next_trait_solver/src/placeholder.rs b/compiler/rustc_next_trait_solver/src/placeholder.rs index 83b2eb6ac6295..94a34208657fb 100644 --- a/compiler/rustc_next_trait_solver/src/placeholder.rs +++ b/compiler/rustc_next_trait_solver/src/placeholder.rs @@ -67,7 +67,7 @@ where current_index: _, } = replacer; - if infcx.cx().assumptions_on_binders() { + if infcx.cx().assumptions_on_binders_any() { for (old, new) in old_universes.into_iter().zip(universe_indices.iter()) { if let (None, Some(new)) = (old, new) { // FIXME(-Zassumptions-on-binders): `replace_bound_vars` does not have enough 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 04d1376d20b9f..ea9d88df19438 100644 --- a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs @@ -14,6 +14,7 @@ use tracing::instrument; use super::assembly::{Candidate, structural_traits}; use crate::delegate::SolverDelegate; +use crate::solve::eval_ctxt::ForallBinderKind; use crate::solve::{ BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource, NoSolution, assembly, }; @@ -267,19 +268,24 @@ 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_with_assumptions(constituent_tys, goal.param_env, |ecx, tys| { - ecx.add_goals( - GoalSource::ImplWhereBound, - tys.into_iter().map(|ty| { - goal.with( - cx, - ty::ClauseKind::HostEffect( - goal.predicate.with_replaced_self_ty(cx, ty), - ), - ) - }), - ) - })?; + ecx.enter_forall_with_assumptions( + constituent_tys, + goal.param_env, + ForallBinderKind::for_self_ty::(self_ty), + |ecx, tys| { + ecx.add_goals( + GoalSource::ImplWhereBound, + tys.into_iter().map(|ty| { + goal.with( + cx, + ty::ClauseKind::HostEffect( + goal.predicate.with_replaced_self_ty(cx, ty), + ), + ) + }), + ) + }, + )?; ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) }) diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index 60813f00dd4c9..ce05d5e52f815 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 @@ -51,6 +51,22 @@ pub mod fast_path; mod probe; mod solver_region_constraints; +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub(super) enum ForallBinderKind { + Other, + CoroutineWitness, +} + +impl ForallBinderKind { + pub(super) fn for_self_ty(self_ty: I::Ty) -> Self { + if matches!(self_ty.kind(), ty::CoroutineWitness(..)) { + Self::CoroutineWitness + } else { + Self::Other + } + } +} + /// The kind of goal we're currently proving. /// /// This has effects on cycle handling handling and on how we compute @@ -886,7 +902,7 @@ where ) -> QueryResultOrRerunNonErased { let Goal { param_env, predicate } = goal; let kind = predicate.kind(); - self.enter_forall_with_assumptions(kind, param_env, |ecx, kind| { + self.enter_forall_with_assumptions(kind, param_env, ForallBinderKind::Other, |ecx, kind| { Ok(match kind { ty::PredicateKind::Clause(ty::ClauseKind::Trait(predicate)) => { ecx.compute_trait_goal(Goal { param_env, predicate }).map(|(r, _via)| r)? @@ -1285,11 +1301,15 @@ where &mut self, value: ty::Binder, param_env: I::ParamEnv, + binder_kind: ForallBinderKind, f: impl FnOnce(&mut Self, T) -> U, ) -> U { self.delegate.enter_forall_without_assumptions(value, |value| { let u = self.delegate.universe(); - let assumptions = if self.cx().assumptions_on_binders() { + let assumptions = if self.cx().assumptions_on_binders_full() + || (self.cx().assumptions_on_binders_min_coroutines() + && binder_kind == ForallBinderKind::CoroutineWitness) + { self.region_assumptions_for_placeholders_in_universe(value.clone(), u, param_env) } else { None @@ -1533,7 +1553,7 @@ where previous call to `try_evaluate_added_goals!`" ); - let goals_certainty = match self.delegate.cx().assumptions_on_binders() { + let goals_certainty = match self.delegate.cx().assumptions_on_binders_any() { true => { let certainty = self.eagerly_handle_placeholders()?; certainty.and(goals_certainty) @@ -1662,7 +1682,7 @@ 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 self.cx().assumptions_on_binders() { + let region_constraints = if self.cx().assumptions_on_binders_any() { ExternalRegionConstraints::NextGen(if let Certainty::Yes = certainty { let constraint = self.delegate.get_solver_region_constraint(); debug_assert_eq!( diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs index 5a4daa5e44fc5..84609fe78d53d 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs @@ -4,16 +4,14 @@ 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::{ - And, Assumptions, LeafRegionConstraint, Or, eagerly_handle_placeholders_in_universe, - propagate_ambiguity, + Assumptions, eagerly_handle_placeholders_in_universe, propagate_ambiguity, }; use rustc_type_ir::{ - AliasTy, Binder, ClauseKind, InferCtxtLike, Interner, OutlivesClause, Region, TypeVisitable, - TypeVisitableExt, TypeVisitor, UniverseIndex, max_universe, + ClauseKind, InferCtxtLike, Interner, OutlivesClause, TypeVisitable, TypeVisitableExt, + TypeVisitor, UniverseIndex, max_universe, }; use tracing::{debug, instrument}; @@ -36,7 +34,7 @@ where u: UniverseIndex, param_env: I::ParamEnv, ) -> Option> { - assert!(self.cx().assumptions_on_binders()); + assert!(self.cx().assumptions_on_binders_any()); struct RawAssumptions<'a, 'b, D: SolverDelegate, I: Interner> { ecx: &'a mut EvalCtxt<'b, D, I>, @@ -150,66 +148,4 @@ where 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: Region) -> Or { - let mut components = Default::default(); - push_outlives_components(self.cx(), ty, &mut components); - self.destructure_components(&components, r) - } - - fn destructure_components(&mut self, components: &[Component], r: Region) -> Or { - components - .into_iter() - .fold(Or::new_true(), |acc, c| Or::build_and(acc, self.destructure_component(c, r))) - } - - fn destructure_component(&mut self, c: &Component, r: Region) -> Or { - use Component::*; - use LeafRegionConstraint::*; - match c { - Region(c_r) => Or::new_leaf(RegionOutlives(*c_r, r, ())), - Placeholder(p) => { - Or::new_leaf(PlaceholderTyOutlives(Ty::new_placeholder(self.cx(), *p), r, ())) - } - Alias(_, alias) => self.destructure_alias_outlives(*alias, r), - UnresolvedInferenceVariable(_) => Or::new_ambig(()), - 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 [`LeafRegionConstraint::AliasTyOutlivesViaEnv`]. - #[instrument(level = "debug", skip(self), ret)] - fn destructure_alias_outlives(&mut self, alias: AliasTy, r: Region) -> Or { - use LeafRegionConstraint::*; - - let item_bounds = - rustc_type_ir::outlives::declared_bounds_from_definition(self.cx(), alias) - .map(|bound| And::new([RegionOutlives(bound, r, ())])); - let item_bound_outlives = Or::new(item_bounds); - - let where_clause_outlives = - Or::new_leaf(AliasTyOutlivesViaEnv(Binder::dummy((alias, r)), ())); - - let mut components = Default::default(); - rustc_type_ir::outlives::compute_alias_components_recursive( - self.cx(), - alias, - &mut components, - ); - let components_outlives = self.destructure_components(&components, r); - - let assumption_outlives = Or::build_or(item_bound_outlives, where_clause_outlives); - Or::build_or(assumption_outlives, components_outlives) - } } diff --git a/compiler/rustc_next_trait_solver/src/solve/mod.rs b/compiler/rustc_next_trait_solver/src/solve/mod.rs index 8d20bcf4c7a6d..451224a627d36 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -92,11 +92,17 @@ where let ty::OutlivesClause(ty, lt) = goal.predicate; let ty = self.normalize(GoalSource::Misc, goal.param_env, ty::Unnormalized::new_wip(ty))?; - if self.cx().assumptions_on_binders() { - use rustc_type_ir::region_constraint::RegionConstraint; - - let constraint = self.destructure_type_outlives(ty, lt); - self.register_solver_region_constraint(RegionConstraint::new_from_or(constraint)); + if self.cx().assumptions_on_binders_any() { + use rustc_type_ir::region_constraint::{ + LeafRegionConstraint, RegionConstraint, destructure_type_outlives, + }; + + let constraint = if self.cx().assumptions_on_binders_min_coroutines() { + RegionConstraint::new_leaf(LeafRegionConstraint::TypeOutlives(ty, lt, ())) + } else { + RegionConstraint::new_from_or(destructure_type_outlives(self.cx(), ty, lt, ())) + }; + self.register_solver_region_constraint(constraint); } else { self.register_ty_outlives(ty, lt); } @@ -120,7 +126,7 @@ where ) -> QueryResultOrRerunNonErased { let ty::OutlivesClause(a, b) = goal.predicate; - if self.cx().assumptions_on_binders() { + if self.cx().assumptions_on_binders_any() { use rustc_type_ir::region_constraint::{LeafRegionConstraint, RegionConstraint}; let constraint = 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 51ec0bcbf5ec2..2aa8a589ba60c 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -21,6 +21,7 @@ use crate::solve::assembly::structural_traits::{self, AsyncCallableRelevantTypes use crate::solve::assembly::{ self, AllowInferenceConstraints, AssembleCandidatesFrom, Candidate, FailedCandidateInfo, }; +use crate::solve::eval_ctxt::ForallBinderKind; use crate::solve::inspect::ProbeKind; use crate::solve::{ BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource, MaybeCause, @@ -1118,6 +1119,7 @@ where ecx.enter_forall_with_assumptions( target_projection, param_env, + ForallBinderKind::Other, |ecx, target_projection| { let source_projection = ecx.instantiate_binder_with_infer(source_projection); @@ -1145,6 +1147,7 @@ where ecx.enter_forall_with_assumptions( target_principal, param_env, + ForallBinderKind::Other, |ecx, target_principal| { let source_principal = ecx.instantiate_binder_with_infer(source_principal); @@ -1180,6 +1183,7 @@ where ecx.enter_forall_with_assumptions( target_projection, param_env, + ForallBinderKind::Other, |ecx, target_projection| { let source_projection = ecx.instantiate_binder_with_infer(matching); ecx.eq(param_env, source_projection, target_projection)?; @@ -1411,9 +1415,11 @@ where ) -> Result>, NoSolution>, ) -> Result, NoSolutionOrRerunNonErased> { self.probe_trait_candidate(source).enter(|ecx| { + let self_ty = goal.predicate.self_ty(); let goals = ecx.enter_forall_with_assumptions( - constituent_tys(ecx, goal.predicate.self_ty())?, + constituent_tys(ecx, self_ty)?, goal.param_env, + ForallBinderKind::for_self_ty::(self_ty), |ecx, tys| { tys.into_iter() .map(|ty| { diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index d30cc29e66298..ca8e05b1035da 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -1023,6 +1023,35 @@ impl ExternEntry { } } +/// The behavior selected by `-Zassumptions-on-binders`. +#[derive(Debug, Copy, Clone, Default, Hash, PartialEq, Eq)] +pub enum AssumptionsOnBinders { + /// Do not deduce outlives assumptions when entering binders. + #[default] + Disabled, + /// Deduce outlives assumptions from every binder. + All, + /// Deduce outlives assumptions only from coroutine-witness binders. + MinCoroutines, +} + +impl AssumptionsOnBinders { + /// Whether any kind of assumptions-on-binders handling is enabled. This is + /// `true` for both the full mode and the minimal coroutine mode. + pub fn any_is_enabled(self) -> bool { + self != AssumptionsOnBinders::Disabled + } + + /// Whether the full mode is enabled, deducing assumptions from every binder. + pub fn is_full(self) -> bool { + self == AssumptionsOnBinders::All + } + + pub fn is_min_coroutines(self) -> bool { + self == AssumptionsOnBinders::MinCoroutines + } +} + #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct NextSolverConfig { /// Whether the new trait solver should be enabled in coherence. @@ -2701,7 +2730,7 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M // `-Zassumptions-on-binders` requires the next trait solver globally. Normalize after // parsing so the effective config is independent of flag order and so consumers that // read `next_solver.globally` directly (e.g. feature-gate checks) see the right value. - if unstable_opts.assumptions_on_binders { + if unstable_opts.assumptions_on_binders.any_is_enabled() { // `NextSolverConfig::default()` has `coherence: true`; the only way `coherence` is // false here is an explicit `-Znext-solver=no`. if !unstable_opts.next_solver.coherence { @@ -3330,11 +3359,11 @@ pub(crate) mod dep_tracking { }; use super::{ - AnnotateMoves, AutoDiff, BranchProtection, CFGuard, CFProtection, CodegenRetagOptions, - CoverageOptions, CrateType, DebugInfo, DebugInfoCompression, ErrorOutputType, FmtDebug, - FunctionReturn, InliningThreshold, InstrumentCoverage, InstrumentMcount, - InstrumentMcountOpts, InstrumentXRay, LinkerPluginLto, LocationDetail, LtoCli, - MirStripDebugInfo, NextSolverConfig, Offload, OptLevel, OutFileName, OutputType, + AnnotateMoves, AssumptionsOnBinders, AutoDiff, BranchProtection, CFGuard, CFProtection, + CodegenRetagOptions, CoverageOptions, CrateType, DebugInfo, DebugInfoCompression, + ErrorOutputType, FmtDebug, FunctionReturn, InliningThreshold, InstrumentCoverage, + InstrumentMcount, InstrumentMcountOpts, InstrumentXRay, LinkerPluginLto, LocationDetail, + LtoCli, MirStripDebugInfo, NextSolverConfig, Offload, OptLevel, OutFileName, OutputType, OutputTypes, PatchableFunctionEntry, PointerAuthOption, Polonius, ResolveDocLinks, SourceFileHashAlgorithm, SplitDwarfKind, SwitchWithOptPath, SymbolManglingVersion, WasiExecModel, @@ -3381,6 +3410,7 @@ pub(crate) mod dep_tracking { impl_dep_tracking_hash_via_hash!( (), AnnotateMoves, + AssumptionsOnBinders, AutoDiff, Offload, bool, diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 8fd9c4da967dc..1f114d65f94e5 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -855,6 +855,8 @@ mod desc { pub(crate) const parse_instrument_xray: &str = "either a boolean (`yes`, `no`, `on`, `off`, etc), or a comma separated list of settings: `always` or `never` (mutually exclusive), `ignore-loops`, `instruction-threshold=N`, `skip-entry`, `skip-exit`"; pub(crate) const parse_unpretty: &str = "`string` or `string=string`"; pub(crate) const parse_treat_err_as_bug: &str = "either no value or a non-negative number"; + pub(crate) const parse_assumptions_on_binders: &str = + "either a boolean (`yes`, `no`, `on`, `off`, etc), or `min_coroutines`"; pub(crate) const parse_next_solver_config: &str = "either `globally` (when used without an argument), `coherence` (default) or `no`"; pub(crate) const parse_lto: &str = @@ -959,6 +961,19 @@ pub mod parse { } } + pub(crate) fn parse_assumptions_on_binders( + slot: &mut AssumptionsOnBinders, + v: Option<&str>, + ) -> bool { + *slot = match v { + Some("y") | Some("yes") | Some("on") | Some("true") | None => AssumptionsOnBinders::All, + Some("n") | Some("no") | Some("off") | Some("false") => AssumptionsOnBinders::Disabled, + Some("min_coroutines") => AssumptionsOnBinders::MinCoroutines, + Some(_) => return false, + }; + true + } + /// Use this for any boolean option that lacks a static default. (The /// actions taken when such an option is not specified will depend on /// other factors, such as other options, or target options.) @@ -2378,9 +2393,10 @@ 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>`); \ - implies `-Znext-solver=globally`"), + assumptions_on_binders: AssumptionsOnBinders = (AssumptionsOnBinders::Disabled, + parse_assumptions_on_binders, [TRACKED], + "allow deducing higher-ranked outlives assumptions from all binders (`for<'a>`), or only \ + coroutine-witness binders with `min_coroutines`; implies `-Znext-solver=globally`"), autodiff: Vec = (Vec::new(), parse_autodiff, [TRACKED], "a list of autodiff flags to enable Mandatory setting: diff --git a/compiler/rustc_trait_selection/src/solve/delegate.rs b/compiler/rustc_trait_selection/src/solve/delegate.rs index 818d8e1a4e0c3..4f8c27cc213c1 100644 --- a/compiler/rustc_trait_selection/src/solve/delegate.rs +++ b/compiler/rustc_trait_selection/src/solve/delegate.rs @@ -155,7 +155,7 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< use ComputeGoalFastPathOutcome as Outcome; // FIXME(-Zassumptions-on-binders): actually handle fast path - if self.tcx.assumptions_on_binders() { + if self.tcx.assumptions_on_binders_any() { return Outcome::NoFastPath; } diff --git a/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs b/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs index 59336396ab11c..f1e794e08c0e3 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs @@ -68,7 +68,7 @@ pub(super) fn fulfillment_error_for_no_solution<'tcx>( } ty::PredicateKind::Clause( ty::ClauseKind::RegionOutlives(_) | ty::ClauseKind::TypeOutlives(_), - ) if infcx.tcx.assumptions_on_binders() => FulfillmentErrorCode::Outlives, + ) if infcx.tcx.assumptions_on_binders_any() => FulfillmentErrorCode::Outlives, ty::PredicateKind::Clause(_) | ty::PredicateKind::DynCompatible(_) | ty::PredicateKind::Ambiguous => { diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index 31a027c15fd01..71f9dd5fd7a11 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -353,7 +353,14 @@ pub trait Interner: type Features: Features; fn features(self) -> Self::Features; - fn assumptions_on_binders(self) -> bool; + /// Whether any `-Zassumptions-on-binders` mode is enabled. + fn assumptions_on_binders_any(self) -> bool; + + /// Whether the full `-Zassumptions-on-binders` mode is enabled. + fn assumptions_on_binders_full(self) -> bool; + + /// Whether `-Zassumptions-on-binders=min_coroutines` is enabled. + fn assumptions_on_binders_min_coroutines(self) -> bool; fn renormalize_rigid_aliases(self) -> bool; diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 9a643b538d93f..78544f7d174cf 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -49,6 +49,7 @@ impl Default for TransitiveRelationBuilder { use crate::data_structures::IndexMap; use crate::fold::TypeSuperFoldable; use crate::inherent::*; +use crate::outlives::{Component, push_outlives_components}; use crate::relate::{Relate, RelateResult, TypeRelation, VarianceDiagInfo}; use crate::{ AliasTy, Binder, BoundRegion, BoundVar, BoundVariableKind, DebruijnIndex, InferCtxtLike, @@ -96,6 +97,11 @@ impl Assumptions { pub enum LeafRegionConstraint { Ambiguity(S), RegionOutlives(Region, Region, S), + /// A type-outlives constraint which has not yet been decomposed into its constituent parts. + /// + /// The minimal coroutine mode keeps these intact until region checking so that enabling the + /// mode does not strengthen the eager leak check. + TypeOutlives(I::Ty, Region, S), /// 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. @@ -128,6 +134,7 @@ impl LeafRegionConstraint { match self { Ambiguity(()) => Ambiguity(span), RegionOutlives(r1, r2, ()) => RegionOutlives(r1, r2, span), + TypeOutlives(ty, r, ()) => TypeOutlives(ty, r, span), AliasTyOutlivesViaEnv(bound_outlives, ()) => { AliasTyOutlivesViaEnv(bound_outlives, span) } @@ -143,6 +150,7 @@ impl LeafRegionC match self { Ambiguity(_) => Ambiguity(()), RegionOutlives(r1, r2, _) => RegionOutlives(r1, r2, ()), + TypeOutlives(ty, r, _) => TypeOutlives(ty, r, ()), AliasTyOutlivesViaEnv(bound_outlives, _) => AliasTyOutlivesViaEnv(bound_outlives, ()), PlaceholderTyOutlives(ty, r, _) => PlaceholderTyOutlives(ty, r, ()), } @@ -153,6 +161,7 @@ impl LeafRegionC let (Ambiguity(s) | RegionOutlives(_, _, s) + | TypeOutlives(_, _, s) | AliasTyOutlivesViaEnv(_, s) | PlaceholderTyOutlives(_, _, s)) = self; s.clone() @@ -426,7 +435,7 @@ impl LeafRegionConstraint { } /// Takes any constraints involving placeholders from the current universe and eagerly checks them. -/// This can be done a few ways: +/// Full assumptions-on-binders mode can do this 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 @@ -438,6 +447,10 @@ impl LeafRegionConstraint { /// 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`. +/// +/// For universes with explicit assumptions, minimal coroutine mode only removes constraints +/// directly implied by them. It leaves every other constraint unchanged so it can be checked in +/// the root inference context. Universes without assumptions use the ordinary eager leak check. #[instrument(level = "debug", skip(infcx), ret)] pub fn eagerly_handle_placeholders_in_universe, I: Interner>( infcx: &Infcx, @@ -446,6 +459,12 @@ pub fn eagerly_handle_placeholders_in_universe RegionConstraint { let assumptions = infcx.get_placeholder_assumptions(u); + if infcx.cx().assumptions_on_binders_min_coroutines() + && let Some(assumptions) = assumptions.as_ref() + { + return drop_constraints_satisfied_by_assumptions(infcx, constraint, u, assumptions); + } + // 1. rewrite type outlives constraints involving things from `u` into either region constraints // involving things from `u` or type outlives constraints not involving things from `u` // @@ -470,6 +489,56 @@ pub fn eagerly_handle_placeholders_in_universe, I: Interner>( + infcx: &Infcx, + constraint: RegionConstraint, + u: UniverseIndex, + assumptions: &Assumptions, +) -> RegionConstraint { + use LeafRegionConstraint::*; + + let region_outlives = |r1, r2| regions_outlived_by(r1, assumptions).any(|r| r == r2); + let type_outlives = |ty, r| { + assumptions.type_outlives.iter().any(|assumption| { + let Some(OutlivesClause(assumed_ty, assumed_r)) = assumption.no_bound_vars() else { + return false; + }; + assumed_ty == ty && region_outlives(assumed_r, r) + }) + }; + let is_satisfied = |constraint: &LeafRegionConstraint| { + // Constraints retained while leaving an inner universe may still mention that universe. + // The assumptions for `u` cannot be used to discharge those constraints. + if max_universe(infcx, constraint.clone()) != u { + return false; + } + + match constraint { + RegionOutlives(r1, r2, ()) => region_outlives(*r1, *r2), + TypeOutlives(ty, r, ()) | PlaceholderTyOutlives(ty, r, ()) => type_outlives(*ty, *r), + Ambiguity(()) | AliasTyOutlivesViaEnv(..) => false, + } + }; + + let has_satisfied_constraint = constraint + .and_constraint + .0 + .iter() + .chain(constraint.or_constraint.0.iter().flat_map(|and| and.0.iter())) + .any(is_satisfied); + if !has_satisfied_constraint { + return constraint; + } + + let filter_and = |and: And| And::new(and.0.into_iter().filter(|c| !is_satisfied(c))); + let and_constraint = filter_and(constraint.and_constraint); + let or_ands: Vec<_> = constraint.or_constraint.0.into_iter().map(filter_and).collect(); + let or_constraint = + if or_ands.iter().any(|and| and.0.is_empty()) { Or::new_true() } else { Or::new(or_ands) }; + + RegionConstraint::new_from_or(Or::build_and(Or::new([and_constraint]), or_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 @@ -492,9 +561,10 @@ fn compute_new_region_constraints, I: Interne and: &And| { for c in &and.0 { match c { - Ambiguity(()) | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) => { - constraints.push(c.clone()) - } + Ambiguity(()) + | TypeOutlives(..) + | PlaceholderTyOutlives(..) + | AliasTyOutlivesViaEnv(..) => constraints.push(c.clone()), RegionOutlives(r1, r2, ()) => { regions.insert(*r1); regions.insert(*r2); @@ -640,7 +710,10 @@ fn pull_region_outlives_constraints_out_of_universe< let mut pulled_constraints = Vec::new(); for c in and.0 { match c { - Ambiguity(()) | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) => { + Ambiguity(()) + | TypeOutlives(..) + | PlaceholderTyOutlives(..) + | AliasTyOutlivesViaEnv(..) => { assert!(max_universe(infcx, c.clone()) < u); pulled_constraints.push(Or::new_leaf(c.clone())); } @@ -696,10 +769,83 @@ fn pull_region_outlives_constraints_out_of_universe< RegionConstraint::new_from_or(Or::build_and(and_constraint, or_constraint)) } -/// Converts type outlives constraints into region outlives constraints. This assumes the *complete* set of -/// assumptions are known. This should not be called until the end of type checking. -/// -/// The returned region constraint will not have *any* PlaceholderTyOutlives or AliasTyOutlivesViaEnv constraints. +/// Converts a type-outlives constraint into constraints for the components of the type. +#[instrument(level = "debug", skip(cx), ret)] +pub fn destructure_type_outlives( + cx: I, + ty: I::Ty, + r: Region, + span: S, +) -> Or +where + S: Clone + std::fmt::Debug + Eq + std::hash::Hash, +{ + let mut components = Default::default(); + push_outlives_components(cx, ty, &mut components); + destructure_type_outlives_components(cx, &components, r, span) +} + +fn destructure_type_outlives_components( + cx: I, + components: &[Component], + r: Region, + span: S, +) -> Or +where + S: Clone + std::fmt::Debug + Eq + std::hash::Hash, +{ + components.into_iter().fold(Or::new_true(), |acc, component| { + Or::build_and(acc, destructure_type_outlives_component(cx, component, r, span.clone())) + }) +} + +fn destructure_type_outlives_component( + cx: I, + component: &Component, + r: Region, + span: S, +) -> Or +where + S: Clone + std::fmt::Debug + Eq + std::hash::Hash, +{ + use LeafRegionConstraint::*; + + match component { + Component::Region(component_r) => Or::new_leaf(RegionOutlives(*component_r, r, span)), + Component::Param(param) => { + Or::new_leaf(PlaceholderTyOutlives(Ty::new_param(cx, *param), r, span)) + } + Component::Placeholder(placeholder) => { + Or::new_leaf(PlaceholderTyOutlives(Ty::new_placeholder(cx, *placeholder), r, span)) + } + Component::Alias(_, alias) => { + let item_bound_outlives = Or::new( + crate::outlives::declared_bounds_from_definition(cx, *alias) + .map(|bound| And::new([RegionOutlives(bound, r, span.clone())])), + ); + let where_clause_outlives = + Or::new_leaf(AliasTyOutlivesViaEnv(Binder::dummy((*alias, r)), span.clone())); + + let mut components = Default::default(); + crate::outlives::compute_alias_components_recursive(cx, *alias, &mut components); + let components_outlives = + destructure_type_outlives_components(cx, &components, r, span); + + Or::build_or( + Or::build_or(item_bound_outlives, where_clause_outlives), + components_outlives, + ) + } + Component::UnresolvedInferenceVariable(_) => Or::new_ambig(span), + Component::EscapingAlias(components) => { + destructure_type_outlives_components(cx, components, r, span) + } + } +} + +/// Converts all type-outlives constraints at the end of type checking, once the complete set of +/// assumptions is known. The returned constraint has no `TypeOutlives`, +/// `PlaceholderTyOutlives`, or `AliasTyOutlivesViaEnv` leaves. #[instrument(level = "debug", skip(infcx), ret)] pub fn destructure_type_outlives_constraints_in_root< Infcx: InferCtxtLike, @@ -720,6 +866,22 @@ pub fn destructure_type_outlives_constraints_in_root< Ambiguity(_) | RegionOutlives(..) => { destructured_constraints.push(Or::new_leaf(c.clone())) } + TypeOutlives(ty, r, span) => { + let constraint = RegionConstraint::new_from_or(destructure_type_outlives( + infcx.cx(), + *ty, + *r, + span.clone(), + )); + destructured_constraints.push( + destructure_type_outlives_constraints_in_root( + infcx, + constraint, + assumptions, + ) + .splatted_and_constraints(), + ); + } PlaceholderTyOutlives(ty, r, span) => destructured_constraints.push(Or::new( regions_outlived_by_placeholder(*ty, assumptions, infcx.cx()).map( move |assumption_r| { @@ -791,6 +953,23 @@ fn rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling< for c in and.0 { match c { Ambiguity(()) | RegionOutlives(..) => rewritten_constraints.push(Or::new_leaf(c)), + TypeOutlives(ty, region, ()) => { + let constraint = RegionConstraint::new_from_or(destructure_type_outlives( + infcx.cx(), + ty, + region, + (), + )); + rewritten_constraints.push( + rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling( + infcx, + constraint, + u, + assumptions, + ) + .splatted_and_constraints(), + ); + } PlaceholderTyOutlives(ty, region, ()) => { rewritten_constraints.push(rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder_handling(infcx, ty, region, u, assumptions)); } @@ -1175,14 +1354,20 @@ impl<'a, Infcx: InferCtxtLike, I: Interner> TypeRelation { self.infcx.enter_forall_with_empty_assumptions(a, |a| { let u = self.infcx.universe(); - self.infcx.insert_placeholder_assumptions(u, Some(Assumptions::empty())); + self.infcx.insert_placeholder_assumptions( + u, + self.cx().assumptions_on_binders_full().then(Assumptions::empty), + ); let b = self.infcx.instantiate_binder_with_infer(b); self.relate(a, b) })?; self.infcx.enter_forall_with_empty_assumptions(b, |b| { let u = self.infcx.universe(); - self.infcx.insert_placeholder_assumptions(u, Some(Assumptions::empty())); + self.infcx.insert_placeholder_assumptions( + u, + self.cx().assumptions_on_binders_full().then(Assumptions::empty), + ); let a = self.infcx.instantiate_binder_with_infer(a); self.relate(a, b) })?; diff --git a/compiler/rustc_type_ir/src/solve/mod.rs b/compiler/rustc_type_ir/src/solve/mod.rs index d1a24e0054115..a273b1a187dd8 100644 --- a/compiler/rustc_type_ir/src/solve/mod.rs +++ b/compiler/rustc_type_ir/src/solve/mod.rs @@ -638,7 +638,7 @@ impl Eq for ExternalConstraintsData {} impl ExternalConstraintsData { pub fn new(cx: I) -> Self { - let region_constraints = match cx.assumptions_on_binders() { + let region_constraints = match cx.assumptions_on_binders_any() { true => ExternalRegionConstraints::NextGen(RegionConstraint::new_true()), false => ExternalRegionConstraints::Old(vec![]), }; diff --git a/tests/ui/assumptions_on_binders/min-coroutines-alias-outlives.rs b/tests/ui/assumptions_on_binders/min-coroutines-alias-outlives.rs new file mode 100644 index 0000000000000..3ada2d4545412 --- /dev/null +++ b/tests/ui/assumptions_on_binders/min-coroutines-alias-outlives.rs @@ -0,0 +1,47 @@ +//@ compile-flags: -Zassumptions-on-binders=min_coroutines +//@ normalize-stderr: "\[[0-9a-f]{4}\]" -> "[HASH]" + +#![feature(test_binder_constraints)] +#![allow(internal_features)] + +trait Trait { + type Assoc; +} + +// Minimal mode keeps type outlives constraints intact instead of destructuring them into their +// components, so these constraints are retained and left for the root inference context. +// +// The `actual` constraint in the expected output is the point of these tests, so do not normalize +// it away: it is what distinguishes the retained `TypeOutlives` leaf from the OR of item bounds, +// env assumptions and components that eager destructuring would produce. + +// The assumption names the component `T` while the goal names the composite `(T,)`. Destructuring +// eagerly would reduce the goal to its component and discharge it against the assumption, which is +// exactly the strengthening of the eager leak check that this mode avoids. Keeping the constraint +// whole means it is retained instead, so this `expect` clause fails. +core::test_binder_constraints! { + impl { + forall<'a> where T: 'a { + //~^ ERROR forall expect clause failed + where (T,): 'a + } expect {} + } +} + +// FIXME(-Zassumptions-on-binders): the assumption on the binder names the very same alias, so this +// ought to be discharged and the `expect` clause ought to hold. It is not, because the assumption +// is lowered without being normalized and so carries a non-rigid alias, while the goal is +// normalized to a rigid one, and the two do not compare equal. See the FIXME about normalizing +// assumptions in `region_assumptions_for_placeholders_in_universe`. Destructuring the constraint +// eagerly would lose the `TypeOutlives` leaf that this matching needs, which is why minimal mode +// keeps it. +core::test_binder_constraints! { + impl { + forall<'a> where T::Assoc: 'a { + //~^ ERROR forall expect clause failed + where T::Assoc: 'a + } expect {} + } +} + +fn main() {} diff --git a/tests/ui/assumptions_on_binders/min-coroutines-alias-outlives.stderr b/tests/ui/assumptions_on_binders/min-coroutines-alias-outlives.stderr new file mode 100644 index 0000000000000..aa955d52f2398 --- /dev/null +++ b/tests/ui/assumptions_on_binders/min-coroutines-alias-outlives.stderr @@ -0,0 +1,61 @@ +error: forall expect clause failed + --> $DIR/min-coroutines-alias-outlives.rs:24:9 + | +LL | forall<'a> where T: 'a { + | ^^^^^^ + | +note: constraint from here + --> $DIR/min-coroutines-alias-outlives.rs:24:9 + | +LL | forall<'a> where T: 'a { + | ^^^^^^ + = note: expected: And( + [], + ) + = note: actual: And( + [ + TypeOutlives( + (T/#0,), + '!1_0.Named(DefId(0:8 ~ min_coroutines_alias_outlives[HASH]::{test_binder_constraints#0}::'a)), + $DIR/min-coroutines-alias-outlives.rs:24:9: 24:15 (#0), + ), + ], + ) + +error: forall expect clause failed + --> $DIR/min-coroutines-alias-outlives.rs:40:9 + | +LL | forall<'a> where T::Assoc: 'a { + | ^^^^^^ + | +note: constraint from here + --> $DIR/min-coroutines-alias-outlives.rs:40:9 + | +LL | forall<'a> where T::Assoc: 'a { + | ^^^^^^ + = note: expected: And( + [], + ) + = note: actual: And( + [ + TypeOutlives( + Alias( + Yes, + Alias { + kind: Projection { + def_id: DefId(0:4 ~ min_coroutines_alias_outlives[HASH]::Trait::Assoc), + }, + args: [ + T/#0, + ], + .. + }, + ), + '!1_0.Named(DefId(0:11 ~ min_coroutines_alias_outlives[HASH]::{test_binder_constraints#1}::'a)), + $DIR/min-coroutines-alias-outlives.rs:40:9: 40:15 (#0), + ), + ], + ) + +error: aborting due to 2 previous errors + diff --git a/tests/ui/assumptions_on_binders/min-coroutines-only-witness-binders.rs b/tests/ui/assumptions_on_binders/min-coroutines-only-witness-binders.rs new file mode 100644 index 0000000000000..706dd8db35ff1 --- /dev/null +++ b/tests/ui/assumptions_on_binders/min-coroutines-only-witness-binders.rs @@ -0,0 +1,26 @@ +//@ compile-flags: -Zassumptions-on-binders=min_coroutines + +use std::marker::PhantomData; + +struct WellFormed<'a, T: 'a>(PhantomData<&'a T>); + +trait Trait {} + +impl<'a, 'b> Trait for WellFormed<'a, &'b ()> +where + &'b (): 'a, +{ +} + +fn require() +where + for<'a, 'b> WellFormed<'a, &'b ()>: Trait, +{ +} + +fn check() { + require(); + //~^ ERROR type annotations needed: cannot satisfy +} + +fn main() {} diff --git a/tests/ui/assumptions_on_binders/min-coroutines-only-witness-binders.stderr b/tests/ui/assumptions_on_binders/min-coroutines-only-witness-binders.stderr new file mode 100644 index 0000000000000..98abf4674a9a1 --- /dev/null +++ b/tests/ui/assumptions_on_binders/min-coroutines-only-witness-binders.stderr @@ -0,0 +1,27 @@ +error[E0283]: type annotations needed: cannot satisfy `for<'a, 'b> WellFormed<'a, &'b ()>: Trait` + --> $DIR/min-coroutines-only-witness-binders.rs:22:5 + | +LL | require(); + | ^^^^^^^^^ + | + = note: cannot satisfy `for<'a, 'b> WellFormed<'a, &'b ()>: Trait` +help: the trait `Trait` is not implemented for `WellFormed<'a, &'b ()>` + but it is implemented for `WellFormed<'_, &()>` + --> $DIR/min-coroutines-only-witness-binders.rs:9:1 + | +LL | / impl<'a, 'b> Trait for WellFormed<'a, &'b ()> +LL | | where +LL | | &'b (): 'a, + | |_______________^ +note: required by a bound in `require` + --> $DIR/min-coroutines-only-witness-binders.rs:17:41 + | +LL | fn require() + | ------- required by a bound in this function +LL | where +LL | for<'a, 'b> WellFormed<'a, &'b ()>: Trait, + | ^^^^^ required by this bound in `require` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0283`. diff --git a/tests/ui/assumptions_on_binders/min-coroutines-retains-unsatisfied-constraints.rs b/tests/ui/assumptions_on_binders/min-coroutines-retains-unsatisfied-constraints.rs new file mode 100644 index 0000000000000..9f8fc1c1b3814 --- /dev/null +++ b/tests/ui/assumptions_on_binders/min-coroutines-retains-unsatisfied-constraints.rs @@ -0,0 +1,19 @@ +//@ compile-flags: -Zassumptions-on-binders=min_coroutines +//@ normalize-stderr: "\n\n$" -> "\n" + +#![feature(test_binder_constraints)] +#![allow(internal_features)] + +core::test_binder_constraints! { + impl<'a, 'b> { + forall<'w> where 'b: 'w { + //~^ ERROR higher-ranked lifetime bound could not be satisfied + 'b: 'w, + 'a: 'b, + } expect { + 'a: 'b, + } + } +} + +fn main() {} diff --git a/tests/ui/assumptions_on_binders/min-coroutines-retains-unsatisfied-constraints.stderr b/tests/ui/assumptions_on_binders/min-coroutines-retains-unsatisfied-constraints.stderr new file mode 100644 index 0000000000000..b0c8cb3f8a743 --- /dev/null +++ b/tests/ui/assumptions_on_binders/min-coroutines-retains-unsatisfied-constraints.stderr @@ -0,0 +1,7 @@ +error: higher-ranked lifetime bound could not be satisfied + --> $DIR/min-coroutines-retains-unsatisfied-constraints.rs:9:9 + | +LL | forall<'w> where 'b: 'w { + | ^^^^^^ + +error: aborting due to 1 previous error diff --git a/tests/ui/assumptions_on_binders/test-infra-works.rs b/tests/ui/assumptions_on_binders/test-infra-works.rs index d8d64d1aac255..66ec09639659e 100644 --- a/tests/ui/assumptions_on_binders/test-infra-works.rs +++ b/tests/ui/assumptions_on_binders/test-infra-works.rs @@ -1,5 +1,7 @@ //@ check-pass -//@ compile-flags: -Zassumptions-on-binders +//@ revisions: assumptions min_coroutines +//@[assumptions] compile-flags: -Zassumptions-on-binders +//@[min_coroutines] compile-flags: -Zassumptions-on-binders=min_coroutines #![feature(test_binder_constraints, non_lifetime_binders)] #![expect(incomplete_features)] @@ -19,6 +21,7 @@ core::test_binder_constraints! { // FIXME(-Zassumptions-on-binders): this should be `impl<'b, 'c: 'b>`, not // `impl<'b, 'c: 'b + 'static>`, but OR isn't actually implemented yet +#[cfg(assumptions)] core::test_binder_constraints! { impl<'b, 'c: 'b + 'static> { forall<'a> where 'b: 'a { @@ -49,7 +52,15 @@ trait Trait { // `impl` should fail because the constraints asserted in `expect` should fail to prove true. Might // be https://github.com/rust-lang/project-assumptions-on-binders/issues/26 // +// The `expect` clauses of this and the next test assert on the full mode's rewrite of alias +// outlives constraints into lower universes. `min_coroutines` deliberately does not rewrite, it +// only drops constraints directly implied by the binder's assumptions and keeps the rest as they +// are, so the rewritten form is specific to `assumptions`. The retained form cannot be spelled in +// an `expect` clause because it still mentions the binder's own lifetime, so `min_coroutines` +// coverage for aliases lives in `min-coroutines-alias-outlives.rs` instead. +// // for<> syntax does direct insert into constraint storage +#[cfg(assumptions)] core::test_binder_constraints! { impl { forall<'a> { @@ -68,6 +79,7 @@ core::test_binder_constraints! { // be https://github.com/rust-lang/project-assumptions-on-binders/issues/26 // // `where` syntax goes through the full clause destructuring and register_obligation pipeline +#[cfg(assumptions)] core::test_binder_constraints! { impl { forall<'a> { @@ -82,4 +94,27 @@ core::test_binder_constraints! { } } +#[cfg(min_coroutines)] +core::test_binder_constraints! { + impl { + // Minimal mode directly discharges constraints proven by the current binder without + // rewriting either placeholder into a lower universe. + forall<'a, 'b> where 'b: 'a { + 'b: 'a, + } expect {} + } +} + +// Minimal mode discharges a type outlives goal when an assumption names the same type. Note that +// this case alone does not pin down whether the constraint was kept whole or destructured, since a +// bare param is its own only component either way; `min-coroutines-alias-outlives.rs` covers that. +#[cfg(min_coroutines)] +core::test_binder_constraints! { + impl { + forall<'a> where T: 'a { + where T: 'a + } expect {} + } +} + fn main() {} diff --git a/tests/ui/async-await/higher-ranked-auto-trait-1.no_assumptions.stderr b/tests/ui/async-await/higher-ranked-auto-trait-1.no_assumptions.stderr index b298a3bf2153a..9b03bda6b5e53 100644 --- a/tests/ui/async-await/higher-ranked-auto-trait-1.no_assumptions.stderr +++ b/tests/ui/async-await/higher-ranked-auto-trait-1.no_assumptions.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> $DIR/higher-ranked-auto-trait-1.rs:37:5 + --> $DIR/higher-ranked-auto-trait-1.rs:39:5 | LL | / async { LL | | let _y = &(); @@ -10,13 +10,13 @@ LL | | drop(_x); LL | | } | |_____^ one type is more general than the other | - = note: expected `async` block `{async block@$DIR/higher-ranked-auto-trait-1.rs:40:19: 40:29}` - found `async` block `{async block@$DIR/higher-ranked-auto-trait-1.rs:40:19: 40:29}` + = note: expected `async` block `{async block@$DIR/higher-ranked-auto-trait-1.rs:42:19: 42:29}` + found `async` block `{async block@$DIR/higher-ranked-auto-trait-1.rs:42:19: 42:29}` = note: no two async blocks, even if identical, have the same type = help: consider pinning your async block and casting it to a trait object error[E0308]: mismatched types - --> $DIR/higher-ranked-auto-trait-1.rs:37:5 + --> $DIR/higher-ranked-auto-trait-1.rs:39:5 | LL | / async { LL | | let _y = &(); @@ -27,8 +27,8 @@ LL | | drop(_x); LL | | } | |_____^ one type is more general than the other | - = note: expected `async` block `{async block@$DIR/higher-ranked-auto-trait-1.rs:40:19: 40:29}` - found `async` block `{async block@$DIR/higher-ranked-auto-trait-1.rs:40:19: 40:29}` + = note: expected `async` block `{async block@$DIR/higher-ranked-auto-trait-1.rs:42:19: 42:29}` + found `async` block `{async block@$DIR/higher-ranked-auto-trait-1.rs:42:19: 42:29}` = note: no two async blocks, even if identical, have the same type = help: consider pinning your async block and casting it to a trait object = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` diff --git a/tests/ui/async-await/higher-ranked-auto-trait-1.rs b/tests/ui/async-await/higher-ranked-auto-trait-1.rs index 740f7e2924545..7e714fc9cac18 100644 --- a/tests/ui/async-await/higher-ranked-auto-trait-1.rs +++ b/tests/ui/async-await/higher-ranked-auto-trait-1.rs @@ -1,8 +1,10 @@ // Repro for . //@ edition: 2021 -//@ revisions: assumptions no_assumptions +//@ revisions: assumptions min_coroutines no_assumptions //@[assumptions] compile-flags: -Zhigher-ranked-assumptions //@[assumptions] check-pass +//@[min_coroutines] compile-flags: -Zassumptions-on-binders=min_coroutines +//@[min_coroutines] check-pass //@[no_assumptions] known-bug: #110338 use std::future::Future; diff --git a/tests/ui/async-await/higher-ranked-auto-trait-10.assumptions.stderr b/tests/ui/async-await/higher-ranked-auto-trait-10.assumptions.stderr index 6fcf1b1eac176..e2990ad69f2c9 100644 --- a/tests/ui/async-await/higher-ranked-auto-trait-10.assumptions.stderr +++ b/tests/ui/async-await/higher-ranked-auto-trait-10.assumptions.stderr @@ -1,5 +1,5 @@ error: implementation of `Foo` is not general enough - --> $DIR/higher-ranked-auto-trait-10.rs:32:5 + --> $DIR/higher-ranked-auto-trait-10.rs:34:5 | LL | Box::new(async move { get_foo(x).await }) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Foo` is not general enough @@ -8,7 +8,7 @@ LL | Box::new(async move { get_foo(x).await }) = note: ...but `Foo<'2>` is actually implemented for the type `&'2 str`, for some specific lifetime `'2` error: implementation of `Foo` is not general enough - --> $DIR/higher-ranked-auto-trait-10.rs:32:5 + --> $DIR/higher-ranked-auto-trait-10.rs:34:5 | LL | Box::new(async move { get_foo(x).await }) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Foo` is not general enough diff --git a/tests/ui/async-await/higher-ranked-auto-trait-10.no_assumptions.stderr b/tests/ui/async-await/higher-ranked-auto-trait-10.no_assumptions.stderr index 6fcf1b1eac176..e2990ad69f2c9 100644 --- a/tests/ui/async-await/higher-ranked-auto-trait-10.no_assumptions.stderr +++ b/tests/ui/async-await/higher-ranked-auto-trait-10.no_assumptions.stderr @@ -1,5 +1,5 @@ error: implementation of `Foo` is not general enough - --> $DIR/higher-ranked-auto-trait-10.rs:32:5 + --> $DIR/higher-ranked-auto-trait-10.rs:34:5 | LL | Box::new(async move { get_foo(x).await }) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Foo` is not general enough @@ -8,7 +8,7 @@ LL | Box::new(async move { get_foo(x).await }) = note: ...but `Foo<'2>` is actually implemented for the type `&'2 str`, for some specific lifetime `'2` error: implementation of `Foo` is not general enough - --> $DIR/higher-ranked-auto-trait-10.rs:32:5 + --> $DIR/higher-ranked-auto-trait-10.rs:34:5 | LL | Box::new(async move { get_foo(x).await }) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Foo` is not general enough diff --git a/tests/ui/async-await/higher-ranked-auto-trait-10.rs b/tests/ui/async-await/higher-ranked-auto-trait-10.rs index 4bfa27961abd0..e49aeff5c3394 100644 --- a/tests/ui/async-await/higher-ranked-auto-trait-10.rs +++ b/tests/ui/async-await/higher-ranked-auto-trait-10.rs @@ -1,8 +1,10 @@ // Repro for . //@ edition: 2021 -//@ revisions: assumptions no_assumptions +//@ revisions: assumptions min_coroutines no_assumptions //@[assumptions] compile-flags: -Zhigher-ranked-assumptions //@[assumptions] known-bug: unknown +//@[min_coroutines] compile-flags: -Zassumptions-on-binders=min_coroutines +//@[min_coroutines] check-pass //@[no_assumptions] known-bug: #110338 use std::any::Any; diff --git a/tests/ui/async-await/higher-ranked-auto-trait-5.no_assumptions.stderr b/tests/ui/async-await/higher-ranked-auto-trait-5.no_assumptions.stderr index 8fa3c7483c89d..98d37a55b4a15 100644 --- a/tests/ui/async-await/higher-ranked-auto-trait-5.no_assumptions.stderr +++ b/tests/ui/async-await/higher-ranked-auto-trait-5.no_assumptions.stderr @@ -1,5 +1,5 @@ error: implementation of `Send` is not general enough - --> $DIR/higher-ranked-auto-trait-5.rs:13:5 + --> $DIR/higher-ranked-auto-trait-5.rs:15:5 | LL | / assert_send(async { LL | | call_me.call().await; diff --git a/tests/ui/async-await/higher-ranked-auto-trait-5.rs b/tests/ui/async-await/higher-ranked-auto-trait-5.rs index 9a8b3f4357c05..ef21c15334531 100644 --- a/tests/ui/async-await/higher-ranked-auto-trait-5.rs +++ b/tests/ui/async-await/higher-ranked-auto-trait-5.rs @@ -1,8 +1,10 @@ // Repro for . //@ edition: 2021 -//@ revisions: assumptions no_assumptions +//@ revisions: assumptions min_coroutines no_assumptions //@[assumptions] compile-flags: -Zhigher-ranked-assumptions //@[assumptions] check-pass +//@[min_coroutines] compile-flags: -Zassumptions-on-binders=min_coroutines +//@[min_coroutines] check-pass //@[no_assumptions] known-bug: #110338 use std::future::Future; diff --git a/tests/ui/async-await/higher-ranked-auto-trait-8.no_assumptions.stderr b/tests/ui/async-await/higher-ranked-auto-trait-8.no_assumptions.stderr index 6208675117b74..ea9a622dfaf7c 100644 --- a/tests/ui/async-await/higher-ranked-auto-trait-8.no_assumptions.stderr +++ b/tests/ui/async-await/higher-ranked-auto-trait-8.no_assumptions.stderr @@ -1,5 +1,5 @@ error: higher-ranked lifetime error - --> $DIR/higher-ranked-auto-trait-8.rs:26:5 + --> $DIR/higher-ranked-auto-trait-8.rs:28:5 | LL | needs_send(use_my_struct(second_struct)); // ERROR | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/async-await/higher-ranked-auto-trait-8.rs b/tests/ui/async-await/higher-ranked-auto-trait-8.rs index 91cef204e44b9..4546e3990fcc9 100644 --- a/tests/ui/async-await/higher-ranked-auto-trait-8.rs +++ b/tests/ui/async-await/higher-ranked-auto-trait-8.rs @@ -1,8 +1,10 @@ // Repro for . //@ edition: 2021 -//@ revisions: assumptions no_assumptions +//@ revisions: assumptions min_coroutines no_assumptions //@[assumptions] compile-flags: -Zhigher-ranked-assumptions //@[assumptions] check-pass +//@[min_coroutines] compile-flags: -Zassumptions-on-binders=min_coroutines +//@[min_coroutines] check-pass //@[no_assumptions] known-bug: #110338 fn needs_send(_val: T) {}