opt?.xs[i] typed and lowered the index over the optional CONTAINER (?[N]T); getElementType returned .unresolved, so index_get reached LLVM with an unresolved element type and panicked. Mirroring the 0101 !-unwrap fix: add lowerOptionalChainIndex (optional_has_value -> some: unwrap + index (index_gep+load for ?*[N]T, else index_get) + optional_wrap; none: const_null; merge -> ?ElemType, element-optional flattened). The typer + dispatch guard compute the element via ptrToArrayElem(child) orelse getElementType(child), so value-arrays, slices, many-pointers, AND pointer-to-array (?*[N]T) children resolve. Null receivers short-circuit (no null deref). Regression: examples/optionals/0915-optional-chain-array-field-index.sx. Verified by 3 adversarial reviews, suite 794/0. Filed broader pre-existing gap 0183 (indexing a non-indexable type panics instead of diagnosing).
4.9 KiB
0181 — ?.-chain (and ?-postfix) on an optional whose child struct contains an ARRAY field panics unresolved type reached LLVM emission
RESOLVED.
opt?.xs[i]typed and lowered the index over the optional CONTAINER (?[N]T) —getElementTypereturned.unresolved, soindex_getreached LLVM with an unresolved element type and panicked. Mirroring the issue-0101!-unwrap fix: addedlowerOptionalChainIndex(src/ir/lower/expr.zig) —optional_has_value→ some: unwrap + index (index_gep+load for?*[N]T, elseindex_get) +optional_wrap; none:const_null; merge →?ElemType(element-optional flattened, so[N]?T→?T). The typer (src/ir/expr_typer.zig) and the dispatch guard both compute the element viaptrToArrayElem(child) orelse getElementType(child)so value-arrays, slices, many-pointers, AND pointer-to-array (?*[N]T) children resolve. Null receivers short-circuit (no null deref, verified in IR). Regression:examples/optionals/0915-optional-chain-array-field-index.sx. Verified by 3 adversarial reviews; suite 794/0. (Broader pre-existing gap found + filed: 0183 — indexing a non-indexable type*T/*[]T/struct panics instead of a diagnostic, reproduces without optionals.)
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.unresolvedarm), reached fromdeclareFunction'sparam.tylowering 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).