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

  1. 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).
  2. Update to the latest nightly; next-solver goal-ordering bugs around normalization are fixed frequently.
  3. Disable -Znext-solver to use the legacy solver's discriminant handling.
  4. 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

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


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