Files
sx/issues/0181-optional-chain-on-struct-with-array-field-unresolved-panic.md
agra fa7c07faf8 fix: comptime reg->value bridge for array-in-aggregate + clean abort on comptime-init failure (issue 0167)
(C) regToValue (comptime_vm.zig) gained no array arm, so a #run returning
an aggregate containing an array bailed 'reg->value: aggregate shape not
bridged yet'. Add an .array arm: read N elements at stride
typeSizeBytes(elem) from the array address, bridge each recursively via
regToValue -> an .aggregate Value (serializeAggregateValue already emits
arrays). Composes with struct fields, nested arrays, array-of-structs,
and the ?Arr optional payload; unbridgeable elements bail loudly.

(E) A global failing #run proceeded into LLVM emission and panicked
'unresolved type reached LLVM emission' when the unresolved const was
used. Add 'if (self.comptime_failed) return;' in emit() after Pass 0 so
it aborts cleanly (exit 1, the comptime diagnostic) across run/ir/build.

Regression: examples/comptime/0644-comptime-run-array-aggregate.sx.
Verified by 3 adversarial reviews, suite 793/0. Filed separate bugs found
during review: 0181 (optional-chain ?. to array field + index panics),
0182 (body-local #run unbridged silently miscompiles).
2026-06-23 11:34:22 +03:00

3.8 KiB

0181 — ?.-chain (and ?-postfix) on an optional whose child struct contains an ARRAY field panics unresolved type reached LLVM emission

Symptom

A ?. optional-chain access (or the ? optional-test postfix used in a member-access chain) on a value of type ?S, where S is a struct that contains an array field, panics:

thread … panic: unresolved type reached LLVM emission — a type resolution failure was not diagnosed/aborted (exit 134).

The same chain on ?S where S has no array field works fine, and the ! force-unwrap chain (opt!.field) on the same array-containing ?S works fine. The defect is specific to the ?/?. operator's receiver-type inference when the optional's child struct contains an array field — that receiver types as .unresolved and reaches LLVM. This is a pure runtime lowering bug: no #run/comptime is involved.

Observed vs expected:

  • Observed: SIGABRT panic (exit 134) at src/backend/llvm/types.zig:196 (toLLVMTypeInfo .unresolved arm), reached from declareFunction's param.ty lowering of a synthesized accessor.
  • Expected: the chain evaluates (prints the field), exactly as the !-unwrap and the non-array ?. forms already do.

Reproduction

Pure runtime, no #run — panics:

#import "modules/std.sx";
Arr3 :: struct { xs: [3]i64; }
mk :: () -> ?Arr3 { r : Arr3 = .{ xs = .[1,2,3] }; return r; }
main :: () { print("{}\n", mk()?.xs[0] ?? 99); }   // PANIC exit 134

Control A — same chain, child struct has NO array field — WORKS, prints 7:

#import "modules/std.sx";
Pt :: struct { x: i64; }
mk :: () -> ?Pt { return Pt.{ x = 7 }; }
main :: () { print("{}\n", mk()?.x ?? 99); }

Control B — same array-containing ?Arr3, but ! force-unwrap — WORKS, prints 1:

#import "modules/std.sx";
Arr3 :: struct { xs: [3]i64; }
mk :: () -> ?Arr3 { r : Arr3 = .{ xs = .[1,2,3] }; return r; }
main :: () { print("{}\n", mk()!.xs[0]); }

(The issue 0167 (E) repro A?.xs[0] hit this same bug — it used ? where ! was meant; with ! the comptime #run ?Arr3 case evaluates. So this is the residual defect that 0167's (E) repro tripped, distinct from 0167 (C)/(E), both of which are fixed.)

Investigation prompt

The ? optional-chaining / optional-test path synthesizes an accessor whose receiver (the unwrapped child) types as .unresolved specifically when the child is a struct containing an array field — mirroring the already-fixed issue-0101 !-unwrap bug (inferExprType had no force_unwrap arm → receiver typed .unresolved). The ! path was fixed (see examples/optionals/0905-optionals-unwrap-field-chain.sx); the ?/?. path has an analogous gap that only surfaces for an array-containing child (a plain-scalar/string child happens to resolve).

Suspected area: src/ir/lower.zig inferExprType (grep for the optional-chain / ? postfix / safe_nav handling) and/or src/ir/lower/ accessor-chain lowering — find where the ?-chain receiver type is computed and why an array-containing struct child yields .unresolved. Compare against the working !-unwrap arm (issue 0101 fix) and apply the same receiver-type flow.

Verification: the first repro above prints 1 and exits 0; controls A and B still pass; add a regression under examples/optionals/ covering ?.-chain on an array-containing ?S (field read + ?? default). Confirm examples/comptime/0644-comptime-run-array-aggregate.sx (issue 0167) still passes.

Provenance

Discovered while implementing issue 0167 (C: comptime reg→value array-in- aggregate bridge; E: clean-abort on comptime-init failure). 0167 (C) and (E) are FIXED and verified; the ?Arr3 access form in 0167's (E) repro tripped this SEPARATE, pre-existing runtime lowering bug (confirmed reproducible on clean HEAD with no #run).