From 599f62abc8e906d66095d4a30a46bc901b7129f1 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 | 28 +++++++++------ 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, 77 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 34ac92faef88c..5effc44ea5c93 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}; @@ -954,7 +955,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, false); - 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, false); + assert_eq!(opts.unstable_opts.assumptions_on_binders, AssumptionsOnBinders::MinCoroutines); assert_eq!(opts.unstable_opts.next_solver, globally); // `-Zassumptions-on-binders` overrides `-Znext-solver=no` regardless of order, since @@ -966,7 +974,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, false); - 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 913f56eba36f5..ca65c6aee766f 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -2861,7 +2861,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 e849f3076a9f3..b3fd64a4db275 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 7e274c66f6f75..23ace4c9582b2 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -1039,6 +1039,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 + } +} + /// Where the new trait solver should be enabled only in coherence or everywhere. #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub enum NextSolverConfig { @@ -2751,7 +2773,7 @@ pub fn build_session_options( // `-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` directly (e.g. feature-gate checks) see the right value. - if unstable_opts.assumptions_on_binders { + if unstable_opts.assumptions_on_binders.is_enabled() { // Only warn when the last `-Znext-solver` given has a value that doesn't enable // the solver globally, so a later `-Znext-solver=globally` overrides an earlier one. let no_next_solver = matches @@ -3384,11 +3406,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, @@ -3435,6 +3457,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 a29309c71e879..a638c9ed7cb1d 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -876,6 +876,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), or `coherence` (default)"; pub(crate) const parse_lto: &str = @@ -980,6 +982,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.) @@ -2398,9 +2413,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 159c2939875b0..e5cba0dd3e8d2 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -350,6 +350,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 86d9a44e758922f3d04b266c6c4da75e6eb05af5 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 | 8 +- .../src/infer/outlives/obligations.rs | 2 +- .../src/canonical/mod.rs | 36 +-- .../src/solve/effect_goals.rs | 32 +-- .../src/solve/eval_ctxt/mod.rs | 29 ++- .../eval_ctxt/solver_region_constraints.rs | 84 +------ .../rustc_next_trait_solver/src/solve/mod.rs | 12 +- .../src/solve/trait_goals.rs | 8 +- .../rustc_type_ir/src/region_constraint.rs | 219 +++++++++++++++++- 10 files changed, 306 insertions(+), 125 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 9d8a133905301..939a2f54e927b 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -2425,6 +2425,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 38dab6ec4875d..cb3a3d9684ca9 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -193,9 +193,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, rustc_type_ir::region_constraint::Assumptions::empty()); + if !self.tcx.assumptions_on_binders_min_coroutines() { + self.placeholder_assumptions_for_next_solver + .borrow_mut() + .insert(u, rustc_type_ir::region_constraint::Assumptions::empty()); + } f(value) }) } diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index 53226265cc9d7..9b702085cf509 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -303,7 +303,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 45587d717b418..31ff3e90e9c78 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, - 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, + rustc_type_ir::region_constraint::Assumptions::empty(), + ); + } +} + fn compute_query_response_instantiation_values_in_universe( delegate: &D, original_values: &[I::GenericArg], @@ -592,7 +598,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.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 bb6fce7691222..41ff950aa3da8 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, }; @@ -268,19 +269,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 3b372876f574b..54d9453b38348 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 @@ -52,6 +52,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 @@ -974,7 +990,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)? @@ -1372,18 +1388,19 @@ 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| { // Invariant: we shouldn't insert empty assumptions if assumptions computation fails. // When handling placeholder constraints, we rely on vacancies to force ambiguity. let u = self.delegate.universe(); + // The minimal coroutine mode only computes assumptions for coroutine witness binders. if self.cx().assumptions_on_binders() - && let Some(assumptions) = self.region_assumptions_for_placeholders_in_universe( - value.clone(), - u, - param_env, - ) + && (!self.cx().assumptions_on_binders_min_coroutines() + || binder_kind == ForallBinderKind::CoroutineWitness) + && let Some(assumptions) = + self.region_assumptions_for_placeholders_in_universe(value.clone(), u, param_env) { self.delegate.insert_placeholder_assumptions(u, assumptions); } 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 8a4d3fba99a39..c8195736ecc55 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,15 +3,12 @@ #[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, -}; +use rustc_type_ir::region_constraint::{Assumptions, eagerly_handle_placeholders_in_universe}; 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}; @@ -120,10 +117,13 @@ where // We rely on placeholder assumptions to evaluate region constraints with placeholders. // But we can fail to compute assumptions due to unresolved type variables. In that case, - // we conservatively bail with ambiguity. - if !self - .delegate - .has_placeholder_assumptions(((smallest_universe + 1)..=largest_universe).into()) + // we conservatively bail with ambiguity. The minimal coroutine mode leaves most binders + // without assumptions and checks each such universe in + // `eagerly_handle_placeholders_in_universe` instead. + if !self.cx().assumptions_on_binders_min_coroutines() + && !self + .delegate + .has_placeholder_assumptions(((smallest_universe + 1)..=largest_universe).into()) { return Ok(Certainty::AMBIGUOUS); } @@ -147,68 +147,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(_) => { - panic!("Shouldn't destructure type outlives when ty vars exist") - } - 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 c9da77eced6d3..bc464146aae88 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -135,10 +135,16 @@ where // the ambiguous branch. Also this guarantees we don't have ty vars when destructuring // type outlives. if self.cx().assumptions_on_binders() { - use rustc_type_ir::region_constraint::RegionConstraint; + use rustc_type_ir::region_constraint::{ + LeafRegionConstraint, RegionConstraint, destructure_type_outlives, + }; - let constraint = self.destructure_type_outlives(ty, lt); - self.register_solver_region_constraint(RegionConstraint::new_from_or(constraint)); + 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 1d3008da399dc..df87ec3fcaec7 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, @@ -1136,6 +1137,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); @@ -1164,6 +1166,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); @@ -1200,6 +1203,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)?; @@ -1502,9 +1506,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 257d0a776403e..305854dee3c55 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -53,6 +53,7 @@ use smallvec::SmallVec; 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::visit::TypeVisitableExt; use crate::{ @@ -174,6 +175,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. @@ -233,6 +239,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) } @@ -248,6 +255,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, ()), } @@ -258,6 +266,7 @@ impl LeafRegionC let (Ambiguity(s) | RegionOutlives(_, _, s) + | TypeOutlives(_, _, s) | AliasTyOutlivesViaEnv(_, s) | PlaceholderTyOutlives(_, _, s)) = self; s.clone() @@ -599,7 +608,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 @@ -611,6 +620,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 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 force ambiguity if a constraint still mentions them. #[instrument(level = "debug", skip(infcx), ret)] pub fn eagerly_handle_placeholders_in_universe, I: Interner>( infcx: &Infcx, @@ -618,6 +631,14 @@ pub fn eagerly_handle_placeholders_in_universe RegionConstraint { assert!(u > UniverseIndex::ROOT, "eagerly handle placeholders called for root universe"); + if infcx.cx().assumptions_on_binders_min_coroutines() { + return if infcx.has_placeholder_assumptions((u..=u).into()) { + let assumptions = infcx.get_placeholder_assumptions(u); + drop_constraints_satisfied_by_assumptions(infcx, constraint, u, &assumptions) + } else { + ambiguous_if_universe_is_constrained(infcx, constraint, u) + }; + } let assumptions = infcx.get_placeholder_assumptions(u); @@ -644,6 +665,74 @@ 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)) +} + +/// Minimal coroutine mode doesn't compute assumptions for binders other than coroutine witnesses. +/// Once constraints between region variables from `u` are dropped, anything that still mentions `u` +/// can't be checked without the assumptions, so the whole constraint becomes ambiguous. +fn ambiguous_if_universe_is_constrained, I: Interner>( + infcx: &Infcx, + constraint: RegionConstraint, + u: UniverseIndex, +) -> RegionConstraint { + let constraint = compute_new_region_constraints(infcx, constraint, u); + let is_constrained = constraint + .and_constraint + .0 + .iter() + .chain(constraint.or_constraint.0.iter().flat_map(|and| and.0.iter())) + .any(|c| max_universe(infcx, c.clone()) == u); + if is_constrained { RegionConstraint::new_ambig(()) } else { 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 @@ -666,9 +755,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); @@ -770,7 +860,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())); } @@ -826,10 +919,85 @@ 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(_) => { + panic!("Shouldn't destructure type outlives when ty vars exist") + } + 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, @@ -850,6 +1018,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| { @@ -921,6 +1105,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)); } From 73429329d484442a08bbc1c5abaf2743e605f9ed 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 +++++++++++- .../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 +- .../higher-ranked-auto-trait-5.rs | 4 ++- ...-ranked-auto-trait-8.no_assumptions.stderr | 2 +- .../higher-ranked-auto-trait-8.rs | 4 ++- 13 files changed, 111 insertions(+), 11 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 f2582a3131a56..d132cb7ba6ea8 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 -Znext-solver=globally +//@ 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 { @@ -90,4 +93,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/witness-auto-trait/higher-ranked-auto-trait-1.rs b/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-1.rs index 902085a7f159f..bd76c491348ca 100644 --- a/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-1.rs +++ b/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-1.rs @@ -1,10 +1,12 @@ // Repro for . //@ edition: 2021 -//@ revisions: assumptions no_assumptions assumptions_next +//@ revisions: assumptions min_coroutines no_assumptions assumptions_next //@[assumptions] compile-flags: -Zhigher-ranked-assumptions //@[assumptions_next] compile-flags: -Zhigher-ranked-assumptions -Znext-solver //@[assumptions] check-pass //@[assumptions_next] check-pass +//@[min_coroutines] compile-flags: -Zassumptions-on-binders=min_coroutines +//@[min_coroutines] check-pass //@[no_assumptions] known-bug: #110338 // We're using this test to just check the way -Zhigher-ranked-assumptions diff --git a/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-10.assumptions.stderr b/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-10.assumptions.stderr index 6fcf1b1eac176..e2990ad69f2c9 100644 --- a/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-10.assumptions.stderr +++ b/tests/ui/async-await/witness-auto-trait/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/witness-auto-trait/higher-ranked-auto-trait-10.no_assumptions.stderr b/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-10.no_assumptions.stderr index 6fcf1b1eac176..e2990ad69f2c9 100644 --- a/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-10.no_assumptions.stderr +++ b/tests/ui/async-await/witness-auto-trait/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/witness-auto-trait/higher-ranked-auto-trait-10.rs b/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-10.rs index 4bfa27961abd0..e49aeff5c3394 100644 --- a/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-10.rs +++ b/tests/ui/async-await/witness-auto-trait/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/witness-auto-trait/higher-ranked-auto-trait-5.no_assumptions.stderr b/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-5.no_assumptions.stderr index a95fd2179322f..765350911e046 100644 --- a/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-5.no_assumptions.stderr +++ b/tests/ui/async-await/witness-auto-trait/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/witness-auto-trait/higher-ranked-auto-trait-5.rs b/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-5.rs index 9a8b3f4357c05..ef21c15334531 100644 --- a/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-5.rs +++ b/tests/ui/async-await/witness-auto-trait/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/witness-auto-trait/higher-ranked-auto-trait-8.no_assumptions.stderr b/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-8.no_assumptions.stderr index 6208675117b74..ea9a622dfaf7c 100644 --- a/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-8.no_assumptions.stderr +++ b/tests/ui/async-await/witness-auto-trait/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/witness-auto-trait/higher-ranked-auto-trait-8.rs b/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-8.rs index 91cef204e44b9..4546e3990fcc9 100644 --- a/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-8.rs +++ b/tests/ui/async-await/witness-auto-trait/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 542d075f8b35cbc420886561c8ffff1b87eb7ca9 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 +- .../src/infer/outlives/obligations.rs | 6 +++--- compiler/rustc_middle/src/ty/context.rs | 14 ++++++++++++-- .../src/ty/context/impl_interner.rs | 8 ++++++-- .../src/canonical/mod.rs | 2 +- .../rustc_next_trait_solver/src/placeholder.rs | 2 +- .../src/solve/eval_ctxt/mod.rs | 17 ++++++++++------- .../eval_ctxt/solver_region_constraints.rs | 2 +- .../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, 55 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 2a103ff70eb6c..62fda0aab985c 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -170,7 +170,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 9b702085cf509..f1a7872659d27 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -150,7 +150,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 @@ -264,7 +264,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.get_solver_region_constraint(); @@ -323,7 +323,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 ca65c6aee766f..53c9485b1b50a 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -2860,10 +2860,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 b3fd64a4db275..9b43339ef3f4c 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 31ff3e90e9c78..e4f004881e49b 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 fdf702248196a..6d193aefb81e1 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 54d9453b38348..11591cdb4587a 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 @@ -1396,11 +1396,14 @@ where // When handling placeholder constraints, we rely on vacancies to force ambiguity. let u = self.delegate.universe(); // The minimal coroutine mode only computes assumptions for coroutine witness binders. - if self.cx().assumptions_on_binders() - && (!self.cx().assumptions_on_binders_min_coroutines() - || binder_kind == ForallBinderKind::CoroutineWitness) - && let Some(assumptions) = - self.region_assumptions_for_placeholders_in_universe(value.clone(), u, param_env) + if (self.cx().assumptions_on_binders_full() + || (self.cx().assumptions_on_binders_min_coroutines() + && binder_kind == ForallBinderKind::CoroutineWitness)) + && let Some(assumptions) = self.region_assumptions_for_placeholders_in_universe( + value.clone(), + u, + param_env, + ) { self.delegate.insert_placeholder_assumptions(u, assumptions); } @@ -1646,7 +1649,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) @@ -1778,7 +1781,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(); constraint 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 c8195736ecc55..4541af7a6a2f2 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 @@ -31,7 +31,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 bc464146aae88..fd7f852a6fd8b 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -134,7 +134,7 @@ where // We drop region constraints in ambiguous response so there's no need to add them in // the ambiguous branch. Also this guarantees we don't have ty vars when destructuring // type outlives. - if self.cx().assumptions_on_binders() { + if self.cx().assumptions_on_binders_any() { use rustc_type_ir::region_constraint::{ LeafRegionConstraint, RegionConstraint, destructure_type_outlives, }; @@ -160,7 +160,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 23ace4c9582b2..b28a31f3a1374 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -1052,10 +1052,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 } @@ -2773,7 +2780,7 @@ pub fn build_session_options( // `-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` 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() { // Only warn when the last `-Znext-solver` given has a value that doesn't enable // the solver globally, so a later `-Znext-solver=globally` overrides an earlier one. let no_next_solver = matches diff --git a/compiler/rustc_trait_selection/src/solve/delegate.rs b/compiler/rustc_trait_selection/src/solve/delegate.rs index 2335868a17b40..7f8b7854b4ea5 100644 --- a/compiler/rustc_trait_selection/src/solve/delegate.rs +++ b/compiler/rustc_trait_selection/src/solve/delegate.rs @@ -157,7 +157,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 634a17dc372ef..eba28898611c3 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 e5cba0dd3e8d2..e1192d74e4481 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -348,8 +348,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 55fb628e1ee3e..34f5c72fcbc35 100644 --- a/compiler/rustc_type_ir/src/solve/mod.rs +++ b/compiler/rustc_type_ir/src/solve/mod.rs @@ -678,7 +678,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 22f147516a9566d041a54a50ddc7664a2379be1a Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Mon, 28 Sep 2026 21:22:40 -0300 Subject: [PATCH 5/7] Install empty placeholder assumptions only in the full mode --- compiler/rustc_infer/src/infer/context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_infer/src/infer/context.rs b/compiler/rustc_infer/src/infer/context.rs index cb3a3d9684ca9..9382c6cdd81b9 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -193,7 +193,7 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { ) -> U { self.enter_forall(value, |value| { let u = self.universe(); - if !self.tcx.assumptions_on_binders_min_coroutines() { + if self.tcx.assumptions_on_binders_full() { self.placeholder_assumptions_for_next_solver .borrow_mut() .insert(u, rustc_type_ir::region_constraint::Assumptions::empty()); From c16661400e5fbc63a13c8a42d7ca0e2654291fd6 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 d132cb7ba6ea8..13b378f605022 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 // // `predicates` syntax goes through the full clause destructuring and register_obligation pipeline +#[cfg(assumptions)] core::test_binder_constraints! { impl { forall<'a> { @@ -104,4 +113,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 44152c2bb84c51b983976b744765b95c6f394b14 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 --- .../rustc_type_ir/src/region_constraint.rs | 2 +- .../min-coroutines-alias-outlives.rs | 6 +-- .../min-coroutines-alias-outlives.stderr | 50 ++++++------------- .../min-coroutines-only-witness-binders.rs | 2 +- ...outines-retains-unsatisfied-constraints.rs | 2 +- .../test-infra-works.rs | 6 +-- ...-ranked-auto-trait-1.no_assumptions.stderr | 12 ++--- .../higher-ranked-auto-trait-1.rs | 2 +- .../higher-ranked-auto-trait-10.rs | 2 +- ...-ranked-auto-trait-5.no_assumptions.stderr | 2 +- .../higher-ranked-auto-trait-5.rs | 4 +- .../higher-ranked-auto-trait-8.rs | 2 +- 12 files changed, 35 insertions(+), 57 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 305854dee3c55..71e0f1adc3dff 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -220,7 +220,7 @@ impl std::fmt::Debug for LeafRegionConstraint { f.write_str(": ")?; r.fmt(f) } - Self::PlaceholderTyOutlives(ty, r, _span) => { + Self::TypeOutlives(ty, r, _span) | Self::PlaceholderTyOutlives(ty, r, _span) => { ty.fmt(f)?; f.write_str(": ")?; r.fmt(f) diff --git a/tests/ui/assumptions_on_binders/min-coroutines-alias-outlives.rs b/tests/ui/assumptions_on_binders/min-coroutines-alias-outlives.rs index 3ada2d4545412..7ed00ac39fbfc 100644 --- a/tests/ui/assumptions_on_binders/min-coroutines-alias-outlives.rs +++ b/tests/ui/assumptions_on_binders/min-coroutines-alias-outlives.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Zassumptions-on-binders=min_coroutines +//@ compile-flags: -Zassumptions-on-binders=min_coroutines -Znext-solver=globally //@ normalize-stderr: "\[[0-9a-f]{4}\]" -> "[HASH]" #![feature(test_binder_constraints)] @@ -23,7 +23,7 @@ core::test_binder_constraints! { impl { forall<'a> where T: 'a { //~^ ERROR forall expect clause failed - where (T,): 'a + predicates (T,): 'a } expect {} } } @@ -39,7 +39,7 @@ core::test_binder_constraints! { impl { forall<'a> where T::Assoc: 'a { //~^ ERROR forall expect clause failed - where T::Assoc: 'a + predicates T::Assoc: 'a } expect {} } } diff --git a/tests/ui/assumptions_on_binders/min-coroutines-alias-outlives.stderr b/tests/ui/assumptions_on_binders/min-coroutines-alias-outlives.stderr index aa955d52f2398..abad746c65e54 100644 --- a/tests/ui/assumptions_on_binders/min-coroutines-alias-outlives.stderr +++ b/tests/ui/assumptions_on_binders/min-coroutines-alias-outlives.stderr @@ -9,18 +9,8 @@ note: constraint from here | 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), - ), - ], - ) + = note: expected: true + = note: actual: (T/#0,): '!1_0.Named(DefId(0:8 ~ min_coroutines_alias_outlives[HASH]::{test_binder_constraints#0}::'a)) error: forall expect clause failed --> $DIR/min-coroutines-alias-outlives.rs:40:9 @@ -33,29 +23,19 @@ note: constraint from here | 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), - ), - ], - ) + = note: expected: true + = note: actual: 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)) 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 index 706dd8db35ff1..f5f14312aca57 100644 --- a/tests/ui/assumptions_on_binders/min-coroutines-only-witness-binders.rs +++ b/tests/ui/assumptions_on_binders/min-coroutines-only-witness-binders.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Zassumptions-on-binders=min_coroutines +//@ compile-flags: -Zassumptions-on-binders=min_coroutines -Znext-solver=globally use std::marker::PhantomData; 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 index 9f8fc1c1b3814..ae4ccccf23c83 100644 --- a/tests/ui/assumptions_on_binders/min-coroutines-retains-unsatisfied-constraints.rs +++ b/tests/ui/assumptions_on_binders/min-coroutines-retains-unsatisfied-constraints.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Zassumptions-on-binders=min_coroutines +//@ compile-flags: -Zassumptions-on-binders=min_coroutines -Znext-solver=globally //@ normalize-stderr: "\n\n$" -> "\n" #![feature(test_binder_constraints)] diff --git a/tests/ui/assumptions_on_binders/test-infra-works.rs b/tests/ui/assumptions_on_binders/test-infra-works.rs index 13b378f605022..960aa9339cb79 100644 --- a/tests/ui/assumptions_on_binders/test-infra-works.rs +++ b/tests/ui/assumptions_on_binders/test-infra-works.rs @@ -1,7 +1,7 @@ //@ check-pass //@ revisions: assumptions min_coroutines -//@[assumptions] compile-flags: -Zassumptions-on-binders -//@[min_coroutines] compile-flags: -Zassumptions-on-binders=min_coroutines +//@[assumptions] compile-flags: -Zassumptions-on-binders -Znext-solver=globally +//@[min_coroutines] compile-flags: -Zassumptions-on-binders=min_coroutines -Znext-solver=globally #![feature(test_binder_constraints, non_lifetime_binders)] #![expect(incomplete_features)] @@ -120,7 +120,7 @@ core::test_binder_constraints! { core::test_binder_constraints! { impl { forall<'a> where T: 'a { - where T: 'a + predicates T: 'a } expect {} } } diff --git a/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-1.no_assumptions.stderr b/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-1.no_assumptions.stderr index b2c56bf95156c..1d1e873905981 100644 --- a/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-1.no_assumptions.stderr +++ b/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-1.no_assumptions.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> $DIR/higher-ranked-auto-trait-1.rs:43:5 + --> $DIR/higher-ranked-auto-trait-1.rs:45: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:46:19: 46:29}` - found `async` block `{async block@$DIR/higher-ranked-auto-trait-1.rs:46:19: 46:29}` + = note: expected `async` block `{async block@$DIR/higher-ranked-auto-trait-1.rs:48:19: 48:29}` + found `async` block `{async block@$DIR/higher-ranked-auto-trait-1.rs:48:19: 48: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:43:5 + --> $DIR/higher-ranked-auto-trait-1.rs:45: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:46:19: 46:29}` - found `async` block `{async block@$DIR/higher-ranked-auto-trait-1.rs:46:19: 46:29}` + = note: expected `async` block `{async block@$DIR/higher-ranked-auto-trait-1.rs:48:19: 48:29}` + found `async` block `{async block@$DIR/higher-ranked-auto-trait-1.rs:48:19: 48: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/witness-auto-trait/higher-ranked-auto-trait-1.rs b/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-1.rs index bd76c491348ca..a99551664cc82 100644 --- a/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-1.rs +++ b/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-1.rs @@ -5,7 +5,7 @@ //@[assumptions_next] compile-flags: -Zhigher-ranked-assumptions -Znext-solver //@[assumptions] check-pass //@[assumptions_next] check-pass -//@[min_coroutines] compile-flags: -Zassumptions-on-binders=min_coroutines +//@[min_coroutines] compile-flags: -Zassumptions-on-binders=min_coroutines -Znext-solver=globally //@[min_coroutines] check-pass //@[no_assumptions] known-bug: #110338 diff --git a/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-10.rs b/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-10.rs index e49aeff5c3394..4969f2121c71a 100644 --- a/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-10.rs +++ b/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-10.rs @@ -3,7 +3,7 @@ //@ 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] compile-flags: -Zassumptions-on-binders=min_coroutines -Znext-solver=globally //@[min_coroutines] check-pass //@[no_assumptions] known-bug: #110338 diff --git a/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-5.no_assumptions.stderr b/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-5.no_assumptions.stderr index 765350911e046..a95fd2179322f 100644 --- a/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-5.no_assumptions.stderr +++ b/tests/ui/async-await/witness-auto-trait/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/witness-auto-trait/higher-ranked-auto-trait-5.rs b/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-5.rs index ef21c15334531..9a8b3f4357c05 100644 --- a/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-5.rs +++ b/tests/ui/async-await/witness-auto-trait/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; diff --git a/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-8.rs b/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-8.rs index 4546e3990fcc9..3ce77b6a2b8e4 100644 --- a/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-8.rs +++ b/tests/ui/async-await/witness-auto-trait/higher-ranked-auto-trait-8.rs @@ -3,7 +3,7 @@ //@ 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] compile-flags: -Zassumptions-on-binders=min_coroutines -Znext-solver=globally //@[min_coroutines] check-pass //@[no_assumptions] known-bug: #110338