feat(stdlib/S2.1a): resolver.zig owning pass + ResolvedProgram scaffold + 3 bare-name domains [additive]
Turn src/ir/resolver.zig from a raw author-collection facade into the OWNING resolution pass: one exhaustive recursive AST walk (exhaustive switch over ast.Node.Data with NO else arm, so a new node kind is a compile error here rather than a silently unvisited subtree) populating a ResolvedProgram. - ResolvedProgram: all 10 node-keyed side tables declared as AutoHashMap(*const ast.Node, ResolvedRef) + symbolic TemplateParamId/ PackParamId registries. ResolvedRef is the S2.1 RAW form — collected author identity (AuthorSet, own ∪ flat), NO verdict (own-wins/ambiguity is S2.2). - Populate the 3 bare-name domains (type / value-const / callable heads) via collectVisibleAuthors(.user_bare_flat); record $T / ..$Ts / $pack[i] as SYMBOLIC template/pack refs, never TypeIds. The 7 head/qualified/foreign domains stay declared-but-empty (S2.1b/c own them). - Slot via Compilation.resolveProgram() after the program_index facts are wired and before lowerRoot; ResolvedProgram owned on Compilation, borrowed *ResolvedProgram lent to ProgramIndex (lowerToIR signature unchanged). - Population proof unit test over real Phase A facts: the 3 tables are non-empty, keyed by node identity, and carry symbolic template/pack refs. ADDITIVE / PARALLEL / UNCONSUMED: lowering still reads the OLD selectors, so single-author output is byte-identical. Gate green: zig build; zig build test (425/425, LSP smoke 574 files no crash); run_examples (540 passed, 0 failed, byte-identical incl. FFI 12xx-14xx + 1615 ios-sim); resolver-target (18 xfail unchanged).
This commit is contained in:
@@ -279,3 +279,186 @@ test "resolver: visibility edge-walk — own + flat visible; namespaced-only onl
|
||||
try std.testing.expect(lower.nameVisibleOverEdges(null, &flat, "main", "secret"));
|
||||
try std.testing.expect(lower.nameVisibleOverEdges(&scopes, null, "main", "secret"));
|
||||
}
|
||||
|
||||
// ── the owning resolution pass — ResolvedProgram population proof (S2.1a) ──
|
||||
|
||||
/// Parse + resolve imports + build the raw facts AND the resolved root the pass
|
||||
/// walks (built from `mod.decls`, exactly as `core.zig` does). `alloc` must be an
|
||||
/// arena that outlives the views.
|
||||
const Resolved = struct {
|
||||
root: *ast.Node,
|
||||
decls: imports.ModuleDecls,
|
||||
flat_import_graph: Graph,
|
||||
import_graph: Graph,
|
||||
};
|
||||
|
||||
fn buildResolved(alloc: std.mem.Allocator, io: std.Io, absdir: []const u8, main_path: []const u8) !Resolved {
|
||||
const main_bytes = try std.Io.Dir.readFileAlloc(.cwd(), io, main_path, alloc, .limited(1 << 20));
|
||||
const main_source = try alloc.dupeZ(u8, main_bytes);
|
||||
var p = parser.Parser.init(alloc, main_source);
|
||||
const parsed = p.parse() catch return error.ParseFailed;
|
||||
|
||||
var diags = errors.DiagnosticList.init(alloc, main_source, main_path);
|
||||
var chain = std.StringHashMap(void).init(alloc);
|
||||
var cache = imports.ModuleCache.init(alloc);
|
||||
var import_graph = Graph.init(alloc);
|
||||
var flat_import_graph = Graph.init(alloc);
|
||||
const stdlib_paths = [_][]const u8{};
|
||||
|
||||
const mod = try imports.resolveImports(
|
||||
alloc,
|
||||
io,
|
||||
parsed,
|
||||
absdir,
|
||||
main_path,
|
||||
&chain,
|
||||
&cache,
|
||||
null,
|
||||
&diags,
|
||||
&stdlib_paths,
|
||||
&import_graph,
|
||||
&flat_import_graph,
|
||||
.{},
|
||||
);
|
||||
|
||||
const facts = try imports.buildImportFacts(alloc, main_path, mod, &cache);
|
||||
|
||||
const root = try alloc.create(ast.Node);
|
||||
root.* = .{ .span = parsed.span, .data = .{ .root = .{ .decls = mod.decls } } };
|
||||
|
||||
return .{
|
||||
.root = root,
|
||||
.decls = facts.decls,
|
||||
.flat_import_graph = flat_import_graph,
|
||||
.import_graph = import_graph,
|
||||
};
|
||||
}
|
||||
|
||||
/// Find the top-level `fn_decl` named `name` in the resolved root, or null.
|
||||
fn findFn(root: *const ast.Node, name: []const u8) ?*const ast.Node {
|
||||
for (root.data.root.decls) |d| {
|
||||
if (d.data == .fn_decl and std.mem.eql(u8, d.data.fn_decl.name, name)) return d;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// The pass populates the three bare-name domains over REAL Phase A facts: a type
|
||||
// reference (own struct author), a value/const reference (own const), and a
|
||||
// callable head (flat-imported author). It keys every entry by NODE IDENTITY, and
|
||||
// records generic-param references SYMBOLICALLY — a `$T` template ref and a
|
||||
// `$args[0]` pack ref — never as a collected author. The seven unpopulated domains
|
||||
// stay empty (S2.1b/c own them).
|
||||
test "resolver: resolve — bare-name domains populated, keyed by node, symbolic generic refs" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
const io = testIo();
|
||||
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "lib.sx", .data = "helper :: () -> s64 { 5 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data =
|
||||
\\#import "lib.sx";
|
||||
\\Point :: struct { x: s64 }
|
||||
\\LIMIT :: 10;
|
||||
\\identity :: (x: $T) -> T { x }
|
||||
\\third :: (..$args) -> $args[0] => args[0];
|
||||
\\use_point :: (p: Point) -> s64 { p.x }
|
||||
\\main :: () -> s32 {
|
||||
\\ n := helper();
|
||||
\\ m := LIMIT;
|
||||
\\ _ = n;
|
||||
\\ _ = m;
|
||||
\\ return 0;
|
||||
\\}
|
||||
\\
|
||||
});
|
||||
|
||||
var dirbuf: [4096]u8 = undefined;
|
||||
const absdir = dirbuf[0..try tmp.dir.realPath(io, &dirbuf)];
|
||||
const main_path = try std.fmt.allocPrint(alloc, "{s}/main.sx", .{absdir});
|
||||
|
||||
var prog = try buildResolved(alloc, io, absdir, main_path);
|
||||
|
||||
var idx = ProgramIndex.init(alloc);
|
||||
defer idx.deinit();
|
||||
idx.module_decls = &prog.decls;
|
||||
idx.flat_import_graph = &prog.flat_import_graph;
|
||||
idx.import_graph = &prog.import_graph;
|
||||
|
||||
var rp = resolver.resolve(prog.root, &idx, main_path, alloc);
|
||||
defer rp.deinit();
|
||||
|
||||
// (1) The three bare-name domains are NON-EMPTY.
|
||||
try std.testing.expect(rp.type_refs.count() > 0);
|
||||
try std.testing.expect(rp.value_refs.count() > 0);
|
||||
try std.testing.expect(rp.callable_refs.count() > 0);
|
||||
|
||||
// (2) Keyed by NODE IDENTITY: the `Point` type reference is keyed by the exact
|
||||
// `use_point` param type-expr node, and resolves RAW to the own struct.
|
||||
const use_point = findFn(prog.root, "use_point") orelse return error.MissingFn;
|
||||
const point_te = use_point.data.fn_decl.params[0].type_expr;
|
||||
const point_ref = rp.type_refs.get(point_te) orelse return error.PointNotKeyed;
|
||||
try std.testing.expect(point_ref == .authors);
|
||||
try std.testing.expect(point_ref.authors.own != null);
|
||||
try std.testing.expectEqual(
|
||||
std.meta.Tag(resolver.RawDeclRef).struct_decl,
|
||||
std.meta.activeTag(point_ref.authors.own.?.raw),
|
||||
);
|
||||
|
||||
// (3) A value/const reference (`LIMIT`) collected RAW to its own author, and a
|
||||
// callable head (`helper`) collected RAW to its FLAT-imported author.
|
||||
var saw_limit = false;
|
||||
var vit = rp.value_refs.iterator();
|
||||
while (vit.next()) |e| {
|
||||
const k = e.key_ptr.*;
|
||||
if (k.data == .identifier and std.mem.eql(u8, k.data.identifier.name, "LIMIT")) {
|
||||
try std.testing.expect(e.value_ptr.* == .authors);
|
||||
try std.testing.expect(e.value_ptr.authors.own != null);
|
||||
saw_limit = true;
|
||||
}
|
||||
}
|
||||
try std.testing.expect(saw_limit);
|
||||
|
||||
var saw_helper = false;
|
||||
var cit = rp.callable_refs.iterator();
|
||||
while (cit.next()) |e| {
|
||||
const k = e.key_ptr.*;
|
||||
try std.testing.expect(k.data == .identifier); // callable heads key bare-name callees
|
||||
if (std.mem.eql(u8, k.data.identifier.name, "helper")) {
|
||||
try std.testing.expect(e.value_ptr.* == .authors);
|
||||
try std.testing.expect(e.value_ptr.authors.flat.len == 1); // authored only in lib.sx
|
||||
saw_helper = true;
|
||||
}
|
||||
}
|
||||
try std.testing.expect(saw_helper);
|
||||
|
||||
// (4) Generic-param references are SYMBOLIC, not authors: a `$T` template ref
|
||||
// and a `$args[0]` pack ref, each backed by a registry entry.
|
||||
try std.testing.expect(rp.template_params.items.len > 0);
|
||||
try std.testing.expect(rp.pack_params.items.len > 0);
|
||||
|
||||
var saw_template = false;
|
||||
var tit = rp.type_refs.iterator();
|
||||
while (tit.next()) |e| {
|
||||
if (e.value_ptr.* == .template) saw_template = true;
|
||||
}
|
||||
try std.testing.expect(saw_template);
|
||||
|
||||
// The `$args[0]` return type is a pack ref keyed by its pack-index node.
|
||||
const third = findFn(prog.root, "third") orelse return error.MissingFn;
|
||||
const ret_pack = third.data.fn_decl.return_type orelse return error.NoReturnType;
|
||||
const pack_ref = rp.type_refs.get(ret_pack) orelse return error.PackNotKeyed;
|
||||
try std.testing.expect(pack_ref == .pack);
|
||||
try std.testing.expectEqual(@as(?u32, 0), pack_ref.pack.index);
|
||||
|
||||
// (5) The seven domains S2.1b/c own stay EMPTY — S2.1a is parallel/unconsumed.
|
||||
try std.testing.expectEqual(@as(u32, 0), rp.namespace_refs.count());
|
||||
try std.testing.expectEqual(@as(u32, 0), rp.generic_struct_heads.count());
|
||||
try std.testing.expectEqual(@as(u32, 0), rp.type_fn_heads.count());
|
||||
try std.testing.expectEqual(@as(u32, 0), rp.protocol_heads.count());
|
||||
try std.testing.expectEqual(@as(u32, 0), rp.foreign_class_refs.count());
|
||||
try std.testing.expectEqual(@as(u32, 0), rp.struct_const_refs.count());
|
||||
try std.testing.expectEqual(@as(u32, 0), rp.ufcs_refs.count());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user