`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.
3.6 KiB
0067 — tuple literal used as a type silently accepts non-type elements
RESOLVED (2026-06-02). Root cause:
type_bridge.resolveTupleLiteralAsTypetreated a tuple literal as a tuple TYPE and, for any element that wasn't type-shaped, emitted astd.debug.printand substituted.s64for that field — a silent fabricated type (the forbidden silent-fallback pattern). The stateful caller (Lowering.resolveTypeArg, used bysize_of) delegated.tuple_literalstraight to that path, sosize_of((s32, 1))compiled and printed16. Fix:
type_bridge.resolveTupleLiteralAsTypenow 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.resolveTupleLiteralTypeArgvalidates each element viatype_bridge.isTypeShapedAstNode, emits a user-facing diagnostic at the offending element's span, and returns.unresolved. It is wired into BOTHresolveTypeArg(size_of/align_of/…) and theresolveTypeWithBindingsname-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(...)andfield_ids.append(alloc, .s64), which violates the compiler fallback rules. - Related callers:
type_bridge.resolveAstTypefor.tuple_literal, andLowering.resolveTypeWithBindingsfallback paths that reachtype_bridge.
Likely fix:
- Replace the
.s64substitution 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
11xxblock forsize_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.