From e9824e4145a1be3aa78354393bb7351f5d656d98 Mon Sep 17 00:00:00 2001 From: sjwang05 <63834813+sjwang05@users.noreply.github.com> Date: Tue, 22 Sep 2026 16:31:53 -0700 Subject: [PATCH] properly decrement available_depth on cycles and provisional cache hits --- .../rustc_type_ir/src/search_graph/mod.rs | 39 +++++++++++-------- .../overflow/cycle-required-depth.rs | 27 +++++++++++++ .../overflow/cycle-required-depth.stderr | 21 ++++++++++ .../provisional-cache-required-depth.rs | 27 +++++++++++++ .../provisional-cache-required-depth.stderr | 21 ++++++++++ 5 files changed, 118 insertions(+), 17 deletions(-) create mode 100644 tests/ui/traits/next-solver/overflow/cycle-required-depth.rs create mode 100644 tests/ui/traits/next-solver/overflow/cycle-required-depth.stderr create mode 100644 tests/ui/traits/next-solver/overflow/provisional-cache-required-depth.rs create mode 100644 tests/ui/traits/next-solver/overflow/provisional-cache-required-depth.stderr diff --git a/compiler/rustc_type_ir/src/search_graph/mod.rs b/compiler/rustc_type_ir/src/search_graph/mod.rs index c081898f26f56..228b6c34b06ed 100644 --- a/compiler/rustc_type_ir/src/search_graph/mod.rs +++ b/compiler/rustc_type_ir/src/search_graph/mod.rs @@ -628,8 +628,8 @@ pub struct SearchGraph, X: Cx = ::Cx> { /// don't need to track the nested goals used while computing a provisional /// cache entry. enum UpdateParentGoalCtxt<'a, X: Cx> { - Ordinary { nested_goals: &'a NestedGoals, min_reachable_available_depth: AvailableDepth }, - CycleOnStack(X::Input), + Ordinary { nested_goals: &'a NestedGoals }, + CycleOnStack { head: X::Input }, ProvisionalCacheHit, } @@ -653,9 +653,12 @@ impl, X: Cx> SearchGraph { heads: impl Iterator, encountered_overflow: bool, context: UpdateParentGoalCtxt<'_, X>, + min_reachable_available_depth: AvailableDepth, ) { if let Some((parent_index, parent)) = stack.last_mut_with_index() { parent.encountered_overflow |= encountered_overflow; + parent.min_reached_available_depth = + parent.min_reached_available_depth.min(min_reachable_available_depth); for (head_index, head) in heads { if let Some(candidate_usages) = &mut parent.candidate_usages { @@ -679,13 +682,11 @@ impl, X: Cx> SearchGraph { } } let parent_depends_on_cycle = match context { - UpdateParentGoalCtxt::Ordinary { nested_goals, min_reachable_available_depth } => { - parent.min_reached_available_depth = - parent.min_reached_available_depth.min(min_reachable_available_depth); + UpdateParentGoalCtxt::Ordinary { nested_goals } => { parent.nested_goals.extend_from_child(step_kind_from_parent, nested_goals); !nested_goals.is_empty() } - UpdateParentGoalCtxt::CycleOnStack(head) => { + UpdateParentGoalCtxt::CycleOnStack { head } => { // We lookup provisional cache entries before detecting cycles. // We therefore can't use a global cache entry if it contains a cycle // whose head is in the provisional cache. @@ -811,7 +812,9 @@ impl, X: Cx> SearchGraph { // - A // - BA cycle // - CB :x: - if let Some(result) = self.lookup_provisional_cache(input, step_kind_from_parent) { + if let Some(result) = + self.lookup_provisional_cache(input, step_kind_from_parent, available_depth) + { return result; } @@ -839,7 +842,9 @@ impl, X: Cx> SearchGraph { // avoid iterating over the stack in case a goal has already been computed. // This may not have an actual performance impact and we could reorder them // as it may reduce the number of `nested_goals` we need to track. - if let Some(result) = self.check_cycle_on_stack(cx, input, step_kind_from_parent) { + if let Some(result) = + self.check_cycle_on_stack(cx, input, step_kind_from_parent, available_depth) + { debug_assert!(validate_cache.is_none(), "global cache and cycle on stack: {input:?}"); return result; } @@ -874,10 +879,8 @@ impl, X: Cx> SearchGraph { step_kind_from_parent, evaluation_result.heads.iter(), evaluation_result.encountered_overflow, - UpdateParentGoalCtxt::Ordinary { - nested_goals: &evaluation_result.nested_goals, - min_reachable_available_depth: available_depth - evaluation_result.required_depth, - }, + UpdateParentGoalCtxt::Ordinary { nested_goals: &evaluation_result.nested_goals }, + available_depth - evaluation_result.required_depth, ); let result = evaluation_result.result; @@ -1119,6 +1122,7 @@ impl, X: Cx> SearchGraph { &mut self, input: X::Input, step_kind_from_parent: PathKind, + available_depth: AvailableDepth, ) -> Option { if !D::ENABLE_PROVISIONAL_CACHE { return None; @@ -1157,6 +1161,7 @@ impl, X: Cx> SearchGraph { heads.iter(), encountered_overflow, UpdateParentGoalCtxt::ProvisionalCacheHit, + available_depth, ); debug!(?head_index, ?path_from_head, "provisional cache hit"); return Some(result); @@ -1278,10 +1283,8 @@ impl, X: Cx> SearchGraph { step_kind_from_parent, heads, encountered_overflow, - UpdateParentGoalCtxt::Ordinary { - nested_goals, - min_reachable_available_depth: available_depth - required_depth, - }, + UpdateParentGoalCtxt::Ordinary { nested_goals }, + available_depth - required_depth, ); debug!(?required_depth, "global cache hit"); @@ -1294,6 +1297,7 @@ impl, X: Cx> SearchGraph { cx: X, input: X::Input, step_kind_from_parent: PathKind, + available_depth: AvailableDepth, ) -> Option { let head_index = self.stack.find(input)?; // We have a nested goal which directly relies on a goal deeper in the stack. @@ -1312,7 +1316,8 @@ impl, X: Cx> SearchGraph { step_kind_from_parent, iter::once((head_index, head)), false, - UpdateParentGoalCtxt::CycleOnStack(input), + UpdateParentGoalCtxt::CycleOnStack { head: input }, + available_depth, ); // Return the provisional result or, if we're in the first iteration, diff --git a/tests/ui/traits/next-solver/overflow/cycle-required-depth.rs b/tests/ui/traits/next-solver/overflow/cycle-required-depth.rs new file mode 100644 index 0000000000000..105460eee8013 --- /dev/null +++ b/tests/ui/traits/next-solver/overflow/cycle-required-depth.rs @@ -0,0 +1,27 @@ +//@ compile-flags: -Znext-solver +//@ check-pass + +// We must properly decrement available_depth when encountering cycles. +// Proving `Foo: Send` results in a cycle: +// - Foo: Send +// ->PhantomData: Send +// ->Foo: Send +// and if we cache this result with too small of a required depth, the second +// `require` call will succeed with the cached result where it would fail if +// the goal were freshly evaluated. + +#![recursion_limit = "6"] + +use std::marker::PhantomData; + +pub struct Foo(PhantomData); +pub struct W(T); + +fn require() {} + +fn main() { + require::(); + require::>>>>>(); + //~^ WARN overflow evaluating the requirement `W>>>>: Send` + //~| WARN this was previously accepted by the compiler but is being phased out +} diff --git a/tests/ui/traits/next-solver/overflow/cycle-required-depth.stderr b/tests/ui/traits/next-solver/overflow/cycle-required-depth.stderr new file mode 100644 index 0000000000000..15e121053c22d --- /dev/null +++ b/tests/ui/traits/next-solver/overflow/cycle-required-depth.stderr @@ -0,0 +1,21 @@ +warning: overflow evaluating the requirement `W>>>>: Send` + --> $DIR/cycle-required-depth.rs:24:5 + | +LL | require::>>>>>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: which requires `W>>>: Send` + = note: which requires `W>>: Send` + = note: which requires `W>: Send` + = note: which requires `W: Send` + = note: which requires `Foo: Send` + = note: and so on... + = help: consider adding a manual `impl` of auto traits like `Send` for intermediate types, if auto traits are involved + = help: or consider increasing the recursion limit by adding a `#![recursion_limit = "12"]` attribute to your crate (`cycle_required_depth`) + = note: this lint is attached to the whole crate and can't be disabled on a per-function basis + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #159228 + = note: `#[warn(recursion_depth_exceeding_limit)]` (part of `#[warn(future_incompatible)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/traits/next-solver/overflow/provisional-cache-required-depth.rs b/tests/ui/traits/next-solver/overflow/provisional-cache-required-depth.rs new file mode 100644 index 0000000000000..ad070e2d276a3 --- /dev/null +++ b/tests/ui/traits/next-solver/overflow/provisional-cache-required-depth.rs @@ -0,0 +1,27 @@ +//@ compile-flags: -Znext-solver +//@ check-pass + +#![recursion_limit = "6"] +use std::marker::PhantomData; + +// We need to decrement available_depth on provisional cache hits. +// e.g., in the following example: +// A: Send, avail = 6 +// -> PhantomData: Send, avail = 5 +// -> A: Send, avail = 4 +// -> B: Send, avail = 5 +// -> C: Send, avail = 4 +// -> PhantomData: Send, provisional cache hit, avail = 4 if we don't decrement +struct A(PhantomData, B); +struct B(C); +struct C(PhantomData); +struct W(T); + +fn require() {} + +fn main() { + require::(); + require::>>>>(); + //~^ WARN overflow evaluating the requirement `W>>>: Send` + //~| WARN this was previously accepted by the compiler but is being phased out +} diff --git a/tests/ui/traits/next-solver/overflow/provisional-cache-required-depth.stderr b/tests/ui/traits/next-solver/overflow/provisional-cache-required-depth.stderr new file mode 100644 index 0000000000000..a4ee2a2d87078 --- /dev/null +++ b/tests/ui/traits/next-solver/overflow/provisional-cache-required-depth.stderr @@ -0,0 +1,21 @@ +warning: overflow evaluating the requirement `W>>>: Send` + --> $DIR/provisional-cache-required-depth.rs:24:5 + | +LL | require::>>>>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: which requires `W>>: Send` + = note: which requires `W>: Send` + = note: which requires `W: Send` + = note: which requires `A: Send` + = note: which requires `B: Send` + = note: and so on... + = help: consider adding a manual `impl` of auto traits like `Send` for intermediate types, if auto traits are involved + = help: or consider increasing the recursion limit by adding a `#![recursion_limit = "12"]` attribute to your crate (`provisional_cache_required_depth`) + = note: this lint is attached to the whole crate and can't be disabled on a per-function basis + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #159228 + = note: `#[warn(recursion_depth_exceeding_limit)]` (part of `#[warn(future_incompatible)]`) on by default + +warning: 1 warning emitted +