fix: optional-chain index opt?.xs[i] over array/ptr-array field (issue 0181)

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).
This commit is contained in:
agra
2026-06-23 12:29:29 +03:00
parent fa7c07faf8
commit 4ca466fa96
9 changed files with 242 additions and 0 deletions

View File

@@ -1,5 +1,20 @@
# 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

View File

@@ -0,0 +1,47 @@
# 0183 — indexing a non-indexable type (`*T`, `*[]T`, struct, …) panics instead of a clean diagnostic
## Symptom
`expr[i]` where `expr`'s type is not array / slice / many-pointer / string —
e.g. a single-element pointer `*T`, a pointer-to-slice `*[]T`, or a struct — does
NOT emit a type error. It falls through `lowerIndexExpr` to an `index_get` with an
`.unresolved` element type and reaches codegen, panicking `unresolved type
reached LLVM emission` (exit 134). Pure runtime, no optionals/comptime. (`[*]T`
many-pointers and `[N]T`/`[]T`/`string` ARE indexable and unaffected.)
Found during adversarial review of issue 0181 (the optional-chain index fix); the
same fall-through underlies the `?*[]T`/`?*T`/`?struct` chain-index panics, but it
reproduces identically WITHOUT optional chaining, so it is a separate, broader
gap in the index lowering.
## Reproduction
```sx
#import "modules/std.sx";
main :: () {
x := 5;
p : *i64 = @x;
print("{}\n", p[0]); // panic: unresolved type reached LLVM emission, exit 134
}
```
Also panics: indexing a `*[]i64` (pointer-to-slice), indexing a plain struct
value. Expected: a located diagnostic, e.g. `error: cannot index a value of type
'*i64' (use a many-pointer '[*]T', or dereference first)`, exit 1.
## Investigation prompt
`src/ir/lower/expr.zig` `lowerIndexExpr`: after the array / slice / many-pointer /
string / optional-chain dispatch arms, the fall-through emits `index_get` with
`getElementType(obj_ty)` even when that is `.unresolved`. Add a final guard: if
the object type is not indexable (element type resolves to `.unresolved` and the
type isn't a recognized indexable shape), emit
`self.diagnostics.addFmt(.err, span, "cannot index a value of type '{s}'", .{...})`
and return a placeholder — never emit an `index_get` with an unresolved element
type. Mirror the located-diagnostic + placeholder pattern used elsewhere in the
lowering. The static typer (`src/ir/expr_typer.zig` `index_expr`) should likewise
yield `.unresolved` (already does) so this is the single choke point. Follow the
no-silent-fallback rule (here it's a loud PANIC, which must become a clean
diagnostic). Verify: the repro exits 1 with the diagnostic; `[*]T`/`[]T`/`[N]T`/
`string`/optional-chain indexing all still work. Add an
`examples/diagnostics/12xx-index-non-indexable.sx` negative regression.