2.7 KiB
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"); }printsokthenerror: unhandled error reached main: error.and exits 1. - Expected: exit 0 (specs.md §11: "the exit code is
0for void /-> !success"). Adding an explicit trailingreturn;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
#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:
#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.)