feat(stdlib/S2.1b): namespace-qualified + 3 head domains on the owning pass [additive]
On the S2.1a exhaustive traversal, populate four more ResolvedProgram side tables, still RAW / PARALLEL / UNCONSUMED: - namespace-qualified references: an `alias.member` field_access whose base alias is a NamespaceEdges[ambient_source] target resolves via collectNamespaceAuthors into namespace_refs, keyed by the access node. - the three HEAD domains at parameterized_type_expr heads, binned by the resolved author's decl kind: a struct with type params -> generic_struct_heads, a fn/const-wrapped fn with type params -> type_fn_heads, a protocol -> protocol_heads. RAW: the whole author set is recorded with no winner picked; a name authored as >1 head kind lands a distinct entry in every matching table. Lowering still reads the old selectors and resolved_program has no consumer, so generated output is byte-identical. ResolvedRef stays RAW (selection is S2.2); generics stay symbolic. S2.1c (foreign-class / struct-const / UFCS) owns the remaining three tables. Extends the population proof: a resolver unit test asserting all four tables are non-empty + node-keyed with the expected RAW authors. Gate (all exit 0): zig build; zig build test (All 427 mod + exe + LSP sweep 574); tests/run_examples.sh (540 passed, byte-identical); tests/resolver-target (18 xfail, 0 leaked); m3te ios-sim via the main sx binary.
This commit is contained in:
@@ -184,9 +184,11 @@ fn containsAuthor(list: []const RawAuthor, b: RawAuthor) bool {
|
||||
//
|
||||
// S2.1a populates the three BARE-NAME domains (type / value-const / callable) via
|
||||
// `collectVisibleAuthors`, and records generic-param references ($T, ..$Ts,
|
||||
// $pack[i]) SYMBOLICALLY (template/pack ids, never TypeIds). The remaining seven
|
||||
// tables are DECLARED but stay empty until S2.1b (namespace-qualified + the three
|
||||
// head domains) and S2.1c (foreign-class / struct-const / UFCS) populate them.
|
||||
// $pack[i]) SYMBOLICALLY (template/pack ids, never TypeIds). S2.1b adds the
|
||||
// namespace-qualified table (`alias.member` resolved via `collectNamespaceAuthors`)
|
||||
// and the three HEAD tables (generic-struct / type-fn / protocol), binned by the
|
||||
// resolved author's decl kind at `parameterized_type_expr` heads. The remaining
|
||||
// three tables (foreign-class / struct-const / UFCS) stay empty until S2.1c.
|
||||
|
||||
/// A symbolic id for one enclosing generic TYPE/VALUE param (`$T`, `$N`), assigned
|
||||
/// by the pass and indexing `ResolvedProgram.template_params`. Process-local.
|
||||
@@ -375,6 +377,34 @@ fn lookupGeneric(scope: ?*const Frame, name: []const u8) ?GenericMatch {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Bin ONE raw author by the head kind it can author: a struct with type params (a
|
||||
/// generic-struct head), a fn / const-wrapped fn with type params (a type-function
|
||||
/// head), or a protocol. The `type_params.len > 0` gate is the head test — a
|
||||
/// non-generic struct or a zero-type-param fn authors no head kind and sets
|
||||
/// nothing. The `const_decl` arm unwraps a `Name :: struct/fn(...)` const exactly
|
||||
/// as `structDeclOfRaw` / `fnDeclOfRaw` do.
|
||||
fn classifyHeadKind(raw: RawDeclRef, gs: *bool, tf: *bool, pr: *bool) void {
|
||||
switch (raw) {
|
||||
.struct_decl => |sd| if (sd.type_params.len > 0) {
|
||||
gs.* = true;
|
||||
},
|
||||
.fn_decl => |fd| if (fd.type_params.len > 0) {
|
||||
tf.* = true;
|
||||
},
|
||||
.const_decl => |cd| switch (cd.value.data) {
|
||||
.struct_decl => |*sd| if (sd.type_params.len > 0) {
|
||||
gs.* = true;
|
||||
},
|
||||
.fn_decl => |*fd| if (fd.type_params.len > 0) {
|
||||
tf.* = true;
|
||||
},
|
||||
else => {},
|
||||
},
|
||||
.protocol_decl => pr.* = true,
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// The single owning traversal. Holds the author collector + the `ResolvedProgram`
|
||||
/// it populates; threads `Ctx` (ambient source + generic scope) down the tree.
|
||||
const ResolvePass = struct {
|
||||
@@ -464,10 +494,17 @@ const ResolvePass = struct {
|
||||
self.visitAll(c.args, here);
|
||||
},
|
||||
.field_access => |*fa| {
|
||||
// namespace-qualified / struct-const / UFCS receivers are
|
||||
// S2.1b/c — a BARE identifier receiver is left unclassified here;
|
||||
// a compound receiver is recursed so its inner refs are collected.
|
||||
if (fa.object.data != .identifier) self.visit(fa.object, here);
|
||||
// `alias.member` whose base alias is a namespace import edge of the
|
||||
// ambient source resolves via `collectNamespaceAuthors` into the
|
||||
// namespace-qualified table (S2.1b). A non-alias bare receiver
|
||||
// (struct-const / UFCS / local value) stays unclassified — S2.1c —
|
||||
// and is not walked as a value ref; a compound receiver is recursed
|
||||
// so its inner refs are collected.
|
||||
if (fa.object.data == .identifier) {
|
||||
self.classifyNamespaceQualified(node, fa.object.data.identifier.name, fa.field, here.source);
|
||||
} else {
|
||||
self.visit(fa.object, here);
|
||||
}
|
||||
},
|
||||
.pack_index_type_expr => |*p| self.recordPack(&self.out.type_refs, node, p.pack_name, p.index, here.scope),
|
||||
.comptime_pack_ref => |*p| self.recordPack(&self.out.value_refs, node, p.pack_name, null, here.scope),
|
||||
@@ -475,8 +512,10 @@ const ResolvePass = struct {
|
||||
if (e.name) |name| self.recordAuthors(&self.out.type_refs, node, name, here.source);
|
||||
},
|
||||
.parameterized_type_expr => |*p| {
|
||||
// the head (generic-struct / type-fn / protocol) is S2.1b; the
|
||||
// type args are ordinary references, collected now.
|
||||
// the head (`Name(args)`) is binned by its resolved author's decl
|
||||
// kind into the generic-struct / type-fn / protocol head tables
|
||||
// (S2.1b); the type args are ordinary references, collected now.
|
||||
self.classifyHead(node, p.name, p.is_raw, here);
|
||||
self.visitAll(p.args, here);
|
||||
},
|
||||
|
||||
@@ -690,6 +729,83 @@ const ResolvePass = struct {
|
||||
self.replaceRef(table, node, .{ .authors = set });
|
||||
}
|
||||
|
||||
/// `alias.member`: when `alias` is a namespace import edge of `from`, resolve
|
||||
/// `member` against that already-selected target via `collectNamespaceAuthors`
|
||||
/// (NO graph walk) and record it into the namespace-qualified table. A base that
|
||||
/// is not a namespace alias (struct-const / UFCS / local value — S2.1c) records
|
||||
/// nothing here.
|
||||
fn classifyNamespaceQualified(self: *ResolvePass, node: *const ast.Node, alias: []const u8, member: []const u8, from: []const u8) void {
|
||||
const edges = self.res.index.namespace_edges orelse return;
|
||||
const aliases = edges.get(from) orelse return;
|
||||
const target = aliases.get(alias) orelse return;
|
||||
const set = self.res.collectNamespaceAuthors(target, member);
|
||||
if (set.distinctCount() == 0) return;
|
||||
self.replaceRef(&self.out.namespace_refs, node, .{ .authors = set });
|
||||
}
|
||||
|
||||
/// A parameterized head (`Name(args)`) binned by its resolved author's decl
|
||||
/// kind: a generic struct (struct with type params) → `generic_struct_heads`;
|
||||
/// a type-function (fn / const-wrapped fn with type params) → `type_fn_heads`; a
|
||||
/// protocol → `protocol_heads`. RAW — the whole author set is recorded with no
|
||||
/// winner picked, so a name authored as more than one head kind across modules
|
||||
/// lands a distinct entry in every matching table. A head naming a generic param
|
||||
/// in scope is symbolic (not an author); a name with no user author (builtins
|
||||
/// like `Vector`, undeclared) or only non-head authors is omitted.
|
||||
fn classifyHead(self: *ResolvePass, node: *const ast.Node, name: []const u8, is_raw: bool, ctx: Ctx) void {
|
||||
if (!is_raw and lookupGeneric(ctx.scope, name) != null) return;
|
||||
const set = self.res.collectVisibleAuthors(name, ctx.source, .user_bare_flat);
|
||||
if (set.distinctCount() == 0) return;
|
||||
|
||||
var gs = false;
|
||||
var tf = false;
|
||||
var pr = false;
|
||||
if (set.own) |a| classifyHeadKind(a.raw, &gs, &tf, &pr);
|
||||
for (set.flat) |a| classifyHeadKind(a.raw, &gs, &tf, &pr);
|
||||
|
||||
var tables: [3]*NodeRefTable = undefined;
|
||||
var n: usize = 0;
|
||||
if (gs) {
|
||||
tables[n] = &self.out.generic_struct_heads;
|
||||
n += 1;
|
||||
}
|
||||
if (tf) {
|
||||
tables[n] = &self.out.type_fn_heads;
|
||||
n += 1;
|
||||
}
|
||||
if (pr) {
|
||||
tables[n] = &self.out.protocol_heads;
|
||||
n += 1;
|
||||
}
|
||||
if (n == 0) {
|
||||
// an author exists but is not a head kind (e.g. a non-generic struct or
|
||||
// a zero-type-param fn) — own this set's allocation, then drop it.
|
||||
if (set.flat.len > 0) self.out.alloc.free(set.flat);
|
||||
return;
|
||||
}
|
||||
// each table OWNS its `AuthorSet.flat`; give the first match the collected
|
||||
// slice and a fresh copy to every subsequent table so `deinit` frees each
|
||||
// exactly once.
|
||||
self.replaceRef(tables[0], node, .{ .authors = set });
|
||||
var i: usize = 1;
|
||||
while (i < n) : (i += 1) {
|
||||
self.replaceRef(tables[i], node, .{ .authors = self.dupAuthorSet(set) });
|
||||
}
|
||||
}
|
||||
|
||||
/// A shallow copy of an `AuthorSet` with its own freshly-allocated `flat` slice
|
||||
/// (the `RawAuthor` elements are borrowed AST pointers + source strings, so the
|
||||
/// copy is shallow). Lets one head reference be recorded into several head
|
||||
/// tables without aliasing the owned slice.
|
||||
fn dupAuthorSet(self: *ResolvePass, set: AuthorSet) AuthorSet {
|
||||
return .{
|
||||
.own = set.own,
|
||||
.flat = if (set.flat.len > 0)
|
||||
(self.out.alloc.dupe(RawAuthor, set.flat) catch @panic("resolve: OOM"))
|
||||
else
|
||||
&.{},
|
||||
};
|
||||
}
|
||||
|
||||
fn recordTemplate(self: *ResolvePass, table: *NodeRefTable, node: *const ast.Node, m: GenericMatch) void {
|
||||
self.replaceRef(table, node, .{ .template = self.internTemplate(m) });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user