feat(stdlib/S2.2): selection verdicts in the resolver [additive]
Move the author-SELECTION SEMANTICS (the verdicts) into the resolver. Every
`.authors` ResolvedRef now carries the verdict the resolver COMPUTES above the
collector — own-wins / single-flat-visible / ≥2-ambiguous / not-visible /
type-vs-value domain-filtered — evaluated over the DOMAIN-ELIGIBLE subset of the
collected author set (`eligibleKind`). This folds the per-kind selection the old
lower-side selectors carried (selectNominalLeaf / flatTypeAuthorCount /
selectModuleConst / selectPlainCallableAuthor / selectGenericStructHead /
headTypeGate / headFnLeak) into ONE uniform computation, closing the
protocol / error-set / foreign per-kind surfaces (E6c/d/e) as resolver behavior.
Template/pack grammar stays carried as `.template` / `.pack` refs — NO
`sig_registration_mode`.
ADDITIVE / PARALLEL / UNCONSUMED: lowering still reads the old selectors, so the
verdict changes no generated byte. No file outside resolver.zig reads
ResolvedRef, so byte-identity is structural. ResolvedRef.authors is wrapped into
{ set, verdict } (the RAW set is preserved; the verdict filters).
Resolver unit tests prove the verdicts on real Phase A facts: the five bare-type
outcomes incl. the type-vs-value filter, and the resolver-target classes the old
selectors get WRONG — 0811 error-set / 0821 protocol-head / 0829 generic-struct
all → ambiguous (two flat authors, none own) and → own-wins (own author present).
The resolver-target corpus stays xfail in run_examples (unconsumed until S3.9);
verdicts asserted via the harness, not by flipping goldens.
Gate: zig build && zig build test (430) && tests/run_examples.sh (540 byte-identical),
all exit 0; tests/resolver-target 18 xfail unchanged.
This commit is contained in:
@@ -192,7 +192,19 @@ fn containsAuthor(list: []const RawAuthor, b: RawAuthor) bool {
|
||||
// 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.
|
||||
// ten domains are now populated — still PARALLEL / UNCONSUMED.
|
||||
//
|
||||
// S2.2 adds the VERDICT layer: every `.authors` ref now carries the selection
|
||||
// outcome the resolver COMPUTES above the collector — own-wins / single-flat-visible
|
||||
// / ≥2-ambiguous / not-visible / type-vs-value domain-filtered — evaluated over the
|
||||
// DOMAIN-ELIGIBLE subset of the author set (`eligibleKind`). This folds the per-kind
|
||||
// selection semantics the old lower-side selectors carried (`selectNominalLeaf` /
|
||||
// `flatTypeAuthorCount` / `selectModuleConst` / `selectPlainCallableAuthor` /
|
||||
// `selectGenericStructHead` / `headTypeGate` / `headFnLeak`) into ONE uniform
|
||||
// computation, closing the protocol / error-set / foreign per-kind surfaces as
|
||||
// resolver behavior. The template/pack grammar is the `.template` / `.pack` refs
|
||||
// above — NO `sig_registration_mode`. STILL ADDITIVE / PARALLEL / UNCONSUMED:
|
||||
// lowering reads the old selectors, so the verdict changes no generated byte.
|
||||
|
||||
/// A symbolic id for one enclosing generic TYPE/VALUE param (`$T`, `$N`), assigned
|
||||
/// by the pass and indexing `ResolvedProgram.template_params`. Process-local.
|
||||
@@ -229,12 +241,45 @@ pub const PackRef = struct {
|
||||
index: ?u32 = null,
|
||||
};
|
||||
|
||||
/// What ONE reference site resolves to — the S2.1 RAW form. `authors` carries the
|
||||
/// collected author identity (own ∪ flat, diamond-deduped) with NO verdict:
|
||||
/// own-wins / direct-flat ambiguity selection is S2.2. `template` / `pack` are
|
||||
/// symbolic generic-param references.
|
||||
/// The selection verdict computed (S2.2) above a reference's collected author set
|
||||
/// — the own-wins / single-flat-visible / ≥2-ambiguous layer the S0 ledger places
|
||||
/// "above the collectors, producing ResolvedRef". Evaluated over the DOMAIN-ELIGIBLE
|
||||
/// subset of the author set (so a same-name VALUE never decides a TYPE reference —
|
||||
/// the type-vs-value `domain_filtered` outcome). ADDITIVE / PARALLEL / UNCONSUMED:
|
||||
/// lowering still reads the old selectors, so producing these changes no byte.
|
||||
pub const Verdict = enum {
|
||||
/// The querying module's OWN author is eligible — it wins outright, regardless
|
||||
/// of how many same-name flat authors exist.
|
||||
own_wins,
|
||||
/// Exactly ONE eligible flat-visible author, no own — the byte-identical
|
||||
/// single-author path.
|
||||
single,
|
||||
/// ≥2 distinct eligible flat-visible authors, no own — a genuine collision the
|
||||
/// source cannot disambiguate (the LOUD diagnostic at lowering / S3).
|
||||
ambiguous,
|
||||
/// No eligible author is flat-visible, but the name IS authored for this domain
|
||||
/// in some module — reachable only over a namespace edge ⇒ a not-visible leak.
|
||||
not_visible,
|
||||
/// Visible same-name author(s) exist but NONE is eligible for this domain (a
|
||||
/// same-name VALUE for a TYPE reference, etc.) and the name is authored for this
|
||||
/// domain nowhere — the type-vs-value filter excluded every visible candidate.
|
||||
domain_filtered,
|
||||
};
|
||||
|
||||
/// A collected author set paired with the verdict the resolver computed over it.
|
||||
/// `set` is the RAW collection (own ∪ flat, diamond-deduped — the S2.1 form, owned
|
||||
/// here); `verdict` is the S2.2 selection outcome over its domain-eligible subset.
|
||||
pub const ResolvedAuthors = struct {
|
||||
set: AuthorSet,
|
||||
verdict: Verdict,
|
||||
};
|
||||
|
||||
/// What ONE reference site resolves to. `authors` carries the collected author
|
||||
/// identity plus its computed verdict; `template` / `pack` are symbolic generic-param
|
||||
/// references (no author, no verdict — the template grammar the resolver carries as
|
||||
/// refs instead of a mutable `sig_registration_mode`).
|
||||
pub const ResolvedRef = union(enum) {
|
||||
authors: AuthorSet,
|
||||
authors: ResolvedAuthors,
|
||||
template: TemplateParamId,
|
||||
pack: PackRef,
|
||||
};
|
||||
@@ -294,7 +339,7 @@ pub const ResolvedProgram = struct {
|
||||
for (self.allTables()) |t| {
|
||||
var it = t.valueIterator();
|
||||
while (it.next()) |ref| switch (ref.*) {
|
||||
.authors => |a| if (a.flat.len > 0) self.alloc.free(a.flat),
|
||||
.authors => |a| if (a.set.flat.len > 0) self.alloc.free(a.set.flat),
|
||||
.template, .pack => {},
|
||||
};
|
||||
t.deinit();
|
||||
@@ -455,6 +500,85 @@ fn authorSetHasStructConst(set: AuthorSet, field: []const u8) bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// The `*StructDecl` a raw author wraps (bare or `Name :: struct(...)` const), or
|
||||
/// null when the author is not a struct. Mirrors lowering's `structDeclOfRaw`.
|
||||
fn structDeclOf(raw: RawDeclRef) ?*const ast.StructDecl {
|
||||
return switch (raw) {
|
||||
.struct_decl => |sd| sd,
|
||||
.const_decl => |cd| if (cd.value.data == .struct_decl) &cd.value.data.struct_decl else null,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// The `*FnDecl` a raw author wraps (bare or `Name :: fn(...)` const), or null when
|
||||
/// the author is not a function. Mirrors lowering's `fnDeclOfRaw`.
|
||||
fn fnDeclOf(raw: RawDeclRef) ?*const ast.FnDecl {
|
||||
return switch (raw) {
|
||||
.fn_decl => |fd| fd,
|
||||
.const_decl => |cd| if (cd.value.data == .fn_decl) &cd.value.data.fn_decl else null,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// A PLAIN free function — no type params, an ordinary (non-`#foreign`/`#builtin`/
|
||||
/// `#compiler`) body — the only callable kind the bare-call verdict counts. Mirrors
|
||||
/// lowering's `isPlainFreeFn`.
|
||||
fn isPlainFreeFnDecl(fd: *const ast.FnDecl) bool {
|
||||
if (fd.type_params.len > 0) return false;
|
||||
return switch (fd.body.data) {
|
||||
.foreign_expr, .builtin_expr, .compiler_expr => false,
|
||||
else => true,
|
||||
};
|
||||
}
|
||||
|
||||
/// The reference domains a verdict is computed over. Each carries its own
|
||||
/// eligibility filter (`eligibleKind`), so the own-wins / ambiguity count surveys
|
||||
/// only the authors that can actually decide THIS kind of reference — a same-name
|
||||
/// value never decides a type, a non-generic struct never authors a generic head.
|
||||
const Domain = enum {
|
||||
bare_type,
|
||||
value_const,
|
||||
callable,
|
||||
generic_struct_head,
|
||||
type_fn_head,
|
||||
protocol_head,
|
||||
foreign_class,
|
||||
struct_const,
|
||||
namespace_member,
|
||||
ufcs,
|
||||
};
|
||||
|
||||
/// Whether `raw` is an author ELIGIBLE to decide a reference in `domain` — the
|
||||
/// type-vs-value domain filter applied BEFORE the own-wins / ambiguity count.
|
||||
/// `field` is the accessed member name (struct-const domain only; ignored
|
||||
/// elsewhere). Mirrors the per-kind author predicates the old lowering selectors
|
||||
/// gate on (`isNamedTypeKind`, `isPlainFreeFn`, `typeFnAuthor`, the `classifyHeadKind`
|
||||
/// struct/fn `type_params.len > 0` test, `structHasConstMember`).
|
||||
fn eligibleKind(domain: Domain, raw: RawDeclRef, field: ?[]const u8) bool {
|
||||
return switch (domain) {
|
||||
// Foreign classes are routed to their own domain before the type verdict, so
|
||||
// a bare TYPE author is a non-foreign named type. A type ALIAS (`Name :: <type>`,
|
||||
// a `const_decl`) is recognised by lowering via the E0 source-keyed alias cache,
|
||||
// which the resolver does not yet carry — alias authorship folds in when the
|
||||
// alias facts move into the resolver (a later S2/S4 refinement), not here.
|
||||
.bare_type => switch (raw) {
|
||||
.struct_decl, .enum_decl, .union_decl, .error_set_decl, .protocol_decl => true,
|
||||
else => false,
|
||||
},
|
||||
.value_const => raw == .const_decl,
|
||||
.callable => if (fnDeclOf(raw)) |fd| isPlainFreeFnDecl(fd) else false,
|
||||
.generic_struct_head => if (structDeclOf(raw)) |sd| sd.type_params.len > 0 else false,
|
||||
.type_fn_head => if (fnDeclOf(raw)) |fd| fd.type_params.len > 0 else false,
|
||||
.protocol_head => raw == .protocol_decl,
|
||||
.foreign_class => raw == .foreign_class_decl,
|
||||
.struct_const => structHasConstMember(raw, field orelse return false),
|
||||
// A namespace member is already selected against ONE namespace target, so any
|
||||
// kind the member declares is the unambiguous author.
|
||||
.namespace_member => true,
|
||||
.ufcs => fnDeclOf(raw) != null,
|
||||
};
|
||||
}
|
||||
|
||||
/// The single owning traversal. Holds the author collector + the `ResolvedProgram`
|
||||
/// it populates; threads `Ctx` (ambient source + generic scope) down the tree.
|
||||
const ResolvePass = struct {
|
||||
@@ -554,7 +678,7 @@ const ResolvePass = struct {
|
||||
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);
|
||||
self.recordAuthors(.callable, &self.out.callable_refs, c.callee, cname, here.source);
|
||||
}
|
||||
} else {
|
||||
self.visit(c.callee, here);
|
||||
@@ -581,7 +705,7 @@ const ResolvePass = struct {
|
||||
.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),
|
||||
.error_type_expr => |*e| {
|
||||
if (e.name) |name| self.recordAuthors(&self.out.type_refs, node, name, here.source);
|
||||
if (e.name) |name| self.recordAuthors(.bare_type, &self.out.type_refs, node, name, here.source);
|
||||
},
|
||||
.parameterized_type_expr => |*p| {
|
||||
// the head (`Name(args)`) is binned by its resolved author's decl
|
||||
@@ -810,7 +934,7 @@ const ResolvePass = struct {
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.recordAuthors(&self.out.type_refs, node, te.name, ctx.source);
|
||||
self.recordAuthors(.bare_type, &self.out.type_refs, node, te.name, ctx.source);
|
||||
}
|
||||
|
||||
/// A value-position identifier: a generic value/type param in scope (shadowing)
|
||||
@@ -823,28 +947,79 @@ const ResolvePass = struct {
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.recordAuthors(&self.out.value_refs, node, id.name, ctx.source);
|
||||
self.recordAuthors(.value_const, &self.out.value_refs, node, id.name, ctx.source);
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// 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 {
|
||||
/// Collect a bare name's authors AND compute its `domain` verdict. A name whose
|
||||
/// author is a `foreign_class_decl` is routed to `foreign_class_refs` (its own
|
||||
/// S2.1c domain, with the foreign-class verdict) instead of the passed
|
||||
/// type/value/callable table. Records own-wins / single / ambiguous when an
|
||||
/// eligible author is visible, and `not_visible` when the name is authored for
|
||||
/// this domain only over a namespace edge; a builtin / local / undeclared
|
||||
/// spelling (no visible author and none authored anywhere) is dropped, exactly
|
||||
/// as S2.1 dropped the empty set.
|
||||
fn recordAuthors(self: *ResolvePass, domain: Domain, 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 });
|
||||
const foreign = authorSetIsForeignClass(set);
|
||||
const dom: Domain = if (foreign) .foreign_class else domain;
|
||||
const dest = if (foreign) &self.out.foreign_class_refs else table;
|
||||
const verdict = self.verdictOver(dom, name, set, null);
|
||||
// Nothing visible AND not a domain author anywhere → a builtin / local /
|
||||
// undeclared spelling, never a reference of this domain — drop it (the S2.1
|
||||
// empty-set behavior). An empty set owns no `flat` slice to free.
|
||||
if (verdict == .domain_filtered and set.distinctCount() == 0) return;
|
||||
self.replaceRef(dest, node, .{ .authors = .{ .set = set, .verdict = verdict } });
|
||||
}
|
||||
|
||||
/// 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).
|
||||
/// Collect a target name's authors into an explicit table with the `.ufcs`
|
||||
/// verdict and 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). The target is always present, so an empty set is
|
||||
/// simply not recorded (no not-visible leak path here).
|
||||
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);
|
||||
if (set.distinctCount() == 0) return;
|
||||
self.replaceRef(table, node, .{ .authors = set });
|
||||
const verdict = self.verdictOver(.ufcs, name, set, null);
|
||||
self.replaceRef(table, node, .{ .authors = .{ .set = set, .verdict = verdict } });
|
||||
}
|
||||
|
||||
/// The verdict over a collected author set for `domain`: own-wins when the
|
||||
/// querying module's own author is eligible; ≥2 distinct eligible flat authors →
|
||||
/// ambiguous; exactly one → single; none eligible but authored for this domain
|
||||
/// in some (non-flat-visible) module → not_visible; otherwise domain_filtered
|
||||
/// (visible same-name authors of the wrong kind, or nothing authored anywhere).
|
||||
/// `field` is the accessed member (struct-const domain only).
|
||||
fn verdictOver(self: *ResolvePass, domain: Domain, name: []const u8, set: AuthorSet, field: ?[]const u8) Verdict {
|
||||
if (set.own) |o| {
|
||||
if (eligibleKind(domain, o.raw, field)) return .own_wins;
|
||||
}
|
||||
var n: usize = 0;
|
||||
for (set.flat) |fa| {
|
||||
if (eligibleKind(domain, fa.raw, field)) {
|
||||
n += 1;
|
||||
if (n >= 2) return .ambiguous;
|
||||
}
|
||||
}
|
||||
if (n == 1) return .single;
|
||||
if (self.authoredAsDomainAnywhere(domain, name, field)) return .not_visible;
|
||||
return .domain_filtered;
|
||||
}
|
||||
|
||||
/// TRUE iff `name` is authored for `domain` in ANY module's raw facts — the
|
||||
/// not-visible leak detector. Reached only with zero eligible flat-visible
|
||||
/// authors, so a hit means the author is reachable only over a namespace edge
|
||||
/// (had it been a flat edge it would already be in the surveyed set). Mirrors
|
||||
/// lowering's `nameAuthoredAsTypeAnywhere`, generalized over every domain via
|
||||
/// `eligibleKind`.
|
||||
fn authoredAsDomainAnywhere(self: *ResolvePass, domain: Domain, name: []const u8, field: ?[]const u8) bool {
|
||||
const decls = self.res.index.module_decls orelse return false;
|
||||
var it = decls.valueIterator();
|
||||
while (it.next()) |m| {
|
||||
if (m.names.get(name)) |ref| {
|
||||
if (eligibleKind(domain, ref, field)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// `alias.member`: when `alias` is a namespace import edge of `from`, resolve
|
||||
@@ -858,7 +1033,8 @@ const ResolvePass = struct {
|
||||
const target = aliases.get(alias) orelse return false;
|
||||
const set = self.res.collectNamespaceAuthors(target, member);
|
||||
if (set.distinctCount() == 0) return false;
|
||||
self.replaceRef(&self.out.namespace_refs, node, .{ .authors = set });
|
||||
const verdict = self.verdictOver(.namespace_member, member, set, null);
|
||||
self.replaceRef(&self.out.namespace_refs, node, .{ .authors = .{ .set = set, .verdict = verdict } });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -875,22 +1051,24 @@ const ResolvePass = struct {
|
||||
if (set.flat.len > 0) self.out.alloc.free(set.flat);
|
||||
return false;
|
||||
}
|
||||
self.replaceRef(&self.out.struct_const_refs, node, .{ .authors = set });
|
||||
const verdict = self.verdictOver(.struct_const, base, set, member);
|
||||
self.replaceRef(&self.out.struct_const_refs, node, .{ .authors = .{ .set = set, .verdict = verdict } });
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// protocol → `protocol_heads`. The whole author set is recorded in every
|
||||
/// matching table WITH that head kind's verdict (own-wins / single / ambiguous),
|
||||
/// so a name authored as more than one head kind across modules lands a distinct
|
||||
/// verdict-bearing entry per 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 (std.mem.indexOfScalar(u8, name, '.')) |first_dot| {
|
||||
const set = self.collectQualifiedHeadAuthors(name, first_dot, ctx.source) orelse return;
|
||||
self.classifyHeadSet(node, set);
|
||||
self.classifyHeadSet(node, name, set);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -898,7 +1076,7 @@ const ResolvePass = struct {
|
||||
const set = self.res.collectVisibleAuthors(name, ctx.source, .user_bare_flat);
|
||||
if (set.distinctCount() == 0) return;
|
||||
|
||||
self.classifyHeadSet(node, set);
|
||||
self.classifyHeadSet(node, name, set);
|
||||
}
|
||||
|
||||
/// `alias.Member(args)` reaches this pass as one `parameterized_type_expr`
|
||||
@@ -919,25 +1097,28 @@ const ResolvePass = struct {
|
||||
return set;
|
||||
}
|
||||
|
||||
fn classifyHeadSet(self: *ResolvePass, node: *const ast.Node, set: AuthorSet) void {
|
||||
/// One head table plus the verdict domain whose eligibility it counts.
|
||||
const HeadBin = struct { table: *NodeRefTable, domain: Domain };
|
||||
|
||||
fn classifyHeadSet(self: *ResolvePass, node: *const ast.Node, name: []const u8, set: AuthorSet) void {
|
||||
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 bins: [3]HeadBin = undefined;
|
||||
var n: usize = 0;
|
||||
if (gs) {
|
||||
tables[n] = &self.out.generic_struct_heads;
|
||||
bins[n] = .{ .table = &self.out.generic_struct_heads, .domain = .generic_struct_head };
|
||||
n += 1;
|
||||
}
|
||||
if (tf) {
|
||||
tables[n] = &self.out.type_fn_heads;
|
||||
bins[n] = .{ .table = &self.out.type_fn_heads, .domain = .type_fn_head };
|
||||
n += 1;
|
||||
}
|
||||
if (pr) {
|
||||
tables[n] = &self.out.protocol_heads;
|
||||
bins[n] = .{ .table = &self.out.protocol_heads, .domain = .protocol_head };
|
||||
n += 1;
|
||||
}
|
||||
if (n == 0) {
|
||||
@@ -946,13 +1127,18 @@ const ResolvePass = struct {
|
||||
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 });
|
||||
// each table OWNS its `AuthorSet.flat`; give the first bin the collected
|
||||
// slice and a fresh copy to every subsequent bin so `deinit` frees each
|
||||
// exactly once. The verdict is computed PER head kind — `Box` authored as a
|
||||
// generic struct in one module and a type-fn in another lands an own-wins /
|
||||
// single / ambiguous verdict independently in each table.
|
||||
const v0 = self.verdictOver(bins[0].domain, name, set, null);
|
||||
self.replaceRef(bins[0].table, node, .{ .authors = .{ .set = set, .verdict = v0 } });
|
||||
var i: usize = 1;
|
||||
while (i < n) : (i += 1) {
|
||||
self.replaceRef(tables[i], node, .{ .authors = self.dupAuthorSet(set) });
|
||||
const dup = self.dupAuthorSet(set);
|
||||
const vi = self.verdictOver(bins[i].domain, name, dup, null);
|
||||
self.replaceRef(bins[i].table, node, .{ .authors = .{ .set = dup, .verdict = vi } });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -987,7 +1173,7 @@ const ResolvePass = struct {
|
||||
|
||||
fn releaseRef(self: *ResolvePass, ref: ResolvedRef) void {
|
||||
switch (ref) {
|
||||
.authors => |a| if (a.flat.len > 0) self.out.alloc.free(a.flat),
|
||||
.authors => |a| if (a.set.flat.len > 0) self.out.alloc.free(a.set.flat),
|
||||
.template, .pack => {},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user