convergeClosureShapeSets, checkErrorFlow, and the unknown-type loop ran under whatever current_source_file the previous phase left behind — closure-literal annotations resolved (and reject/unknown-type diagnostics rendered) against an arbitrary module. Latent while std.sx was a single file (the ambient happened to be the main file); the re-export facade restructure exposed it. Each walk now pins setCurrentSourceFile per decl / per fn (body.source_file is already stamped by resolveImports). Coverage: examples 0129/1047/1049/1052/ 1053/1056 against the facade std.sx. Gates: zbt 426/426, suite 588/588.
285 lines
14 KiB
Zig
285 lines
14 KiB
Zig
const std = @import("std");
|
|
const ast = @import("../ast.zig");
|
|
const lower = @import("lower.zig");
|
|
|
|
const Node = ast.Node;
|
|
const Lowering = lower.Lowering;
|
|
|
|
/// The converged error-analysis facts lowering consumes (PLAN-ARCH A5.1): each
|
|
/// pure-failable function's inferred error-tag set, and each bare-`!` closure
|
|
/// SHAPE's inferred set. Backing maps currently live on `Lowering` (the facade
|
|
/// writes `self.l.*`); `facts()` returns a view over them.
|
|
pub const ErrorFacts = struct {
|
|
inferred_error_sets: std.StringHashMap([]const u32),
|
|
shape_inferred_sets: std.StringHashMap([]const u32),
|
|
};
|
|
|
|
/// Whole-program error-set convergence (architecture phase A5.1), extracted
|
|
/// from `Lowering`. Owns the fix-point traversals that converge inferred
|
|
/// `!` error sets (`convergeInferredErrorSets`) and bare-`!` closure-shape sets
|
|
/// (`convergeClosureShapeSets`), plus the AST collectors that feed them.
|
|
///
|
|
/// A `*Lowering` facade (Principle 5, like `CallResolver`/`ProtocolResolver`):
|
|
/// it reads the declaration map (`fn_ast_map`) + tag registry and writes the
|
|
/// `inferred_error_sets` / `shape_inferred_sets` maps that still live on
|
|
/// `Lowering` (consumers read them there). The per-closure-literal contribution
|
|
/// (`recordClosureShape`) + its type/shape helpers stay in `Lowering`; this
|
|
/// module calls back for that and reaches its own `collectErrorSites` via the
|
|
/// facade.
|
|
pub const ErrorAnalysis = struct {
|
|
l: *Lowering,
|
|
|
|
pub fn facts(self: ErrorAnalysis) ErrorFacts {
|
|
return .{
|
|
.inferred_error_sets = self.l.inferred_error_sets,
|
|
.shape_inferred_sets = self.l.shape_inferred_sets,
|
|
};
|
|
}
|
|
|
|
/// Collect the error TAGS raised + the `try`-call EDGES of a function body,
|
|
/// for the inferred-set fix-point. Stops at nested function boundaries.
|
|
pub fn collectErrorSites(self: ErrorAnalysis, node: *const Node, tags: *std.ArrayList(u32), edges: *std.ArrayList([]const u8)) void {
|
|
switch (node.data) {
|
|
.raise_stmt => |rs| {
|
|
if (Lowering.isErrorTagLiteralNode(rs.tag)) {
|
|
tags.append(self.l.alloc, self.l.module.types.internTag(rs.tag.data.field_access.field)) catch {};
|
|
}
|
|
self.collectErrorSites(rs.tag, tags, edges);
|
|
},
|
|
.try_expr => |te| {
|
|
if (Lowering.callTargetName(te.operand)) |nm| edges.append(self.l.alloc, nm) catch {};
|
|
self.collectErrorSites(te.operand, tags, edges);
|
|
},
|
|
.block => |b| for (b.stmts) |s| self.collectErrorSites(s, tags, edges),
|
|
.if_expr => |ie| {
|
|
self.collectErrorSites(ie.condition, tags, edges);
|
|
self.collectErrorSites(ie.then_branch, tags, edges);
|
|
if (ie.else_branch) |eb| self.collectErrorSites(eb, tags, edges);
|
|
},
|
|
.while_expr => |w| {
|
|
self.collectErrorSites(w.condition, tags, edges);
|
|
self.collectErrorSites(w.body, tags, edges);
|
|
},
|
|
.for_expr => |f| {
|
|
for (f.iterables) |it| {
|
|
self.collectErrorSites(it.expr, tags, edges);
|
|
if (it.range_end) |re| self.collectErrorSites(re, tags, edges);
|
|
}
|
|
self.collectErrorSites(f.body, tags, edges);
|
|
},
|
|
.return_stmt => |r| if (r.value) |v| self.collectErrorSites(v, tags, edges),
|
|
.var_decl => |v| if (v.value) |val| self.collectErrorSites(val, tags, edges),
|
|
.const_decl => |c| self.collectErrorSites(c.value, tags, edges),
|
|
.destructure_decl => |d| self.collectErrorSites(d.value, tags, edges),
|
|
.assignment => |a| {
|
|
self.collectErrorSites(a.target, tags, edges);
|
|
self.collectErrorSites(a.value, tags, edges);
|
|
},
|
|
.multi_assign => |m| {
|
|
for (m.targets) |t| self.collectErrorSites(t, tags, edges);
|
|
for (m.values) |v| self.collectErrorSites(v, tags, edges);
|
|
},
|
|
.call => |c| {
|
|
self.collectErrorSites(c.callee, tags, edges);
|
|
for (c.args) |a| self.collectErrorSites(a, tags, edges);
|
|
},
|
|
.binary_op => |b| {
|
|
self.collectErrorSites(b.lhs, tags, edges);
|
|
self.collectErrorSites(b.rhs, tags, edges);
|
|
},
|
|
.unary_op => |u| self.collectErrorSites(u.operand, tags, edges),
|
|
.deref_expr => |d| self.collectErrorSites(d.operand, tags, edges),
|
|
.force_unwrap => |fu| self.collectErrorSites(fu.operand, tags, edges),
|
|
.null_coalesce => |nc| {
|
|
self.collectErrorSites(nc.lhs, tags, edges);
|
|
self.collectErrorSites(nc.rhs, tags, edges);
|
|
},
|
|
.field_access => |fa| self.collectErrorSites(fa.object, tags, edges),
|
|
.index_expr => |ix| {
|
|
self.collectErrorSites(ix.object, tags, edges);
|
|
self.collectErrorSites(ix.index, tags, edges);
|
|
},
|
|
.spread_expr => |s| self.collectErrorSites(s.operand, tags, edges),
|
|
.catch_expr => |ce| {
|
|
self.collectErrorSites(ce.operand, tags, edges);
|
|
self.collectErrorSites(ce.body, tags, edges);
|
|
},
|
|
.defer_stmt => |d| self.collectErrorSites(d.expr, tags, edges),
|
|
.push_stmt => |p| {
|
|
self.collectErrorSites(p.context_expr, tags, edges);
|
|
self.collectErrorSites(p.body, tags, edges);
|
|
},
|
|
.array_literal => |al| for (al.elements) |el| self.collectErrorSites(el, tags, edges),
|
|
.tuple_literal => |tl| for (tl.elements) |el| self.collectErrorSites(el.value, tags, edges),
|
|
// Stop at nested function boundaries; leaves contribute nothing.
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
/// Whole-program fix-point that converges each top-level bare-`!` function's
|
|
/// inferred error set (ERR E1.4b). Runs after `scanDecls` (ASTs + named
|
|
/// error sets registered) and before body lowering, so `lowerTry`'s
|
|
/// named-caller widening sees the converged callee sets. Also emits the
|
|
/// empty-inferred warning. Scope: pure-failable functions (value-carrying
|
|
/// raise/try aren't lowered yet — E2).
|
|
pub fn convergeInferredErrorSets(self: ErrorAnalysis) void {
|
|
const Node_ = struct {
|
|
tags: std.ArrayList(u32),
|
|
edges: std.ArrayList([]const u8),
|
|
rt: ?*const Node,
|
|
};
|
|
var work = std.StringHashMap(Node_).init(self.l.alloc);
|
|
defer work.deinit();
|
|
|
|
// Seed each bare-`!` function with its direct escape sites.
|
|
var it = self.l.program_index.fn_ast_map.iterator();
|
|
while (it.next()) |e| {
|
|
const fd = e.value_ptr.*;
|
|
if (!Lowering.astIsPureBareInferred(fd.return_type)) continue;
|
|
var tags = std.ArrayList(u32).empty;
|
|
var edges = std.ArrayList([]const u8).empty;
|
|
self.collectErrorSites(fd.body, &tags, &edges);
|
|
work.put(e.key_ptr.*, .{ .tags = tags, .edges = edges, .rt = fd.return_type }) catch {};
|
|
}
|
|
|
|
// Union edge contributions until no set grows (monotone → terminates).
|
|
var changed = true;
|
|
while (changed) {
|
|
changed = false;
|
|
var wit = work.iterator();
|
|
while (wit.next()) |we| {
|
|
for (we.value_ptr.edges.items) |callee| {
|
|
const callee_tags: []const u32 = blk: {
|
|
if (work.getPtr(callee)) |cc| break :blk cc.tags.items;
|
|
if (self.l.program_index.fn_ast_map.get(callee)) |cfd| {
|
|
if (Lowering.astPureNamedSet(cfd.return_type)) |nm| {
|
|
break :blk self.l.namedSetTags(nm) orelse &.{};
|
|
}
|
|
}
|
|
break :blk &.{};
|
|
};
|
|
for (callee_tags) |t| {
|
|
if (!Lowering.containsTag(we.value_ptr.tags.items, t)) {
|
|
we.value_ptr.tags.append(self.l.alloc, t) catch {};
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Store the converged sets (sorted) and warn on empty inferred sets.
|
|
var sit = work.iterator();
|
|
while (sit.next()) |se| {
|
|
const sorted = self.l.alloc.dupe(u32, se.value_ptr.tags.items) catch continue;
|
|
std.mem.sort(u32, sorted, {}, std.sort.asc(u32));
|
|
self.l.inferred_error_sets.put(se.key_ptr.*, sorted) catch {};
|
|
if (sorted.len == 0 and !std.mem.eql(u8, se.key_ptr.*, "main")) {
|
|
if (self.l.diagnostics) |diags| {
|
|
if (se.value_ptr.rt) |rt| {
|
|
diags.addFmt(.warn, rt.span, "function '{s}' is declared `!` but never errors — drop the `!`", .{se.key_ptr.*});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Whole-program union of each bare-`!` closure/fn-type SHAPE's escape set
|
|
/// (ERR E5.1 sub-feature 2). Walks every function body for closure literals;
|
|
/// each bare-`!` failable literal contributes its raises (+ `try named_fn()`
|
|
/// edges, resolved against the name-keyed converged sets) to the node shared
|
|
/// by all occurrences of its value-signature shape. A `try slot(x)` against
|
|
/// any matching-shape slot then widens against this union.
|
|
pub fn convergeClosureShapeSets(self: ErrorAnalysis) void {
|
|
// Pin the visibility context to each fn's DEFINING module
|
|
// (body.source_file, stamped by resolveImports) — a closure literal's
|
|
// param/return annotations must resolve where the fn is written, not
|
|
// against whatever module the previous pipeline phase happened to
|
|
// leave as the ambient context (issue 0122).
|
|
const saved = self.l.current_source_file;
|
|
defer self.l.setCurrentSourceFile(saved);
|
|
var it = self.l.program_index.fn_ast_map.iterator();
|
|
while (it.next()) |e| {
|
|
self.l.setCurrentSourceFile(e.value_ptr.*.body.source_file orelse saved);
|
|
self.collectClosureShapes(e.value_ptr.*.body);
|
|
}
|
|
}
|
|
|
|
/// Recurse the AST collecting closure-literal shape contributions. Unlike
|
|
/// `collectErrorSites`, this descends THROUGH lambda boundaries (a nested
|
|
/// closure is its own shape, and may itself contain closures). The
|
|
/// per-literal recording (`recordClosureShape`) stays in `Lowering`.
|
|
fn collectClosureShapes(self: ErrorAnalysis, node: *const Node) void {
|
|
switch (node.data) {
|
|
.lambda => |lam| {
|
|
self.l.recordClosureShape(&lam);
|
|
self.collectClosureShapes(lam.body);
|
|
},
|
|
.block => |b| for (b.stmts) |s| self.collectClosureShapes(s),
|
|
.if_expr => |ie| {
|
|
self.collectClosureShapes(ie.condition);
|
|
self.collectClosureShapes(ie.then_branch);
|
|
if (ie.else_branch) |eb| self.collectClosureShapes(eb);
|
|
},
|
|
.while_expr => |w| {
|
|
self.collectClosureShapes(w.condition);
|
|
self.collectClosureShapes(w.body);
|
|
},
|
|
.for_expr => |f| {
|
|
for (f.iterables) |it| {
|
|
self.collectClosureShapes(it.expr);
|
|
if (it.range_end) |re| self.collectClosureShapes(re);
|
|
}
|
|
self.collectClosureShapes(f.body);
|
|
},
|
|
.return_stmt => |r| if (r.value) |v| self.collectClosureShapes(v),
|
|
.raise_stmt => |rs| self.collectClosureShapes(rs.tag),
|
|
.var_decl => |v| if (v.value) |val| self.collectClosureShapes(val),
|
|
.const_decl => |c| self.collectClosureShapes(c.value),
|
|
.destructure_decl => |d| self.collectClosureShapes(d.value),
|
|
.assignment => |a| {
|
|
self.collectClosureShapes(a.target);
|
|
self.collectClosureShapes(a.value);
|
|
},
|
|
.multi_assign => |m| {
|
|
for (m.targets) |t| self.collectClosureShapes(t);
|
|
for (m.values) |v| self.collectClosureShapes(v);
|
|
},
|
|
.call => |c| {
|
|
self.collectClosureShapes(c.callee);
|
|
for (c.args) |a| self.collectClosureShapes(a);
|
|
},
|
|
.binary_op => |b| {
|
|
self.collectClosureShapes(b.lhs);
|
|
self.collectClosureShapes(b.rhs);
|
|
},
|
|
.unary_op => |u| self.collectClosureShapes(u.operand),
|
|
.deref_expr => |d| self.collectClosureShapes(d.operand),
|
|
.force_unwrap => |fu| self.collectClosureShapes(fu.operand),
|
|
.null_coalesce => |nc| {
|
|
self.collectClosureShapes(nc.lhs);
|
|
self.collectClosureShapes(nc.rhs);
|
|
},
|
|
.field_access => |fa| self.collectClosureShapes(fa.object),
|
|
.index_expr => |ix| {
|
|
self.collectClosureShapes(ix.object);
|
|
self.collectClosureShapes(ix.index);
|
|
},
|
|
.spread_expr => |s| self.collectClosureShapes(s.operand),
|
|
.try_expr => |te| self.collectClosureShapes(te.operand),
|
|
.catch_expr => |ce| {
|
|
self.collectClosureShapes(ce.operand);
|
|
self.collectClosureShapes(ce.body);
|
|
},
|
|
.defer_stmt => |d| self.collectClosureShapes(d.expr),
|
|
.push_stmt => |p| {
|
|
self.collectClosureShapes(p.context_expr);
|
|
self.collectClosureShapes(p.body);
|
|
},
|
|
.array_literal => |al| for (al.elements) |el| self.collectClosureShapes(el),
|
|
.tuple_literal => |tl| for (tl.elements) |el| self.collectClosureShapes(el.value),
|
|
else => {},
|
|
}
|
|
}
|
|
};
|