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:
214
src/imports.zig
214
src/imports.zig
@@ -537,6 +537,10 @@ pub const NamespaceTarget = struct {
|
||||
target_module_path: []const u8,
|
||||
own_decls: []const *Node,
|
||||
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`.
|
||||
@@ -615,6 +619,216 @@ pub fn buildImportFacts(
|
||||
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
|
||||
/// explicit name + span. `addOwnDecl` / `addNamespace` return `false` when the
|
||||
/// name is already in this module's scope and drop the second author; without
|
||||
|
||||
Reference in New Issue
Block a user