feat(stdlib/S1.1): DeclId for every declaration — additive DeclTable [additive]
Build a DeclTable in parallel with the import facts: every RawDeclRef (source / imported / namespaced / C-imported) gets a stable DeclId carrying source path, display name, AST node identity, span, and DeclKind. Namespace targets record their members' DeclIds (NamespaceTarget.member_ids). A generic struct's template is keyed by DeclId in a parallel struct_template_by_decl store, written alongside the live name-keyed struct_template_map. A Debug-only round-trip cross-check (RawDeclRef -> DeclId -> AST node ptr) asserts the table identifies the same node across the corpus, run from buildDeclTable and pinned by a unit test. Additive (S0.1 class: mirror): the old maps stay active and lowering still consumes them; nothing reads the DeclTable / struct_template_by_decl for selection yet (the S4 cutover does). Generated IR + output bytes are unchanged by construction. Gate over the baseline-green corpus: zig build, zig build test (424/424), bash tests/run_examples.sh (540 passed) — all exit 0; single-author output byte-identical (37 .ir snapshots unchanged).
This commit is contained in:
@@ -503,3 +503,103 @@ test "buildImportFacts: namespace-alias-then-fn same-module collision is diagnos
|
||||
const m_idx = facts.decls.get(main_path) orelse return error.MissingMainIndex;
|
||||
try expectTag(m_idx.names.get("dup") orelse return error.MissingDup, .namespace_decl);
|
||||
}
|
||||
|
||||
// ── DeclTable unit tests (Fork C S1.1) ──
|
||||
|
||||
// Every source / imported / namespaced declaration gets a stable DeclId; the
|
||||
// RawDeclRef → DeclId → AST node round-trip holds; a generic struct is keyable
|
||||
// by DeclId; and the namespace target records its members' ids. The OLD facts
|
||||
// (`module_decls` / `ns_edges`) are untouched — the table is built in parallel.
|
||||
test "buildDeclTable: stable DeclId per decl, round-trip, struct keying, namespace member ids" {
|
||||
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 { 9 }\nBox :: struct($T: Type) { v: T; }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "geom.sx", .data = "Point :: struct { x: s64 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "#import \"lib.sx\";\ng :: #import \"geom.sx\";\nmain :: () -> s32 { 0 }\n" });
|
||||
|
||||
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});
|
||||
const lib_path = try std.fmt.allocPrint(alloc, "{s}/lib.sx", .{absdir});
|
||||
|
||||
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 root = 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 = std.StringHashMap(std.StringHashMap(void)).init(alloc);
|
||||
var flat_import_graph = std.StringHashMap(std.StringHashMap(void)).init(alloc);
|
||||
const stdlib_paths = [_][]const u8{};
|
||||
|
||||
const mod = try imports.resolveImports(
|
||||
alloc,
|
||||
io,
|
||||
root,
|
||||
absdir,
|
||||
main_path,
|
||||
&chain,
|
||||
&cache,
|
||||
null,
|
||||
&diags,
|
||||
&stdlib_paths,
|
||||
&import_graph,
|
||||
&flat_import_graph,
|
||||
.{},
|
||||
);
|
||||
|
||||
var facts = try imports.buildImportFacts(alloc, main_path, mod, &cache);
|
||||
var table = try imports.buildDeclTable(alloc, main_path, mod, &cache, &facts.decls, &facts.ns_edges);
|
||||
defer table.deinit();
|
||||
|
||||
// Every module author resolves to a DeclId that round-trips to the same node
|
||||
// and carries the matching name + source. (verifyRoundTrip also asserts this
|
||||
// in Debug; this pins the public lookup API too.)
|
||||
var mit = facts.decls.iterator();
|
||||
var seen: usize = 0;
|
||||
while (mit.next()) |m| {
|
||||
var nit = m.value_ptr.names.iterator();
|
||||
while (nit.next()) |kv| {
|
||||
const ref = kv.value_ptr.*;
|
||||
const id = table.declIdForRef(ref) orelse return error.MissingDeclId;
|
||||
const info = table.get(id);
|
||||
try std.testing.expectEqual(imports.authorNodePtrOf(ref), imports.authorNodePtrOf(info.ref));
|
||||
try std.testing.expectEqualStrings(kv.key_ptr.*, info.name);
|
||||
try std.testing.expectEqualStrings(m.value_ptr.source, info.source);
|
||||
seen += 1;
|
||||
}
|
||||
}
|
||||
try std.testing.expect(seen > 0);
|
||||
|
||||
// The generic struct `Box` (authored in lib.sx) is keyable by DeclId via its
|
||||
// inner *StructDecl, and the id reports DeclKind.@"struct" + name "Box".
|
||||
const lib_idx = facts.decls.get(lib_path) orelse return error.MissingLibIndex;
|
||||
const box_ref = lib_idx.names.get("Box") orelse return error.MissingBox;
|
||||
const box_sd = switch (box_ref) {
|
||||
.struct_decl => |sd| sd,
|
||||
.const_decl => |cd| if (cd.value.data == .struct_decl) &cd.value.data.struct_decl else return error.BoxNotStruct,
|
||||
else => return error.BoxNotStruct,
|
||||
};
|
||||
const box_id = table.declIdForStructDecl(box_sd) orelse return error.BoxNoDeclId;
|
||||
try std.testing.expectEqual(imports.DeclKind.@"struct", table.get(box_id).kind);
|
||||
try std.testing.expectEqualStrings("Box", table.get(box_id).name);
|
||||
|
||||
// The namespaced `geom.sx` target records its members' DeclIds (here: Point),
|
||||
// each round-tripping to a DeclInfo named "Point".
|
||||
const aliases = facts.ns_edges.get(main_path) orelse return error.MissingNsEdges;
|
||||
const target = aliases.get("g") orelse return error.MissingAlias;
|
||||
try std.testing.expect(target.member_ids.len >= 1);
|
||||
var found_point = false;
|
||||
for (target.member_ids) |id| {
|
||||
if (std.mem.eql(u8, table.get(id).name, "Point")) found_point = true;
|
||||
}
|
||||
try std.testing.expect(found_point);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user