ERR/E1.4b: whole-program inferred error sets + empty-inferred warning
The type-convergence side of E1.4 (the SCC slice). A bare `-> !` function's error set is now converged whole-program from its literal raises plus the sets of the pure-failable functions it `try`s. - convergeInferredErrorSets: a pre-lowering fix-point pass (lowerRoot Pass 1d, after scanDecls / before body lowering) that walks each top-level bare-`!` function's body AST (collectErrorSites, stopping at nested-fn boundaries) for literal `raise error.X` tags + pure `try g()` edges, then unions each set with its edges' sets until stable. Stored in a side map `inferred_error_sets` (fn name -> sorted []u32) — sidesteps the name-only error-set interning collision (the shared `!` placeholder stays empty). - lowerTry widening: a named caller `try`-ing a bare-`!` callee now checks the callee's converged set (previously a false-negative — the empty placeholder was trivially a subset). Factored diagTagsNotInSet out of checkErrorSetSubset. - empty-inferred warning: a top-level non-main bare-`!` function with an empty converged set warns. Not user-visible yet (the compile driver renders diagnostics only on failure — a LANG follow-up), so unit-tested on the DiagnosticList. - corrected two now-stale bail messages (failable-`or` -> E2.4; value-carrying `try` -> E2). Deferred to E2.4: failable-`or` chains / value-terminators (and `try` fallback routing) — gated on the value-carrying tuple ABI. Tests: examples/223-inferred-error-sets.sx (transitive convergence + widening passes, exit 7), examples/224-inferred-widening-reject.sx (transitive widening rejection, exit 1), unit test in lower.test.zig. Gates: zig build, zig build test, 262/262 examples.
This commit is contained in:
@@ -650,3 +650,63 @@ test "pack projection: same-name type-arg + method warns (Decision 4)" {
|
||||
try std.testing.expectEqual(Lowering.PackProjection{ .type_arg = 0 }, lowering.resolvePackProjection("Shadowy", "value", .type_position));
|
||||
try std.testing.expectEqual(Lowering.PackProjection{ .method = 0 }, lowering.resolvePackProjection("Shadowy", "value", .value_position));
|
||||
}
|
||||
|
||||
test "E1.4b converge inferred error sets: empty -> warning, raising -> converged set" {
|
||||
// The empty-inferred warning isn't user-visible yet (the compile driver
|
||||
// only renders diagnostics on failure — a LANG follow-up), so validate the
|
||||
// SCC's emission + set computation directly on the DiagnosticList.
|
||||
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 diags = errors.DiagnosticList.init(alloc, "", "test.sx");
|
||||
defer diags.deinit();
|
||||
|
||||
var lowering = Lowering.init(&module);
|
||||
lowering.diagnostics = &diags;
|
||||
|
||||
// stub :: () -> ! { return; } — bare `!`, never raises.
|
||||
const stub_rt = alloc.create(Node) catch unreachable;
|
||||
stub_rt.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .error_type_expr = .{ .name = null } } };
|
||||
const stub_ret = alloc.create(Node) catch unreachable;
|
||||
stub_ret.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .return_stmt = .{ .value = null } } };
|
||||
const stub_body = alloc.create(Node) catch unreachable;
|
||||
const stub_stmts: []const *Node = &.{stub_ret};
|
||||
stub_body.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .block = .{ .stmts = stub_stmts } } };
|
||||
const stub_fd = ast.FnDecl{ .name = "stub", .params = &.{}, .return_type = stub_rt, .body = stub_body };
|
||||
|
||||
// raiser :: () -> ! { raise error.Foo; } — bare `!`, raises Foo.
|
||||
const r_rt = alloc.create(Node) catch unreachable;
|
||||
r_rt.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .error_type_expr = .{ .name = null } } };
|
||||
const r_err = alloc.create(Node) catch unreachable;
|
||||
r_err.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .identifier = .{ .name = "error" } } };
|
||||
const r_fa = alloc.create(Node) catch unreachable;
|
||||
r_fa.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .field_access = .{ .object = r_err, .field = "Foo" } } };
|
||||
const r_raise = alloc.create(Node) catch unreachable;
|
||||
r_raise.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .raise_stmt = .{ .tag = r_fa } } };
|
||||
const r_body = alloc.create(Node) catch unreachable;
|
||||
const r_stmts: []const *Node = &.{r_raise};
|
||||
r_body.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .block = .{ .stmts = r_stmts } } };
|
||||
const raiser_fd = ast.FnDecl{ .name = "raiser", .params = &.{}, .return_type = r_rt, .body = r_body };
|
||||
|
||||
lowering.fn_ast_map.put("stub", &stub_fd) catch unreachable;
|
||||
lowering.fn_ast_map.put("raiser", &raiser_fd) catch unreachable;
|
||||
|
||||
lowering.convergeInferredErrorSets();
|
||||
|
||||
// raiser converges to {Foo} (non-empty); stub to ∅.
|
||||
try std.testing.expectEqual(@as(usize, 1), (lowering.inferred_error_sets.get("raiser") orelse unreachable).len);
|
||||
try std.testing.expectEqual(@as(usize, 0), (lowering.inferred_error_sets.get("stub") orelse unreachable).len);
|
||||
|
||||
// The empty-set (stub) warns; the raising one does not.
|
||||
var stub_warned = false;
|
||||
var raiser_warned = false;
|
||||
for (diags.items.items) |d| {
|
||||
if (d.level != .warn) continue;
|
||||
if (std.mem.indexOf(u8, d.message, "stub") != null) stub_warned = true;
|
||||
if (std.mem.indexOf(u8, d.message, "raiser") != null) raiser_warned = true;
|
||||
}
|
||||
try std.testing.expect(stub_warned);
|
||||
try std.testing.expect(!raiser_warned);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user