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

87 lines
3.8 KiB
Markdown

# 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:
```sx
#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`:
```sx
#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`:
```sx
#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`).