refactor(ir): factor protocol/impl planning into ProtocolResolver (A4.2 planning increment)
Factor the lookup/planning half of the protocol emission functions into protocols.zig, keeping IR emission in Lowering (PLAN-ARCH A4.2 final increment): - protocolMethodInfos(proto) — the dispatch method table = which methods getOrCreateThunks must thunk. getOrCreateThunks now does PLANNING via this + EMISSION (createProtocolThunk loop) in Lowering. - findVisibleImpls(entries, out) — moved verbatim (pure BFS over the import graph; the cross-module visibility selection behind the 0410 path). tryUserConversion calls it via the resolver. - matchPackImpl(src_ty, pack_key) -> ?PackImplMatch — the pure pack-impl matching loop (prefix + return match) + convert-method find, returning the matched entry + convert fd + src params/ret. tryPackImplMatch consumes it; the binding + monomorphise + call emission stays in Lowering. Emission untouched: createProtocolThunk, buildProtocolValue, and the monomorphise+call tails of tryUserConversion / tryPackImplMatch remain in Lowering. The reentrancy guard, key-build, and the Into no-visible / duplicate / recursive diagnostics stay in tryUserConversion (byte-for-byte). lower.zig net -94 lines. No new pub exposure (uses the existing ParamImplEntry / PackParamImplEntry / formatTypeName surface). protocols.test.zig +3: protocolMethodInfos (method table + null-for-unknown, no silent empty default); findVisibleImpls (falls open with no graph; filters to here + transitive imports); matchPackImpl (selects on prefix+return; null for non-closure source / unknown key). zig build, zig build test, tests/run_examples.sh (357/0) all green — no .ir churn; the 0410/0411/0412 diagnostics are byte-for-byte preserved.
This commit is contained in:
@@ -184,3 +184,101 @@ test "protocols: registerParamImpl flags a same-file duplicate impl" {
|
||||
}
|
||||
try std.testing.expect(dup_reported);
|
||||
}
|
||||
|
||||
// ── Planning (lookup-only; emission stays in Lowering) ───────────────
|
||||
|
||||
test "protocols: protocolMethodInfos lists the methods to materialize thunks for" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
var module = ir_mod.Module.init(alloc);
|
||||
defer module.deinit();
|
||||
var l = Lowering.init(&module);
|
||||
const pr = ProtocolResolver{ .l = &l };
|
||||
|
||||
const methods = [_]ast.ProtocolMethodDecl{ protoMethodReq("draw"), protoMethodReq("area") };
|
||||
const pd = ast.ProtocolDecl{ .name = "Drawable", .methods = &methods };
|
||||
l.registerProtocolDecl(&pd);
|
||||
|
||||
// The registry knows exactly which methods getOrCreateThunks must thunk.
|
||||
const infos = pr.protocolMethodInfos("Drawable").?;
|
||||
try std.testing.expectEqual(@as(usize, 2), infos.len);
|
||||
try std.testing.expectEqualStrings("draw", infos[0].name);
|
||||
try std.testing.expectEqualStrings("area", infos[1].name);
|
||||
// Unknown protocol → null (no silent empty-table default).
|
||||
try std.testing.expect(pr.protocolMethodInfos("Nope") == null);
|
||||
}
|
||||
|
||||
test "protocols: findVisibleImpls filters by transitive import visibility" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
var module = ir_mod.Module.init(alloc);
|
||||
defer module.deinit();
|
||||
var l = Lowering.init(&module);
|
||||
const pr = ProtocolResolver{ .l = &l };
|
||||
|
||||
const here_entry: Lowering.ParamImplEntry = .{ .methods = &.{}, .source_ty = .s64, .target_args = &.{}, .defining_module = "a.sx", .span = .{ .start = 0, .end = 0 } };
|
||||
const other_entry: Lowering.ParamImplEntry = .{ .methods = &.{}, .source_ty = .s64, .target_args = &.{}, .defining_module = "b.sx", .span = .{ .start = 0, .end = 0 } };
|
||||
const entries = [_]Lowering.ParamImplEntry{ here_entry, other_entry };
|
||||
|
||||
// No source-file context → falls open (all entries visible).
|
||||
{
|
||||
var out = std.ArrayList(Lowering.ParamImplEntry).empty;
|
||||
defer out.deinit(alloc);
|
||||
pr.findVisibleImpls(&entries, &out);
|
||||
try std.testing.expectEqual(@as(usize, 2), out.items.len);
|
||||
}
|
||||
|
||||
// From `a.sx`, which imports nothing: only the `a.sx` impl is visible.
|
||||
{
|
||||
var graph = std.StringHashMap(std.StringHashMap(void)).init(alloc);
|
||||
graph.put("a.sx", std.StringHashMap(void).init(alloc)) catch unreachable;
|
||||
l.program_index.import_graph = &graph;
|
||||
l.current_source_file = "a.sx";
|
||||
var out = std.ArrayList(Lowering.ParamImplEntry).empty;
|
||||
defer out.deinit(alloc);
|
||||
pr.findVisibleImpls(&entries, &out);
|
||||
try std.testing.expectEqual(@as(usize, 1), out.items.len);
|
||||
try std.testing.expectEqualStrings("a.sx", out.items[0].defining_module);
|
||||
}
|
||||
}
|
||||
|
||||
test "protocols: matchPackImpl selects a pack impl whose prefix + return match" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
var module = ir_mod.Module.init(alloc);
|
||||
defer module.deinit();
|
||||
var l = Lowering.init(&module);
|
||||
const pr = ProtocolResolver{ .l = &l };
|
||||
|
||||
// A pack impl source `Closure(..$args) -> void` (pack_start = 0).
|
||||
const pack_src = module.types.closureTypePack(&.{}, .void, 0);
|
||||
const convert = ast.FnDecl{ .name = "convert", .params = &.{}, .return_type = null, .body = emptyBody(alloc) };
|
||||
const conv_methods = [_]*const ast.FnDecl{&convert};
|
||||
const pack_entry: Lowering.PackParamImplEntry = .{
|
||||
.methods = &conv_methods,
|
||||
.source_pack_ty = pack_src,
|
||||
.target_args = &.{},
|
||||
.defining_module = "test.sx",
|
||||
.span = .{ .start = 0, .end = 0 },
|
||||
.pack_var_name = "args",
|
||||
.ret_var_name = null,
|
||||
};
|
||||
var list = std.ArrayList(Lowering.PackParamImplEntry).empty;
|
||||
list.append(alloc, pack_entry) catch unreachable;
|
||||
const pack_key = "Into\x00Block";
|
||||
l.param_impl_pack_map.put(pack_key, list) catch unreachable;
|
||||
|
||||
// A concrete `Closure() -> void` source matches (no fixed prefix, void ret).
|
||||
const src = module.types.closureType(&.{}, .void);
|
||||
const m = pr.matchPackImpl(src, pack_key).?;
|
||||
try std.testing.expectEqualStrings("convert", m.convert_fd.name);
|
||||
try std.testing.expectEqual(@as(usize, 0), m.src_params.len);
|
||||
try std.testing.expectEqual(TypeId.void, m.src_ret);
|
||||
|
||||
// A non-closure source does not match; an unknown key does not match.
|
||||
try std.testing.expect(pr.matchPackImpl(.s64, pack_key) == null);
|
||||
try std.testing.expect(pr.matchPackImpl(src, "Into\x00Nope") == null);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user