Error-set convergence now lives in src/ir/error_analysis.zig behind a *Lowering facade (ErrorAnalysis), mirroring the other domain extractions. Moved verbatim: - convergeInferredErrorSets (whole-program inferred-`!` SCC fix-point), - convergeClosureShapeSets, - collectErrorSites / collectClosureShapes (the AST collectors). Added ErrorFacts (the PLAN-ARCH shape: inferred_error_sets + shape_inferred_sets) + a facts() view over the maps, which stay on Lowering for now (consumers read them via self.*). recordClosureShape and its deep type/shape helper web stay in Lowering; it reaches the moved collectErrorSites via self.errorAnalysis(). Lowering keeps convergeInferredErrorSets / convergeClosureShapeSets as thin pub wrappers (the lowering pipeline + the E1.4b unit test call them); collectErrorSites / collectClosureShapes are deleted (no fallback). New pub: isErrorTagLiteralNode / callTargetName / astIsPureBareInferred / astPureNamedSet / containsTag / namedSetTags / recordClosureShape (the moved collectors / facade reach them). lower.zig net -216 lines. The 2 convergence unit tests (transitive SCC across a try edge; closure-shape union) moved from lower.test.zig to error_analysis.test.zig and now drive the facade directly; the E1.4b test stays in lower.test.zig via the wrapper. Module named error_analysis.zig, NOT errors.zig (src/errors.zig is the DiagnosticList). zig build, zig build test, tests/run_examples.sh (357/0) all green — no .ir churn.
96 lines
4.7 KiB
Zig
96 lines
4.7 KiB
Zig
// Tests for error_analysis.zig — the error-set convergence owner
|
|
// (`ErrorAnalysis`). Reached via `ir.ErrorAnalysis{ .l = &lowering }`, mirroring
|
|
// the other facade tests. Moved here from lower.test.zig when the convergence
|
|
// traversals moved out of `Lowering` (A5.1 sub-step 2). The whole-program
|
|
// fix-point + closure-shape union are what A5.1 must preserve.
|
|
|
|
const std = @import("std");
|
|
const ast = @import("../ast.zig");
|
|
const Node = ast.Node;
|
|
|
|
const ir_mod = @import("ir.zig");
|
|
const TypeId = ir_mod.TypeId;
|
|
const Lowering = ir_mod.Lowering;
|
|
const ErrorAnalysis = ir_mod.ErrorAnalysis;
|
|
|
|
fn mk(alloc: std.mem.Allocator, data: ast.Node.Data) *Node {
|
|
const n = alloc.create(Node) catch unreachable;
|
|
n.* = .{ .span = .{ .start = 0, .end = 0 }, .data = data };
|
|
return n;
|
|
}
|
|
|
|
test "error_analysis: convergeInferredErrorSets propagates a callee set across a try edge" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = ir_mod.Module.init(alloc);
|
|
defer module.deinit();
|
|
var lowering = Lowering.init(&module);
|
|
const ea = ErrorAnalysis{ .l = &lowering };
|
|
|
|
// raiser :: () -> ! { raise error.Foo; }
|
|
const r_rt = mk(alloc, .{ .error_type_expr = .{ .name = null } });
|
|
const r_err = mk(alloc, .{ .identifier = .{ .name = "error" } });
|
|
const r_fa = mk(alloc, .{ .field_access = .{ .object = r_err, .field = "Foo" } });
|
|
const r_raise = mk(alloc, .{ .raise_stmt = .{ .tag = r_fa } });
|
|
const r_body = mk(alloc, .{ .block = .{ .stmts = &[_]*Node{r_raise} } });
|
|
const raiser_fd = ast.FnDecl{ .name = "raiser", .params = &.{}, .return_type = r_rt, .body = r_body };
|
|
|
|
// caller :: () -> ! { try raiser(); } — no direct raise; inherits {Foo}.
|
|
const c_rt = mk(alloc, .{ .error_type_expr = .{ .name = null } });
|
|
const c_callee = mk(alloc, .{ .identifier = .{ .name = "raiser" } });
|
|
const c_call = mk(alloc, .{ .call = .{ .callee = c_callee, .args = &.{} } });
|
|
const c_try = mk(alloc, .{ .try_expr = .{ .operand = c_call } });
|
|
const c_body = mk(alloc, .{ .block = .{ .stmts = &[_]*Node{c_try} } });
|
|
const caller_fd = ast.FnDecl{ .name = "caller", .params = &.{}, .return_type = c_rt, .body = c_body };
|
|
|
|
lowering.program_index.fn_ast_map.put("raiser", &raiser_fd) catch unreachable;
|
|
lowering.program_index.fn_ast_map.put("caller", &caller_fd) catch unreachable;
|
|
|
|
ea.convergeInferredErrorSets();
|
|
|
|
const foo = module.types.internTag("Foo");
|
|
const raiser_set = lowering.inferred_error_sets.get("raiser") orelse unreachable;
|
|
try std.testing.expectEqual(@as(usize, 1), raiser_set.len);
|
|
try std.testing.expectEqual(foo, raiser_set[0]);
|
|
// The caller raises nothing directly but converges to {Foo} via the edge.
|
|
const caller_set = lowering.inferred_error_sets.get("caller") orelse unreachable;
|
|
try std.testing.expectEqual(@as(usize, 1), caller_set.len);
|
|
try std.testing.expectEqual(foo, caller_set[0]);
|
|
|
|
// facts() exposes the same converged store.
|
|
try std.testing.expect(ea.facts().inferred_error_sets.get("caller") != null);
|
|
}
|
|
|
|
test "error_analysis: convergeClosureShapeSets unions a bare-! closure literal's raises" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = ir_mod.Module.init(alloc);
|
|
defer module.deinit();
|
|
var lowering = Lowering.init(&module);
|
|
const ea = ErrorAnalysis{ .l = &lowering };
|
|
|
|
// host :: () { () -> ! { raise error.Bar; }; } — a bare-`!` closure literal
|
|
// sitting in `host`'s body; its raises union into the shape set.
|
|
const lam_rt = mk(alloc, .{ .error_type_expr = .{ .name = null } });
|
|
const l_err = mk(alloc, .{ .identifier = .{ .name = "error" } });
|
|
const l_fa = mk(alloc, .{ .field_access = .{ .object = l_err, .field = "Bar" } });
|
|
const l_raise = mk(alloc, .{ .raise_stmt = .{ .tag = l_fa } });
|
|
const lam_body = mk(alloc, .{ .block = .{ .stmts = &[_]*Node{l_raise} } });
|
|
const lambda = mk(alloc, .{ .lambda = .{ .params = &.{}, .return_type = lam_rt, .body = lam_body } });
|
|
const host_body = mk(alloc, .{ .block = .{ .stmts = &[_]*Node{lambda} } });
|
|
const host_fd = ast.FnDecl{ .name = "host", .params = &.{}, .return_type = null, .body = host_body };
|
|
|
|
lowering.program_index.fn_ast_map.put("host", &host_fd) catch unreachable;
|
|
|
|
ea.convergeClosureShapeSets();
|
|
|
|
// Exactly one closure shape recorded, carrying {Bar}.
|
|
try std.testing.expectEqual(@as(u32, 1), lowering.shape_inferred_sets.count());
|
|
var it = lowering.shape_inferred_sets.valueIterator();
|
|
const tags = it.next().?.*;
|
|
try std.testing.expectEqual(@as(usize, 1), tags.len);
|
|
try std.testing.expectEqual(module.types.internTag("Bar"), tags[0]);
|
|
}
|