rust-lang/rust · critical

`ConstKind::Param` should have been canonicalized to `Placeh

Error message

`ConstKind::Param` should have been canonicalized to `Placeholder`

What it means

An `unreachable!` panic inside `compute_const_arg_has_type_goal` in the new trait solver, asserting that a const argument of `ConstKind::Param` (a generic const parameter, e.g. `N` in `fn foo<const N: usize>()`) should never reach this code because canonicalization is expected to have rewritten it to `ConstKind::Placeholder` first. The canonicalizer lifts late-bound/param vars into placeholders so the solver sees a fully closed term; hitting this arm means the const was leaked into the goal without that rewrite. It is a compiler-internal invariant violation, not a normal user error.

Source

Thrown at compiler/rustc_next_trait_solver/src/solve/mod.rs:273

                    .evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
                    .map_err(Into::into);
            }
            ty::ConstKind::Error(_) => {
                return self
                    .evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
                    .map_err(Into::into);
            }
            ty::ConstKind::Alias(ty::IsRigid::Yes, alias_const) => {
                alias_const.type_of(self.cx()).skip_norm_wip()
            }
            ty::ConstKind::Alias(ty::IsRigid::No, _) => unimplemented!(
                "non-rigid unevaluated constant for compute_const_arg_has_type_goal: {ct:?}"
            ),
            ty::ConstKind::Expr(_) => unimplemented!(
                "`feature(generic_const_exprs)` is not supported in the new trait solver"
            ),
            ty::ConstKind::Param(_) => {
                unreachable!("`ConstKind::Param` should have been canonicalized to `Placeholder`")
            }
            ty::ConstKind::Bound(_, _) => panic!("escaping bound vars in {:?}", ct),
            ty::ConstKind::Value(cv) => cv.ty(),
            ty::ConstKind::Placeholder(placeholder) => {
                placeholder.find_const_ty_from_env(goal.param_env)
            }
        };

        self.eq(goal.param_env, ct_ty, ty)?;
        self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes).map_err(Into::into)
    }
}

#[derive(Debug)]
enum MergeCandidateInfo {
    AlwaysApplicable(usize),
    EqualResponse,
}

View on GitHub (pinned to 22057b88b0)

Solutions

  1. Disable the next trait solver (`-Znext-solver=no` or remove `-Znext-solver`) and reproduce on the stable solver to confirm it is a new-solver-specific bug.
  2. Reduce the crate to a minimal reproducer involving a const generic parameter in a normalization/where-clause position and file an issue against rust-lang/rust with the `A-traits` and `F-next-solver` labels.
  3. If you maintain a crate that calls the solver directly, ensure every const argument is canonicalized (rewriting `ConstKind::Param` → `Placeholder`) before it is passed into `evaluate_added_goals_and_make_canonical_response`.
  4. Pin to an older nightly where the invariant held while the upstream fix lands.

Example fix

// before — const param leaks into a normalization goal
fn f<const N: usize>() where [(); N]: Sized {}
// after — avoid routing the const param through a NormalizesTo goal,
// or wait for the canonicalizer to emit Placeholder before the goal
fn f<const N: usize>() where [(); N]: Sized {} // works on stable solver
Defensive patterns

Strategy: type-guard

Validate before calling

// Before invoking the solver with a const argument, confirm every
// const in the query is already canonicalized (Param kinds should
// have been turned into Placeholder by the canonicalizer).
fn ensure_const_canonicalized(ct: &Const<'_>) -> Result<(), &'static str> {
    use rustc_middle::ty::ConstKind;
    match ct.kind() {
        ConstKind::Param(_) => Err("const carries a non-canonicalized Param kind; canonicalize first"),
        _ => Ok(()),
    }
}
for ct in query.consts() { ensure_const_canonicalized(ct)?; }

Type guard

// Returns true only when the const has no raw Param kind, i.e. it is
// safe to hand to the next solver.
fn is_canonicalized_const(ct: &Const<'_>) -> bool {
    use rustc_middle::ty::ConstKind;
    !matches!(ct.kind(), ConstKind::Param(_))
}

Try / catch

// The solver panics on a violated invariant; wrap the call in
// catch_unwind only as a last-resort containment, then degrade.
use std::panic;
let res = panic::catch_unwind(panic::AssertUnwindSafe(|| solver.evaluate(goal)));
match res {
    Ok(o)  => o,
    Err(_) => { /* fall back to the legacy solver */ }
}

Prevention

When it happens

Trigger: Reached when a `NormalizesTo`/`const_arg_has_type` goal is evaluated whose const argument still carries `ConstKind::Param` after the canonical→placeholder rewrite that `EvalCtxt::canonicalize_const_arg` is responsible for. Concretely, a const generic parameter appears in a position where the solver tries to structurally normalize the const's type (e.g. `<const N: usize>` used inside an alias or where-clause on the new solver, `-Znext-solver`).

Common situations: Using unstable const generics features (`generic_const_exprs`, const generic parameters in associated type bounds, min_generic_const_args) on a nightly compiler with the next trait solver enabled. Often surfaces after a rustc upgrade changes how param consts are canonicalized, or in crates that build their own goals via the solver API rather than going through normal type checking.

Related errors


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