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:
27
examples/diagnostics/1203-diagnostics-index-non-indexable.sx
Normal file
27
examples/diagnostics/1203-diagnostics-index-non-indexable.sx
Normal 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)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
@@ -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)
|
||||||
|
| ^
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -1,5 +1,21 @@
|
|||||||
# 0183 — indexing a non-indexable type (`*T`, `*[]T`, struct, …) panics instead of a clean diagnostic
|
# 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
|
## Symptom
|
||||||
|
|
||||||
`expr[i]` where `expr`'s type is not array / slice / many-pointer / string —
|
`expr[i]` where `expr`'s type is not array / slice / many-pointer / string —
|
||||||
|
|||||||
46
issues/0184-untyped-positional-literal-no-target-panics.md
Normal file
46
issues/0184-untyped-positional-literal-no-target-panics.md
Normal 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 `.{ }`.)
|
||||||
@@ -1840,6 +1840,37 @@ pub fn lowerIndexExpr(self: *Lowering, ie: *const ast.IndexExpr) Ref {
|
|||||||
return self.builder.load(gep, elem);
|
return self.builder.load(gep, elem);
|
||||||
}
|
}
|
||||||
const elem_ty = self.getElementType(obj_ty);
|
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);
|
return self.builder.emit(.{ .index_get = .{ .lhs = obj, .rhs = idx } }, elem_ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user