additive: compute resolver type-demanded verdicts
This commit is contained in:
@@ -901,6 +901,117 @@ fn typeParamVerdict(rp: *const resolver.ResolvedProgram, root: *const ast.Node,
|
||||
return refVerdict(rp.type_refs.get(node) orelse return null);
|
||||
}
|
||||
|
||||
fn firstCallArg(func: *const ast.Node, callee_name: []const u8) ?*const ast.Node {
|
||||
if (func.data != .fn_decl) return null;
|
||||
return firstCallArgInNode(func.data.fn_decl.body, callee_name);
|
||||
}
|
||||
|
||||
fn firstCallArgInNode(node: *const ast.Node, callee_name: []const u8) ?*const ast.Node {
|
||||
switch (node.data) {
|
||||
.call => |*c| {
|
||||
if (c.callee.data == .identifier and
|
||||
std.mem.eql(u8, c.callee.data.identifier.name, callee_name) and
|
||||
c.args.len > 0)
|
||||
{
|
||||
return c.args[0];
|
||||
}
|
||||
if (firstCallArgInNode(c.callee, callee_name)) |found| return found;
|
||||
for (c.args) |arg| if (firstCallArgInNode(arg, callee_name)) |found| return found;
|
||||
},
|
||||
.block => |*b| for (b.stmts) |stmt| {
|
||||
if (firstCallArgInNode(stmt, callee_name)) |found| return found;
|
||||
},
|
||||
.var_decl => |*vd| {
|
||||
if (vd.value) |v| if (firstCallArgInNode(v, callee_name)) |found| return found;
|
||||
},
|
||||
.const_decl => |*cd| if (firstCallArgInNode(cd.value, callee_name)) |found| return found,
|
||||
.binary_op => |*b| {
|
||||
if (firstCallArgInNode(b.lhs, callee_name)) |found| return found;
|
||||
if (firstCallArgInNode(b.rhs, callee_name)) |found| return found;
|
||||
},
|
||||
.if_expr => |*e| {
|
||||
if (firstCallArgInNode(e.condition, callee_name)) |found| return found;
|
||||
if (firstCallArgInNode(e.then_branch, callee_name)) |found| return found;
|
||||
if (e.else_branch) |b| if (firstCallArgInNode(b, callee_name)) |found| return found;
|
||||
},
|
||||
.match_expr => |*e| {
|
||||
if (firstCallArgInNode(e.subject, callee_name)) |found| return found;
|
||||
for (e.arms) |arm| {
|
||||
if (arm.pattern) |pat| if (firstCallArgInNode(pat, callee_name)) |found| return found;
|
||||
if (firstCallArgInNode(arm.body, callee_name)) |found| return found;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn localDeclValue(func: *const ast.Node, name: []const u8) ?*const ast.Node {
|
||||
if (func.data != .fn_decl) return null;
|
||||
const body = func.data.fn_decl.body;
|
||||
if (body.data != .block) return null;
|
||||
for (body.data.block.stmts) |stmt| switch (stmt.data) {
|
||||
.var_decl => |*vd| if (std.mem.eql(u8, vd.name, name)) return vd.value,
|
||||
.const_decl => |*cd| if (std.mem.eql(u8, cd.name, name)) return cd.value,
|
||||
else => {},
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
fn localDeclType(func: *const ast.Node, name: []const u8) ?*const ast.Node {
|
||||
if (func.data != .fn_decl) return null;
|
||||
const body = func.data.fn_decl.body;
|
||||
if (body.data != .block) return null;
|
||||
for (body.data.block.stmts) |stmt| switch (stmt.data) {
|
||||
.var_decl => |*vd| if (std.mem.eql(u8, vd.name, name)) return vd.type_annotation,
|
||||
.const_decl => |*cd| if (std.mem.eql(u8, cd.name, name)) return cd.type_annotation,
|
||||
else => {},
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
fn firstMatchPattern(root: *const ast.Node, name: []const u8) ?*const ast.Node {
|
||||
return firstMatchPatternInNode(root, name);
|
||||
}
|
||||
|
||||
fn firstMatchPatternInNode(node: *const ast.Node, name: []const u8) ?*const ast.Node {
|
||||
switch (node.data) {
|
||||
.root => |*r| for (r.decls) |decl| {
|
||||
if (firstMatchPatternInNode(decl, name)) |found| return found;
|
||||
},
|
||||
.fn_decl => |*fd| {
|
||||
if (firstMatchPatternInNode(fd.body, name)) |found| return found;
|
||||
},
|
||||
.block => |*b| for (b.stmts) |stmt| {
|
||||
if (firstMatchPatternInNode(stmt, name)) |found| return found;
|
||||
},
|
||||
.var_decl => |*vd| {
|
||||
if (vd.value) |v| if (firstMatchPatternInNode(v, name)) |found| return found;
|
||||
},
|
||||
.const_decl => |*cd| if (firstMatchPatternInNode(cd.value, name)) |found| return found,
|
||||
.if_expr => |*e| {
|
||||
if (firstMatchPatternInNode(e.condition, name)) |found| return found;
|
||||
if (firstMatchPatternInNode(e.then_branch, name)) |found| return found;
|
||||
if (e.else_branch) |b| if (firstMatchPatternInNode(b, name)) |found| return found;
|
||||
},
|
||||
.match_expr => |*e| {
|
||||
for (e.arms) |arm| {
|
||||
if (arm.pattern) |pat| {
|
||||
const pat_name = switch (pat.data) {
|
||||
.identifier => |id| id.name,
|
||||
.type_expr => |te| te.name,
|
||||
else => "",
|
||||
};
|
||||
if (std.mem.eql(u8, pat_name, name)) return pat;
|
||||
}
|
||||
if (firstMatchPatternInNode(arm.body, name)) |found| return found;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
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),
|
||||
@@ -994,6 +1105,84 @@ test "resolver: verdicts — own-wins / single / ambiguous / domain-filtered / n
|
||||
try std.testing.expect(saw_thing_value);
|
||||
}
|
||||
|
||||
test "resolver: verdicts — foreign classes obey the requested domain filter" {
|
||||
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 = "vals.sx", .data = "MixedValue :: 42;\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "types.sx", .data = "MixedType :: struct { x: s64 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "foreign.sx", .data =
|
||||
\\MixedValue :: #foreign #objc_class("MixedValue") {}
|
||||
\\MixedType :: #foreign #objc_class("MixedType") {}
|
||||
\\OnlyForeign :: #foreign #objc_class("OnlyForeign") {}
|
||||
\\
|
||||
});
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data =
|
||||
\\#import "vals.sx";
|
||||
\\#import "types.sx";
|
||||
\\#import "foreign.sx";
|
||||
\\use_foreign :: (x: OnlyForeign) -> s64 { 0 }
|
||||
\\use_mixed_type :: (x: MixedType) -> s64 { 0 }
|
||||
\\read_value :: () -> s64 { MixedValue }
|
||||
\\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});
|
||||
|
||||
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();
|
||||
|
||||
// A pure foreign class still routes to the foreign-class side table.
|
||||
const use_foreign = findFn(prog.root, "use_foreign") orelse return error.MissingUseForeign;
|
||||
const only_foreign = use_foreign.data.fn_decl.params[0].type_expr;
|
||||
try std.testing.expect(rp.type_refs.get(only_foreign) == null);
|
||||
const only_foreign_ref = rp.foreign_class_refs.get(only_foreign) orelse return error.OnlyForeignNotKeyed;
|
||||
try std.testing.expectEqual(resolver.Verdict.single, refVerdict(only_foreign_ref).?);
|
||||
|
||||
// A value-position name with one flat const author plus one same-name foreign
|
||||
// class is selected as a value const, not hijacked into foreign_class_refs.
|
||||
var saw_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, "MixedValue")) {
|
||||
try std.testing.expectEqual(resolver.Verdict.single, refVerdict(e.value_ptr.*).?);
|
||||
_ = flatAuthorOfKind(e.value_ptr.authors.set, .const_decl) orelse return error.MixedValueConstMissing;
|
||||
try std.testing.expect(rp.foreign_class_refs.get(k) == null);
|
||||
saw_value = true;
|
||||
}
|
||||
}
|
||||
try std.testing.expect(saw_value);
|
||||
|
||||
// A type-position name with one flat struct author plus one same-name foreign
|
||||
// class is a type-domain ambiguity. The foreign class is type-eligible, but it
|
||||
// does not preempt the struct author by routing the site away from type_refs.
|
||||
const use_mixed = findFn(prog.root, "use_mixed_type") orelse return error.MissingUseMixed;
|
||||
const mixed_type = use_mixed.data.fn_decl.params[0].type_expr;
|
||||
try std.testing.expect(rp.foreign_class_refs.get(mixed_type) == null);
|
||||
const mixed_ref = rp.type_refs.get(mixed_type) orelse return error.MixedTypeNotKeyed;
|
||||
try std.testing.expectEqual(resolver.Verdict.ambiguous, refVerdict(mixed_ref).?);
|
||||
_ = flatAuthorOfKind(mixed_ref.authors.set, .struct_decl) orelse return error.MixedStructMissing;
|
||||
_ = flatAuthorOfKind(mixed_ref.authors.set, .foreign_class_decl) orelse return error.MixedForeignMissing;
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -1020,10 +1209,24 @@ test "resolver: verdicts — resolver-target corpus (0811/0821/0829 → ambiguou
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "ambig.sx", .data =
|
||||
\\#import "a.sx";
|
||||
\\#import "b.sx";
|
||||
\\describe :: ($T: Type) -> s32 {
|
||||
\\ r := if T == {
|
||||
\\ case IoErr: 1;
|
||||
\\ else: 0;
|
||||
\\ }
|
||||
\\ r
|
||||
\\}
|
||||
\\fail :: (e: IoErr) -> s32 { 0 }
|
||||
\\fail_io :: () -> !IoErr { raise error.Disk; }
|
||||
\\use_cmp :: (c: Cmp(s64)) -> s64 { 0 }
|
||||
\\use_box :: (b: Box(s64)) -> s64 { 0 }
|
||||
\\main :: () -> s32 { 0 }
|
||||
\\main :: () -> s32 {
|
||||
\\ sz := size_of(IoErr);
|
||||
\\ e : IoErr = error.Disk;
|
||||
\\ t : Type = IoErr;
|
||||
\\ k := describe(s64);
|
||||
\\ return 0;
|
||||
\\}
|
||||
\\
|
||||
});
|
||||
// Querying module authors its OWN IoErr / Cmp / Box → own-wins, even against the
|
||||
@@ -1034,10 +1237,24 @@ test "resolver: verdicts — resolver-target corpus (0811/0821/0829 → ambiguou
|
||||
\\IoErr :: error { Disk, Net }
|
||||
\\Cmp :: protocol(T: Type) { get :: () -> T; }
|
||||
\\Box :: struct($T: Type) { value: T }
|
||||
\\describe :: ($T: Type) -> s32 {
|
||||
\\ r := if T == {
|
||||
\\ case IoErr: 1;
|
||||
\\ else: 0;
|
||||
\\ }
|
||||
\\ r
|
||||
\\}
|
||||
\\fail :: (e: IoErr) -> s32 { 0 }
|
||||
\\fail_io :: () -> !IoErr { raise error.Disk; }
|
||||
\\use_cmp :: (c: Cmp(s64)) -> s64 { 0 }
|
||||
\\use_box :: (b: Box(s64)) -> s64 { 0 }
|
||||
\\main :: () -> s32 { 0 }
|
||||
\\main :: () -> s32 {
|
||||
\\ sz := size_of(IoErr);
|
||||
\\ e : IoErr = error.Disk;
|
||||
\\ t : Type = IoErr;
|
||||
\\ k := describe(s64);
|
||||
\\ return 0;
|
||||
\\}
|
||||
\\
|
||||
});
|
||||
|
||||
@@ -1059,6 +1276,25 @@ test "resolver: verdicts — resolver-target corpus (0811/0821/0829 → ambiguou
|
||||
|
||||
// 0811-class: bare error-set type reference → ambiguous.
|
||||
try std.testing.expectEqual(resolver.Verdict.ambiguous, typeParamVerdict(&rp, prog.root, "fail").?);
|
||||
const fail_io = findFn(prog.root, "fail_io") orelse return error.FailIoMissing;
|
||||
const fail_err = fail_io.data.fn_decl.return_type orelse return error.FailIoRetMissing;
|
||||
try std.testing.expectEqual(resolver.Verdict.ambiguous, refVerdict(rp.type_refs.get(fail_err) orelse return error.FailIoTypeNotKeyed).?);
|
||||
|
||||
const main_fn = findFn(prog.root, "main") orelse return error.MainMissing;
|
||||
const size_arg = firstCallArg(main_fn, "size_of") orelse return error.SizeArgMissing;
|
||||
try std.testing.expectEqual(resolver.Verdict.ambiguous, refVerdict(rp.type_refs.get(size_arg) orelse return error.SizeArgTypeNotKeyed).?);
|
||||
try std.testing.expect(rp.value_refs.get(size_arg) == null);
|
||||
|
||||
const local_e_ty = localDeclType(main_fn, "e") orelse return error.LocalETypeMissing;
|
||||
try std.testing.expectEqual(resolver.Verdict.ambiguous, refVerdict(rp.type_refs.get(local_e_ty) orelse return error.LocalETypeNotKeyed).?);
|
||||
|
||||
const type_value = localDeclValue(main_fn, "t") orelse return error.TypeValueMissing;
|
||||
try std.testing.expectEqual(resolver.Verdict.ambiguous, refVerdict(rp.type_refs.get(type_value) orelse return error.TypeValueTypeNotKeyed).?);
|
||||
try std.testing.expect(rp.value_refs.get(type_value) == null);
|
||||
|
||||
const match_pat = firstMatchPattern(prog.root, "IoErr") orelse return error.MatchPatternMissing;
|
||||
try std.testing.expectEqual(resolver.Verdict.ambiguous, refVerdict(rp.type_refs.get(match_pat) orelse return error.MatchPatternTypeNotKeyed).?);
|
||||
try std.testing.expect(rp.value_refs.get(match_pat) == null);
|
||||
|
||||
// 0821-class: parameterized protocol head → ambiguous (protocol_heads).
|
||||
const cmp_head = paramTypeNode(prog.root, "use_cmp").?;
|
||||
@@ -1088,6 +1324,22 @@ test "resolver: verdicts — resolver-target corpus (0811/0821/0829 → ambiguou
|
||||
defer rp.deinit();
|
||||
|
||||
try std.testing.expectEqual(resolver.Verdict.own_wins, typeParamVerdict(&rp, prog.root, "fail").?);
|
||||
const fail_io = findFn(prog.root, "fail_io") orelse return error.FailIoMissing;
|
||||
const fail_err = fail_io.data.fn_decl.return_type orelse return error.FailIoRetMissing;
|
||||
try std.testing.expectEqual(resolver.Verdict.own_wins, refVerdict(rp.type_refs.get(fail_err) orelse return error.FailIoTypeNotKeyed).?);
|
||||
|
||||
const main_fn = findFn(prog.root, "main") orelse return error.MainMissing;
|
||||
const size_arg = firstCallArg(main_fn, "size_of") orelse return error.SizeArgMissing;
|
||||
try std.testing.expectEqual(resolver.Verdict.own_wins, refVerdict(rp.type_refs.get(size_arg) orelse return error.SizeArgTypeNotKeyed).?);
|
||||
|
||||
const local_e_ty = localDeclType(main_fn, "e") orelse return error.LocalETypeMissing;
|
||||
try std.testing.expectEqual(resolver.Verdict.own_wins, refVerdict(rp.type_refs.get(local_e_ty) orelse return error.LocalETypeNotKeyed).?);
|
||||
|
||||
const type_value = localDeclValue(main_fn, "t") orelse return error.TypeValueMissing;
|
||||
try std.testing.expectEqual(resolver.Verdict.own_wins, refVerdict(rp.type_refs.get(type_value) orelse return error.TypeValueTypeNotKeyed).?);
|
||||
|
||||
const match_pat = firstMatchPattern(prog.root, "IoErr") orelse return error.MatchPatternMissing;
|
||||
try std.testing.expectEqual(resolver.Verdict.own_wins, refVerdict(rp.type_refs.get(match_pat) orelse return error.MatchPatternTypeNotKeyed).?);
|
||||
|
||||
const cmp_head = paramTypeNode(prog.root, "use_cmp").?;
|
||||
const cmp_ref = rp.protocol_heads.get(cmp_head) orelse return error.CmpHeadNotKeyed;
|
||||
|
||||
Reference in New Issue
Block a user