docs: file issue 0190 (void-failable fall-through leaves error slot uninitialized)
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# 0190 — void failable (`-> !`) implicit fall-through leaves the error slot uninitialized
|
||||
|
||||
**Status:** OPEN
|
||||
|
||||
## Symptom
|
||||
|
||||
A `-> !` (void failable) function that exits by **implicit fall-through**
|
||||
(no explicit `return;`) does not initialize its error-channel slot, so a
|
||||
caller (or `main`) reads a non-zero garbage tag and reports a phantom
|
||||
unhandled error.
|
||||
|
||||
- Observed: `main :: () -> ! { print("ok\n"); }` prints `ok` then
|
||||
`error: unhandled error reached main: error.` and exits **1**.
|
||||
- Expected: exit **0** (specs.md §11: "the exit code is `0` for void /
|
||||
`-> !` success"). Adding an explicit trailing `return;` makes it exit 0.
|
||||
|
||||
This is the silent-uninitialized-slot failure mode: the success path
|
||||
should write "no error" into the channel just like an explicit `return;`
|
||||
does, but the fall-through path skips it.
|
||||
|
||||
## Reproduction
|
||||
|
||||
```sx
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () -> ! {
|
||||
print("ok\n");
|
||||
}
|
||||
```
|
||||
|
||||
Run: `./zig-out/bin/sx run repro.sx` → prints `ok`, then
|
||||
`error: unhandled error reached main: error.`, exit 1 (should be 0).
|
||||
|
||||
A non-`main` void failable shows the same uninitialized slot downstream:
|
||||
|
||||
```sx
|
||||
#import "modules/std.sx";
|
||||
|
||||
noop :: () -> ! { } // falls through, no `return;`
|
||||
main :: () {
|
||||
noop() catch (e) { print("phantom: {}\n", e); } // fires spuriously
|
||||
}
|
||||
```
|
||||
|
||||
Workaround (confirms root cause): an explicit `return;` at the end of the
|
||||
`-> !` body initializes the slot and the phantom error disappears.
|
||||
|
||||
## Investigation prompt
|
||||
|
||||
The error channel for a `-> !` function is the last slot of the return
|
||||
aggregate (specs.md §12 ABI). An explicit `return;` lowers to a write of
|
||||
the "no error" sentinel into that slot; the **implicit fall-through** exit
|
||||
path (end of body with no `return`) apparently omits that write, leaving
|
||||
the slot whatever was on the stack.
|
||||
|
||||
Likely area: the function-epilogue / failable-return lowering in
|
||||
`src/ir/lower/` (the path that synthesizes the implicit return for a
|
||||
body that falls off the end — search for where a void/`-> !` function's
|
||||
trailing fall-through is lowered, and where the error slot's "no error"
|
||||
sentinel is written on the explicit-`return;` path). The fix: the
|
||||
implicit fall-through of a failable function must initialize the error
|
||||
slot to "no error" exactly like `return;` does.
|
||||
|
||||
Verification: the two repros above must exit 0 / not fire the catch;
|
||||
`examples/errors/1026-errors-failable-main.sx` (which currently passes
|
||||
only because it ends in `return;`) must keep passing. Add a regression
|
||||
example: a `-> !` function (and a `main :: () -> !`) that succeeds by
|
||||
fall-through with no explicit `return;`.
|
||||
|
||||
(Found by adversarial review during the tuple-syntax-cutover docs pass,
|
||||
commit `989e18b7`. Pre-existing — independent of the tuple change.)
|
||||
Reference in New Issue
Block a user