ERR/E1.3: raise sema + pure-failable lowering
`raise EXPR` now terminates a failable function via the error channel. Scope (Option 2): full raise sema checks + lowering for the pure-failable shape (`-> !` / `-> !Named`); the value-carrying `-> (T..., !)` shape bails loudly, deferred to E2's error-channel tuple ABI. - lowerStmt + tryLowerAsExpr: `.raise_stmt` -> lowerRaise (also routes a raise that is a block's last statement, which previously hit unknown_expr) - lowerRaise: failable-context check (effectiveReturnType + errorChannelOf); literal membership via lowerErrorTagLiteral; variable form subset-checked via checkErrorSetSubset; pure-failable emits ret(tag) - lowerErrorTagLiteral skips membership for the bare-`!` inferred placeholder - plain `return;` in a pure-failable fn emits ret(0) (success / no error) - parser: in_defer_body flag rejects `raise` inside a `defer` body Tests: examples/219-raise.sx (positive, exit 8), examples/220-raise-rejections.sx (3 sema rejections, exit 1), inline parser test for raise-in-defer. Gates: zig build, zig build test, 258/258 examples.
This commit is contained in:
@@ -32,6 +32,11 @@ pub const Parser = struct {
|
||||
/// rejected — an error during cleanup has no propagation target. E1.7
|
||||
/// extends this to the full {try, return, break, continue} set.
|
||||
in_onfail_body: bool = false,
|
||||
/// When true (set while parsing a `defer` body), a `raise` statement is
|
||||
/// rejected — same reason as `onfail`: cleanup runs while the function is
|
||||
/// already exiting, so there is nothing to propagate to. E1.7 extends this
|
||||
/// to the full {try, return, break, continue} set.
|
||||
in_defer_body: bool = false,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, source: [:0]const u8) Parser {
|
||||
var lexer = Lexer.init(source);
|
||||
@@ -2010,6 +2015,9 @@ pub const Parser = struct {
|
||||
if (self.current.tag == .kw_defer) {
|
||||
const start = self.current.loc.start;
|
||||
self.advance();
|
||||
const saved_defer = self.in_defer_body;
|
||||
self.in_defer_body = true;
|
||||
defer self.in_defer_body = saved_defer;
|
||||
const deferred = try self.parseExpr();
|
||||
try self.expect(.semicolon);
|
||||
return try self.createNode(start, .{ .defer_stmt = .{ .expr = deferred } });
|
||||
@@ -2021,6 +2029,9 @@ pub const Parser = struct {
|
||||
if (self.in_onfail_body) {
|
||||
return self.fail("`raise` is not allowed inside an `onfail` body — an error during cleanup has no propagation target");
|
||||
}
|
||||
if (self.in_defer_body) {
|
||||
return self.fail("`raise` is not allowed inside a `defer` body — an error during cleanup has no propagation target");
|
||||
}
|
||||
self.advance();
|
||||
const tag_expr = try self.parseExpr();
|
||||
try self.expect(.semicolon);
|
||||
@@ -4247,6 +4258,13 @@ test "E0.2 raise rejected inside an onfail body" {
|
||||
try std.testing.expectError(error.ParseError, parser.parse());
|
||||
}
|
||||
|
||||
test "E1.3 raise rejected inside a defer body" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var parser = Parser.init(arena.allocator(), "f :: () { defer { raise error.X; } }");
|
||||
try std.testing.expectError(error.ParseError, parser.parse());
|
||||
}
|
||||
|
||||
test "E0.2 onfail with binding and block body" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
Reference in New Issue
Block a user