refactor(ir): extract protocol/impl lookup into protocols.zig (A4.2 step 2)
Move the pure protocol/impl conformance lookups into one module,
src/ir/protocols.zig, behind a *Lowering facade (ProtocolResolver), mirroring
GenericResolver / CallResolver. Per PLAN-ARCH A4.2 ("move pure lookup first;
keep emission in Lowering"), this increment moves only the read-only queries:
- getProtocolInfo (is a type a registered protocol + its method table),
- hasImplPlain (have the (protocol, type) thunks been materialized),
- packArgConformsTo (impl-declaration-level conformance for ..xs: P).
Registration (registerProtocolDecl / registerImplBlock / registerParamImpl) and
all IR emission (createProtocolThunk / buildProtocolValue / tryUserConversion /
getOrCreateThunks) stay in Lowering for the later increments. The state maps
(protocol_thunk_map / param_impl_map on Lowering, protocol_decl_map /
protocol_ast_map in ProgramIndex) stay put; the facade reads them via self.l.* —
no map migration.
Lowering keeps getProtocolInfo as a thin pub wrapper (~9 callers incl.
calls.zig); hasImplPlain + packArgConformsTo are deleted (no fallback), their 3
call sites (computeHasImpl x2, the pack-conformance check x1) routed through
self.protocolResolver(). formatTypeName widened to pub (the lookups use it);
protocolResolver() accessor added.
protocols.test.zig (wired into the barrel) drives ProtocolResolver directly:
getProtocolInfo (registered vs builtin/plain-struct + wrapper delegation),
hasImplPlain (thunk-map materialization), packArgConformsTo (non-parameterised
requires <ty>.<m> in fn_ast_map; trivially-true for an erased protocol value;
false for unknown protocol).
zig build, zig build test, tests/run_examples.sh (357/0) all green — no .ir
snapshot churn; the 0410/0411/0412 rejection anchors still pass.
This commit is contained in:
@@ -25,6 +25,7 @@ const PackResolver = @import("packs.zig").PackResolver;
|
||||
const ExprTyper = @import("expr_typer.zig").ExprTyper;
|
||||
const CallResolver = @import("calls.zig").CallResolver;
|
||||
const GenericResolver = @import("generics.zig").GenericResolver;
|
||||
const ProtocolResolver = @import("protocols.zig").ProtocolResolver;
|
||||
const semantic_diagnostics = @import("semantic_diagnostics.zig");
|
||||
|
||||
const TypeId = types.TypeId;
|
||||
@@ -3860,8 +3861,8 @@ pub const Lowering = struct {
|
||||
/// reports a diagnostic if it wants).
|
||||
fn computeHasImpl(self: *Lowering, proto_node: *const Node, ty: TypeId) bool {
|
||||
switch (proto_node.data) {
|
||||
.identifier => |id| return self.hasImplPlain(id.name, ty),
|
||||
.type_expr => |te| return self.hasImplPlain(te.name, ty),
|
||||
.identifier => |id| return self.protocolResolver().hasImplPlain(id.name, ty),
|
||||
.type_expr => |te| return self.protocolResolver().hasImplPlain(te.name, ty),
|
||||
.call => |c| {
|
||||
const p_name: []const u8 = switch (c.callee.data) {
|
||||
.identifier => |id| id.name,
|
||||
@@ -3888,52 +3889,6 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn hasImplPlain(self: *Lowering, p_name: []const u8, ty: TypeId) bool {
|
||||
const ty_name = self.formatTypeName(ty);
|
||||
const thunk_key = std.fmt.allocPrint(self.alloc, "{s}\x00{s}", .{ p_name, ty_name }) catch return false;
|
||||
return self.protocol_thunk_map.contains(thunk_key);
|
||||
}
|
||||
|
||||
/// Does `ty` conform to protocol `p_name` (under SOME type-args for a
|
||||
/// parameterised protocol)? Used to check protocol-pack elements
|
||||
/// (`..xs: P`), where each element's protocol type-args are inferred from
|
||||
/// its impl rather than written out.
|
||||
///
|
||||
/// Conformance is queried at the IMPL-DECLARATION level (not via
|
||||
/// `protocol_thunk_map`, which is only populated lazily when a protocol
|
||||
/// VALUE is created with `xx`):
|
||||
/// - Parameterised `P`: any `param_impl_map` key `P\x00<args>\x00<mangle(ty)>`.
|
||||
/// - Non-parameterised `P`: every required (non-default) method `m` is
|
||||
/// registered as `<ty>.<m>` in `fn_ast_map` (how `registerImplBlock`
|
||||
/// records a non-parameterised impl).
|
||||
/// An arg already of the protocol's own (erased) type trivially conforms.
|
||||
fn packArgConformsTo(self: *Lowering, p_name: []const u8, ty: TypeId) bool {
|
||||
// Arg already erased to the protocol struct itself (e.g. `xx a`).
|
||||
if (!ty.isBuiltin()) {
|
||||
const info = self.module.types.get(ty);
|
||||
if (info == .@"struct" and info.@"struct".is_protocol and
|
||||
std.mem.eql(u8, self.module.types.getString(info.@"struct".name), p_name)) return true;
|
||||
}
|
||||
const pd = self.program_index.protocol_ast_map.get(p_name) orelse return false;
|
||||
if (pd.type_params.len > 0) {
|
||||
const prefix = std.fmt.allocPrint(self.alloc, "{s}\x00", .{p_name}) catch return false;
|
||||
const suffix = std.fmt.allocPrint(self.alloc, "\x00{s}", .{self.mangleTypeName(ty)}) catch return false;
|
||||
var it = self.param_impl_map.keyIterator();
|
||||
while (it.next()) |k| {
|
||||
if (std.mem.startsWith(u8, k.*, prefix) and std.mem.endsWith(u8, k.*, suffix)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Non-parameterised: require each non-default method as `<ty>.<m>`.
|
||||
const ty_name = self.formatTypeName(ty);
|
||||
for (pd.methods) |m| {
|
||||
if (m.default_body != null) continue;
|
||||
const q = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ ty_name, m.name }) catch return false;
|
||||
if (!self.program_index.fn_ast_map.contains(q)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Evaluate a compile-time condition for `inline if`.
|
||||
/// Handles: `ident == .variant`, `ident != .variant`, `ident == int`, `ident != int`.
|
||||
fn evalComptimeCondition(self: *Lowering, node: *const Node) ?bool {
|
||||
@@ -10378,7 +10333,7 @@ pub const Lowering = struct {
|
||||
if (pack_protocol) |proto| {
|
||||
if (self.program_index.protocol_ast_map.contains(proto)) {
|
||||
for (call_node.args[pack_start..], pack_arg_types.items) |arg_node, arg_ty| {
|
||||
if (!self.packArgConformsTo(proto, arg_ty)) {
|
||||
if (!self.protocolResolver().packArgConformsTo(proto, arg_ty)) {
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, arg_node.span, "pack argument of type '{s}' does not conform to protocol '{s}'", .{ self.formatTypeName(arg_ty), proto });
|
||||
}
|
||||
@@ -11265,7 +11220,7 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
/// Format a type name for display (e.g. "*Point", "[]s32", "[3]f64").
|
||||
fn formatTypeName(self: *Lowering, ty: TypeId) []const u8 {
|
||||
pub fn formatTypeName(self: *Lowering, ty: TypeId) []const u8 {
|
||||
// Builtin types: use their canonical name
|
||||
if (ty == .s8) return "s8";
|
||||
if (ty == .s16) return "s16";
|
||||
@@ -13829,12 +13784,11 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
/// Get protocol info for a TypeId (if it's a protocol type).
|
||||
/// Protocol lookup. Thin delegation to the canonical owner
|
||||
/// (`ProtocolResolver`, `protocols.zig`); kept on `Lowering` because ~9
|
||||
/// callers (dispatch sites here + `calls.zig`) reach it.
|
||||
pub fn getProtocolInfo(self: *Lowering, ty: TypeId) ?ProtocolDeclInfo {
|
||||
if (ty.isBuiltin()) return null;
|
||||
const info = self.module.types.get(ty);
|
||||
if (info != .@"struct") return null;
|
||||
const name = self.module.types.getString(info.@"struct".name);
|
||||
return self.program_index.protocol_decl_map.get(name);
|
||||
return self.protocolResolver().getProtocolInfo(ty);
|
||||
}
|
||||
|
||||
/// Get or create thunks for a (protocol, concrete_type) pair.
|
||||
@@ -14257,6 +14211,10 @@ pub const Lowering = struct {
|
||||
return .{ .l = self };
|
||||
}
|
||||
|
||||
pub fn protocolResolver(self: *Lowering) ProtocolResolver {
|
||||
return .{ .l = self };
|
||||
}
|
||||
|
||||
/// Lower the `xx` operator (type coercion).
|
||||
/// Uses self.target_type for context when available. Handles:
|
||||
/// - Any → concrete type: unbox_any
|
||||
|
||||
Reference in New Issue
Block a user