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