rust-lang/rust · critical

we never retry stalled queries if the parent was erased

Error message

we never retry stalled queries if the parent was erased

What it means

Internal `unreachable!` invariant assertion in the trait solver's stalled-query fast path (`rerunning_stalled_goal_may_make_progress`). After a stalled goal previously succeeded in erased (opaque-erasing) mode, the solver asks `should_rerun_after_erased_canonicalization` whether to retry; one possible answer is `EagerlyPropagateToParent`, which is only valid in the full `EvalCtxt` path (mod.rs:757) where opaque accesses can be propagated to the parent query. The fast-path cache invariant asserts this never happens for a cached stalled goal — if it does, the stalled-query cache is inconsistent with the typing mode and the compiler ICEs.

Source

Thrown at compiler/rustc_next_trait_solver/src/solve/eval_ctxt/fast_path.rs:91

                .opaque_types_storage_num_entries()
                .needs_reevaluation(num_opaques_in_storage)
            {
                // Unless this goal previously succeeded in erased mode.
                // If the stalled goal successfully evaluated while erasing opaque types,
                // and the current state of the opaque type storage is not different in a way that is
                // relevant, this stalled goal cannot make any progress and we set this variable to true.
                let mut previous_erased_run_is_still_valid = false;

                if let &SucceededInErased::Yes { accessed_opaques } = previously_succeeded_in_erased
                {
                    match should_rerun_after_erased_canonicalization(
                        accessed_opaques,
                        delegate.typing_mode_raw(),
                        &delegate.clone_opaque_types_lookup_table(),
                    ) {
                        RerunDecision::Yes => {}
                        RerunDecision::EagerlyPropagateToParent => {
                            unreachable!("we never retry stalled queries if the parent was erased")
                        }
                        RerunDecision::No => {
                            previous_erased_run_is_still_valid = true;
                        }
                    }
                }

                if !previous_erased_run_is_still_valid {
                    return MayMakeProgress;
                }
            }
        }
    }

    // Otherwise, we can be sure that this stalled goal cannot make any progress
    // and we can exit early.
    WontMakeProgress(stalled_certainty)
}

View on GitHub (pinned to 22057b88b0)

Solutions

  1. Retry with `-Zdisable-fast-paths` — it short-circuits this fast path and forces the slow path (mod.rs:757) which legitimately handles `EagerlyPropagateToParent`; if that compiles, the bug is fast-path-specific.
  2. Reduce the reproducer by deleting `impl Trait`/async blocks until the ICE vanishes, then report the minimal case upstream as a next-solver ICE.
  3. Bisect nightlies — opaque/erased-canonicalization logic changes frequently in the next solver.
  4. If on a feature branch of rustc, check whether `should_rerun_after_erased_canonicalization` or the stalled-cache population was recently modified; the invariant mismatch is usually a stale `SucceededInErased::Yes` stored where `No` should have been.

Example fix

// before (rustc invocation triggering the ICE)
rustc -Znext-solver crate_with_heavy_impl_trait

// after — bypass the fast path to confirm and keep compiling while upstream fixes it
rustc -Znext-solver -Zdisable-fast-paths crate_with_heavy_impl_trait
Defensive patterns

Strategy: fallback

Validate before calling

// build.rs: detect the trigger pattern (opaque types / impl Trait) in const-adjacent code
// so you can fall back to explicit named types BEFORE the solver ICEs.
fn main() {
    for file in ["src/lib.rs"] {
        let src = std::fs::read_to_string(file).unwrap_or_default();
        let in_const = src.lines().any(|l| l.contains("const fn") || l.contains("const "));
        let uses_opaque = src.lines().any(|l| l.contains("-> impl ") || l.contains("async "));
        if in_const && uses_opaque {
            eprintln!("warning: {} mixes `impl Trait`/async with const evaluation; the next-solver can hit the erased-parent ICE (fast_path.rs:91). Replace opaque returns with explicit named types.", file);
        }
    }
    println!("cargo:rerun-if-changed=src/lib.rs");
}

Prevention

When it happens

Trigger: Reached when `rerunning_stalled_goal_may_make_progress` re-checks a previously stalled goal whose `previously_succeeded_in_erased == Yes`, the opaque-type storage has changed enough to need reevaluation, the original typing mode is `TypingMode::ErasedNotCoherence(MayBeErased)`, and `should_rerun_after_erased_canonicalization` returns `EagerlyPropagateToParent`. Concretely: heavy opaque-type inference (async blocks, `impl Trait`, RPITs) under `-Znext-solver` where a goal is stalled, partially evaluated in erased mode, then re-probed by the fast path.

Common situations: Complex generic code with many `impl Trait`/async opaques compiled with `-Znext-solver`; nightly toolchain regression in the trait solver's opaque-type / erased-canonicalization handling; enabling `-Zdisable-fast-paths` may mask it (it forces `MayMakeProgress`); large monomorphized crates that stress the stalled-query cache.

Related errors


AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03). Data as JSON: /data/errors/1a31bd165d388367.json. Report an issue: GitHub.