Files
sx/issues/0174-tuple-positional-literal-element-not-coerced.md
agra 28bb101a4a fix: literal element typing — typed-array null element, tuple coercion, positional var element (0173-0175)
0173: resolveArrayLiteralType gained no arm for [N]T/[]T heads, so a
([2]?i64).[...] head lost its ?i64 element type and a bare null reached
LLVM as const_null(.unresolved). Route structural heads through
resolveTypeWithBindings; validate an undefined element name in the head
via UnknownTypeChecker (semantic_diagnostics.zig) instead of a silent
empty-struct stub (no-silent-fallback).

0174: positional .{...} against a TUPLE target now coerces each element
to TupleInfo.fields[i] (was neither struct nor array, so uncoerced).

0175: a positional struct literal with a bare-variable element was
misclassified as a named shorthand (parser puns .{x} -> x=x), zeroing
the fields. has_names now consults the struct definition to reclassify a
punned non-field name as positional; positional coercion uses the
lowered value's real getRefType.

Regressions: optionals/0914, types/0199, types/0200, diagnostics/1196.
Verified by 4 adversarial reviews; suite 784/0. Filed adjacent bug 0176
(protocol-typed struct field method call aborts).
2026-06-23 00:25:28 +03:00

2.5 KiB

0174 — positional literal for a TUPLE target does not coerce elements (same corruption class as 0168)

RESOLVED. lowerStructLiteral's positional branch coerced struct fields and array/vector elements but not TUPLE targets (a tuple is neither — empty struct_fields, .unresolved array_elem_ty), so a bare element was stored raw into the field slot (a {T,i1} optional read back absent). Fix (src/ir/lower/expr.zig): compute tuple_fields from TupleInfo.fields and fold it into a unified elem_target (struct_fields[i].tytuple_fields[i]array_elem_ty) that steers per-element target_type and drives coerceToType. Verified across optional/int→float/protocol/slice/enum/nested tuple elements + named tuples by 4 adversarial reviews. Regression: examples/types/0199-types-tuple-positional-optional-element.sx.

Symptom

A positional literal .{ a, b } whose target is a TUPLE does not coerce its elements to the tuple's field types. When a field type is an optional (or any type the element doesn't already match), the raw element is stored into the field slot with the wrong shape — e.g. a bare i64 into a {i64,i1} optional slot — so the value reads back wrong (a present optional reads as absent). Silent miscompile. This is the tuple analogue of issue 0168 (which fixed the array/vector case).

Reproduction

#import "modules/std.sx";
main :: () {
  t : (?i64, f64) = .{ 7, 3.0 };
  print("{}\n", t.0 ?? -1);   // prints "-1" — WRONG, expected 7
}

Expected: 7 (field 0 is a present ?i64). Observed: -1 (read as absent).

Investigation prompt

src/ir/lower/expr.zig lowerStructLiteral positional branch. Issue 0168 added element coercion for array/vector targets via array_elem_ty, but a TUPLE target is neither a struct (so getStructFields returns empty → the i < struct_fields.len field-coercion path doesn't fire) nor array/vector (so array_elem_ty is .unresolved). Extend the positional branch to recognize a .tuple target: fetch the tuple's per-field types (TupleInfo.fields) and coerce element i to fields[i] (mirroring the struct-field path, which uses struct_fields[i].ty). Set target_type per element so a nested untyped literal element resolves too. Follow the no-silent-fallback rule. Verify: the repro prints 7; a tuple with mixed element coercions (int→float, concrete→protocol, array→slice) initializes correctly; named tuples (x: ?i64, y: f64) too. Add an examples/types/01xx-tuple-positional-optional-element.sx regression.