diff --git a/examples/193-protocol-pack-methods.sx b/examples/193-protocol-pack-methods.sx new file mode 100644 index 0000000..811f063 --- /dev/null +++ b/examples/193-protocol-pack-methods.sx @@ -0,0 +1,31 @@ +// Feature 1 — protocol-interface method calls on heterogeneous pack elements. +// +// `..xs: Greeter` binds per call shape; each `xs[i]` is the concrete element, +// and calling the protocol's own method `greet()` on it dispatches to that +// element's impl. Elements may be DIFFERENT concrete types (Dog, Cat) as long +// as each conforms to Greeter — this is the protocol-interface access the +// pack is for. (Protocol method decls omit the implicit `self`; impls list it.) + +#import "modules/std.sx"; + +Greeter :: protocol { + greet :: () -> s64; +} + +Dog :: struct { age: s64; } +Cat :: struct { lives: s64; } +impl Greeter for Dog { greet :: (self: *Dog) -> s64 => self.age; } +impl Greeter for Cat { greet :: (self: *Cat) -> s64 => self.lives * 100; } + +pair_sum :: (..xs: Greeter) -> s64 { + return xs[0].greet() + xs[1].greet(); +} + +main :: () -> s32 { + d := Dog.{ age = 3 }; + c := Cat.{ lives = 9 }; + print("dog+cat={}\n", pair_sum(d, c)); // 3 + 900 = 903 (heterogeneous) + print("cat+dog={}\n", pair_sum(c, d)); // 900 + 3 = 903 (order swapped) + print("dog+dog={}\n", pair_sum(d, Dog.{ age = 4 })); // 3 + 4 = 7 + 0; +} diff --git a/src/ir/lower.zig b/src/ir/lower.zig index ec66f01..d0d21d0 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -3032,27 +3032,44 @@ pub const Lowering = struct { return self.protocol_thunk_map.contains(thunk_key); } - /// Does `ty` conform to protocol `p_name` under SOME type-args? Used to - /// check protocol-pack elements (`..xs: P`), where each element's - /// protocol type-args are inferred from its impl rather than written out. - /// Covers plain protocols (`protocol_thunk_map`) and parameterised ones - /// (any `param_impl_map` key `P\x00\x00`). An arg already - /// of the protocol's own (erased) type trivially conforms. + /// 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\x00`. + /// - Non-parameterised `P`: every required (non-default) method `m` is + /// registered as `.` 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 { - if (self.hasImplPlain(p_name, ty)) return true; // 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 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; + const pd = self.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; } - return false; + // Non-parameterised: require each non-default method as `.`. + 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.fn_ast_map.contains(q)) return false; + } + return true; } /// Evaluate a compile-time condition for `inline if`. diff --git a/tests/expected/193-protocol-pack-methods.exit b/tests/expected/193-protocol-pack-methods.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/193-protocol-pack-methods.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/193-protocol-pack-methods.txt b/tests/expected/193-protocol-pack-methods.txt new file mode 100644 index 0000000..2f50f29 --- /dev/null +++ b/tests/expected/193-protocol-pack-methods.txt @@ -0,0 +1,3 @@ +dog+cat=903 +cat+dog=903 +dog+dog=7