From 48280300344ebab97ad3745018e40e81ff7c05f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vicente=20Gusm=C3=A3o?= Date: Wed, 5 Aug 2026 14:22:25 +0100 Subject: [PATCH 1/2] Diagnostics ICE when replaying proof trees with next-solver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When diagnostics replay proof tree state, rebuilding a canonical state can fail to match the current inference state. With -Znext-solver=globally, this could panic because inference variables created to replay a canonical state were placed in the wrong universe relative to placeholders bound by the same state. Fix this by threading a `prev_universe` through proof tree replay: `InspectGoal` now stores the caller-side universe at the time the goal was created, and `instantiate_canonical_state` reuses that same base universe (instead of recomputing it) when creating any replay-only fresh variables and when reconstructing placeholders from the canonical state, ensuring all canonical states for a single goal share a consistent universe mapping. Add a regression test for the higher-ranked PartialEq and PartialOrd case. Signed-off-by: Vicente Gusmão --- .../src/canonical/mod.rs | 37 +++++++++++++++++-- .../rustc_next_trait_solver/src/delegate.rs | 3 +- .../src/solve/delegate.rs | 9 +++-- .../src/solve/inspect/analyse.rs | 24 ++++++++++-- .../foreach-partial-eq-next-solver.rs | 14 +++++++ .../foreach-partial-eq-next-solver.stderr | 19 ++++++++++ 6 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 tests/ui/traits/non_lifetime_binders/foreach-partial-eq-next-solver.rs create mode 100644 tests/ui/traits/non_lifetime_binders/foreach-partial-eq-next-solver.stderr diff --git a/compiler/rustc_next_trait_solver/src/canonical/mod.rs b/compiler/rustc_next_trait_solver/src/canonical/mod.rs index 1e4e3a90f9c4a..e33203d33ab6f 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/mod.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/mod.rs @@ -165,6 +165,27 @@ where delegate.create_next_universe(); } + compute_query_response_instantiation_values_in_universe( + delegate, + original_values, + response, + span, + prev_universe, + ) +} + +fn compute_query_response_instantiation_values_in_universe( + delegate: &D, + original_values: &[I::GenericArg], + response: &Canonical, + span: I::Span, + prev_universe: ty::UniverseIndex, +) -> CanonicalVarValues +where + D: SolverDelegate, + I: Interner, + T: ResponseT, +{ let var_values = response.value.var_values(); assert_eq!(original_values.len(), var_values.len()); @@ -542,6 +563,7 @@ pub fn instantiate_canonical_state( delegate: &D, span: I::Span, param_env: I::ParamEnv, + prev_universe: ty::UniverseIndex, orig_values: &mut ThinVec, state: inspect::CanonicalState, ) -> T @@ -552,14 +574,23 @@ where { // In case any fresh inference variables have been created between `state` // 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(); + } orig_values.extend( state.value.var_values.var_values.as_slice()[orig_values.len()..] .iter() - .map(|&arg| delegate.fresh_var_for_kind_with_span(arg, span)), + .map(|&arg| delegate.fresh_var_for_kind(arg, span, max_universe)), ); - let instantiation = - compute_query_response_instantiation_values(delegate, orig_values, &state, span); + let instantiation = compute_query_response_instantiation_values_in_universe( + delegate, + orig_values, + &state, + span, + prev_universe, + ); let inspect::State { var_values, data } = delegate.instantiate_canonical(state, instantiation); diff --git a/compiler/rustc_next_trait_solver/src/delegate.rs b/compiler/rustc_next_trait_solver/src/delegate.rs index 7b66667486fc9..21a36b3785172 100644 --- a/compiler/rustc_next_trait_solver/src/delegate.rs +++ b/compiler/rustc_next_trait_solver/src/delegate.rs @@ -26,10 +26,11 @@ pub trait SolverDelegate: Deref + Sized { span: ::Span, ) -> ComputeGoalFastPathOutcome; - fn fresh_var_for_kind_with_span( + fn fresh_var_for_kind( &self, arg: ::GenericArg, span: ::Span, + universe: ty::UniverseIndex, ) -> ::GenericArg; // FIXME: Uplift the leak check into this crate. diff --git a/compiler/rustc_trait_selection/src/solve/delegate.rs b/compiler/rustc_trait_selection/src/solve/delegate.rs index bb1e6c168c47b..bacd373a78268 100644 --- a/compiler/rustc_trait_selection/src/solve/delegate.rs +++ b/compiler/rustc_trait_selection/src/solve/delegate.rs @@ -285,17 +285,18 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< } } - fn fresh_var_for_kind_with_span( + fn fresh_var_for_kind( &self, arg: ty::GenericArg<'tcx>, span: Span, + universe: ty::UniverseIndex, ) -> ty::GenericArg<'tcx> { match arg.kind() { ty::GenericArgKind::Lifetime(_) => { - self.next_region_var(RegionVariableOrigin::Misc(span)).into() + self.next_region_var_in_universe(RegionVariableOrigin::Misc(span), universe).into() } - ty::GenericArgKind::Type(_) => self.next_ty_var(span).into(), - ty::GenericArgKind::Const(_) => self.next_const_var(span).into(), + ty::GenericArgKind::Type(_) => self.next_ty_var_in_universe(span, universe).into(), + ty::GenericArgKind::Const(_) => self.next_const_var_in_universe(span, universe).into(), } } diff --git a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs index 3c581d15f0376..f7d6fe2481b2d 100644 --- a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs +++ b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs @@ -32,6 +32,7 @@ pub struct InspectGoal<'a, 'tcx> { infcx: &'a SolverDelegate<'tcx>, depth: usize, orig_values: ThinVec>, + prev_universe: ty::UniverseIndex, goal: Goal<'tcx, ty::Predicate<'tcx>>, result: Result, final_revision: &'tcx inspect::Probe>, @@ -102,7 +103,14 @@ impl<'a, 'tcx> InspectCandidate<'a, 'tcx> { match **step { inspect::ProbeStep::AddGoal(source, goal) => instantiated_goals.push(( source, - instantiate_canonical_state(infcx, span, param_env, &mut orig_values, goal), + instantiate_canonical_state( + infcx, + span, + param_env, + self.goal.prev_universe, + &mut orig_values, + goal, + ), )), inspect::ProbeStep::RecordImplArgs { .. } => {} inspect::ProbeStep::MakeCanonicalResponse { .. } @@ -110,8 +118,14 @@ impl<'a, 'tcx> InspectCandidate<'a, 'tcx> { } } - let () = - instantiate_canonical_state(infcx, span, param_env, &mut orig_values, self.final_state); + let () = instantiate_canonical_state( + infcx, + span, + param_env, + self.goal.prev_universe, + &mut orig_values, + self.final_state, + ); instantiated_goals .into_iter() @@ -139,6 +153,7 @@ impl<'a, 'tcx> InspectCandidate<'a, 'tcx> { infcx, span, param_env, + self.goal.prev_universe, &mut orig_values, impl_args, ); @@ -147,6 +162,7 @@ impl<'a, 'tcx> InspectCandidate<'a, 'tcx> { infcx, span, param_env, + self.goal.prev_universe, &mut orig_values, self.final_state, ); @@ -320,6 +336,7 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> { source: GoalSource, ) -> Self { let infcx = <&SolverDelegate<'tcx>>::from(infcx); + let prev_universe = infcx.universe(); let inspect::GoalEvaluation { uncanonicalized_goal, orig_values, final_revision, result } = root; @@ -331,6 +348,7 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> { infcx, depth, orig_values, + prev_universe, goal: eager_resolve_vars(&**infcx, uncanonicalized_goal), result, final_revision, diff --git a/tests/ui/traits/non_lifetime_binders/foreach-partial-eq-next-solver.rs b/tests/ui/traits/non_lifetime_binders/foreach-partial-eq-next-solver.rs new file mode 100644 index 0000000000000..a48dd465750c6 --- /dev/null +++ b/tests/ui/traits/non_lifetime_binders/foreach-partial-eq-next-solver.rs @@ -0,0 +1,14 @@ +//@ compile-flags: -Znext-solver=globally + +#![allow(incomplete_features)] +#![feature(non_lifetime_binders)] + +fn auto_trait() +where + for T: PartialEq + PartialOrd, +{} + +fn main() { + auto_trait(); + //~^ ERROR can't compare `T` with `T` +} diff --git a/tests/ui/traits/non_lifetime_binders/foreach-partial-eq-next-solver.stderr b/tests/ui/traits/non_lifetime_binders/foreach-partial-eq-next-solver.stderr new file mode 100644 index 0000000000000..dc4dedd75c320 --- /dev/null +++ b/tests/ui/traits/non_lifetime_binders/foreach-partial-eq-next-solver.stderr @@ -0,0 +1,19 @@ +error[E0277]: can't compare `T` with `T` + --> $DIR/foreach-partial-eq-next-solver.rs:12:5 + | +LL | auto_trait(); + | ^^^^^^^^^^^^ no implementation for `T < T` and `T > T` + | + = help: the trait `PartialOrd` is not implemented for `T` +note: required by a bound in `auto_trait` + --> $DIR/foreach-partial-eq-next-solver.rs:8:27 + | +LL | fn auto_trait() + | ---------- required by a bound in this function +LL | where +LL | for T: PartialEq + PartialOrd, + | ^^^^^^^^^^ required by this bound in `auto_trait` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. From 1cf12a5f1bbaeef1b7ba4b801f72fa3285f04e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vicente=20Gusm=C3=A3o?= Date: Mon, 10 Aug 2026 18:19:23 +0100 Subject: [PATCH 2/2] Add explanation to regression test --- .../foreach-partial-eq-next-solver.rs | 9 +++++++++ .../foreach-partial-eq-next-solver.stderr | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/ui/traits/non_lifetime_binders/foreach-partial-eq-next-solver.rs b/tests/ui/traits/non_lifetime_binders/foreach-partial-eq-next-solver.rs index a48dd465750c6..e9f27ecb7338a 100644 --- a/tests/ui/traits/non_lifetime_binders/foreach-partial-eq-next-solver.rs +++ b/tests/ui/traits/non_lifetime_binders/foreach-partial-eq-next-solver.rs @@ -1,5 +1,14 @@ //@ compile-flags: -Znext-solver=globally +// Regression test for . +// +// This used to ICE with `-Znext-solver=globally`. +// The ICE happened because the `PartialOrd` bound fails causing +// diagnostics to replay the proof tree in order to find the +// best nested-goal. During that replay, it needs to create a +// fresh inference variable for the higher-ranked `T` but it was +// creating it in the wrong universe. + #![allow(incomplete_features)] #![feature(non_lifetime_binders)] diff --git a/tests/ui/traits/non_lifetime_binders/foreach-partial-eq-next-solver.stderr b/tests/ui/traits/non_lifetime_binders/foreach-partial-eq-next-solver.stderr index dc4dedd75c320..19921e3e06eed 100644 --- a/tests/ui/traits/non_lifetime_binders/foreach-partial-eq-next-solver.stderr +++ b/tests/ui/traits/non_lifetime_binders/foreach-partial-eq-next-solver.stderr @@ -1,12 +1,12 @@ error[E0277]: can't compare `T` with `T` - --> $DIR/foreach-partial-eq-next-solver.rs:12:5 + --> $DIR/foreach-partial-eq-next-solver.rs:21:5 | LL | auto_trait(); | ^^^^^^^^^^^^ no implementation for `T < T` and `T > T` | = help: the trait `PartialOrd` is not implemented for `T` note: required by a bound in `auto_trait` - --> $DIR/foreach-partial-eq-next-solver.rs:8:27 + --> $DIR/foreach-partial-eq-next-solver.rs:17:27 | LL | fn auto_trait() | ---------- required by a bound in this function