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:
12
src/core.zig
12
src/core.zig
@@ -38,6 +38,10 @@ pub const Compilation = struct {
|
|||||||
/// Namespace import edges (`importer → alias → NamespaceTarget`), built by
|
/// Namespace import edges (`importer → alias → NamespaceTarget`), built by
|
||||||
/// `imports.buildImportFacts`. Borrowed by `ProgramIndex.namespace_edges`.
|
/// `imports.buildImportFacts`. Borrowed by `ProgramIndex.namespace_edges`.
|
||||||
namespace_edges: imports.NamespaceEdges,
|
namespace_edges: imports.NamespaceEdges,
|
||||||
|
/// Stable `DeclId` for every declaration (Fork C S1), built by
|
||||||
|
/// `imports.buildDeclTable` in parallel with the import facts. Borrowed by
|
||||||
|
/// `ProgramIndex.decl_table`.
|
||||||
|
decl_table: imports.DeclTable,
|
||||||
ir_emitter: ?ir.LLVMEmitter = null,
|
ir_emitter: ?ir.LLVMEmitter = null,
|
||||||
/// Lowered IR module, kept alive past `generateCode` so post-link
|
/// Lowered IR module, kept alive past `generateCode` so post-link
|
||||||
/// callbacks can re-enter the interpreter to invoke sx functions
|
/// callbacks can re-enter the interpreter to invoke sx functions
|
||||||
@@ -68,6 +72,7 @@ pub const Compilation = struct {
|
|||||||
.module_fns = imports.ModuleFns.init(allocator),
|
.module_fns = imports.ModuleFns.init(allocator),
|
||||||
.module_decls = imports.ModuleDecls.init(allocator),
|
.module_decls = imports.ModuleDecls.init(allocator),
|
||||||
.namespace_edges = imports.NamespaceEdges.init(allocator),
|
.namespace_edges = imports.NamespaceEdges.init(allocator),
|
||||||
|
.decl_table = imports.DeclTable.init(allocator),
|
||||||
.target_config = target_config,
|
.target_config = target_config,
|
||||||
.stdlib_paths = stdlib_paths,
|
.stdlib_paths = stdlib_paths,
|
||||||
};
|
};
|
||||||
@@ -143,6 +148,12 @@ pub const Compilation = struct {
|
|||||||
self.module_decls = facts.decls;
|
self.module_decls = facts.decls;
|
||||||
self.namespace_edges = facts.ns_edges;
|
self.namespace_edges = facts.ns_edges;
|
||||||
|
|
||||||
|
// DeclTable (Fork C S1): a stable DeclId for every declaration, built in
|
||||||
|
// parallel from the SAME modules. Additive — nothing consumes it for
|
||||||
|
// selection yet, so generated IR + bytes are unchanged. Updates
|
||||||
|
// `namespace_edges` in place to record each target's member ids.
|
||||||
|
self.decl_table = try imports.buildDeclTable(self.allocator, self.file_path, mod, &cache, &self.module_decls, &self.namespace_edges);
|
||||||
|
|
||||||
// Store main file source in import_sources so error reporting can find it
|
// Store main file source in import_sources so error reporting can find it
|
||||||
self.import_sources.put(self.file_path, self.source) catch {};
|
self.import_sources.put(self.file_path, self.source) catch {};
|
||||||
|
|
||||||
@@ -312,6 +323,7 @@ pub const Compilation = struct {
|
|||||||
lowering.program_index.module_fns = &self.module_fns;
|
lowering.program_index.module_fns = &self.module_fns;
|
||||||
lowering.program_index.module_decls = &self.module_decls;
|
lowering.program_index.module_decls = &self.module_decls;
|
||||||
lowering.program_index.namespace_edges = &self.namespace_edges;
|
lowering.program_index.namespace_edges = &self.namespace_edges;
|
||||||
|
lowering.program_index.decl_table = &self.decl_table;
|
||||||
lowering.lowerRoot(root);
|
lowering.lowerRoot(root);
|
||||||
if (self.diagnostics.hasErrors()) return error.CompileError;
|
if (self.diagnostics.hasErrors()) return error.CompileError;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
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);
|
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);
|
||||||
|
}
|
||||||
|
|||||||
214
src/imports.zig
214
src/imports.zig
@@ -537,6 +537,10 @@ pub const NamespaceTarget = struct {
|
|||||||
target_module_path: []const u8,
|
target_module_path: []const u8,
|
||||||
own_decls: []const *Node,
|
own_decls: []const *Node,
|
||||||
is_pub: bool = false,
|
is_pub: bool = false,
|
||||||
|
/// The `DeclId` of each member in `own_decls`, in slice order. Filled by
|
||||||
|
/// `buildDeclTable` (empty until then). Lets a member be addressed by stable
|
||||||
|
/// id without re-deriving it from the node pointer.
|
||||||
|
member_ids: []const DeclId = &.{},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// `importer_source → alias → NamespaceTarget`.
|
/// `importer_source → alias → NamespaceTarget`.
|
||||||
@@ -615,6 +619,216 @@ pub fn buildImportFacts(
|
|||||||
return .{ .decls = decls, .ns_edges = ns_edges };
|
return .{ .decls = decls, .ns_edges = ns_edges };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── DeclTable: a stable DeclId for every declaration (Fork C S1, additive) ──
|
||||||
|
//
|
||||||
|
// `buildDeclTable` lifts every `RawDeclRef` the import facts hold into a stable
|
||||||
|
// `DeclId` carrying source + name + AST node identity + span + `DeclKind`. It is
|
||||||
|
// built in PARALLEL with the old maps and nothing in lowering consumes it for
|
||||||
|
// selection yet (S4 makes it the fact-store key), so generated IR + bytes are
|
||||||
|
// unchanged by construction.
|
||||||
|
|
||||||
|
/// The taxonomy of a declaration, mirroring the `RawDeclRef` variants so a
|
||||||
|
/// `DeclTable` row carries its kind without re-switching on the AST node.
|
||||||
|
pub const DeclKind = enum {
|
||||||
|
function,
|
||||||
|
constant,
|
||||||
|
@"struct",
|
||||||
|
@"enum",
|
||||||
|
@"union",
|
||||||
|
error_set,
|
||||||
|
protocol,
|
||||||
|
foreign_class,
|
||||||
|
namespace,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn declKindOf(ref: RawDeclRef) DeclKind {
|
||||||
|
return switch (ref) {
|
||||||
|
.fn_decl => .function,
|
||||||
|
.const_decl => .constant,
|
||||||
|
.struct_decl => .@"struct",
|
||||||
|
.enum_decl => .@"enum",
|
||||||
|
.union_decl => .@"union",
|
||||||
|
.error_set_decl => .error_set,
|
||||||
|
.protocol_decl => .protocol,
|
||||||
|
.foreign_class_decl => .foreign_class,
|
||||||
|
.namespace_decl => .namespace,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The AST node identity a `RawDeclRef` wraps — the inner decl pointer every
|
||||||
|
/// variant holds (the same identity `resolver.zig` selects authors by). This is
|
||||||
|
/// the key the `DeclTable` indexes and round-trips on.
|
||||||
|
pub fn authorNodePtrOf(ref: RawDeclRef) usize {
|
||||||
|
return switch (ref) {
|
||||||
|
inline else => |p| @intFromPtr(p),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `*const ast.StructDecl` a top-level decl node carries, or null when it is
|
||||||
|
/// not a struct — a bare `struct_decl` or a `const_decl` whose value is one,
|
||||||
|
/// both unwrapping to the same inner decl (mirrors lower's `structDeclOfRaw`).
|
||||||
|
fn structDeclPtrOf(decl: *const Node) ?*const ast.StructDecl {
|
||||||
|
return switch (decl.data) {
|
||||||
|
.struct_decl => &decl.data.struct_decl,
|
||||||
|
.const_decl => |cd| if (cd.value.data == .struct_decl) &cd.value.data.struct_decl else null,
|
||||||
|
else => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A stable identifier for one declaration, assigned by `DeclTable` in module-
|
||||||
|
/// walk order. Process-local: it indexes the table's `entries` (S5 stabilizes it
|
||||||
|
/// to `(source, index)` for the LSP, per the deep-dive's R5).
|
||||||
|
pub const DeclId = enum(u32) { _ };
|
||||||
|
|
||||||
|
/// One `DeclTable` row: a `RawDeclRef` lifted to a stable `DeclId`, with its
|
||||||
|
/// authoring source path, display name, AST span, and `DeclKind`. `ref` is the
|
||||||
|
/// same raw author the import facts hold (its AST node identity is `id`'s key).
|
||||||
|
pub const DeclInfo = struct {
|
||||||
|
id: DeclId,
|
||||||
|
source: []const u8,
|
||||||
|
name: []const u8,
|
||||||
|
ref: RawDeclRef,
|
||||||
|
span: ast.Span,
|
||||||
|
kind: DeclKind,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Stable `DeclId` for every source / namespaced / imported / C-imported decl.
|
||||||
|
/// `entries` is indexed by `DeclId`; `by_node` reverse-maps the AST node
|
||||||
|
/// identity (`authorNodePtrOf`) to its id; `by_struct` maps a generic struct's
|
||||||
|
/// inner `*StructDecl` to its id (so a template registered during lowering can
|
||||||
|
/// be keyed by `DeclId`). Borrowed by `ProgramIndex.decl_table`.
|
||||||
|
pub const DeclTable = struct {
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
entries: std.ArrayList(DeclInfo) = .empty,
|
||||||
|
by_node: std.AutoHashMap(usize, DeclId),
|
||||||
|
by_struct: std.AutoHashMap(usize, DeclId),
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator) DeclTable {
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
.by_node = std.AutoHashMap(usize, DeclId).init(alloc),
|
||||||
|
.by_struct = std.AutoHashMap(usize, DeclId).init(alloc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *DeclTable) void {
|
||||||
|
self.entries.deinit(self.alloc);
|
||||||
|
self.by_node.deinit();
|
||||||
|
self.by_struct.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: *const DeclTable, id: DeclId) DeclInfo {
|
||||||
|
return self.entries.items[@intFromEnum(id)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `DeclId` for an AST node (by its `RawDeclRef` identity), or null when
|
||||||
|
/// the node never entered the table.
|
||||||
|
pub fn declIdForRef(self: *const DeclTable, ref: RawDeclRef) ?DeclId {
|
||||||
|
return self.by_node.get(authorNodePtrOf(ref));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `DeclId` for a generic struct template's inner `*StructDecl`, or null.
|
||||||
|
pub fn declIdForStructDecl(self: *const DeclTable, sd: *const ast.StructDecl) ?DeclId {
|
||||||
|
return self.by_struct.get(@intFromPtr(sd));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Intern one top-level decl node, returning its (possibly pre-existing)
|
||||||
|
/// `DeclId`. First-wins / diamond dedup by node identity, matching how the
|
||||||
|
/// scalar import facts dedup. The caller guarantees `rawDeclRefOf(decl)` is
|
||||||
|
/// non-null (so `declName` is too).
|
||||||
|
fn intern(self: *DeclTable, source: []const u8, decl: *const Node) !DeclId {
|
||||||
|
const ref = rawDeclRefOf(decl).?;
|
||||||
|
const key = authorNodePtrOf(ref);
|
||||||
|
if (self.by_node.get(key)) |existing| return existing;
|
||||||
|
const id: DeclId = @enumFromInt(@as(u32, @intCast(self.entries.items.len)));
|
||||||
|
try self.entries.append(self.alloc, .{
|
||||||
|
.id = id,
|
||||||
|
.source = source,
|
||||||
|
.name = decl.data.declName().?,
|
||||||
|
.ref = ref,
|
||||||
|
.span = decl.span,
|
||||||
|
.kind = declKindOf(ref),
|
||||||
|
});
|
||||||
|
try self.by_node.put(key, id);
|
||||||
|
if (structDeclPtrOf(decl)) |sd| try self.by_struct.put(@intFromPtr(sd), id);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn internModule(self: *DeclTable, source: []const u8, own_decls: []const *Node) !void {
|
||||||
|
for (own_decls) |decl| {
|
||||||
|
if (rawDeclRefOf(decl) == null) continue;
|
||||||
|
_ = try self.intern(source, decl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Debug cross-check (S1.1 acceptance): every `RawDeclRef` the import facts
|
||||||
|
/// hold round-trips `RawDeclRef → DeclId → AST node ptr` back to the same
|
||||||
|
/// node, with matching name. Asserts; call only under `builtin.mode == .Debug`.
|
||||||
|
pub fn verifyRoundTrip(self: *const DeclTable, decls: *const ModuleDecls, ns_edges: *const NamespaceEdges) void {
|
||||||
|
var mit = decls.iterator();
|
||||||
|
while (mit.next()) |m| {
|
||||||
|
var nit = m.value_ptr.names.iterator();
|
||||||
|
while (nit.next()) |kv| {
|
||||||
|
const ref = kv.value_ptr.*;
|
||||||
|
const id = self.declIdForRef(ref) orelse @panic("DeclTable round-trip: module ref has no DeclId");
|
||||||
|
const info = self.get(id);
|
||||||
|
std.debug.assert(authorNodePtrOf(info.ref) == authorNodePtrOf(ref));
|
||||||
|
std.debug.assert(std.mem.eql(u8, info.name, kv.key_ptr.*));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var nsit = ns_edges.iterator();
|
||||||
|
while (nsit.next()) |imp| {
|
||||||
|
var ait = imp.value_ptr.valueIterator();
|
||||||
|
while (ait.next()) |target| {
|
||||||
|
for (target.own_decls) |decl| {
|
||||||
|
const ref = rawDeclRefOf(decl) orelse continue;
|
||||||
|
const id = self.declIdForRef(ref) orelse @panic("DeclTable round-trip: ns member has no DeclId");
|
||||||
|
std.debug.assert(authorNodePtrOf(self.get(id).ref) == authorNodePtrOf(ref));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Build the `DeclTable` from the resolved program + the import facts: every
|
||||||
|
/// module author (main + cache) interned first, then every namespace member
|
||||||
|
/// (reusing the module author's id when it is also a module decl, minting a new
|
||||||
|
/// id for a synthetic C-import member). `ns_edges` is updated in place so each
|
||||||
|
/// `NamespaceTarget.member_ids` lists its members' ids. Built from the SAME
|
||||||
|
/// modules `buildImportFacts` walks; no IR lowering required.
|
||||||
|
pub fn buildDeclTable(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
main_path: []const u8,
|
||||||
|
main_mod: ResolvedModule,
|
||||||
|
cache: *const ModuleCache,
|
||||||
|
decls: *const ModuleDecls,
|
||||||
|
ns_edges: *NamespaceEdges,
|
||||||
|
) !DeclTable {
|
||||||
|
var table = DeclTable.init(allocator);
|
||||||
|
try table.internModule(main_path, main_mod.own_decls);
|
||||||
|
var it = cache.iterator();
|
||||||
|
while (it.next()) |entry| {
|
||||||
|
try table.internModule(entry.key_ptr.*, entry.value_ptr.own_decls);
|
||||||
|
}
|
||||||
|
|
||||||
|
var nsit = ns_edges.iterator();
|
||||||
|
while (nsit.next()) |imp| {
|
||||||
|
var ait = imp.value_ptr.valueIterator();
|
||||||
|
while (ait.next()) |target| {
|
||||||
|
var ids = std.ArrayList(DeclId).empty;
|
||||||
|
for (target.own_decls) |decl| {
|
||||||
|
if (rawDeclRefOf(decl) == null) continue;
|
||||||
|
const id = try table.intern(target.target_module_path, decl);
|
||||||
|
try ids.append(allocator, id);
|
||||||
|
}
|
||||||
|
target.member_ids = try ids.toOwnedSlice(allocator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (builtin.mode == .Debug) table.verifyRoundTrip(decls, ns_edges);
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
/// Surface a same-module duplicate top-level declaration as a hard error at an
|
/// Surface a same-module duplicate top-level declaration as a hard error at an
|
||||||
/// explicit name + span. `addOwnDecl` / `addNamespace` return `false` when the
|
/// explicit name + span. `addOwnDecl` / `addNamespace` return `false` when the
|
||||||
/// name is already in this module's scope and drop the second author; without
|
/// name is already in this module's scope and drop the second author; without
|
||||||
|
|||||||
@@ -15487,6 +15487,17 @@ pub const Lowering = struct {
|
|||||||
const tmpl = self.buildGenericStructTemplate(sd, source_file) orelse return;
|
const tmpl = self.buildGenericStructTemplate(sd, source_file) orelse return;
|
||||||
self.program_index.struct_template_map.put(tmpl.name, tmpl) catch {};
|
self.program_index.struct_template_map.put(tmpl.name, tmpl) catch {};
|
||||||
|
|
||||||
|
// S1.1 (additive): key the template by DeclId in parallel. Nothing
|
||||||
|
// reads this for selection yet; `struct_template_map` stays the live
|
||||||
|
// consumer. A template whose decl is not in the table (comptime /
|
||||||
|
// block-local registration with facts unwired) keeps only the
|
||||||
|
// name-keyed entry.
|
||||||
|
if (self.program_index.decl_table) |dt| {
|
||||||
|
if (dt.declIdForStructDecl(sd)) |id| {
|
||||||
|
self.program_index.struct_template_by_decl.put(id, tmpl) catch {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Register methods under "TemplateName.method" in fn_ast_map
|
// Register methods under "TemplateName.method" in fn_ast_map
|
||||||
for (sd.methods) |method_node| {
|
for (sd.methods) |method_node| {
|
||||||
if (method_node.data == .fn_decl) {
|
if (method_node.data == .fn_decl) {
|
||||||
|
|||||||
@@ -628,6 +628,10 @@ pub const ProgramIndex = struct {
|
|||||||
/// `imports.buildImportFacts`, carrying each alias's resolved target path.
|
/// `imports.buildImportFacts`, carrying each alias's resolved target path.
|
||||||
/// Borrowed view.
|
/// Borrowed view.
|
||||||
namespace_edges: ?*imports.NamespaceEdges = null,
|
namespace_edges: ?*imports.NamespaceEdges = null,
|
||||||
|
/// Stable `DeclId` for every declaration, built by `imports.buildDeclTable`
|
||||||
|
/// in parallel with the import facts. Borrowed view; nothing in lowering
|
||||||
|
/// consumes it for selection yet (additive — S4 makes it the fact-store key).
|
||||||
|
decl_table: ?*imports.DeclTable = null,
|
||||||
|
|
||||||
// ── Declaration maps ──
|
// ── Declaration maps ──
|
||||||
/// Function name → AST decl.
|
/// Function name → AST decl.
|
||||||
@@ -649,6 +653,11 @@ pub const ProgramIndex = struct {
|
|||||||
type_alias_map: std.StringHashMap(TypeId) = std.StringHashMap(TypeId).init(std.heap.page_allocator),
|
type_alias_map: std.StringHashMap(TypeId) = std.StringHashMap(TypeId).init(std.heap.page_allocator),
|
||||||
/// Generic struct name → template.
|
/// Generic struct name → template.
|
||||||
struct_template_map: std.StringHashMap(StructTemplate) = std.StringHashMap(StructTemplate).init(std.heap.page_allocator),
|
struct_template_map: std.StringHashMap(StructTemplate) = std.StringHashMap(StructTemplate).init(std.heap.page_allocator),
|
||||||
|
/// `DeclId` → generic struct template — the DeclId-keyed analogue of
|
||||||
|
/// `struct_template_map`, built in parallel during `registerStructDecl`.
|
||||||
|
/// Nothing reads it for selection yet; `struct_template_map` stays the live
|
||||||
|
/// consumer until the S4 cutover.
|
||||||
|
struct_template_by_decl: std.AutoHashMap(imports.DeclId, StructTemplate) = std.AutoHashMap(imports.DeclId, StructTemplate).init(std.heap.page_allocator),
|
||||||
/// Protocol name → protocol info.
|
/// Protocol name → protocol info.
|
||||||
protocol_decl_map: std.StringHashMap(ProtocolDeclInfo) = std.StringHashMap(ProtocolDeclInfo).init(std.heap.page_allocator),
|
protocol_decl_map: std.StringHashMap(ProtocolDeclInfo) = std.StringHashMap(ProtocolDeclInfo).init(std.heap.page_allocator),
|
||||||
/// Protocol name → AST node.
|
/// Protocol name → AST node.
|
||||||
@@ -687,7 +696,7 @@ pub const ProgramIndex = struct {
|
|||||||
|
|
||||||
pub fn deinit(self: *ProgramIndex) void {
|
pub fn deinit(self: *ProgramIndex) void {
|
||||||
// Owned maps only — module_scopes / import_graph / flat_import_graph /
|
// Owned maps only — module_scopes / import_graph / flat_import_graph /
|
||||||
// module_fns / module_decls / namespace_edges are borrowed.
|
// module_fns / module_decls / namespace_edges / decl_table are borrowed.
|
||||||
self.import_flags.deinit();
|
self.import_flags.deinit();
|
||||||
self.fn_ast_map.deinit();
|
self.fn_ast_map.deinit();
|
||||||
self.qualified_fn_source.deinit();
|
self.qualified_fn_source.deinit();
|
||||||
@@ -695,6 +704,7 @@ pub const ProgramIndex = struct {
|
|||||||
self.global_names.deinit();
|
self.global_names.deinit();
|
||||||
self.type_alias_map.deinit();
|
self.type_alias_map.deinit();
|
||||||
self.struct_template_map.deinit();
|
self.struct_template_map.deinit();
|
||||||
|
self.struct_template_by_decl.deinit();
|
||||||
self.protocol_decl_map.deinit();
|
self.protocol_decl_map.deinit();
|
||||||
self.protocol_ast_map.deinit();
|
self.protocol_ast_map.deinit();
|
||||||
self.module_const_map.deinit();
|
self.module_const_map.deinit();
|
||||||
|
|||||||
Reference in New Issue
Block a user