diff --git a/examples/diagnostics/1203-diagnostics-index-non-indexable.sx b/examples/diagnostics/1203-diagnostics-index-non-indexable.sx new file mode 100644 index 00000000..f4b267a2 --- /dev/null +++ b/examples/diagnostics/1203-diagnostics-index-non-indexable.sx @@ -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) +} diff --git a/examples/diagnostics/expected/1203-diagnostics-index-non-indexable.exit b/examples/diagnostics/expected/1203-diagnostics-index-non-indexable.exit new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/examples/diagnostics/expected/1203-diagnostics-index-non-indexable.exit @@ -0,0 +1 @@ +1 diff --git a/examples/diagnostics/expected/1203-diagnostics-index-non-indexable.stderr b/examples/diagnostics/expected/1203-diagnostics-index-non-indexable.stderr new file mode 100644 index 00000000..ccd193fd --- /dev/null +++ b/examples/diagnostics/expected/1203-diagnostics-index-non-indexable.stderr @@ -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) + | ^ diff --git a/examples/diagnostics/expected/1203-diagnostics-index-non-indexable.stdout b/examples/diagnostics/expected/1203-diagnostics-index-non-indexable.stdout new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/diagnostics/expected/1203-diagnostics-index-non-indexable.stdout @@ -0,0 +1 @@ + diff --git a/issues/0183-index-non-indexable-type-panics.md b/issues/0183-index-non-indexable-type-panics.md index 48f63c68..3f4e8143 100644 --- a/issues/0183-index-non-indexable-type-panics.md +++ b/issues/0183-index-non-indexable-type-panics.md @@ -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 ''` 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 — diff --git a/issues/0184-untyped-positional-literal-no-target-panics.md b/issues/0184-untyped-positional-literal-no-target-panics.md new file mode 100644 index 00000000..0680e4bc --- /dev/null +++ b/issues/0184-untyped-positional-literal-no-target-panics.md @@ -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 `.{ }`.) diff --git a/src/ir/lower/expr.zig b/src/ir/lower/expr.zig index 33cd6894..b4962af1 100644 --- a/src/ir/lower/expr.zig +++ b/src/ir/lower/expr.zig @@ -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); }