Files
sx/issues/0190-void-failable-fallthrough-uninitialized-error-slot.md

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"); } 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

#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.)