rust-lang/rust · critical
unexpected self ty `{:?}` when normalizing `<T as Discrimina
Error message
unexpected self ty `{:?}` when normalizing `<T as DiscriminantKind>::Discriminant` What it means
consider_builtin_discriminant_kind_candidate (normalizes_to.rs:1024) computes the concrete `<T as DiscriminantKind>::Discriminant` type by matching on self_ty.kind(). The final arm panics for Infer(TyVar|Fresh*), non-rigid Alias (still-normalizing projection), and Bound types. By invariant, a discriminant query should only run after the self type is concrete, a rigid alias, a param, or a placeholder (all handled in earlier arms that return early). Reaching this panic means an unresolved inference variable, an un-normalized projection, or a bound type leaked into the discriminant computation.
Source
Thrown at compiler/rustc_next_trait_solver/src/solve/normalizes_to.rs:1024
ty::UnsafeBinder(_) => {
// FIXME(unsafe_binders): instantiate this with placeholders?? i guess??
unimplemented!("discr subgoal...")
}
// Given an alias, parameter, or placeholder we add an impl candidate normalizing to a rigid
// alias. In case there's a where-bound further constraining this alias it is preferred over
// this impl candidate anyways. It's still a bit scuffed.
ty::Alias(ty::IsRigid::Yes, _) | ty::Param(_) | ty::Placeholder(..) => {
return ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
ecx.instantiate_normalizes_to_as_rigid(goal)?;
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
});
}
ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_))
| ty::Alias(ty::IsRigid::No, _)
| ty::Bound(..) => panic!(
"unexpected self ty `{:?}` when normalizing `<T as DiscriminantKind>::Discriminant`",
goal.predicate.self_ty()
),
};
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
ecx.instantiate_normalizes_to_term(goal, discriminant_ty.into())?;
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
}
fn consider_builtin_destruct_candidate(
_ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>,
) -> Result<Candidate<I>, NoSolutionOrRerunNonErased> {
panic!("`Destruct` does not have an associated type: {:?}", goal);
}
View on GitHub (pinned to 22057b88b0)
Solutions
- Add a bound or concrete annotation so the self type is resolved before its discriminant is queried (e.g. monomorphize or constrain T to a known enum/ADT).
- Update to the latest nightly; next-solver goal-ordering bugs around normalization are fixed frequently.
- Disable -Znext-solver to use the legacy solver's discriminant handling.
- File an ICE with the self_ty from the panic and a minimal reproducer (mem::discriminant / niche-layout code).
Example fix
// before: discriminant queried on an unconstrained generic
fn disc<T>(t: &T) {
let _ = std::mem::discriminant(t); // <T as DiscriminantKind>::Discriminant on unknown T
}
// after: constrain T to a concrete enum
fn disc<E: Copy>(t: &E) where E: 'static {
// callers pass a known enum; discriminant type is statically determined
let _ = std::mem::discriminant(t);
} Defensive patterns
Strategy: validation
Validate before calling
// DiscriminantKind::Discriminant must normalize against a concrete Self.
// Pin Self to a concrete enum/struct type:
enum Tagged { A = 1, B = 2 }
fn tag_discriminant() -> <Tagged as core::marker::DiscriminantKind>::Discriminant { 1 as _ }
Type guard
trait KnownDisc: core::marker::DiscriminantKind {}
impl<T: Sized + 'static> KnownDisc for T {}
fn disc<T: KnownDisc>() -> <T as core::marker::DiscriminantKind>::Discriminant { unimplemented!() }
Prevention
- Project `Discriminant` only after `T` is fully inferred and Sized.
- Avoid nested generic params whose discriminant depends on an unresolved inference variable.
- Prefer `core::mem::Discriminant<T>` from `core::mem::discriminant` over manual normalization.
When it happens
Trigger: Code that forces `<T as DiscriminantKind>::Discriminant` while T is an unconstrained inference variable, a non-rigid projection that has not finished normalizing, or a bound type — e.g. mem::discriminant, niche-layout checks, or enum-representation queries on a fully generic T with no constraint, under -Znext-solver with a goal-ordering regression.
Common situations: Generic code over `T` that inspects discriminants or relies on niche layout without bounding T; next-solver nightly regressions in normalization ordering; fuzzing the solver with partially-resolved types.
Related errors
- unexpected self ty `{:?}` when normalizing `<T as Pointee>::
- no such associated type in `AsyncFn*`: {:?}
- `Tuple` does not have an associated type: {:?}
- `FusedIterator` does not have an associated type: {:?}
- unexpected associated item `{:?}` for `{self_ty:?}`
AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03).
Data as JSON: /data/errors/316de354f0ae01ea.json.
Report an issue: GitHub.