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
- 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.
- 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.
- 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`.
- 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
- Always run arguments through `instantiate_canonical` / canonicalization before entering the next solver.
- Never feed raw `ConstKind::Param` consts straight into a goal; substitute parameters first.
- When building goals programmatically, prefer `ty::Const::from_anon_const` paths that already canonicalize.
- Re-run on a fresh tcx after substituting generic params so no escaping Param survives.
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
- escaping bound vars in {:?}
- unexpected orig_value: {ct:?}
- ConstEquate should not be emitted when `-Znext-solver` is ac
- unexpected const kind: {:?}
- non-rigid unevaluated constant for compute_const_arg_has_typ
AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03).
Data as JSON: /data/errors/4ae7aa6c024433dc.json.
Report an issue: GitHub.