ERR/E1.7: reject bare failable calls in defer/onfail cleanup bodies
A `defer`/`onfail` body runs while the block is already exiting, so a
failable call there has nowhere to propagate its error. The parser
already bans `try`/`raise`/`return`/`break`/`continue` in cleanup bodies
(f9dd965); this adds the remaining sema rule — a bare (un-absorbed)
failable call must be absorbed locally with `catch` or `or <value>`.
Implemented in the shared error-flow pass (`checkCleanupBody` /
`checkCleanupNode` / `cleanupReject` in ir/lower.zig): when the walk hits
a `defer`/`onfail`, it scans the body transitively (through blocks, `if`,
loops, match arms, `catch` handlers; stopping at nested closures) and
flags any still-failable expression. `catch` / `or value` strip the
error channel, so `exprIsFailable` is false for them — only an unhandled
failable trips the check. This completes ERR PLAN E0–E5 plus the two
deferred E1 follow-ups (E1.7 + E1.8).
New regressions: 1048 (catch/or-value absorbed forms compile + run) and
1049 (bare failable in defer and onfail rejected, exit 1).
Filed issue 0065: a braced `defer { … }` / value-block body routes
through `parseExpr` (not `parseBlock` like `onfail`), so it can't parse a
destructure or `catch`-statement inside. Orthogonal to E1.7 — the spec'd
cleanup absorbers (`catch` / `or value`) parse fine in a `defer` body.
Gates: zig build, zig build test, run_examples.sh -> 340 passed, 0 failed.
This commit is contained in:
28
examples/1048-errors-cleanup-absorption.sx
Normal file
28
examples/1048-errors-cleanup-absorption.sx
Normal file
@@ -0,0 +1,28 @@
|
||||
// Failable calls in cleanup bodies must be absorbed locally (ERR step E1.7). A
|
||||
// `defer` / `onfail` body runs while the block is already exiting, so a failable
|
||||
// it calls has nowhere to propagate — it must be handled in place with `catch`
|
||||
// or an `or <value>` terminator. This file shows the accepted forms; the bare
|
||||
// (un-absorbed) form is rejected in 1049.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
E :: error { Bad }
|
||||
|
||||
failing :: () -> !E { raise error.Bad; }
|
||||
recover :: () -> (s32, !E) { raise error.Bad; }
|
||||
|
||||
work :: (n: s32) -> !E {
|
||||
defer print("defer: always\n"); // plain cleanup
|
||||
onfail { failing() catch e print("onfail: caught (catch)\n"); } // catch absorbs
|
||||
onfail { x := recover() or 7; print("onfail: x={} (or)\n", x); } // or-value absorbs
|
||||
if n < 0 { raise error.Bad; }
|
||||
return;
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
print("[error]\n");
|
||||
a := work(-1); // raises → onfail bodies fire, then defer (reverse decl order)
|
||||
print("[ok]\n");
|
||||
b := work(2); // success → only defer fires
|
||||
return 0;
|
||||
}
|
||||
23
examples/1049-errors-cleanup-absorption-reject.sx
Normal file
23
examples/1049-errors-cleanup-absorption-reject.sx
Normal file
@@ -0,0 +1,23 @@
|
||||
// Rejection counterpart to 1048 (ERR step E1.7). A bare (un-absorbed) failable
|
||||
// call in a `defer` / `onfail` body is a compile error — the block is already
|
||||
// exiting, so the error has nowhere to propagate. It must be absorbed locally
|
||||
// with `catch` or `or <value>`. Both a `defer` and an `onfail` bare call are
|
||||
// flagged; the program never runs (exit 1).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
E :: error { Bad }
|
||||
|
||||
failing :: () -> !E { raise error.Bad; }
|
||||
|
||||
work :: (n: s32) -> !E {
|
||||
defer failing(); // REJECTED: bare failable in a defer body
|
||||
onfail { failing(); } // REJECTED: bare failable in an onfail body
|
||||
if n < 0 { raise error.Bad; }
|
||||
return;
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
a := work(-1);
|
||||
return 0;
|
||||
}
|
||||
1
examples/expected/1048-errors-cleanup-absorption.exit
Normal file
1
examples/expected/1048-errors-cleanup-absorption.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/expected/1048-errors-cleanup-absorption.stderr
Normal file
1
examples/expected/1048-errors-cleanup-absorption.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
6
examples/expected/1048-errors-cleanup-absorption.stdout
Normal file
6
examples/expected/1048-errors-cleanup-absorption.stdout
Normal file
@@ -0,0 +1,6 @@
|
||||
[error]
|
||||
onfail: x=7 (or)
|
||||
onfail: caught (catch)
|
||||
defer: always
|
||||
[ok]
|
||||
defer: always
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,11 @@
|
||||
error: a bare failable call in a `defer` body has nowhere to send its error — the block is already exiting; absorb it locally with `catch` or `or <value>`
|
||||
--> /Users/agra/projects/sx/examples/1049-errors-cleanup-absorption-reject.sx:14:12
|
||||
|
|
||||
14 | defer failing(); // REJECTED: bare failable in a defer body
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: a bare failable call in a `onfail` body has nowhere to send its error — the block is already exiting; absorb it locally with `catch` or `or <value>`
|
||||
--> /Users/agra/projects/sx/examples/1049-errors-cleanup-absorption-reject.sx:15:14
|
||||
|
|
||||
15 | onfail { failing(); } // REJECTED: bare failable in an onfail body
|
||||
| ^^^^^^^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Reference in New Issue
Block a user