rust-lang/rust · critical

projecting to field of non-ADT {ty}

Error message

projecting to field of non-ADT {ty}

What it means

`Place::project_to_field` only accepts places whose type is `ty::Adt`; for any other kind (primitive, array, tuple, closure, etc.) it panics. rustc_middle enforces this precondition because field indices in `PlaceElem::Field` are indexed against an ADT's `non_enum_variant().fields`, which has no meaning for non-ADT types.

Source

Thrown at compiler/rustc_middle/src/mir/statement.rs:449

    pub fn project_deeper(self, more_projections: &[PlaceElem<'tcx>], tcx: TyCtxt<'tcx>) -> Self {
        if more_projections.is_empty() {
            return self;
        }

        self.as_ref().project_deeper(more_projections, tcx)
    }

    /// Return a place that projects to a field of the current place.
    ///
    /// The type of the current place must be an ADT.
    pub fn project_to_field(
        self,
        idx: FieldIdx,
        local_decls: &impl HasLocalDecls<'tcx>,
        tcx: TyCtxt<'tcx>,
    ) -> Self {
        let ty = self.ty(local_decls, tcx).ty;
        let ty::Adt(adt, args) = ty.kind() else { panic!("projecting to field of non-ADT {ty}") };
        let field = &adt.non_enum_variant().fields[idx];
        let field_ty = field.ty(tcx, args).skip_norm_wip();
        self.project_deeper(&[ProjectionElem::Field(idx, field_ty)], tcx)
    }

    pub fn ty_from<D>(
        local: Local,
        projection: &[PlaceElem<'tcx>],
        local_decls: &D,
        tcx: TyCtxt<'tcx>,
    ) -> PlaceTy<'tcx>
    where
        D: ?Sized + HasLocalDecls<'tcx>,
    {
        // If there's a field projection element in `projection`, we *could* skip everything
        // before that, but on 2026-01-31 a perf experiment showed no benefit from doing so.
        PlaceTy::from_ty(local_decls.local_decls()[local].ty).multi_projection_ty(tcx, projection)
    }

View on GitHub (pinned to 22057b88b0)

Solutions

  1. Before calling `project_to_field`, dispatch on `place.ty(...).ty.kind()` and only call it for `ty::Adt(..)`; route tuples, closures, and arrays through their dedicated projection logic.
  2. If you meant tuple element access, use `ProjectionElem::Field` with the tuple's per-element field type via `PlaceTy::projection_ty`, not `Place::project_to_field`.
  3. For closure/coroutine captures, ensure the place is the synthesized closure-state ADT (or use the capture-path API) before treating it as ADT.
  4. Reproduce with `-Zmir-opt-level=0`; if stock rustc hits this in user code, file an ICE with the backtrace and minimal repro.

Example fix

// before
let fp = place.project_to_field(idx, local_decls, tcx);
// panic: projecting to field of non-ADT [T; 3]

// after
match place.ty(local_decls, tcx).ty.kind() {
    ty::Adt(..) => place.project_to_field(idx, local_decls, tcx),
    _ => /* handle tuple/array/closure case explicitly */
}
Defensive patterns

Strategy: type-guard

Validate before calling

// project_to_field() asserts the base type is an ADT (ty::Adt(..)).
// Verify the type kind BEFORE projecting.
use rustc_middle::ty::{self, Ty};
fn is_field_projectable(ty: Ty<'_>) -> bool {
    matches!(ty.kind(), ty::Adt(..)) // structs, enums, unions
}
// Usage:
//   let base_ty = place.ty(local_decls, tcx).ty;
//   if is_field_projectable(base_ty) {
//       place.project_to_field(idx, local_decls, tcx);
//   } else {
//       // tuples, closures, arrays, slices, primitives -> use the correct projection
//   }

Type guard

// Strict narrowing: only proceed when the type is exactly an ADT.
use rustc_middle::ty::{self, Ty};
fn require_adt<'tcx>(ty: Ty<'tcx>) -> Option<(&'tcx ty::AdtDef<'tcx>, &'tcx ty::GenericArgsRef<'tcx>)> {
    match ty.kind() {
        ty::Adt(def, args) => Some((def, args)),
        _ => None,
    }
}
// Pattern:
//   match require_adt(base_ty) {
//       Some((def, args)) => place.project_to_field(idx, local_decls, tcx),
//       None => /* pick the right projection for tuples/closures/arrays */
//   }

Prevention

When it happens

Trigger: Triggered by code that calls `place.project_to_field(idx, local_decls, tcx)` on a place whose `.ty(...).ty` is not an ADT — for instance a custom MIR pass or analysis that walks `PlaceElem::Field` without first checking the base type, or hand-built MIR that uses `Field` projection on a tuple/array/closure place.

Common situations: Out-of-tree MIR tooling assuming all field projections target structs; mir-opt regression that mis-types a downcast place; closure/coroutine state-access where the place type is `Closure`/`Coroutine` rather than the synthesized ADT; user-reported ICE on code that hand-rolls field access via macros that lower to MIR field projection.

Related errors


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