diff --git a/examples/1051-errors-cleanup-closure-boundary.sx b/examples/1051-errors-cleanup-closure-boundary.sx index 6a657c2..51527ae 100644 --- a/examples/1051-errors-cleanup-closure-boundary.sx +++ b/examples/1051-errors-cleanup-closure-boundary.sx @@ -1,15 +1,23 @@ // A closure literal inside a `defer` / `onfail` body is its OWN function -// boundary (ERR step E1.7). The cleanup-absorption check stops at the lambda: -// the E1.7 "no bare failable in cleanup" rule and the parser's `try`/`raise` -// ban both apply only to the cleanup block itself, not to a closure declared -// inside it. Within the closure, normal failable rules resume — `try` -// propagates through the closure's own `!E` channel, and value-slot liveness -// (E1.8) is analysed per-boundary, so `v` is live under its `if !err` guard. +// boundary (ERR step E1.7). Two boundary effects, both pinned here: // -// Locks the closure-boundary arms of the error-flow pass (`checkCleanupNode`'s -// `.lambda` stop + `flowExpr`'s `.lambda` recursion) before A5.2 extracts the -// pass into its own module. Constructible since issue 0073 (closure literal in -// a `defer` body no longer segfaults lowering — see 0310). +// (a) `checkCleanupNode` sees a bare lambda STATEMENT as a `.lambda` node and +// STOPS — it does not descend into the lambda body. So the bare failable +// inside the lambda is the lambda's concern, not a cleanup violation +// (were the `.lambda` arm to recurse, this bare `failing()` would reject +// like the ones in 1052). +// +// (b) value-slot liveness (E1.8) is analysed per-boundary: `flowExpr` recurses +// into the lambda via `analyzeFnBody`, so a value slot read inside the +// lambda must prove its own error absent — `v` here is live under its +// `if !err` guard. (The rejecting counterpart is 1053.) +// +// Also: `try` is legal inside the lambda (it propagates through the lambda's own +// `!E` channel) even though it is parser-banned in the cleanup body directly. +// +// Locks the closure-boundary arms of the error-flow pass before A5.2 extracts it +// into its own module. Constructible since issue 0073 (closure literal in a +// `defer` body no longer segfaults lowering — see 0310). #import "modules/std.sx"; @@ -20,9 +28,10 @@ recover :: () -> (s32, !E) { return 21; } work :: () { defer { - // Own boundary: `try` is legal here (it would be parser-banned in the - // defer body directly), and the bare failable is governed by the - // closure's `!E` signature, not the cleanup rule. + // (a) bare lambda statement — checkCleanupNode stops at the `.lambda`. + () -> !E { failing(); }; + + // (b) called closure — its body is analysed as its own boundary. emit := () -> !E { v, err := recover(); if !err { print("defer closure: v={}\n", v); } // E1.8: live under guard diff --git a/examples/1053-errors-nested-lambda-liveness-reject.sx b/examples/1053-errors-nested-lambda-liveness-reject.sx new file mode 100644 index 0000000..09b41f2 --- /dev/null +++ b/examples/1053-errors-nested-lambda-liveness-reject.sx @@ -0,0 +1,31 @@ +// Value-slot liveness (ERR step E1.8) is analysed inside a nested lambda as its +// OWN boundary: `flowExpr` recurses into a lambda literal via `analyzeFnBody`. +// Reading a failable's value slot inside the lambda where its error is NOT +// proven absent is rejected — even though the lambda is never called and the +// outer function proves nothing for it. +// +// Negative counterpart to 1051(b): were `flowExpr`'s `.lambda` recursion +// removed, the lambda body would go un-analysed and this read would slip +// through. The program never runs (exit 1). + +#import "modules/std.sx"; + +E :: error { Bad } + +parse :: (n: s32) -> (s32, !E) { + if n < 0 { raise error.Bad; } + return n * 10; +} + +build :: () { + emit := () -> s32 { + v, err := parse(5); + return v; // REJECTED: err not proven absent (inside lambda) + }; + print("unreached\n"); +} + +main :: () -> s32 { + build(); + return 0; +} diff --git a/examples/expected/1053-errors-nested-lambda-liveness-reject.exit b/examples/expected/1053-errors-nested-lambda-liveness-reject.exit new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/examples/expected/1053-errors-nested-lambda-liveness-reject.exit @@ -0,0 +1 @@ +1 diff --git a/examples/expected/1053-errors-nested-lambda-liveness-reject.stderr b/examples/expected/1053-errors-nested-lambda-liveness-reject.stderr new file mode 100644 index 0000000..7fcbd9e --- /dev/null +++ b/examples/expected/1053-errors-nested-lambda-liveness-reject.stderr @@ -0,0 +1,5 @@ +error: value `v` from a failable can be used only where its error `err` is proven absent — guard the use with `if !err { … }`, or return early with `if err { return; }` before reading `v` + --> examples/1053-errors-nested-lambda-liveness-reject.sx:23:16 + | +23 | return v; // REJECTED: err not proven absent (inside lambda) + | ^ diff --git a/examples/expected/1053-errors-nested-lambda-liveness-reject.stdout b/examples/expected/1053-errors-nested-lambda-liveness-reject.stdout new file mode 100644 index 0000000..e69de29