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. ThisView on GitHub (pinned to 22057b88b0)
Solutions
- 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.
- Reduce to a minimal inherent-associated-type or weak-alias test and file under `A-traits` / `F-inherent-associates`.
- 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
- Only call normalizes_to on terms whose kind is an `Alias(Projection, ..)`.
- Add an assertion or guard at the boundary where you produce terms to feed the solver.
- Distinguish inherent types and opaque types from projections before normalizing.
- Prefer `tcx.normalize_erasing_regions` after confirming the term is a projection.
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
- `ConstKind::Param` should have been canonicalized to `Placeh
- escaping bound vars in {:?}
- reservation impl for trait with assoc item: {:?}
- trait aliases do not have associated types: {:?}
- `Sized`/`MetaSized` does not have an associated type: {:?}
AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03).
Data as JSON: /data/errors/6cc706b81fc6b83f.json.
Report an issue: GitHub.