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).
This commit is contained in:
agra
2026-06-23 17:29:12 +03:00
parent 097d23d909
commit 95c9c0df4c
7 changed files with 133 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,46 @@
# 0184 — an untyped positional literal `.{ ... }` with no inferable target type panics instead of diagnosing
## Symptom
A positional struct/tuple literal `.{ a, b, ... }` used where NO target type is
available (e.g. `t := .{ 1, 2, 3 };` with no annotation) cannot resolve its type
and is left `.unresolved`. Any later use (`t.0`, `t[0]`, passing it, etc.) then
panics `unresolved type reached LLVM emission` (exit 134) — with no diagnostic.
The literal's type is never resolved upstream nor reported.
Found during adversarial review of issue 0183 (the index guard correctly DEFERS
on an already-`.unresolved` object to avoid double-reporting, so this surfaces as
the upstream panic rather than the index diagnostic).
## Reproduction
```sx
#import "modules/std.sx";
main :: () {
t := .{ 1, 2, 3 }; // untyped positional literal, no target type
print("{}\n", t.0); // panic: unresolved type reached LLVM emission, exit 134
}
```
Expected: a located diagnostic, e.g. `error: cannot infer the type of this `.{ }`
literal — annotate the binding (`t : (i64, i64, i64) = …` or a struct type) or
provide a target type`, exit 1. (A TYPED positional literal `t : (i64,i64,i64) =
.{1,2,3}` or `S.{...}` works.)
## Investigation prompt
`src/ir/lower/expr.zig` `lowerStructLiteral` (and `expr_typer.zig`'s inference for
an untyped `.{ }`). When a positional `.{ }` literal has no `self.target_type`
(and isn't a named struct literal that names its own type), its `struct_init.ty`
stays `.unresolved` and flows to codegen → panic. Add a diagnostic at the literal
site: if a `.{ }` literal cannot determine a target/struct type, emit
`self.diagnostics.addFmt(.err, span, "cannot infer the type of this '.{{ }}'
literal — annotate the binding or provide a target type", .{})` and return a
placeholder (so `hasErrors()` aborts before codegen), instead of emitting an
`.unresolved`-typed `struct_init`. Follow the no-silent-fallback rule (here it is
a loud PANIC that must become a clean diagnostic). Verify: the repro exits 1 with
the diagnostic; a TYPED positional literal (annotated binding, `S.{...}`,
array/tuple target) still works. Add an `examples/diagnostics/12xx-...` negative
regression. (Related: 0173 closed the same silent-fallback for typed
`.[...]`-array-literal heads with undefined element names; this is the
no-target-type variant for `.{ }`.)