Files
sx/issues/0175-positional-struct-literal-variable-element-zeroed.md
agra 5a436eddb1 fix: coerce array/vector literal elements to element type (issue 0168)
[N]?T arrays were corrupted: a positional literal .{ null, 7 } stored
bare T/null elements into {T,i1} optional slots because array elements
were never coerced (getStructFields is empty for an array, so the
i<struct_fields.len field-coercion gate never fired). A present element
then read back as absent and direct indexing segfaulted.

lowerStructLiteral's positional branch now computes array_elem_ty for
array/vector targets and coerces each element to it; lowerArrayLiteral
generalizes its slice-only coercion to coerce every element via
coerceToType (layout-aware: scalar->{T,i1}, pointer-sentinel->one-word,
array->slice, concrete->protocol). Verified by 3 adversarial reviews,
suite 780/0.

Regression: examples/optionals/0913-optionals-array-of-optionals.sx.
Filed adjacent pre-existing bugs: 0173 (typed .[null,..] element), 0174
(tuple positional-element coercion), 0175 (positional struct literal
variable element zeroed).
2026-06-22 22:50:20 +03:00

2.2 KiB

0175 — positional struct literal with a VARIABLE element silently zeroes the field

Symptom

A positional struct literal S.{ x, ... } whose element is a VARIABLE reference (not a literal constant) silently stores 0 instead of the variable's value. The NAMED form S.{ a = x, ... } works correctly. Silent miscompile. Pre-existing (reproduces on clean master; surfaced while fixing issue 0168).

Reproduction

#import "modules/std.sx";
P :: struct { a: i64 = 0; b: i64 = 0; }
main :: () {
  x := 5;
  p : P = .{ x, 2 };           // positional, variable first element
  print("{} {}\n", p.a, p.b);  // prints "0 0" — WRONG, expected "5 2"
}

Expected: 5 2. Observed: 0 0 (the variable element zeroed; note even the 2 literal field is wrong here — the whole positional path mis-coerces once a variable element is present). The named form P.{ a = x, b = 2 } prints 5 2.

A related crash: [2]P = .{ .{ x, 2 }, .{ 3, 4 } } with an i32 variable x aborted the LLVM verifier on master (Invalid InsertValueInst operands); after the issue 0168 fix it no longer crashes but still prints the residual 0 … for the variable element — confirming the root cause is the positional-element coercion, independent of 0168.

Investigation prompt

src/ir/lower/expr.zig lowerStructLiteral positional branch, the field coercion at the i < struct_fields.len path (~expr.zig:235-237): it computes src_ty = self.inferExprType(fi.value) then coerceToType(val, src_ty, struct_fields[i].ty). For a variable reference element, inferExprType appears to return a wrong/narrower type, causing coerceToType to mis-narrow/zero the value. Investigate why inferExprType(variable_ref) disagrees with the value's actual getRefType, and prefer coercing from the lowered value's real type (self.builder.getRefType(val)) rather than a re-inferred source type — or fix the inference for a bare variable element. Verify: the repro prints 5 2; a positional literal mixing variable + literal + expression elements; with field types needing real coercion (i32 var → i64 field, concrete var → protocol field). Add an examples/types/01xx-positional-struct-literal-variable-element.sx regression.