Files
sx/issues/0183-index-non-indexable-type-panics.md
agra 4ca466fa96 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).
2026-06-23 12:29:29 +03:00

2.3 KiB

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

#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.