Files
sx/issues/0067-tuple-literal-type-nontype-fallback.md
agra 744decc6a1 fix(ir): reject non-type elements in tuple-literal-as-type (issue 0067)
`size_of((s32, 1))` treated the tuple literal as a tuple TYPE: for the non-type
element `1` it emitted a `std.debug.print` and substituted `.s64` for that field,
then compiled and printed a bogus size — a silent fabricated type (the forbidden
silent-fallback pattern).

Fix:
- type_bridge.resolveTupleLiteralAsType: a non-type element now yields
  `.unresolved` (no `.s64`, no debug print) — it refuses to fabricate a tuple.
  type_bridge is stateless, so this is the binding-free backstop.
- New stateful Lowering.resolveTupleLiteralTypeArg validates each element via
  isTypeShapedAstNode, emits a user-facing diagnostic at the offending element's
  span, and returns `.unresolved`. Wired into resolveTypeArg (size_of/align_of/…)
  and the resolveTypeWithBindings name-fallback; type_bridge builds the tuple
  only after validation passes.

Regression: examples/1116-diagnostics-tuple-type-nontype-element-rejected.sx
(exit 1 + diagnostic). Valid `(s32, s32)` still works (0115). Gate: zig build,
zig build test, run_examples 351/0.
2026-06-02 15:51:04 +03:00

3.6 KiB

0067 — tuple literal used as a type silently accepts non-type elements

RESOLVED (2026-06-02). Root cause: type_bridge.resolveTupleLiteralAsType treated a tuple literal as a tuple TYPE and, for any element that wasn't type-shaped, emitted a std.debug.print and substituted .s64 for that field — a silent fabricated type (the forbidden silent-fallback pattern). The stateful caller (Lowering.resolveTypeArg, used by size_of) delegated .tuple_literal straight to that path, so size_of((s32, 1)) compiled and printed 16. Fix:

  • type_bridge.resolveTupleLiteralAsType now returns .unresolved (no .s64, no debug print) when any element is not type-shaped — it refuses to fabricate a tuple. (type_bridge is stateless, so this is the binding-free backstop.)
  • New stateful Lowering.resolveTupleLiteralTypeArg validates each element via type_bridge.isTypeShapedAstNode, emits a user-facing diagnostic at the offending element's span, and returns .unresolved. It is wired into BOTH resolveTypeArg (size_of/align_of/…) and the resolveTypeWithBindings name-fallback; type_bridge builds the tuple only after validation passes. Regression test: examples/1116-diagnostics-tuple-type-nontype-element-rejected.sx (exit 1 + diagnostic). Valid (s32, s32) still works (examples/0115-types-compound-type-in-expression.sx). Suite 351/0.

Symptom

size_of((s32, 1)) treats the tuple literal as a tuple TYPE even though 1 is not a type. The compiler prints an internal type_bridge debug line, then silently substitutes .s64 for that slot and compiles successfully.

Observed:

type_bridge: tuple literal element is not a type (tag=int_literal) — cannot use as tuple type
bad tuple type size = 16

Expected: a user-facing compiler diagnostic rejecting the non-type tuple element, with no fabricated tuple type and no successful run.

Reproduction

#import "modules/std.sx";

main :: () -> s32 {
  print("bad tuple type size = {}\n", size_of((s32, 1)));
  0
}

Run:

./zig-out/bin/sx run .sx-tmp/probe-tuple-literal-type-fallback.sx

The repro is standalone; the inline source above is sufficient to recreate the scratch file under .sx-tmp/.

Investigation prompt

Fix issue 0067: tuple literals reinterpreted as tuple types must reject non-type elements instead of silently fabricating .s64 fields.

Suspected area:

  • src/ir/type_bridge.zig, resolveTupleLiteralAsType
  • The current non-type branch does std.debug.print(...) and field_ids.append(alloc, .s64), which violates the compiler fallback rules.
  • Related callers: type_bridge.resolveAstType for .tuple_literal, and Lowering.resolveTypeWithBindings fallback paths that reach type_bridge.

Likely fix:

  • Replace the .s64 substitution with a real diagnostic path and an unmistakable failure result (.unresolved, or a nullable/result return that forces callers to handle the failure).
  • Make the diagnostic user-facing via the lowering diagnostics plumbing, not std.debug.print.
  • Preserve the valid behavior pinned by examples/0115-types-compound-type-in-expression.sx, where (s32, s32) in a type-demanding site resolves as a tuple type.

Verification:

  • Add a focused diagnostics example in the 11xx block for size_of((s32, 1)) expecting exit 1 and a clear diagnostic.
  • Run:
zig build
zig build test
bash tests/run_examples.sh

Expected result: the new invalid tuple-type repro fails with a diagnostic, the valid 0115 tuple-type example still passes, and the full suite remains green.