lang 2.4: protocol-interface method calls on pack elements + conformance fix
Calling a protocol method on a pack element now works: `xs[i].greet()` on a `..xs: Greeter` pack dispatches to the concrete element's impl, and elements may be heterogeneous (Dog, Cat). This is the protocol-interface access the pack is for. (Protocol method decls omit the implicit `self`; impls list it — the earlier malformed `(self: *Self)` decls were why dispatch looked broken.) Also fixes packArgConformsTo for non-parameterised protocols: it queried `protocol_thunk_map`, which is only populated lazily when a protocol VALUE is built with `xx`, so it false-negatived valid conformers. Now it queries impl-declaration state directly — `param_impl_map` for parameterised protocols, or `<ty>.<method>` entries in `fn_ast_map` for non-parameterised ones. examples/193-protocol-pack-methods.sx (heterogeneous Dog+Cat pack, per-element greet(), order-independent).
This commit is contained in:
31
examples/193-protocol-pack-methods.sx
Normal file
31
examples/193-protocol-pack-methods.sx
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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<args>\x00<mangle(ty)>`). 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<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 {
|
||||
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 `<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.fn_ast_map.contains(q)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Evaluate a compile-time condition for `inline if`.
|
||||
|
||||
1
tests/expected/193-protocol-pack-methods.exit
Normal file
1
tests/expected/193-protocol-pack-methods.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
3
tests/expected/193-protocol-pack-methods.txt
Normal file
3
tests/expected/193-protocol-pack-methods.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
dog+cat=903
|
||||
cat+dog=903
|
||||
dog+dog=7
|
||||
Reference in New Issue
Block a user