Files
sx/issues/0183-index-non-indexable-type-panics.md
agra 95c9c0df4c fix: diagnose indexing a non-indexable type instead of panicking (issue 0183)
lowerIndexExpr fell through to an index_get with an .unresolved element
type for any non-indexable object (*T, *[]T, struct, scalar), reaching
codegen -> 'unresolved type reached LLVM emission' panic. Add a guard
after all indexable arms: if getElementType(obj_ty) is .unresolved and
obj_ty is itself resolved (genuinely non-indexable, not a prior-error
placeholder), emit a located 'cannot index a value of type <T>'
diagnostic + placeholder (hasErrors aborts before codegen). A single
pointer hints by pointee: ptr-to-scalar -> many-pointer/dereference;
ptr-to-array/slice -> dereference first. No false-positives (generics,
aliases, late-resolved, every indexable shape verified).

Regression: examples/diagnostics/1203-diagnostics-index-non-indexable.sx.
Verified by 3 adversarial reviews, suite 799/0. Filed adjacent pre-existing
panic 0184 (untyped positional .{ } literal with no target type).
2026-06-23 17:29:12 +03:00

3.4 KiB

0183 — indexing a non-indexable type (*T, *[]T, struct, …) panics instead of a clean diagnostic

RESOLVED. lowerIndexExpr (src/ir/lower/expr.zig) fell through to an index_get with an .unresolved element type for any non-indexable object, reaching codegen → panic. Added a guard after all indexable arms: if getElementType(obj_ty) is .unresolved and obj_ty is itself resolved (so a genuinely non-indexable type, not a prior-error placeholder), emit a located cannot index a value of type '<T>' diagnostic and return a placeholder (hasErrors() aborts before codegen). A single pointer hints by pointee: pointer-to-scalar → "use a many-pointer [*]T, or dereference first"; pointer-to-array/slice → "dereference first ((*p)[i])". No false-positives — generics, type aliases, late-resolved objects, and every indexable shape ([N]T/[]T/[*]T/string/Vector/*[N]T/optional-chain) still work (verified by 3 adversarial reviews; suite 799/0). Regression: examples/diagnostics/1203-diagnostics-index-non-indexable.sx. (Adjacent pre-existing panic found + filed: 0184 — an untyped positional .{ } literal with no target type panics; the guard correctly defers on it.)

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.