Files
sx/issues/0184-untyped-positional-literal-no-target-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

2.3 KiB

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

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