ERR/E1.4c: noreturn plumbing for divergence shapes

Type the divergence shapes as `noreturn` so a `catch` body that diverges
(E1.5) unifies with the failable's success type. The plan's original
"E1.4b", renumbered E1.4c (the SCC slice took the "E1.4b" label).

- inferExprType: `return` / `raise` / `break` / `continue` -> .noreturn
  (removed `.return_stmt` from the statements-are-`.void` group)
- if-else unification: a `.noreturn` branch yields the other branch's type;
  both diverging -> `.noreturn`
- block-ending-in-divergence propagates `.noreturn` (existing block arm)
- calls to `-> noreturn` already type via Function.ret (verified)
- made inferExprType pub for the unit test

Scope: the essential divergence shapes. Deferred `unreachable` (not a
keyword in sx — a separate feature, no current consumer) and infinite-loop
`noreturn` detection (rare). No observable consumer until E1.5's catch body,
so validated by a unit test, not an example.

Tests: unit test `E1.4c noreturn typing` in lower.test.zig (each shape ->
noreturn; block propagation; if-else unification). Gates: zig build,
zig build test, 262/262 examples (no new examples).
This commit is contained in:
agra
2026-05-31 20:33:13 +03:00
parent d2cba4e460
commit 696a749bd5
2 changed files with 77 additions and 7 deletions

View File

@@ -710,3 +710,61 @@ test "E1.4b converge inferred error sets: empty -> warning, raising -> converged
try std.testing.expect(stub_warned);
try std.testing.expect(!raiser_warned);
}
test "E1.4c noreturn typing: divergence shapes + if-else unification + block propagation" {
const alloc = std.testing.allocator;
var module = ir_mod.Module.init(alloc);
defer module.deinit();
var lowering = Lowering.init(&module);
const mk = struct {
fn node(a: std.mem.Allocator, data: ast.Node.Data) *Node {
const n = a.create(Node) catch unreachable;
n.* = .{ .span = .{ .start = 0, .end = 0 }, .data = data };
return n;
}
};
// return; / break; / continue; / raise error.X → noreturn
const ret = mk.node(alloc, .{ .return_stmt = .{ .value = null } });
defer alloc.destroy(ret);
const brk = mk.node(alloc, .{ .break_expr = {} });
defer alloc.destroy(brk);
const cont = mk.node(alloc, .{ .continue_expr = {} });
defer alloc.destroy(cont);
const err_id = mk.node(alloc, .{ .identifier = .{ .name = "error" } });
defer alloc.destroy(err_id);
const fa = mk.node(alloc, .{ .field_access = .{ .object = err_id, .field = "X" } });
defer alloc.destroy(fa);
const raise = mk.node(alloc, .{ .raise_stmt = .{ .tag = fa } });
defer alloc.destroy(raise);
try std.testing.expectEqual(TypeId.noreturn, lowering.inferExprType(ret));
try std.testing.expectEqual(TypeId.noreturn, lowering.inferExprType(brk));
try std.testing.expectEqual(TypeId.noreturn, lowering.inferExprType(cont));
try std.testing.expectEqual(TypeId.noreturn, lowering.inferExprType(raise));
// Block whose last statement diverges → noreturn.
const five = mk.node(alloc, .{ .int_literal = .{ .value = 5 } });
defer alloc.destroy(five);
const blk_stmts: []const *Node = &.{ five, ret };
const blk = mk.node(alloc, .{ .block = .{ .stmts = blk_stmts } });
defer alloc.destroy(blk);
try std.testing.expectEqual(TypeId.noreturn, lowering.inferExprType(blk));
// if-else with one diverging branch unifies to the other branch's type;
// both diverging → noreturn.
const lit = mk.node(alloc, .{ .int_literal = .{ .value = 1 } });
defer alloc.destroy(lit);
const then_div = mk.node(alloc, .{ .if_expr = .{ .condition = lit, .then_branch = ret, .else_branch = lit, .is_inline = false } });
defer alloc.destroy(then_div);
try std.testing.expectEqual(TypeId.s64, lowering.inferExprType(then_div)); // then diverges → else (s64)
const else_div = mk.node(alloc, .{ .if_expr = .{ .condition = lit, .then_branch = lit, .else_branch = ret, .is_inline = false } });
defer alloc.destroy(else_div);
try std.testing.expectEqual(TypeId.s64, lowering.inferExprType(else_div)); // then is s64
const both_div = mk.node(alloc, .{ .if_expr = .{ .condition = lit, .then_branch = ret, .else_branch = brk, .is_inline = false } });
defer alloc.destroy(both_div);
try std.testing.expectEqual(TypeId.noreturn, lowering.inferExprType(both_div));
}