From fdeab0efd498ec7481c4ac05d80afffb50ad59c8 Mon Sep 17 00:00:00 2001 From: agra Date: Sun, 31 May 2026 17:14:02 +0300 Subject: [PATCH] =?UTF-8?q?ERR/E0.3:=20parser=20test=20consolidation=20?= =?UTF-8?q?=E2=80=94=20Phase=20E0=20complete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fills the E0.3 coverage gaps E0.1/E0.2's inline tests hadn't hit and adds an end-to-end integration parse. Test-only; no production code change. - `try` in statement position (`try must_init();`). - `try` over a parenthesized or-chain (`try (foo() or boo())`) — distinct from `try foo() or try boo()`. - `or` value-terminator (`parse(s) or 0`). - Integration: a full `parse :: (s) -> (s32, !ParseErr) { onfail / try / or / catch / if { raise } / return }` — asserts the trailing `!ParseErr`, the five body statement kinds, and that `in_onfail_body` is correctly scoped (the later if-block `raise` is allowed). Tests stay inline in parser.zig (consistent with the existing 24 + E0.1/E0.2 inline tests). 37 ERR parser tests total; every new AST node has a round-trip test. zig build test (268) and 254/254 examples green. --- src/parser.zig | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/parser.zig b/src/parser.zig index 45de9a3..1222ccf 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -4336,3 +4336,68 @@ test "E0.2 round-trip print: catch match-body form" { const v = try e02FirstValue(a, "f :: () { v := foo() catch e == { case .Empty: 0; else: 1; }; }"); try e02ExpectPrints(a, v, "foo() catch e == { case .Empty: 0; else: 1; }"); } + +// ── ERR step E0.3 — coverage consolidation (gaps + integration) ── + +test "E0.3 try in statement position (propagate, discard value)" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const s = try e02FirstStmt(arena.allocator(), "f :: () { try must_init(); }"); + try std.testing.expect(s.data == .try_expr); + try std.testing.expect(s.data.try_expr.operand.data == .call); +} + +test "E0.3 try over a parenthesized or-chain: try (foo() or boo())" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const v = try e02FirstValue(arena.allocator(), "f :: () { v := try (foo() or boo()); }"); + // Distinct from `try foo() or try boo()`: here `try` wraps the whole chain. + try std.testing.expect(v.data == .try_expr); + try std.testing.expect(v.data.try_expr.operand.data == .binary_op); + try std.testing.expect(v.data.try_expr.operand.data.binary_op.op == .or_op); +} + +test "E0.3 or value-terminator: parse(s) or 0" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const v = try e02FirstValue(arena.allocator(), "f :: () { v := parse(s) or 0; }"); + try std.testing.expect(v.data == .binary_op and v.data.binary_op.op == .or_op); + try std.testing.expect(v.data.binary_op.lhs.data == .call); + try std.testing.expect(v.data.binary_op.rhs.data == .int_literal); +} + +test "E0.3 full failable function parses end-to-end (all E0 forms)" { + const source = + \\parse :: (s: string) -> (s32, !ParseErr) { + \\ onfail e { cleanup(s); } + \\ v := try inner(s) or 0; + \\ w := other(s) catch e2 { return 0; }; + \\ if bad(s) { raise error.BadDigit; } + \\ return v; + \\} + ; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var parser = Parser.init(arena.allocator(), source); + const root = try parser.parse(); + const decl = root.data.root.decls[0]; + try std.testing.expect(decl.data == .fn_decl); + try std.testing.expectEqualStrings("parse", decl.data.fn_decl.name); + // return type is a multi-value result list ending in `!ParseErr` + const rt = decl.data.fn_decl.return_type.?; + try std.testing.expect(rt.data == .tuple_type_expr); + const fields = rt.data.tuple_type_expr.field_types; + try std.testing.expect(fields[fields.len - 1].data == .error_type_expr); + try std.testing.expectEqualStrings("ParseErr", fields[fields.len - 1].data.error_type_expr.name.?); + // body statement kinds + const stmts = decl.data.fn_decl.body.data.block.stmts; + try std.testing.expectEqual(@as(usize, 5), stmts.len); + try std.testing.expect(stmts[0].data == .onfail_stmt); + try std.testing.expect(stmts[1].data == .var_decl and stmts[1].data.var_decl.value.?.data == .binary_op); + try std.testing.expect(stmts[2].data == .var_decl and stmts[2].data.var_decl.value.?.data == .catch_expr); + try std.testing.expect(stmts[3].data == .if_expr); + try std.testing.expect(stmts[4].data == .return_stmt); + // the onfail flag was restored: the raise inside the (separate) if-block is allowed + const then_block = stmts[3].data.if_expr.then_branch; + try std.testing.expect(then_block.data.block.stmts[0].data == .raise_stmt); +}