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).
102 lines
4.9 KiB
Markdown
102 lines
4.9 KiB
Markdown
# 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`) — `getElementType` returned `.unresolved`, so `index_get`
|
|
> reached LLVM with an unresolved element type and panicked. Mirroring the
|
|
> issue-0101 `!`-unwrap fix: added `lowerOptionalChainIndex`
|
|
> (`src/ir/lower/expr.zig`) — `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, so `[N]?T` →
|
|
> `?T`). The typer (`src/ir/expr_typer.zig`) and the dispatch guard both 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, 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` `.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`).
|