{"id":"20bf8511390be256","repo":"rust-lang/rust","slug":"projecting-to-field-of-non-adt-ty","errorCode":null,"errorMessage":"projecting to field of non-ADT {ty}","messagePattern":"projecting to field of non-ADT (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"compiler/rustc_middle/src/mir/statement.rs","lineNumber":449,"sourceCode":"    pub fn project_deeper(self, more_projections: &[PlaceElem<'tcx>], tcx: TyCtxt<'tcx>) -> Self {\n        if more_projections.is_empty() {\n            return self;\n        }\n\n        self.as_ref().project_deeper(more_projections, tcx)\n    }\n\n    /// Return a place that projects to a field of the current place.\n    ///\n    /// The type of the current place must be an ADT.\n    pub fn project_to_field(\n        self,\n        idx: FieldIdx,\n        local_decls: &impl HasLocalDecls<'tcx>,\n        tcx: TyCtxt<'tcx>,\n    ) -> Self {\n        let ty = self.ty(local_decls, tcx).ty;\n        let ty::Adt(adt, args) = ty.kind() else { panic!(\"projecting to field of non-ADT {ty}\") };\n        let field = &adt.non_enum_variant().fields[idx];\n        let field_ty = field.ty(tcx, args).skip_norm_wip();\n        self.project_deeper(&[ProjectionElem::Field(idx, field_ty)], tcx)\n    }\n\n    pub fn ty_from<D>(\n        local: Local,\n        projection: &[PlaceElem<'tcx>],\n        local_decls: &D,\n        tcx: TyCtxt<'tcx>,\n    ) -> PlaceTy<'tcx>\n    where\n        D: ?Sized + HasLocalDecls<'tcx>,\n    {\n        // If there's a field projection element in `projection`, we *could* skip everything\n        // before that, but on 2026-01-31 a perf experiment showed no benefit from doing so.\n        PlaceTy::from_ty(local_decls.local_decls()[local].ty).multi_projection_ty(tcx, projection)\n    }","sourceCodeStart":431,"sourceCodeEnd":467,"githubUrl":"https://github.com/rust-lang/rust/blob/22057b88b091743bc0fd8d592a9264f0a6951403/compiler/rustc_middle/src/mir/statement.rs#L431-L467","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","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`.","For closure/coroutine captures, ensure the place is the synthesized closure-state ADT (or use the capture-path API) before treating it as ADT.","Reproduce with `-Zmir-opt-level=0`; if stock rustc hits this in user code, file an ICE with the backtrace and minimal repro."],"exampleFix":"// before\nlet fp = place.project_to_field(idx, local_decls, tcx);\n// panic: projecting to field of non-ADT [T; 3]\n\n// after\nmatch place.ty(local_decls, tcx).ty.kind() {\n    ty::Adt(..) => place.project_to_field(idx, local_decls, tcx),\n    _ => /* handle tuple/array/closure case explicitly */\n}","handlingStrategy":"type-guard","validationCode":"// project_to_field() asserts the base type is an ADT (ty::Adt(..)).\n// Verify the type kind BEFORE projecting.\nuse rustc_middle::ty::{self, Ty};\nfn is_field_projectable(ty: Ty<'_>) -> bool {\n    matches!(ty.kind(), ty::Adt(..)) // structs, enums, unions\n}\n// Usage:\n//   let base_ty = place.ty(local_decls, tcx).ty;\n//   if is_field_projectable(base_ty) {\n//       place.project_to_field(idx, local_decls, tcx);\n//   } else {\n//       // tuples, closures, arrays, slices, primitives -> use the correct projection\n//   }","typeGuard":"// Strict narrowing: only proceed when the type is exactly an ADT.\nuse rustc_middle::ty::{self, Ty};\nfn require_adt<'tcx>(ty: Ty<'tcx>) -> Option<(&'tcx ty::AdtDef<'tcx>, &'tcx ty::GenericArgsRef<'tcx>)> {\n    match ty.kind() {\n        ty::Adt(def, args) => Some((def, args)),\n        _ => None,\n    }\n}\n// Pattern:\n//   match require_adt(base_ty) {\n//       Some((def, args)) => place.project_to_field(idx, local_decls, tcx),\n//       None => /* pick the right projection for tuples/closures/arrays */\n//   }","tryCatchPattern":null,"preventionTips":["`project_to_field` is for ADTs only; tuples, closures, coroutine state, arrays, and slices each have their own field/tuple-index projection — choose the one matching the base type kind.","Always derive the projection element from `ty.kind()` rather than assuming a place is an ADT because of how it was written in source.","When lowering field accesses in a macro/lint, branch on `ty.kind()` first (Adt vs Tuple vs Closure vs ...) and emit the matching PlaceElem.","If you refactor a type from struct to tuple/struct-to-array, audit every call site that projects a field — a previously-valid ADT projection becomes this panic."],"tags":["rustc","mir","type-system","internal-invariant"],"analyzedSha":"22057b88b091743bc0fd8d592a9264f0a6951403","analyzedAt":"2026-08-03T08:09:25.915Z","schemaVersion":2}