feat(stdlib/S2.1c): foreign-class + struct-const + UFCS — final 3 domains on the owning pass [additive]
On the S2.1a owning traversal, populate the last three ResolvedProgram side
tables, closing planspec S2.1's full-population acceptance (all ten domains):
- foreign_class_refs: a bare reference whose collected author is a
foreign_class_decl is routed here (its own domain) instead of the bare
type/value/callable table.
- struct_const_refs: a Type.CONST field access whose base resolves to a
struct author carrying that const member (mirrors lowering's struct_const_map).
- ufcs_refs: a ufcs_alias decl (alias -> target author) plus its UFCS-rewrite
call sites (alias(args), incl. pipe-desugared), via a global traversal-ordered
alias map mirroring lowering's flat ufcs_alias_map.
Still PARALLEL / UNCONSUMED / RAW: lowering reads the OLD selectors, no consumer
cut over, ResolvedRef stays raw. Byte-identical vs baseline (540 examples).
Population proof extended: a new resolver.test fixture exercises all ten domains
at once and asserts each side table non-empty + node-keyed for the three new ones.
This commit is contained in:
@@ -703,3 +703,145 @@ test "resolver: resolve — namespace-qualified + generic-struct/type-fn/protoco
|
|||||||
try std.testing.expectEqual(@as(u32, 0), rp.struct_const_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());
|
try std.testing.expectEqual(@as(u32, 0), rp.ufcs_refs.count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── the owning resolution pass — S2.1c final three domains + FULL population ──
|
||||||
|
|
||||||
|
// S2.1c closes the set on the SAME traversal, still RAW / PARALLEL / UNCONSUMED.
|
||||||
|
// (1) A bare reference whose author is a `foreign_class_decl` (`o: Obj`) is routed
|
||||||
|
// into foreign_class_refs — NOT type_refs. (2) A `Type.CONST` field access whose
|
||||||
|
// base resolves to a struct carrying that const member (`Phys.GRAVITY`) fills
|
||||||
|
// struct_const_refs, keyed by the field_access node. (3) A UFCS alias
|
||||||
|
// (`plus :: ufcs adder`) is keyed by its decl node AND its rewrite call site
|
||||||
|
// (`plus(1, 2)`) is keyed by the callee, both resolving to the target's author.
|
||||||
|
// This fixture exercises ALL TEN domains at once, proving the full-population
|
||||||
|
// acceptance: every ResolvedProgram side table is non-empty and node-keyed.
|
||||||
|
test "resolver: resolve — S2.1c foreign-class/struct-const/UFCS + all ten domains populated" {
|
||||||
|
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 = "ns.sx", .data = "helper_fn :: () -> s64 { 7 }\n" });
|
||||||
|
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data =
|
||||||
|
\\#import "lib.sx";
|
||||||
|
\\g :: #import "ns.sx";
|
||||||
|
\\Obj :: #foreign #objc_class("Obj") {
|
||||||
|
\\ alloc :: () -> *Obj;
|
||||||
|
\\}
|
||||||
|
\\Phys :: struct { mass: s64; GRAVITY :: 10; }
|
||||||
|
\\Box :: struct($T: Type) { value: T }
|
||||||
|
\\Make :: ($T: Type) -> Type { return [3]T; }
|
||||||
|
\\Cmp :: protocol(T: Type) { get :: () -> T; }
|
||||||
|
\\LIMIT :: 5;
|
||||||
|
\\adder :: (a: s64, b: s64) -> s64 { a + b }
|
||||||
|
\\plus :: ufcs adder;
|
||||||
|
\\use_phys :: (p: Phys) -> s64 { p.mass }
|
||||||
|
\\use_obj :: (o: Obj) -> s64 { 0 }
|
||||||
|
\\use_box :: (b: Box(s64)) -> s64 { 0 }
|
||||||
|
\\use_make :: (m: Make(s64)) -> s64 { 0 }
|
||||||
|
\\use_cmp :: (c: Cmp(s64)) -> s64 { 0 }
|
||||||
|
\\read_const :: () -> s64 { Phys.GRAVITY }
|
||||||
|
\\read_ns :: () -> s64 { g.helper_fn() }
|
||||||
|
\\call_alias :: () -> s64 { plus(1, 2) }
|
||||||
|
\\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.namespace_edges = &prog.ns_edges;
|
||||||
|
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();
|
||||||
|
|
||||||
|
// (0) FULL population: every one of the ten domains is 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);
|
||||||
|
try std.testing.expect(rp.namespace_refs.count() > 0);
|
||||||
|
try std.testing.expect(rp.generic_struct_heads.count() > 0);
|
||||||
|
try std.testing.expect(rp.type_fn_heads.count() > 0);
|
||||||
|
try std.testing.expect(rp.protocol_heads.count() > 0);
|
||||||
|
try std.testing.expect(rp.foreign_class_refs.count() > 0);
|
||||||
|
try std.testing.expect(rp.struct_const_refs.count() > 0);
|
||||||
|
try std.testing.expect(rp.ufcs_refs.count() > 0);
|
||||||
|
|
||||||
|
const struct_tag = std.meta.Tag(resolver.RawDeclRef).struct_decl;
|
||||||
|
const fn_tag = std.meta.Tag(resolver.RawDeclRef).fn_decl;
|
||||||
|
const foreign_tag = std.meta.Tag(resolver.RawDeclRef).foreign_class_decl;
|
||||||
|
|
||||||
|
// (1) Foreign-class ref: `o: Obj` is keyed by the exact param type-expr node in
|
||||||
|
// foreign_class_refs, resolves RAW to the foreign_class_decl author — and is
|
||||||
|
// NOT mis-recorded into the bare type_refs table.
|
||||||
|
const use_obj = findFn(prog.root, "use_obj") orelse return error.MissingFn;
|
||||||
|
const obj_te = use_obj.data.fn_decl.params[0].type_expr;
|
||||||
|
const obj_ref = rp.foreign_class_refs.get(obj_te) orelse return error.ObjNotKeyed;
|
||||||
|
try std.testing.expect(obj_ref == .authors);
|
||||||
|
try std.testing.expect(obj_ref.authors.own != null);
|
||||||
|
try std.testing.expectEqual(foreign_tag, std.meta.activeTag(obj_ref.authors.own.?.raw));
|
||||||
|
try std.testing.expect(rp.type_refs.get(obj_te) == null);
|
||||||
|
|
||||||
|
// (2) Struct constant: `Phys.GRAVITY` is keyed by its field_access node in
|
||||||
|
// struct_const_refs and resolves RAW to the owning struct author.
|
||||||
|
var saw_const = false;
|
||||||
|
var sit = rp.struct_const_refs.iterator();
|
||||||
|
while (sit.next()) |e| {
|
||||||
|
const k = e.key_ptr.*;
|
||||||
|
try std.testing.expect(k.data == .field_access); // struct-const keys the access node
|
||||||
|
if (std.mem.eql(u8, k.data.field_access.field, "GRAVITY")) {
|
||||||
|
try std.testing.expect(e.value_ptr.* == .authors);
|
||||||
|
try std.testing.expect(e.value_ptr.authors.own != null);
|
||||||
|
try std.testing.expectEqual(struct_tag, std.meta.activeTag(e.value_ptr.authors.own.?.raw));
|
||||||
|
saw_const = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try std.testing.expect(saw_const);
|
||||||
|
|
||||||
|
// (3a) UFCS alias decl: the `plus :: ufcs adder` node is keyed in ufcs_refs and
|
||||||
|
// resolves RAW to the target fn (`adder`) author.
|
||||||
|
const plus_decl = findDecl(prog.root, "plus", .ufcs_alias) orelse return error.MissingUfcsAlias;
|
||||||
|
const plus_ref = rp.ufcs_refs.get(plus_decl) orelse return error.UfcsAliasNotKeyed;
|
||||||
|
try std.testing.expect(plus_ref == .authors);
|
||||||
|
try std.testing.expect(plus_ref.authors.own != null);
|
||||||
|
try std.testing.expectEqual(fn_tag, std.meta.activeTag(plus_ref.authors.own.?.raw));
|
||||||
|
|
||||||
|
// (3b) UFCS rewrite site: the `plus(1, 2)` callee identifier is keyed in
|
||||||
|
// ufcs_refs (NOT callable_refs) and resolves to the SAME target author.
|
||||||
|
var saw_site = false;
|
||||||
|
var uit = rp.ufcs_refs.iterator();
|
||||||
|
while (uit.next()) |e| {
|
||||||
|
const k = e.key_ptr.*;
|
||||||
|
if (k.data == .identifier and std.mem.eql(u8, k.data.identifier.name, "plus")) {
|
||||||
|
try std.testing.expect(e.value_ptr.* == .authors);
|
||||||
|
try std.testing.expect(e.value_ptr.authors.own != null);
|
||||||
|
try std.testing.expectEqual(fn_tag, std.meta.activeTag(e.value_ptr.authors.own.?.raw));
|
||||||
|
saw_site = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try std.testing.expect(saw_site);
|
||||||
|
|
||||||
|
// A UFCS-rewritten callee is NOT also recorded as an ordinary callable head.
|
||||||
|
var cit = rp.callable_refs.iterator();
|
||||||
|
while (cit.next()) |e| {
|
||||||
|
try std.testing.expect(!std.mem.eql(u8, e.key_ptr.*.data.identifier.name, "plus"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -187,8 +187,12 @@ fn containsAuthor(list: []const RawAuthor, b: RawAuthor) bool {
|
|||||||
// $pack[i]) SYMBOLICALLY (template/pack ids, never TypeIds). S2.1b adds the
|
// $pack[i]) SYMBOLICALLY (template/pack ids, never TypeIds). S2.1b adds the
|
||||||
// namespace-qualified table (`alias.member` resolved via `collectNamespaceAuthors`)
|
// namespace-qualified table (`alias.member` resolved via `collectNamespaceAuthors`)
|
||||||
// and the three HEAD tables (generic-struct / type-fn / protocol), binned by the
|
// 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
|
// resolved author's decl kind at `parameterized_type_expr` heads. S2.1c closes the
|
||||||
// three tables (foreign-class / struct-const / UFCS) stay empty until S2.1c.
|
// set with the final three: a bare reference whose author is a `foreign_class_decl`
|
||||||
|
// is routed to `foreign_class_refs`; a `Type.CONST` field access whose base author
|
||||||
|
// is a struct carrying that const member fills `struct_const_refs`; and a UFCS
|
||||||
|
// alias (`alias :: ufcs target`) plus its rewrite call sites fill `ufcs_refs`. All
|
||||||
|
// ten domains are now populated — still PARALLEL / UNCONSUMED / RAW.
|
||||||
|
|
||||||
/// A symbolic id for one enclosing generic TYPE/VALUE param (`$T`, `$N`), assigned
|
/// A symbolic id for one enclosing generic TYPE/VALUE param (`$T`, `$N`), assigned
|
||||||
/// by the pass and indexing `ResolvedProgram.template_params`. Process-local.
|
/// by the pass and indexing `ResolvedProgram.template_params`. Process-local.
|
||||||
@@ -327,7 +331,9 @@ pub fn resolve(
|
|||||||
var pass = ResolvePass{
|
var pass = ResolvePass{
|
||||||
.res = Resolver.init(index, alloc),
|
.res = Resolver.init(index, alloc),
|
||||||
.out = ResolvedProgram.init(alloc),
|
.out = ResolvedProgram.init(alloc),
|
||||||
|
.ufcs_aliases = std.StringHashMap([]const u8).init(alloc),
|
||||||
};
|
};
|
||||||
|
defer pass.ufcs_aliases.deinit();
|
||||||
pass.visit(root, .{ .source = main_file, .scope = null });
|
pass.visit(root, .{ .source = main_file, .scope = null });
|
||||||
return pass.out;
|
return pass.out;
|
||||||
}
|
}
|
||||||
@@ -405,11 +411,56 @@ fn classifyHeadKind(raw: RawDeclRef, gs: *bool, tf: *bool, pr: *bool) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True when an author set resolves to a `foreign_class_decl` — the own author
|
||||||
|
/// decides when present, else any flat author. Such a reference is routed to
|
||||||
|
/// `foreign_class_refs` (its own domain) instead of the bare type/value table.
|
||||||
|
fn authorSetIsForeignClass(set: AuthorSet) bool {
|
||||||
|
if (set.own) |a| return std.meta.activeTag(a.raw) == .foreign_class_decl;
|
||||||
|
for (set.flat) |a| {
|
||||||
|
if (std.meta.activeTag(a.raw) == .foreign_class_decl) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A struct author carrying a `const_decl` member named `field` — the RAW shape
|
||||||
|
/// `Type.CONST` field access resolves to. Mirrors the lowering `struct_const_map`
|
||||||
|
/// domain, which is struct-level constants only; enums / other decls carry no
|
||||||
|
/// const members, so only `struct_decl` matches.
|
||||||
|
fn structHasConstMember(raw: RawDeclRef, field: []const u8) bool {
|
||||||
|
return switch (raw) {
|
||||||
|
.struct_decl => |sd| blk: {
|
||||||
|
for (sd.constants) |c| {
|
||||||
|
if (c.data == .const_decl and std.mem.eql(u8, c.data.const_decl.name, field))
|
||||||
|
break :blk true;
|
||||||
|
}
|
||||||
|
break :blk false;
|
||||||
|
},
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Any author in the set (own or flat) is a struct with a const member `field`.
|
||||||
|
fn authorSetHasStructConst(set: AuthorSet, field: []const u8) bool {
|
||||||
|
if (set.own) |a| {
|
||||||
|
if (structHasConstMember(a.raw, field)) return true;
|
||||||
|
}
|
||||||
|
for (set.flat) |a| {
|
||||||
|
if (structHasConstMember(a.raw, field)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// The single owning traversal. Holds the author collector + the `ResolvedProgram`
|
/// The single owning traversal. Holds the author collector + the `ResolvedProgram`
|
||||||
/// it populates; threads `Ctx` (ambient source + generic scope) down the tree.
|
/// it populates; threads `Ctx` (ambient source + generic scope) down the tree.
|
||||||
const ResolvePass = struct {
|
const ResolvePass = struct {
|
||||||
res: Resolver,
|
res: Resolver,
|
||||||
out: ResolvedProgram,
|
out: ResolvedProgram,
|
||||||
|
/// `alias name → target name` for every `ufcs_alias` seen so far on the walk.
|
||||||
|
/// Global (not block-scoped) and populated in traversal order, mirroring
|
||||||
|
/// lowering's flat `ufcs_alias_map`; lets a UFCS-rewrite call site resolve to
|
||||||
|
/// the alias target's author. Scratch — freed when `resolve` returns, NOT part
|
||||||
|
/// of the owned `ResolvedProgram`.
|
||||||
|
ufcs_aliases: std.StringHashMap([]const u8),
|
||||||
|
|
||||||
/// Visit ONE node, then recurse into its children. A stamped
|
/// Visit ONE node, then recurse into its children. A stamped
|
||||||
/// `node.source_file` (top-level decls, and cross-module fn bodies whose bare
|
/// `node.source_file` (top-level decls, and cross-module fn bodies whose bare
|
||||||
@@ -485,23 +536,35 @@ const ResolvePass = struct {
|
|||||||
.identifier => self.classifyValue(node, here),
|
.identifier => self.classifyValue(node, here),
|
||||||
.call => |*c| {
|
.call => |*c| {
|
||||||
if (c.callee.data == .identifier) {
|
if (c.callee.data == .identifier) {
|
||||||
// bare-name callable HEAD — recorded here, not re-walked as a
|
const cname = c.callee.data.identifier.name;
|
||||||
|
// a UFCS-alias callee (`alias(args)`, incl. the parser's
|
||||||
|
// pipe-desugared `x |> alias()`) resolves to the alias TARGET's
|
||||||
|
// author → ufcs_refs (S2.1c). Any other bare callee is an
|
||||||
|
// ordinary callable HEAD — recorded here, not re-walked as a
|
||||||
// value ref.
|
// value ref.
|
||||||
self.recordAuthors(&self.out.callable_refs, c.callee, c.callee.data.identifier.name, here.source);
|
if (self.ufcs_aliases.get(cname)) |target| {
|
||||||
|
self.recordAuthorsInto(&self.out.ufcs_refs, c.callee, target, here.source);
|
||||||
|
} else {
|
||||||
|
self.recordAuthors(&self.out.callable_refs, c.callee, cname, here.source);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.visit(c.callee, here);
|
self.visit(c.callee, here);
|
||||||
}
|
}
|
||||||
self.visitAll(c.args, here);
|
self.visitAll(c.args, here);
|
||||||
},
|
},
|
||||||
.field_access => |*fa| {
|
.field_access => |*fa| {
|
||||||
// `alias.member` whose base alias is a namespace import edge of the
|
// `alias.member` whose base is a namespace import edge of the
|
||||||
// ambient source resolves via `collectNamespaceAuthors` into the
|
// ambient source resolves via `collectNamespaceAuthors` into the
|
||||||
// namespace-qualified table (S2.1b). A non-alias bare receiver
|
// namespace-qualified table (S2.1b). Otherwise `Type.CONST` — a base
|
||||||
// (struct-const / UFCS / local value) stays unclassified — S2.1c —
|
// resolving to a struct author that carries the named const member —
|
||||||
// and is not walked as a value ref; a compound receiver is recursed
|
// fills `struct_const_refs` (S2.1c). A receiver that is neither (a
|
||||||
// so its inner refs are collected.
|
// local value / instance field access) records nothing and is not
|
||||||
|
// walked as a value ref; a compound receiver is recursed so its
|
||||||
|
// inner refs are collected.
|
||||||
if (fa.object.data == .identifier) {
|
if (fa.object.data == .identifier) {
|
||||||
self.classifyNamespaceQualified(node, fa.object.data.identifier.name, fa.field, here.source);
|
const base = fa.object.data.identifier.name;
|
||||||
|
if (!self.classifyNamespaceQualified(node, base, fa.field, here.source))
|
||||||
|
_ = self.classifyStructConst(node, base, fa.field, here.source);
|
||||||
} else {
|
} else {
|
||||||
self.visit(fa.object, here);
|
self.visit(fa.object, here);
|
||||||
}
|
}
|
||||||
@@ -675,9 +738,17 @@ const ResolvePass = struct {
|
|||||||
.foreign_expr,
|
.foreign_expr,
|
||||||
.library_decl,
|
.library_decl,
|
||||||
.framework_decl,
|
.framework_decl,
|
||||||
.ufcs_alias,
|
|
||||||
.c_import_decl,
|
.c_import_decl,
|
||||||
=> {},
|
=> {},
|
||||||
|
|
||||||
|
// `alias :: ufcs target` — record the alias→target binding (the
|
||||||
|
// target's RAW author) keyed by the decl node, and remember the alias
|
||||||
|
// name so its rewrite call sites resolve to the same target. The map is
|
||||||
|
// global / traversal-ordered, mirroring lowering's flat `ufcs_alias_map`.
|
||||||
|
.ufcs_alias => |ua| {
|
||||||
|
self.ufcs_aliases.put(ua.name, ua.target) catch @panic("resolve: OOM");
|
||||||
|
self.recordAuthorsInto(&self.out.ufcs_refs, node, ua.target, here.source);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -722,8 +793,20 @@ const ResolvePass = struct {
|
|||||||
|
|
||||||
/// RAW author collection for a bare name. Only recorded when the name has ≥1
|
/// RAW author collection for a bare name. Only recorded when the name has ≥1
|
||||||
/// visible author (own or flat); a builtin / local / undeclared spelling has
|
/// visible author (own or flat); a builtin / local / undeclared spelling has
|
||||||
/// none and is omitted — this is what keeps the tables to genuine authors.
|
/// none and is omitted — this is what keeps the tables to genuine authors. A
|
||||||
|
/// name whose author is a `foreign_class_decl` is routed to `foreign_class_refs`
|
||||||
|
/// (its own S2.1c domain) instead of the passed bare type/value/callable table.
|
||||||
fn recordAuthors(self: *ResolvePass, table: *NodeRefTable, node: *const ast.Node, name: []const u8, from: []const u8) void {
|
fn recordAuthors(self: *ResolvePass, table: *NodeRefTable, node: *const ast.Node, name: []const u8, from: []const u8) void {
|
||||||
|
const set = self.res.collectVisibleAuthors(name, from, .user_bare_flat);
|
||||||
|
if (set.distinctCount() == 0) return;
|
||||||
|
const dest = if (authorSetIsForeignClass(set)) &self.out.foreign_class_refs else table;
|
||||||
|
self.replaceRef(dest, node, .{ .authors = set });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// RAW author collection into an explicit table, with NO foreign-class routing —
|
||||||
|
/// the destination domain is already chosen by the caller (UFCS rewrite sites
|
||||||
|
/// and alias decls, whose target is always a free function).
|
||||||
|
fn recordAuthorsInto(self: *ResolvePass, table: *NodeRefTable, node: *const ast.Node, name: []const u8, from: []const u8) void {
|
||||||
const set = self.res.collectVisibleAuthors(name, from, .user_bare_flat);
|
const set = self.res.collectVisibleAuthors(name, from, .user_bare_flat);
|
||||||
if (set.distinctCount() == 0) return;
|
if (set.distinctCount() == 0) return;
|
||||||
self.replaceRef(table, node, .{ .authors = set });
|
self.replaceRef(table, node, .{ .authors = set });
|
||||||
@@ -731,16 +814,34 @@ const ResolvePass = struct {
|
|||||||
|
|
||||||
/// `alias.member`: when `alias` is a namespace import edge of `from`, resolve
|
/// `alias.member`: when `alias` is a namespace import edge of `from`, resolve
|
||||||
/// `member` against that already-selected target via `collectNamespaceAuthors`
|
/// `member` against that already-selected target via `collectNamespaceAuthors`
|
||||||
/// (NO graph walk) and record it into the namespace-qualified table. A base that
|
/// (NO graph walk) and record it into the namespace-qualified table. Returns
|
||||||
/// is not a namespace alias (struct-const / UFCS / local value — S2.1c) records
|
/// whether it recorded — a base that is not a namespace alias records nothing
|
||||||
/// nothing here.
|
/// here and lets the caller try the struct-const path.
|
||||||
fn classifyNamespaceQualified(self: *ResolvePass, node: *const ast.Node, alias: []const u8, member: []const u8, from: []const u8) void {
|
fn classifyNamespaceQualified(self: *ResolvePass, node: *const ast.Node, alias: []const u8, member: []const u8, from: []const u8) bool {
|
||||||
const edges = self.res.index.namespace_edges orelse return;
|
const edges = self.res.index.namespace_edges orelse return false;
|
||||||
const aliases = edges.get(from) orelse return;
|
const aliases = edges.get(from) orelse return false;
|
||||||
const target = aliases.get(alias) orelse return;
|
const target = aliases.get(alias) orelse return false;
|
||||||
const set = self.res.collectNamespaceAuthors(target, member);
|
const set = self.res.collectNamespaceAuthors(target, member);
|
||||||
if (set.distinctCount() == 0) return;
|
if (set.distinctCount() == 0) return false;
|
||||||
self.replaceRef(&self.out.namespace_refs, node, .{ .authors = set });
|
self.replaceRef(&self.out.namespace_refs, node, .{ .authors = set });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Type.CONST`: when `base` resolves to a struct author carrying a const member
|
||||||
|
/// named `member`, record the base's RAW author set into `struct_const_refs`
|
||||||
|
/// (keyed by the field-access node) — the owning-type identity the constant
|
||||||
|
/// lives on. A base with authors but no matching const member, or no author at
|
||||||
|
/// all (a local value receiver), records nothing and releases its allocation.
|
||||||
|
/// Returns whether it recorded.
|
||||||
|
fn classifyStructConst(self: *ResolvePass, node: *const ast.Node, base: []const u8, member: []const u8, from: []const u8) bool {
|
||||||
|
const set = self.res.collectVisibleAuthors(base, from, .user_bare_flat);
|
||||||
|
if (set.distinctCount() == 0) return false;
|
||||||
|
if (!authorSetHasStructConst(set, member)) {
|
||||||
|
if (set.flat.len > 0) self.out.alloc.free(set.flat);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.replaceRef(&self.out.struct_const_refs, node, .{ .authors = set });
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A parameterized head (`Name(args)`) binned by its resolved author's decl
|
/// A parameterized head (`Name(args)`) binned by its resolved author's decl
|
||||||
|
|||||||
Reference in New Issue
Block a user