Rollup merge of #149904 - ShoyuVanilla:ns-remove-sg-hack, r=lcnr

`-Znext-solver` Remove the forced ambiguity hack from search graph

As discussed in https://github.com/rust-lang/trait-system-refactor-initiative/issues/257

r? lcnr
This commit is contained in:
Jana Dönszelmann 2026-02-17 14:18:44 +01:00 committed by GitHub
commit c108ad5617
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 48 additions and 28 deletions

View file

@ -916,10 +916,9 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
/// heads from the stack. This may not necessarily mean that we've actually /// heads from the stack. This may not necessarily mean that we've actually
/// reached a fixpoint for that cycle head, which impacts the way we rebase /// reached a fixpoint for that cycle head, which impacts the way we rebase
/// provisional cache entries. /// provisional cache entries.
#[derive_where(Debug; X: Cx)] #[derive(Debug)]
enum RebaseReason<X: Cx> { enum RebaseReason {
NoCycleUsages, NoCycleUsages,
Ambiguity(X::AmbiguityInfo),
Overflow, Overflow,
/// We've actually reached a fixpoint. /// We've actually reached a fixpoint.
/// ///
@ -956,7 +955,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D, X> {
&mut self, &mut self,
cx: X, cx: X,
stack_entry: &StackEntry<X>, stack_entry: &StackEntry<X>,
rebase_reason: RebaseReason<X>, rebase_reason: RebaseReason,
) { ) {
let popped_head_index = self.stack.next_index(); let popped_head_index = self.stack.next_index();
#[allow(rustc::potential_query_instability)] #[allow(rustc::potential_query_instability)]
@ -1035,9 +1034,6 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D, X> {
// is not actually equal to the final provisional result. We // is not actually equal to the final provisional result. We
// need to discard the provisional cache entry in this case. // need to discard the provisional cache entry in this case.
RebaseReason::NoCycleUsages => return false, RebaseReason::NoCycleUsages => return false,
RebaseReason::Ambiguity(info) => {
*result = D::propagate_ambiguity(cx, input, info);
}
RebaseReason::Overflow => *result = D::fixpoint_overflow_result(cx, input), RebaseReason::Overflow => *result = D::fixpoint_overflow_result(cx, input),
RebaseReason::ReachedFixpoint(None) => {} RebaseReason::ReachedFixpoint(None) => {}
RebaseReason::ReachedFixpoint(Some(path_kind)) => { RebaseReason::ReachedFixpoint(Some(path_kind)) => {
@ -1352,27 +1348,6 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D, X> {
return EvaluationResult::finalize(stack_entry, encountered_overflow, result); return EvaluationResult::finalize(stack_entry, encountered_overflow, result);
} }
// If computing this goal results in ambiguity with no constraints,
// we do not rerun it. It's incredibly difficult to get a different
// response in the next iteration in this case. These changes would
// likely either be caused by incompleteness or can change the maybe
// cause from ambiguity to overflow. Returning ambiguity always
// preserves soundness and completeness even if the goal is be known
// to succeed or fail.
//
// This prevents exponential blowup affecting multiple major crates.
// As we only get to this branch if we haven't yet reached a fixpoint,
// we also taint all provisional cache entries which depend on the
// current goal.
if let Some(info) = D::is_ambiguous_result(result) {
self.rebase_provisional_cache_entries(
cx,
&stack_entry,
RebaseReason::Ambiguity(info),
);
return EvaluationResult::finalize(stack_entry, encountered_overflow, result);
};
// If we've reached the fixpoint step limit, we bail with overflow and taint all // If we've reached the fixpoint step limit, we bail with overflow and taint all
// provisional cache entries which depend on the current goal. // provisional cache entries which depend on the current goal.
i += 1; i += 1;

View file

@ -0,0 +1,45 @@
//@ check-pass
//@ compile-flags: -Znext-solver
// Regression test for https://github.com/rust-lang/trait-system-refactor-initiative/issues/257.
#![feature(rustc_attrs)]
#![expect(internal_features)]
#![rustc_no_implicit_bounds]
pub trait Bound {}
impl Bound for u8 {}
pub trait Proj {
type Assoc;
}
impl<U: Bound> Proj for U {
type Assoc = U;
}
impl Proj for MyField {
type Assoc = u8;
}
// While wf-checking the global bounds of `fn foo`, elaborating this outlives predicate triggered a
// cycle in the search graph along a particular probe path, which was not an actual solution.
// That cycle then resulted in a forced false-positive ambiguity due to a performance hack in the
// search graph and then ended up floundering the root goal evaluation.
pub trait Field: Proj<Assoc: Bound + 'static> {}
struct MyField;
impl Field for MyField {}
trait IdReqField {
type This;
}
impl<F: Field> IdReqField for F {
type This = F;
}
fn foo()
where
<MyField as IdReqField>::This: Field,
{
}
fn main() {}