rust-lang/rust · critical

expected projection, found {kind:?}

Error message

expected projection, found {kind:?}

What it means

A `panic!` inside the `error_response` closure of `consider_impl_candidate`, which builds an error term (`Ty::new_error` / `Const::new_error`) when projection fails. The match expects `AliasTermKind::ProjectionTy` or `ProjectionConst`; any other alias kind (inherent, weak, or a future kind) reaching here means the goal was not actually a projection alias — the dispatch logic that routed it to an impl candidate is inconsistent with the alias kind it carries.

Source

Thrown at compiler/rustc_next_trait_solver/src/solve/normalizes_to.rs:315

            // See <https://github.com/rust-lang/trait-system-refactor-initiative/issues/185>.
            ecx.try_evaluate_added_goals()?;

            // Add GAT where clauses from the trait's definition. This is necessary
            // for soundness until we properly handle implied bounds on binders,
            // see tests/ui/generic-associated-types/must-prove-where-clauses-on-norm.rs.
            ecx.add_goals(
                GoalSource::AliasWellFormed,
                cx.own_clauses_of(alias_def_id.into())
                    .iter_instantiated(cx, goal.predicate.alias.args)
                    .map(Unnormalized::skip_norm_wip)
                    .map(|clause| goal.with(cx, clause)),
            )?;

            let error_response = |ecx: &mut EvalCtxt<'_, D>, guar| {
                let error_term = match goal.predicate.alias.kind {
                    ty::AliasTermKind::ProjectionTy { .. } => Ty::new_error(cx, guar).into(),
                    ty::AliasTermKind::ProjectionConst { .. } => Const::new_error(cx, guar).into(),
                    kind => panic!("expected projection, found {kind:?}"),
                };
                ecx.instantiate_normalizes_to_term(goal, error_term)?;
                ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
            };

            let target_item_def_id =
                match ecx.fetch_eligible_assoc_item(goal_trait_ref, alias_def_id, impl_def_id) {
                    FetchEligibleAssocItemResponse::Found(target_item_def_id) => target_item_def_id,
                    FetchEligibleAssocItemResponse::NotFound(tm) => {
                        match tm {
                            // In case the associated item is hidden due to specialization,
                            // normalizing this associated item is always ambiguous. Treating
                            // the associated item as rigid would be incomplete and allow for
                            // overlapping impls, see #105782.
                            //
                            // As this ambiguity is unavoidable we emit a nested ambiguous
                            // goal instead of using `Certainty::AMBIGUOUS`. This allows us to
                            // return the nested goals to the parent `AliasRelate` goal. This

View on GitHub (pinned to 22057b88b0)

Solutions

  1. Confirm the goal's `alias` is genuinely a `ProjectionTy`/`ProjectionConst` before it is handed to `consider_impl_candidate`; inherent/weak aliases should be dispatched elsewhere.
  2. Reduce to a minimal inherent-associated-type or weak-alias test and file under `A-traits` / `F-inherent-associates`.
  3. Run without `-Znext-solver` as a workaround.

Example fix

// before — inherent associated type normalized under new solver
impl Foo for () { type Assoc = i32; }  // inherent alias T::Assoc
// after — route inherent aliases through their own candidate, or use stable solver
Defensive patterns

Strategy: type-guard

Validate before calling

// normalizes_to expects an alias (projection) term. Reject anything
// else before calling.
use rustc_middle::ty::{TermKind, AliasTyKind};
fn term_is_projection(term: Term<'_>) -> bool {
    matches!(
        term.kind(),
        TermKind::Ty(ty) if matches!(ty.kind(), ty::Alias(AliasTyKind::Projection, _))
    )
}
if !term_is_projection(term) { return Err("term is not a projection"); }

Type guard

// Narrows a Term to a projection alias suitable for normalizes_to.
fn as_projection<'tcx>(term: Term<'tcx>) -> Option<ty::AliasTy<'tcx>> {
    match term.kind() {
        TermKind::Ty(ty::TyKind::Alias(ty::AliasTyKind::Projection, p)) => Some(*p),
        _ => None,
    }
}

Try / catch

use std::panic;
let r = panic::catch_unwind(panic::AssertUnwindSafe(|| solver.normalizes_to(goal)));
match r {
    Ok(v) => v,
    Err(_) => Err("normalizes_to only accepts projection terms; caller passed a non-alias"),
}

Prevention

When it happens

Trigger: A `NormalizesTo` goal whose `alias.kind` is not a projection (e.g. an inherent associated type `T::Assoc` or a weak alias) reaches the impl-candidate error path during projection in the new solver. Typically an internal dispatch mistake rather than user code.

Common situations: Nightly features that introduce new `AliasTermKind` variants, or refactors of how inherent/weak aliases are assembled. Users see it as an ICE when normalizing an inherent associated type under `-Znext-solver`.

Related errors


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