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

@@ -0,0 +1,27 @@
// Indexing a value whose type is NOT an indexable shape — a single-element
// pointer `*T`, a pointer-to-slice `*[]T`, or a plain struct — is a type
// error, diagnosed at lowering with a located message.
//
// Regression (issue 0183): `expr[i]` on a non-indexable base fell through
// `lowerIndexExpr` to an `index_get` carrying an `.unresolved` element type,
// which reached emit_llvm and panicked ("unresolved type reached LLVM
// emission", exit 134) with no source location. The guard now rejects it
// cleanly (exit 1). Indexable shapes — `[N]T` / `[]T` / `[*]T` / `string` /
// `Vector` / `*[N]T` and the optional-chain forms — are unaffected.
//
// For a single pointer `*T` the message hints at the indexable alternatives
// (many-pointer `[*]T`, or dereference first); other non-indexable types get
// the bare "cannot index a value of type '...'" form.
#import "modules/std.sx";
S :: struct { a: i64; }
main :: () {
x := 5;
p : *i64 = @x;
print("{}\n", p[0]); // error: '*i64' is not indexable (hint form)
s := S.{ a = 1 };
print("{}\n", s[0]); // error: 'S' is not indexable (bare form)
}

View File

@@ -0,0 +1,11 @@
error: cannot index a value of type '*i64' — use a many-pointer '[*]T', or dereference first
--> examples/diagnostics/1203-diagnostics-index-non-indexable.sx:23:19
|
23 | print("{}\n", p[0]); // error: '*i64' is not indexable (hint form)
| ^
error: cannot index a value of type 'S'
--> examples/diagnostics/1203-diagnostics-index-non-indexable.sx:26:19
|
26 | print("{}\n", s[0]); // error: 'S' is not indexable (bare form)
| ^

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 `.{ }`.)

View File

@@ -1840,6 +1840,37 @@ pub fn lowerIndexExpr(self: *Lowering, ie: *const ast.IndexExpr) Ref {
return self.builder.load(gep, elem);
}
const elem_ty = self.getElementType(obj_ty);
// Final guard: the object is not an indexable shape. `getElementType`
// recognizes every indexable type (`[N]T` array, `[]T` slice, `[*]T`
// many-pointer, `Vector`, `string`) and `ptrToArrayElem` handled `*[N]T`
// above; an `.unresolved` element here means the base is a single pointer
// `*T`, a pointer-to-slice `*[]T`, a struct, or another non-indexable type.
// Emitting an `index_get` with `.unresolved` would slip past lowering and
// panic in emit_llvm ("unresolved type reached LLVM emission", issue 0183).
// Diagnose here and return a placeholder so hasErrors() aborts before
// codegen. Skip an already-`.unresolved` object type: it comes from a PRIOR
// error (e.g. an undefined name) already diagnosed — re-reporting would
// duplicate the message (mirrors the issue-0172 `??` guard).
if (elem_ty == .unresolved and obj_ty != .unresolved) {
if (self.diagnostics) |d| {
const is_single_ptr = !obj_ty.isBuiltin() and self.module.types.get(obj_ty) == .pointer;
if (is_single_ptr) {
// If the pointee is itself indexable (slice/array), the right
// advice is to dereference (`(*p)[i]`); the many-pointer hint
// only applies to a pointer-to-scalar.
const pointee = self.module.types.get(obj_ty).pointer.pointee;
const pointee_indexable = (self.ptrToArrayElem(obj_ty) orelse self.getElementType(pointee)) != .unresolved;
if (pointee_indexable) {
d.addFmt(.err, ie.object.span, "cannot index a value of type '{s}' — dereference first (e.g. `(*p)[i]`)", .{self.formatTypeName(obj_ty)});
} else {
d.addFmt(.err, ie.object.span, "cannot index a value of type '{s}' — use a many-pointer '[*]T', or dereference first", .{self.formatTypeName(obj_ty)});
}
} else {
d.addFmt(.err, ie.object.span, "cannot index a value of type '{s}'", .{self.formatTypeName(obj_ty)});
}
}
return self.builder.constInt(0, .i64); // placeholder — hasErrors() aborts before codegen
}
return self.builder.emit(.{ .index_get = .{ .lhs = obj, .rhs = idx } }, elem_ty);
}