docs: file issue 0191 (coerceToType welds type-incompatible value into return slot, no diagnostic)

This commit is contained in:
agra
2026-06-25 22:40:43 +03:00
parent df1327e316
commit 6b8bce1aba

View File

@@ -0,0 +1,69 @@
# 0191 — `coerceToType` silently welds a type-incompatible value into the return slot (no diagnostic)
**Status:** OPEN
## Symptom
Returning a value whose type is not coercible to the declared return
type (e.g. a `string` where `i64` is expected) is **silently accepted**:
the compiler reinterprets the bytes instead of emitting a type-mismatch
diagnostic, producing garbage at runtime with **exit 0**.
- Observed: `bad :: () -> i64 { "not an int" }` compiles and runs,
printing a garbage integer (the string's pointer reinterpreted as
`i64`), exit 0.
- Expected: a clear "cannot return 'string' where 'i64' is expected"
(type-mismatch) diagnostic + non-zero exit.
This is the silent-clobber failure mode the project forbids: a bad
coercion fabricated rather than rejected.
## Reproduction
```sx
#import "modules/std.sx";
bad :: () -> i64 { "not an int" } // trailing expression
main :: () { print("{}\n", bad()); }
```
Run: `./zig-out/bin/sx run repro.sx` → exit 0, prints e.g. `4352919116`
(should error). The explicit-return form is identical:
```sx
bad :: () -> i64 { return "not an int"; }
```
It is **not** failable-specific — a plain non-failable return reproduces
it. A value-failable `-> i64 !E { "not an int" }` welds the 16-byte
string struct into the declared `i64` slot (`ret { i64, i32 } { {ptr,i64}
..., i32 0 }`), so the caller's `catch` can even phantom-fire on the
garbage tag.
## Investigation prompt
Root cause: `coerceToType` (in `src/ir/lower/` — grep for `pub fn
coerceToType`) performs the requested coercion (or a bit-reinterpret)
WITHOUT first checking that the source type is actually coercible to the
destination. The value/explicit-return/failable-success return paths all
call it and trust the result. The companion `lowerFailableSuccessReturn`
inherits the same gap.
Likely fix: `coerceToType` should validate coercibility (the same rules
used for assignment / call-argument coercion) and, when the source type
cannot coerce to the destination, emit a
`diagnostics.addFmt(.err, span, "cannot coerce '<src>' to '<dst>'...")`
and return a sentinel (or have callers handle a `null`/`.unresolved`),
rather than reinterpreting bytes. Thread the offending value's span
through (the return paths already plumb a span — see issue 0190's
`lowerValueBody`/`lowerReturn` work).
Verification: the repros above must error with a type-mismatch diagnostic
+ exit 1; legitimate coercions (`i32``i64`, `&T``*T`, numeric
widening, struct-literal → struct, etc.) must keep working; the full
corpus must stay green. Add a diagnostics regression example for the
incompatible-return case.
(Found by adversarial review during the issue-0190 fix, commit
`df1327e3`. Pre-existing — independent of the 0190 change; reproduces on
plain non-failable returns.)