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:
@@ -372,8 +372,8 @@ fn expectTypeRefOwnTag(
|
||||
) !void {
|
||||
const ref = rp.type_refs.get(node) orelse return error.MissingTypeRef;
|
||||
try std.testing.expect(ref == .authors);
|
||||
try std.testing.expect(ref.authors.own != null);
|
||||
try std.testing.expectEqual(expected, std.meta.activeTag(ref.authors.own.?.raw));
|
||||
try std.testing.expect(ref.authors.set.own != null);
|
||||
try std.testing.expectEqual(expected, std.meta.activeTag(ref.authors.set.own.?.raw));
|
||||
}
|
||||
|
||||
// The pass populates the three bare-name domains over REAL Phase A facts: a type
|
||||
@@ -435,10 +435,10 @@ test "resolver: resolve — bare-name domains populated, keyed by node, symbolic
|
||||
const point_te = use_point.data.fn_decl.params[0].type_expr;
|
||||
const point_ref = rp.type_refs.get(point_te) orelse return error.PointNotKeyed;
|
||||
try std.testing.expect(point_ref == .authors);
|
||||
try std.testing.expect(point_ref.authors.own != null);
|
||||
try std.testing.expect(point_ref.authors.set.own != null);
|
||||
try std.testing.expectEqual(
|
||||
std.meta.Tag(resolver.RawDeclRef).struct_decl,
|
||||
std.meta.activeTag(point_ref.authors.own.?.raw),
|
||||
std.meta.activeTag(point_ref.authors.set.own.?.raw),
|
||||
);
|
||||
|
||||
// (3) A value/const reference (`LIMIT`) collected RAW to its own author, and a
|
||||
@@ -449,7 +449,7 @@ test "resolver: resolve — bare-name domains populated, keyed by node, symbolic
|
||||
const k = e.key_ptr.*;
|
||||
if (k.data == .identifier and std.mem.eql(u8, k.data.identifier.name, "LIMIT")) {
|
||||
try std.testing.expect(e.value_ptr.* == .authors);
|
||||
try std.testing.expect(e.value_ptr.authors.own != null);
|
||||
try std.testing.expect(e.value_ptr.authors.set.own != null);
|
||||
saw_limit = true;
|
||||
}
|
||||
}
|
||||
@@ -462,7 +462,7 @@ test "resolver: resolve — bare-name domains populated, keyed by node, symbolic
|
||||
try std.testing.expect(k.data == .identifier); // callable heads key bare-name callees
|
||||
if (std.mem.eql(u8, k.data.identifier.name, "helper")) {
|
||||
try std.testing.expect(e.value_ptr.* == .authors);
|
||||
try std.testing.expect(e.value_ptr.authors.flat.len == 1); // authored only in lib.sx
|
||||
try std.testing.expect(e.value_ptr.authors.set.flat.len == 1); // authored only in lib.sx
|
||||
saw_helper = true;
|
||||
}
|
||||
}
|
||||
@@ -632,8 +632,8 @@ test "resolver: resolve — namespace-qualified + generic-struct/type-fn/protoco
|
||||
try std.testing.expect(k.data == .field_access); // namespace refs key the access node
|
||||
if (std.mem.eql(u8, k.data.field_access.field, "helper_fn")) {
|
||||
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));
|
||||
try std.testing.expect(e.value_ptr.authors.set.own != null);
|
||||
try std.testing.expectEqual(fn_tag, std.meta.activeTag(e.value_ptr.authors.set.own.?.raw));
|
||||
saw_ns = true;
|
||||
}
|
||||
}
|
||||
@@ -646,8 +646,8 @@ test "resolver: resolve — namespace-qualified + generic-struct/type-fn/protoco
|
||||
try std.testing.expect(box_head.data == .parameterized_type_expr);
|
||||
const box_ref = rp.generic_struct_heads.get(box_head) orelse return error.BoxHeadNotKeyed;
|
||||
try std.testing.expect(box_ref == .authors);
|
||||
try std.testing.expect(box_ref.authors.own != null);
|
||||
try std.testing.expectEqual(struct_tag, std.meta.activeTag(box_ref.authors.own.?.raw));
|
||||
try std.testing.expect(box_ref.authors.set.own != null);
|
||||
try std.testing.expectEqual(struct_tag, std.meta.activeTag(box_ref.authors.set.own.?.raw));
|
||||
// and it is NOT mis-binned into the other head tables.
|
||||
try std.testing.expect(rp.type_fn_heads.get(box_head) == null);
|
||||
try std.testing.expect(rp.protocol_heads.get(box_head) == null);
|
||||
@@ -658,8 +658,8 @@ test "resolver: resolve — namespace-qualified + generic-struct/type-fn/protoco
|
||||
try std.testing.expect(make_head.data == .parameterized_type_expr);
|
||||
const make_ref = rp.type_fn_heads.get(make_head) orelse return error.MakeHeadNotKeyed;
|
||||
try std.testing.expect(make_ref == .authors);
|
||||
try std.testing.expect(make_ref.authors.own != null);
|
||||
try std.testing.expectEqual(fn_tag, std.meta.activeTag(make_ref.authors.own.?.raw));
|
||||
try std.testing.expect(make_ref.authors.set.own != null);
|
||||
try std.testing.expectEqual(fn_tag, std.meta.activeTag(make_ref.authors.set.own.?.raw));
|
||||
|
||||
// (4) Protocol head: the `Cmp(s64)` value-type param keyed in protocol_heads.
|
||||
const use_cmp = findFn(prog.root, "use_cmp") orelse return error.MissingFn;
|
||||
@@ -667,8 +667,8 @@ test "resolver: resolve — namespace-qualified + generic-struct/type-fn/protoco
|
||||
try std.testing.expect(cmp_head.data == .parameterized_type_expr);
|
||||
const cmp_ref = rp.protocol_heads.get(cmp_head) orelse return error.CmpHeadNotKeyed;
|
||||
try std.testing.expect(cmp_ref == .authors);
|
||||
try std.testing.expect(cmp_ref.authors.own != null);
|
||||
try std.testing.expectEqual(protocol_tag, std.meta.activeTag(cmp_ref.authors.own.?.raw));
|
||||
try std.testing.expect(cmp_ref.authors.set.own != null);
|
||||
try std.testing.expectEqual(protocol_tag, std.meta.activeTag(cmp_ref.authors.set.own.?.raw));
|
||||
|
||||
// (4b) Namespace-qualified parameterized heads are folded by the parser into
|
||||
// a single parameterized_type_expr name (`g.NsBox`), not a field_access.
|
||||
@@ -680,9 +680,9 @@ test "resolver: resolve — namespace-qualified + generic-struct/type-fn/protoco
|
||||
try std.testing.expectEqualStrings("g.NsBox", ns_box_head.data.parameterized_type_expr.name);
|
||||
const ns_box_ref = rp.generic_struct_heads.get(ns_box_head) orelse return error.NsBoxHeadNotKeyed;
|
||||
try std.testing.expect(ns_box_ref == .authors);
|
||||
try std.testing.expect(ns_box_ref.authors.own != null);
|
||||
try std.testing.expectEqual(struct_tag, std.meta.activeTag(ns_box_ref.authors.own.?.raw));
|
||||
try std.testing.expectEqualStrings(lib_path, ns_box_ref.authors.own.?.source);
|
||||
try std.testing.expect(ns_box_ref.authors.set.own != null);
|
||||
try std.testing.expectEqual(struct_tag, std.meta.activeTag(ns_box_ref.authors.set.own.?.raw));
|
||||
try std.testing.expectEqualStrings(lib_path, ns_box_ref.authors.set.own.?.source);
|
||||
try std.testing.expect(rp.type_fn_heads.get(ns_box_head) == null);
|
||||
try std.testing.expect(rp.protocol_heads.get(ns_box_head) == null);
|
||||
|
||||
@@ -692,9 +692,9 @@ test "resolver: resolve — namespace-qualified + generic-struct/type-fn/protoco
|
||||
try std.testing.expectEqualStrings("g.NsMake", ns_make_head.data.parameterized_type_expr.name);
|
||||
const ns_make_ref = rp.type_fn_heads.get(ns_make_head) orelse return error.NsMakeHeadNotKeyed;
|
||||
try std.testing.expect(ns_make_ref == .authors);
|
||||
try std.testing.expect(ns_make_ref.authors.own != null);
|
||||
try std.testing.expectEqual(fn_tag, std.meta.activeTag(ns_make_ref.authors.own.?.raw));
|
||||
try std.testing.expectEqualStrings(lib_path, ns_make_ref.authors.own.?.source);
|
||||
try std.testing.expect(ns_make_ref.authors.set.own != null);
|
||||
try std.testing.expectEqual(fn_tag, std.meta.activeTag(ns_make_ref.authors.set.own.?.raw));
|
||||
try std.testing.expectEqualStrings(lib_path, ns_make_ref.authors.set.own.?.source);
|
||||
try std.testing.expect(rp.generic_struct_heads.get(ns_make_head) == null);
|
||||
try std.testing.expect(rp.protocol_heads.get(ns_make_head) == null);
|
||||
|
||||
@@ -704,9 +704,9 @@ test "resolver: resolve — namespace-qualified + generic-struct/type-fn/protoco
|
||||
try std.testing.expectEqualStrings("g.NsCmp", ns_cmp_head.data.parameterized_type_expr.name);
|
||||
const ns_cmp_ref = rp.protocol_heads.get(ns_cmp_head) orelse return error.NsCmpHeadNotKeyed;
|
||||
try std.testing.expect(ns_cmp_ref == .authors);
|
||||
try std.testing.expect(ns_cmp_ref.authors.own != null);
|
||||
try std.testing.expectEqual(protocol_tag, std.meta.activeTag(ns_cmp_ref.authors.own.?.raw));
|
||||
try std.testing.expectEqualStrings(lib_path, ns_cmp_ref.authors.own.?.source);
|
||||
try std.testing.expect(ns_cmp_ref.authors.set.own != null);
|
||||
try std.testing.expectEqual(protocol_tag, std.meta.activeTag(ns_cmp_ref.authors.set.own.?.raw));
|
||||
try std.testing.expectEqualStrings(lib_path, ns_cmp_ref.authors.set.own.?.source);
|
||||
try std.testing.expect(rp.generic_struct_heads.get(ns_cmp_head) == null);
|
||||
try std.testing.expect(rp.type_fn_heads.get(ns_cmp_head) == null);
|
||||
|
||||
@@ -810,8 +810,8 @@ test "resolver: resolve — S2.1c foreign-class/struct-const/UFCS + all ten doma
|
||||
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(obj_ref.authors.set.own != null);
|
||||
try std.testing.expectEqual(foreign_tag, std.meta.activeTag(obj_ref.authors.set.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
|
||||
@@ -823,8 +823,8 @@ test "resolver: resolve — S2.1c foreign-class/struct-const/UFCS + all ten doma
|
||||
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));
|
||||
try std.testing.expect(e.value_ptr.authors.set.own != null);
|
||||
try std.testing.expectEqual(struct_tag, std.meta.activeTag(e.value_ptr.authors.set.own.?.raw));
|
||||
saw_const = true;
|
||||
}
|
||||
}
|
||||
@@ -835,8 +835,8 @@ test "resolver: resolve — S2.1c foreign-class/struct-const/UFCS + all ten doma
|
||||
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));
|
||||
try std.testing.expect(plus_ref.authors.set.own != null);
|
||||
try std.testing.expectEqual(fn_tag, std.meta.activeTag(plus_ref.authors.set.own.?.raw));
|
||||
|
||||
// (3b) UFCS rewrite sites: both the forward `plus(3, 4)` site and the later
|
||||
// `plus(1, 2)` site are keyed in ufcs_refs (NOT callable_refs) and
|
||||
@@ -845,8 +845,8 @@ test "resolver: resolve — S2.1c foreign-class/struct-const/UFCS + all ten doma
|
||||
const before_alias_callee = firstBareCallCallee(before_alias, "plus") orelse return error.MissingBeforeAliasCallee;
|
||||
const before_ref = rp.ufcs_refs.get(before_alias_callee) orelse return error.BeforeAliasUfcsNotKeyed;
|
||||
try std.testing.expect(before_ref == .authors);
|
||||
try std.testing.expect(before_ref.authors.own != null);
|
||||
try std.testing.expectEqual(fn_tag, std.meta.activeTag(before_ref.authors.own.?.raw));
|
||||
try std.testing.expect(before_ref.authors.set.own != null);
|
||||
try std.testing.expectEqual(fn_tag, std.meta.activeTag(before_ref.authors.set.own.?.raw));
|
||||
try std.testing.expect(rp.callable_refs.get(before_alias_callee) == null);
|
||||
|
||||
var saw_after_site = false;
|
||||
@@ -855,8 +855,8 @@ test "resolver: resolve — S2.1c foreign-class/struct-const/UFCS + all ten doma
|
||||
const k = e.key_ptr.*;
|
||||
if (k != before_alias_callee and 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));
|
||||
try std.testing.expect(e.value_ptr.authors.set.own != null);
|
||||
try std.testing.expectEqual(fn_tag, std.meta.activeTag(e.value_ptr.authors.set.own.?.raw));
|
||||
saw_after_site = true;
|
||||
}
|
||||
}
|
||||
@@ -868,3 +868,233 @@ test "resolver: resolve — S2.1c foreign-class/struct-const/UFCS + all ten doma
|
||||
try std.testing.expect(!std.mem.eql(u8, e.key_ptr.*.data.identifier.name, "plus"));
|
||||
}
|
||||
}
|
||||
|
||||
// ── the verdict layer (S2.2) ───────────────────────────────────────────────
|
||||
|
||||
/// The verdict on a `.authors` resolved ref, or null for a template/pack ref.
|
||||
fn refVerdict(ref: resolver.ResolvedRef) ?resolver.Verdict {
|
||||
return switch (ref) {
|
||||
.authors => |a| a.verdict,
|
||||
.template, .pack => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// The first flat author of `set` whose raw decl is `kind`, or null — lets a test
|
||||
/// pick the domain-eligible author out of an UNFILTERED same-name set.
|
||||
fn flatAuthorOfKind(set: resolver.AuthorSet, comptime kind: std.meta.Tag(resolver.RawDeclRef)) ?resolver.RawAuthor {
|
||||
for (set.flat) |fa| {
|
||||
if (std.meta.activeTag(fa.raw) == kind) return fa;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// The first parameter's type-expr node of the top-level fn `name`.
|
||||
fn paramTypeNode(root: *const ast.Node, name: []const u8) ?*const ast.Node {
|
||||
const fnd = findFn(root, name) orelse return null;
|
||||
if (fnd.data.fn_decl.params.len == 0) return null;
|
||||
return fnd.data.fn_decl.params[0].type_expr;
|
||||
}
|
||||
|
||||
/// The verdict of the first-param TYPE reference of fn `name` from `rp.type_refs`.
|
||||
fn typeParamVerdict(rp: *const resolver.ResolvedProgram, root: *const ast.Node, name: []const u8) ?resolver.Verdict {
|
||||
const node = paramTypeNode(root, name) orelse return null;
|
||||
return refVerdict(rp.type_refs.get(node) orelse return null);
|
||||
}
|
||||
|
||||
// S2.2 attaches the selection verdict to every `.authors` ref, computed over the
|
||||
// DOMAIN-ELIGIBLE subset of the collected author set. This proves all five outcomes
|
||||
// on the BARE-TYPE domain over real Phase A facts: own_wins (own author),
|
||||
// single (one flat type author), ambiguous (≥2 flat type authors), domain_filtered
|
||||
// (the only visible same-name author is a VALUE — the type-vs-value filter excludes
|
||||
// it), and not_visible (authored as a type only over a namespace edge). The same
|
||||
// fixture proves the type-vs-value filter from the VALUE side: a name authored as a
|
||||
// struct AND a value-const resolves to the STRUCT in type position and the CONST in
|
||||
// value position.
|
||||
test "resolver: verdicts — own-wins / single / ambiguous / domain-filtered / not-visible" {
|
||||
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 = "lib1.sx", .data = "Widget :: struct { x: s64 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "onlyval.sx", .data = "OnlyVal :: 7;\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "p.sx", .data = "Secret :: struct { s: s64 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "a.sx", .data = "Dup :: struct { d: s64 }\nThing :: struct { t: s64 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "b.sx", .data = "Dup :: struct { d: s64 }\nThing :: 99;\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data =
|
||||
\\#import "lib1.sx";
|
||||
\\#import "onlyval.sx";
|
||||
\\#import "a.sx";
|
||||
\\#import "b.sx";
|
||||
\\g :: #import "p.sx";
|
||||
\\Gadget :: struct { y: s64 }
|
||||
\\use_own :: (x: Gadget) -> s64 { 0 }
|
||||
\\use_single :: (x: Widget) -> s64 { 0 }
|
||||
\\use_ambig :: (x: Dup) -> s64 { 0 }
|
||||
\\use_filtered :: (x: OnlyVal) -> s64 { 0 }
|
||||
\\use_notvis :: (x: Secret) -> s64 { 0 }
|
||||
\\use_thing :: (x: Thing) -> s64 { 0 }
|
||||
\\read_thing :: () -> s64 { Thing }
|
||||
\\main :: () -> s32 { 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});
|
||||
const a_path = try std.fmt.allocPrint(alloc, "{s}/a.sx", .{absdir});
|
||||
const b_path = try std.fmt.allocPrint(alloc, "{s}/b.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();
|
||||
|
||||
// The five bare-TYPE verdicts.
|
||||
try std.testing.expectEqual(resolver.Verdict.own_wins, typeParamVerdict(&rp, prog.root, "use_own").?);
|
||||
try std.testing.expectEqual(resolver.Verdict.single, typeParamVerdict(&rp, prog.root, "use_single").?);
|
||||
try std.testing.expectEqual(resolver.Verdict.ambiguous, typeParamVerdict(&rp, prog.root, "use_ambig").?);
|
||||
try std.testing.expectEqual(resolver.Verdict.domain_filtered, typeParamVerdict(&rp, prog.root, "use_filtered").?);
|
||||
try std.testing.expectEqual(resolver.Verdict.not_visible, typeParamVerdict(&rp, prog.root, "use_notvis").?);
|
||||
|
||||
// Type-vs-value filter: `Thing` (struct in a.sx, value-const in b.sx). The RAW
|
||||
// set keeps BOTH same-name flat authors; the verdict is computed over the
|
||||
// domain-eligible subset only — in TYPE position just the struct counts → single,
|
||||
// and the eligible author is the struct, sourced to a.sx.
|
||||
const thing_type = paramTypeNode(prog.root, "use_thing").?;
|
||||
const thing_type_ref = rp.type_refs.get(thing_type) orelse return error.ThingTypeNotKeyed;
|
||||
try std.testing.expectEqual(resolver.Verdict.single, refVerdict(thing_type_ref).?);
|
||||
try std.testing.expectEqual(@as(usize, 2), thing_type_ref.authors.set.flat.len); // unfiltered
|
||||
const thing_struct = flatAuthorOfKind(thing_type_ref.authors.set, .struct_decl) orelse return error.NoThingStruct;
|
||||
try std.testing.expectEqualStrings(a_path, thing_struct.source);
|
||||
|
||||
// In VALUE position only the const is eligible → single, and the eligible author
|
||||
// is the const, sourced to b.sx — the same two-author set, the other domain.
|
||||
var saw_thing_value = false;
|
||||
var vit = rp.value_refs.iterator();
|
||||
while (vit.next()) |e| {
|
||||
const k = e.key_ptr.*;
|
||||
if (k.data == .identifier and std.mem.eql(u8, k.data.identifier.name, "Thing")) {
|
||||
try std.testing.expectEqual(resolver.Verdict.single, refVerdict(e.value_ptr.*).?);
|
||||
const thing_const = flatAuthorOfKind(e.value_ptr.authors.set, .const_decl) orelse return error.NoThingConst;
|
||||
try std.testing.expectEqualStrings(b_path, thing_const.source);
|
||||
saw_thing_value = true;
|
||||
}
|
||||
}
|
||||
try std.testing.expect(saw_thing_value);
|
||||
}
|
||||
|
||||
// The acceptance proof: querying the resolver produces the TARGET verdicts for the
|
||||
// resolver-target corpus (which the OLD per-kind selectors get WRONG on this base) —
|
||||
// 0811-class error-set, 0821-class protocol head, and 0829-class generic-struct
|
||||
// (concrete `*Box` prefix) all → AMBIGUOUS when two flat modules author them and the
|
||||
// querying module authors none; all → OWN_WINS when the querying module authors its
|
||||
// own. This is the uniform verdict the old error-set / protocol / param-impl
|
||||
// selectors fail to compute (they silently pick a global last-wins author). The
|
||||
// corpus stays xfail in run_examples (lowering does not consume these until S3.9);
|
||||
// the verdicts are asserted HERE via the resolver test harness, not by flipping any
|
||||
// golden.
|
||||
test "resolver: verdicts — resolver-target corpus (0811/0821/0829 → ambiguous; own-wins)" {
|
||||
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();
|
||||
|
||||
const author_module = "IoErr :: error { Disk, Net }\nCmp :: protocol(T: Type) { get :: () -> T; }\nBox :: struct($T: Type) { value: T }\n";
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "a.sx", .data = author_module });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "b.sx", .data = author_module });
|
||||
// Querying module authors NONE itself → every bare reference is ambiguous.
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "ambig.sx", .data =
|
||||
\\#import "a.sx";
|
||||
\\#import "b.sx";
|
||||
\\fail :: (e: IoErr) -> s32 { 0 }
|
||||
\\use_cmp :: (c: Cmp(s64)) -> s64 { 0 }
|
||||
\\use_box :: (b: Box(s64)) -> s64 { 0 }
|
||||
\\main :: () -> s32 { 0 }
|
||||
\\
|
||||
});
|
||||
// Querying module authors its OWN IoErr / Cmp / Box → own-wins, even against the
|
||||
// two same-name flat imports.
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "ownwins.sx", .data =
|
||||
\\#import "a.sx";
|
||||
\\#import "b.sx";
|
||||
\\IoErr :: error { Disk, Net }
|
||||
\\Cmp :: protocol(T: Type) { get :: () -> T; }
|
||||
\\Box :: struct($T: Type) { value: T }
|
||||
\\fail :: (e: IoErr) -> s32 { 0 }
|
||||
\\use_cmp :: (c: Cmp(s64)) -> s64 { 0 }
|
||||
\\use_box :: (b: Box(s64)) -> s64 { 0 }
|
||||
\\main :: () -> s32 { 0 }
|
||||
\\
|
||||
});
|
||||
|
||||
var dirbuf: [4096]u8 = undefined;
|
||||
const absdir = dirbuf[0..try tmp.dir.realPath(io, &dirbuf)];
|
||||
|
||||
// (1) AMBIGUOUS — the 08xx SILENT-RESOLVE target.
|
||||
{
|
||||
const ambig_path = try std.fmt.allocPrint(alloc, "{s}/ambig.sx", .{absdir});
|
||||
var prog = try buildResolved(alloc, io, absdir, ambig_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, ambig_path, alloc);
|
||||
defer rp.deinit();
|
||||
|
||||
// 0811-class: bare error-set type reference → ambiguous.
|
||||
try std.testing.expectEqual(resolver.Verdict.ambiguous, typeParamVerdict(&rp, prog.root, "fail").?);
|
||||
|
||||
// 0821-class: parameterized protocol head → ambiguous (protocol_heads).
|
||||
const cmp_head = paramTypeNode(prog.root, "use_cmp").?;
|
||||
try std.testing.expect(cmp_head.data == .parameterized_type_expr);
|
||||
const cmp_ref = rp.protocol_heads.get(cmp_head) orelse return error.CmpHeadNotKeyed;
|
||||
try std.testing.expectEqual(resolver.Verdict.ambiguous, refVerdict(cmp_ref).?);
|
||||
|
||||
// 0829-class: concrete generic-struct head (`Box(..)`) → ambiguous
|
||||
// (generic_struct_heads).
|
||||
const box_head = paramTypeNode(prog.root, "use_box").?;
|
||||
try std.testing.expect(box_head.data == .parameterized_type_expr);
|
||||
const box_ref = rp.generic_struct_heads.get(box_head) orelse return error.BoxHeadNotKeyed;
|
||||
try std.testing.expectEqual(resolver.Verdict.ambiguous, refVerdict(box_ref).?);
|
||||
}
|
||||
|
||||
// (2) OWN-WINS — the 08xx OWN-WINS target.
|
||||
{
|
||||
const own_path = try std.fmt.allocPrint(alloc, "{s}/ownwins.sx", .{absdir});
|
||||
var prog = try buildResolved(alloc, io, absdir, own_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, own_path, alloc);
|
||||
defer rp.deinit();
|
||||
|
||||
try std.testing.expectEqual(resolver.Verdict.own_wins, typeParamVerdict(&rp, prog.root, "fail").?);
|
||||
|
||||
const cmp_head = paramTypeNode(prog.root, "use_cmp").?;
|
||||
const cmp_ref = rp.protocol_heads.get(cmp_head) orelse return error.CmpHeadNotKeyed;
|
||||
try std.testing.expectEqual(resolver.Verdict.own_wins, refVerdict(cmp_ref).?);
|
||||
|
||||
const box_head = paramTypeNode(prog.root, "use_box").?;
|
||||
const box_ref = rp.generic_struct_heads.get(box_head) orelse return error.BoxHeadNotKeyed;
|
||||
try std.testing.expectEqual(resolver.Verdict.own_wins, refVerdict(box_ref).?);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user