From 7340aa411bdd47c4319f19cc769df8864220b240 Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Tue, 1 Sep 2026 18:09:27 -0300 Subject: [PATCH 1/7] Add minimal coroutine binder assumption mode --- compiler/rustc_interface/src/tests.rs | 30 ++++++++++------ compiler/rustc_middle/src/ty/context.rs | 6 +++- .../src/ty/context/impl_interner.rs | 4 +++ compiler/rustc_session/src/config.rs | 35 +++++++++++++++---- compiler/rustc_session/src/options.rs | 22 ++++++++++-- compiler/rustc_type_ir/src/interner.rs | 2 ++ 6 files changed, 78 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 0d584be4ad0b0..40024d96b88a7 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}; @@ -953,7 +954,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` @@ -964,7 +972,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); } @@ -977,7 +985,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 9edb12949f4cf..b7f2632e75601 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -2830,7 +2830,11 @@ impl<'tcx> TyCtxt<'tcx> { } pub fn assumptions_on_binders(self) -> bool { - self.sess.opts.unstable_opts.assumptions_on_binders + self.sess.opts.unstable_opts.assumptions_on_binders.is_enabled() + } + + 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 fdaf5cacfbf27..00ab365fc6519 100644 --- a/compiler/rustc_middle/src/ty/context/impl_interner.rs +++ b/compiler/rustc_middle/src/ty/context/impl_interner.rs @@ -322,6 +322,10 @@ impl<'tcx> Interner for TyCtxt<'tcx> { self.assumptions_on_binders() } + fn assumptions_on_binders_min_coroutines(self) -> bool { + self.assumptions_on_binders_min_coroutines() + } + fn renormalize_rigid_aliases(self) -> bool { self.renormalize_rigid_aliases() } diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 753a4ed0d779a..06e827cf6a508 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -1034,6 +1034,28 @@ 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 { + pub fn is_enabled(self) -> bool { + self != AssumptionsOnBinders::Disabled + } + + 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. @@ -2712,7 +2734,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.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 { @@ -3340,11 +3362,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, @@ -3391,6 +3413,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 13fc598d86bfa..131a51a7492bc 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_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index 85a69af53539a..edc9eb6f41e9d 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -348,6 +348,8 @@ pub trait Interner: fn assumptions_on_binders(self) -> bool; + fn assumptions_on_binders_min_coroutines(self) -> bool; + fn renormalize_rigid_aliases(self) -> bool; fn coroutine_hidden_types( From 5f18334386fe8423f27d8cc7f0f0e52a4c3e3ee2 Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Tue, 1 Sep 2026 18:13:03 -0300 Subject: [PATCH 2/7] Implement minimal coroutine binder assumptions Keep type-outlives constraints intact while leaving a minimal-mode binder, then remove only leaves proven by that binder. Ordinary binders continue through the normal eager leak check. --- .../rustc_hir_analysis/src/check/wfcheck.rs | 1 + compiler/rustc_infer/src/infer/context.rs | 6 +- .../src/infer/outlives/obligations.rs | 2 +- .../src/canonical/mod.rs | 36 +-- .../src/solve/effect_goals.rs | 32 +-- .../src/solve/eval_ctxt/mod.rs | 24 +- .../eval_ctxt/solver_region_constraints.rs | 70 +----- .../rustc_next_trait_solver/src/solve/mod.rs | 14 +- .../src/solve/trait_goals.rs | 8 +- .../rustc_type_ir/src/region_constraint.rs | 207 +++++++++++++++++- 10 files changed, 283 insertions(+), 117 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index bde17c39e2bc4..2826a616a41c7 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -2390,6 +2390,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 4d90e60736995..fa604eb75aba7 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -187,9 +187,9 @@ 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_min_coroutines()) + .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 14f534a3e7eb0..4224055d88476 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -317,7 +317,7 @@ impl<'tcx> InferCtxt<'tcx> { b, a, category, ); } - AliasTyOutlivesViaEnv(..) | PlaceholderTyOutlives(..) => { + TypeOutlives(..) | AliasTyOutlivesViaEnv(..) | PlaceholderTyOutlives(..) => { unreachable!() } } diff --git a/compiler/rustc_next_trait_solver/src/canonical/mod.rs b/compiler/rustc_next_trait_solver/src/canonical/mod.rs index 45ae4cadf519c..8bd87999cd532 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/mod.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/mod.rs @@ -162,20 +162,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( @@ -187,6 +174,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() { + // 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], @@ -588,7 +594,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/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 7397d43a48a00..e57daf6ea905f 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 @@ -890,7 +906,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)? @@ -1289,11 +1305,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() + && (!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 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 10dd8585b09c4..7d15719be68d3 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 @@ -3,16 +3,14 @@ #[cfg(feature = "nightly")] use rustc_data_structures::transitive_relation::TransitiveRelationBuilder; 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, Const, InferCtxtLike, Interner, Region, TypeVisitable, - TypeVisitableExt, TypeVisitor, UniverseIndex, + ClauseKind, Const, InferCtxtLike, Interner, TypeVisitable, TypeVisitableExt, TypeVisitor, + UniverseIndex, }; use tracing::{debug, instrument}; @@ -138,66 +136,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 1d7d2dd75c316..fce73e458635f 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -93,10 +93,16 @@ where let ty = self.normalize(GoalSource::Misc, goal.param_env, ty::Unnormalized::new_wip(ty))?; if self.cx().assumptions_on_binders() { - use rustc_type_ir::region_constraint::RegionConstraint; - - let constraint = self.destructure_type_outlives(ty, lt); - self.register_solver_region_constraint(RegionConstraint::new_from_or(constraint)); + 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); } 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 d7c37b6344d23..d07a4241d9147 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, @@ -1111,6 +1112,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); @@ -1139,6 +1141,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); @@ -1175,6 +1178,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)?; @@ -1477,9 +1481,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_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 58763593f9b7e..ca415e5b46b93 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -51,6 +51,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, ClauseKind, DebruijnIndex, @@ -162,6 +163,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. @@ -194,6 +200,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) } @@ -209,6 +216,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, ()), } @@ -219,6 +227,7 @@ impl LeafRegionC let (Ambiguity(s) | RegionOutlives(_, _, s) + | TypeOutlives(_, _, s) | AliasTyOutlivesViaEnv(_, s) | PlaceholderTyOutlives(_, _, s)) = self; s.clone() @@ -490,7 +499,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 @@ -502,6 +511,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, @@ -510,6 +523,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` // @@ -534,6 +553,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 @@ -556,9 +625,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); @@ -704,7 +774,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())); } @@ -760,10 +833,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, @@ -784,6 +930,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| { @@ -855,6 +1017,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)); } @@ -1239,14 +1418,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_min_coroutines()).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_min_coroutines()).then(Assumptions::empty), + ); let a = self.infcx.instantiate_binder_with_infer(a); self.relate(a, b) })?; From 0e9f4c484623a74e05f67c64b641fb645decbcf9 Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Tue, 1 Sep 2026 18:14:13 -0300 Subject: [PATCH 3/7] Add minimal coroutine binder regressions --- .../min-coroutines-only-witness-binders.rs | 26 +++++++++++++++++++ ...min-coroutines-only-witness-binders.stderr | 26 +++++++++++++++++++ ...outines-retains-unsatisfied-constraints.rs | 19 ++++++++++++++ ...nes-retains-unsatisfied-constraints.stderr | 7 +++++ .../test-infra-works.rs | 16 +++++++++++- ...-ranked-auto-trait-1.no_assumptions.stderr | 12 ++++----- .../async-await/higher-ranked-auto-trait-1.rs | 4 ++- ...er-ranked-auto-trait-10.assumptions.stderr | 4 +-- ...ranked-auto-trait-10.no_assumptions.stderr | 4 +-- .../higher-ranked-auto-trait-10.rs | 4 ++- ...-ranked-auto-trait-5.no_assumptions.stderr | 2 +- .../async-await/higher-ranked-auto-trait-5.rs | 4 ++- ...-ranked-auto-trait-8.no_assumptions.stderr | 2 +- .../async-await/higher-ranked-auto-trait-8.rs | 4 ++- 14 files changed, 117 insertions(+), 17 deletions(-) create mode 100644 tests/ui/assumptions_on_binders/min-coroutines-only-witness-binders.rs create mode 100644 tests/ui/assumptions_on_binders/min-coroutines-only-witness-binders.stderr create mode 100644 tests/ui/assumptions_on_binders/min-coroutines-retains-unsatisfied-constraints.rs create mode 100644 tests/ui/assumptions_on_binders/min-coroutines-retains-unsatisfied-constraints.stderr 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..4bb32c7dc1e68 --- /dev/null +++ b/tests/ui/assumptions_on_binders/min-coroutines-only-witness-binders.stderr @@ -0,0 +1,26 @@ +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() +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..d9f4ed2179aff 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 { @@ -82,4 +85,15 @@ 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 {} + } +} + 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) {} From 00cf53ec11b3b1ab5a8cb89dd5ad3fe0eb5896b4 Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Sat, 5 Sep 2026 14:23:53 -0300 Subject: [PATCH 4/7] Name the assumptions-on-binders accessors by mode The predicate was called `assumptions_on_binders`, which read as "the flag is on" and gave no way to ask which mode is active. Callers that gate the shared machinery want "any mode", while callers that gate the eager placeholder rewriting want "the full mode" specifically. Split it into `any_is_enabled`, `is_full` and `is_min_coroutines` on the option, and expose all three through `TyCtxt` and `Interner`. --- compiler/rustc_borrowck/src/type_check/mod.rs | 2 +- .../rustc_infer/src/infer/outlives/obligations.rs | 6 +++--- compiler/rustc_middle/src/ty/context.rs | 14 ++++++++++++-- .../rustc_middle/src/ty/context/impl_interner.rs | 8 ++++++-- .../rustc_next_trait_solver/src/canonical/mod.rs | 2 +- .../rustc_next_trait_solver/src/placeholder.rs | 2 +- .../src/solve/eval_ctxt/mod.rs | 10 +++++----- .../solve/eval_ctxt/solver_region_constraints.rs | 2 +- compiler/rustc_next_trait_solver/src/solve/mod.rs | 4 ++-- compiler/rustc_session/src/config.rs | 11 +++++++++-- .../rustc_trait_selection/src/solve/delegate.rs | 2 +- .../src/solve/fulfill/derive_errors.rs | 2 +- compiler/rustc_type_ir/src/interner.rs | 7 ++++++- compiler/rustc_type_ir/src/solve/mod.rs | 2 +- 14 files changed, 50 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index a886187783b86..d907661cc47b6 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -171,7 +171,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_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index 4224055d88476..2548fb89bd78e 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -162,7 +162,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 @@ -276,7 +276,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(); @@ -337,7 +337,7 @@ impl<'tcx> InferCtxt<'tcx> { pub fn process_registered_region_obligations(&self, outlives_env: &OutlivesEnvironment<'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_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index b7f2632e75601..ddbfb9cba7daa 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -2829,10 +2829,20 @@ 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.is_enabled() + /// 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() } diff --git a/compiler/rustc_middle/src/ty/context/impl_interner.rs b/compiler/rustc_middle/src/ty/context/impl_interner.rs index 00ab365fc6519..04ef29f5b5505 100644 --- a/compiler/rustc_middle/src/ty/context/impl_interner.rs +++ b/compiler/rustc_middle/src/ty/context/impl_interner.rs @@ -318,8 +318,12 @@ 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 { diff --git a/compiler/rustc_next_trait_solver/src/canonical/mod.rs b/compiler/rustc_next_trait_solver/src/canonical/mod.rs index 8bd87999cd532..41f37b6d6eaa7 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/mod.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/mod.rs @@ -180,7 +180,7 @@ where I: Interner, { let new_universe = delegate.create_next_universe(); - if delegate.cx().assumptions_on_binders() { + 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 diff --git a/compiler/rustc_next_trait_solver/src/placeholder.rs b/compiler/rustc_next_trait_solver/src/placeholder.rs index c96aa90a10792..ffbfa9ca38a8e 100644 --- a/compiler/rustc_next_trait_solver/src/placeholder.rs +++ b/compiler/rustc_next_trait_solver/src/placeholder.rs @@ -68,7 +68,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/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index e57daf6ea905f..58837fccad76a 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 @@ -1310,9 +1310,9 @@ where ) -> U { self.delegate.enter_forall_without_assumptions(value, |value| { let u = self.delegate.universe(); - let assumptions = if self.cx().assumptions_on_binders() - && (!self.cx().assumptions_on_binders_min_coroutines() - || binder_kind == ForallBinderKind::CoroutineWitness) + 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 { @@ -1557,7 +1557,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) @@ -1689,7 +1689,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 7d15719be68d3..e6943d9a0ca96 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 @@ -33,7 +33,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>, diff --git a/compiler/rustc_next_trait_solver/src/solve/mod.rs b/compiler/rustc_next_trait_solver/src/solve/mod.rs index fce73e458635f..778202779b978 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -92,7 +92,7 @@ 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() { + if self.cx().assumptions_on_binders_any() { use rustc_type_ir::region_constraint::{ LeafRegionConstraint, RegionConstraint, destructure_type_outlives, }; @@ -126,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_session/src/config.rs b/compiler/rustc_session/src/config.rs index 06e827cf6a508..c6ee3b9eb2eac 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -1047,10 +1047,17 @@ pub enum AssumptionsOnBinders { } impl AssumptionsOnBinders { - pub fn is_enabled(self) -> bool { + /// 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 } @@ -2734,7 +2741,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.is_enabled() { + 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 { diff --git a/compiler/rustc_trait_selection/src/solve/delegate.rs b/compiler/rustc_trait_selection/src/solve/delegate.rs index c67a4bdd329b0..8f305d8e26028 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 7f704d41ab6c5..836fb3ff508ec 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs @@ -69,7 +69,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 edc9eb6f41e9d..d82b03722abc5 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -346,8 +346,13 @@ 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/solve/mod.rs b/compiler/rustc_type_ir/src/solve/mod.rs index c6d88bb6603de..c138b0d9879a6 100644 --- a/compiler/rustc_type_ir/src/solve/mod.rs +++ b/compiler/rustc_type_ir/src/solve/mod.rs @@ -640,7 +640,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![]), }; From b613694feb1311f2b9fcd6887610b2477e0e9cc9 Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Sat, 5 Sep 2026 14:28:02 -0300 Subject: [PATCH 5/7] Install empty placeholder assumptions only in the full mode Entering a binder with no assumptions recorded an empty assumption set whenever the minimal coroutine mode was off, which includes the case where assumptions on binders is disabled entirely. Storing `None` there instead keeps the map meaningful: a universe has an entry only when some mode actually computed one for it. --- compiler/rustc_infer/src/infer/context.rs | 4 +++- compiler/rustc_type_ir/src/region_constraint.rs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_infer/src/infer/context.rs b/compiler/rustc_infer/src/infer/context.rs index fa604eb75aba7..a05f70e58d012 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -187,7 +187,9 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { ) -> U { self.enter_forall(value, |value| { let u = self.universe(); - let assumptions = (!self.tcx.assumptions_on_binders_min_coroutines()) + 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_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index ca415e5b46b93..25050b163c895 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -1420,7 +1420,7 @@ impl<'a, Infcx: InferCtxtLike, I: Interner> TypeRelation let u = self.infcx.universe(); self.infcx.insert_placeholder_assumptions( u, - (!self.cx().assumptions_on_binders_min_coroutines()).then(Assumptions::empty), + self.cx().assumptions_on_binders_full().then(Assumptions::empty), ); let b = self.infcx.instantiate_binder_with_infer(b); self.relate(a, b) @@ -1430,7 +1430,7 @@ impl<'a, Infcx: InferCtxtLike, I: Interner> TypeRelation let u = self.infcx.universe(); self.infcx.insert_placeholder_assumptions( u, - (!self.cx().assumptions_on_binders_min_coroutines()).then(Assumptions::empty), + self.cx().assumptions_on_binders_full().then(Assumptions::empty), ); let a = self.infcx.instantiate_binder_with_infer(a); self.relate(a, b) From 8732751ea6a7a9f1560afa5e4dbe4e1c6b72b57e Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Sat, 5 Sep 2026 14:28:16 -0300 Subject: [PATCH 6/7] Cover type outlives handling in the minimal coroutine mode The shared binder tests only exercised region outlives constraints, and the two alias cases assert on the rewrite that the full mode performs, which the minimal mode deliberately skips. Add a case where an assumption names the component while the goal names the composite. Keeping the constraint whole leaves it for the root, and destructuring it would reduce it to the component and discharge it, so the two representations disagree. Also record that an assumption naming an alias exactly fails to discharge it, because assumptions are lowered without normalization and so compare unequal to the normalized goal. --- .../min-coroutines-alias-outlives.rs | 47 ++++++++++++++ .../min-coroutines-alias-outlives.stderr | 61 +++++++++++++++++++ .../test-infra-works.rs | 21 +++++++ 3 files changed, 129 insertions(+) create mode 100644 tests/ui/assumptions_on_binders/min-coroutines-alias-outlives.rs create mode 100644 tests/ui/assumptions_on_binders/min-coroutines-alias-outlives.stderr 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/test-infra-works.rs b/tests/ui/assumptions_on_binders/test-infra-works.rs index d9f4ed2179aff..66ec09639659e 100644 --- a/tests/ui/assumptions_on_binders/test-infra-works.rs +++ b/tests/ui/assumptions_on_binders/test-infra-works.rs @@ -52,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> { @@ -71,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> { @@ -96,4 +105,16 @@ core::test_binder_constraints! { } } +// 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() {} From 8d8dbdfcff9a48258f1b21a5a7fdc506e01ffd16 Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Fri, 18 Sep 2026 09:27:54 -0300 Subject: [PATCH 7/7] Fix minimal coroutine test expectations --- .../higher-ranked-auto-trait-5.no_assumptions.stderr | 2 +- tests/ui/async-await/higher-ranked-auto-trait-5.rs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) 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 98d37a55b4a15..8fa3c7483c89d 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:15:5 + --> $DIR/higher-ranked-auto-trait-5.rs:13: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 ef21c15334531..9a8b3f4357c05 100644 --- a/tests/ui/async-await/higher-ranked-auto-trait-5.rs +++ b/tests/ui/async-await/higher-ranked-auto-trait-5.rs @@ -1,10 +1,8 @@ // Repro for . //@ edition: 2021 -//@ revisions: assumptions min_coroutines no_assumptions +//@ revisions: assumptions 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;