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.
This commit is contained in:
92
issues/0067-tuple-literal-type-nontype-fallback.md
Normal file
92
issues/0067-tuple-literal-type-nontype-fallback.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# 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:
|
||||
|
||||
```text
|
||||
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
|
||||
|
||||
```sx
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
print("bad tuple type size = {}\n", size_of((s32, 1)));
|
||||
0
|
||||
}
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```sh
|
||||
./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:
|
||||
|
||||
```sh
|
||||
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.
|
||||
Reference in New Issue
Block a user