Assigning to a nonexistent struct field (`p.q = 2` where Point has no `q`) aborted the compiler with the `.unresolved` LLVM tripwire instead of a source diagnostic (issue 0094). The lvalue field lookup never diagnosed a miss: - `lowerAssignment`'s `.field_access` target left `field_ty = .unresolved` when no struct field matched, then built `ptrTo(field_ty)` and stored — so a pointer-to-`.unresolved` reached LLVM emission and tripped the panic. - `lowerExprAsPtr`'s `.field_access` fallback returned `structGepTyped(obj_ptr, 0, .s64, obj_ty)` on a miss — a silent field-0/`.s64` default that mislowered the lvalue. Both sites now reuse the read path's `emitFieldError` (the exact facility `lowerFieldAccessOnType` uses), so read and write reject identically with `field 'q' not found on type 'Point'`. `lowerExprAsPtr` also resolves union/tagged-union fields via `union_gep` (the old `.s64` fallback was silently standing in for union field access — e.g. `u.a[0] = v`), so that path is fixed, not just made loud. The `types.zig` tripwire is untouched: the fix is to never produce `.unresolved` for a missing-field store. Regression tests: - examples/1145-diagnostics-missing-struct-field-assign.sx — negative, both sites error, exit 1. - examples/0165-types-nested-struct-field-assign.sx — positive, nested struct field write + address-of a matched field still work. - src/ir/lower.test.zig — lowering unit test asserting the field-not-found diagnostic for a missing-field assignment.
3.5 KiB
0094 — assigning to a missing struct field panics with "unresolved type reached LLVM emission"
RESOLVED (F0.10). Root cause: the lvalue field lookup never diagnosed a missing field. In
Lowering.lowerAssignment's.field_accesstarget path (src/ir/lower.zig),field_tystarted as.unresolved; when no struct field matched, the code still builtptrTo(field_ty)/structGepTypedand stored — so a pointer-to-.unresolvedreached LLVM emission and tripped thesrc/backend/llvm/types.zigtripwire. The nested lvalue-pointer path (Lowering.lowerExprAsPtr's.field_accessfallback) had the sibling defect: on a miss it returnedstructGepTyped(obj_ptr, 0, .s64, obj_ty)— a silent field-0/.s64default.Fix (
src/ir/lower.zig):
lowerAssignment.field_accesstarget — track afoundflag over the struct-field loop; on a miss, emit the read path's field-not-found diagnostic (emitFieldError) and bail, never constructingptrTo(.unresolved).lowerExprAsPtr.field_access— resolve union/tagged-union fields viaunion_gep(mirroring the write path; the old.s64fallback was silently standing in for union field access), then the struct-field loop, thenemitFieldErroron a genuine miss. The.s64sentinel is gone.Both sites now reuse
emitFieldError(the exact facility the read pathlowerFieldAccessOnTypeuses), so the read and write paths reject identically. Thetypes.zigtripwire is untouched — the fix is to never produce.unresolvedfor a missing-field store.Regression tests:
examples/1145-diagnostics-missing-struct-field-assign.sx(negative — both sites error, exit 1),examples/0165-types-nested-struct-field-assign.sx(positive — nested struct field write + address-of a matched field still work), and a lowering unit test insrc/ir/lower.test.zig("assigning to a missing struct field emits field-not-found, no panic").
Symptom
Assigning to a nonexistent struct field (p.q = ...) panics during LLVM emission instead of reporting a source diagnostic.
Observed: the compiler reaches the .unresolved LLVM tripwire in src/backend/llvm/types.zig:175 via emitStore.
Expected: a normal compile error like field 'q' not found on type 'Point', matching the read-field diagnostic path.
Reproduction
Point :: struct { x: s64; }
main :: () {
p := Point.{ x = 1 };
p.q = 2;
}
Running ./zig-out/bin/sx run repro.sx currently panics with:
panic: unresolved type reached LLVM emission — a type resolution failure was not diagnosed/aborted
Investigation prompt
Fix issue 0094 in the sx compiler: assigning to a missing struct field (p.q = 2) panics with .unresolved reaching LLVM emission instead of emitting a field-not-found diagnostic.
Suspected area: src/ir/lower.zig, especially Lowering.lowerAssignment's .field_access target path around the struct-field lookup (field_ty starts as .unresolved, no matched field diagnoses, then ptrTo(field_ty) is stored) and the related Lowering.lowerExprAsPtr field-access fallback that returns structGepTyped(obj_ptr, 0, .s64, obj_ty) on lookup failure. The fix should make failed lvalue field lookup loud, reusing emitFieldError(obj_ty, field, span) or equivalent, and should not use .s64, .void, or any real type as a sentinel.
Verification: run the repro and expect exit 1 with a source diagnostic field 'q' not found on type 'Point'; no LLVM panic. Then run zig build, zig build test, and bash tests/run_examples.sh.