# 0189 — non-type expression in type position silently fabricates an empty struct **Status:** OPEN ## Symptom A non-type expression used in **type position** — e.g. a `field_access` like `g.a` — is silently accepted and resolved to a bogus zero-field struct `{}` instead of being rejected with a diagnostic. - Observed: `x : g.a = ---;` compiles with **exit 0**, emitting LLVM `alloca {}` (an empty struct) for `x`. `Tuple(i32, g.a)` likewise yields `{ i32, {} }` with a `store ... zeroinitializer`. - Expected: a user-facing "not a type" diagnostic at the offending expression and a clean non-zero exit (never a fabricated empty struct reaching codegen). This is the classic silent-fallback-default failure mode: a lookup that should fail returns a "reasonable-looking" value (`{}`) and ships invisibly. ## Reproduction ```sx S :: struct { a: i32; } g : S = .{ a = 1 }; main :: () -> i32 { x : g.a = ---; // `g.a` is a runtime VALUE, not a type 0 } ``` Run: `./zig-out/bin/sx run repro.sx` → exits 0 (should error). Inspect `./zig-out/bin/sx ir repro.sx` → `x` is `alloca {}`. The tuple form `x : Tuple(i32, g.a) = ---;` reproduces the same fabrication for the second element. (The bug is **not** tuple-specific — it predates and is independent of the `Tuple(...)` syntax cutover; the plain `g.a` case above has no tuple at all.) ## Investigation prompt The fabrication lives in the type-resolution bridge, not in the tuple code. In `src/ir/type_bridge.zig`, `resolveAstType`'s handling of a `field_access` (and likely any non-type-shaped expression) in type position falls through to building a zero-field stub struct rather than returning the `.unresolved` sentinel + a diagnostic. The tuple-element validation arm in `src/ir/lower/generic.zig` (and the literal screen near `src/ir/lower.zig:960`) only rejects the five literal tags (`int/float/string/bool/null_literal`); it leans on `resolveCompound`'s `.unresolved` propagation to catch everything else — which works only when the element actually resolves to `.unresolved`. A `field_access` resolves to the fabricated `{}` stub instead, so it slips through. Likely fix (pick one, verify against the repro): 1. In `type_bridge.resolveAstType`, the `field_access`-in-type-position arm (and any non-type-shaped expression) should emit a diagnostic via `self.diagnostics.addFmt(.err, span, "...")` and return `.unresolved`, never a fabricated empty struct. 2. Broaden the type-element screen to reject **any** non-type-shaped element node up front using `type_bridge.isTypeShapedAstNode` (already used by `resolveTupleLiteralTypeArg`), instead of the explicit literal-tag allowlist. Verification: the repro above must error with a clear "not a type" diagnostic and a non-zero exit; `Tuple(i32, g.a)` must reject too; the existing `examples/diagnostics/1116` (literal non-type element) must keep passing. Add a regression example for the `field_access` case. (Found by adversarial review during the tuple-syntax cutover, commit `989e18b7`.)