docs: file issue 0191 (coerceToType welds type-incompatible value into return slot, no diagnostic)
This commit is contained in:
@@ -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.)
|
||||
Reference in New Issue
Block a user