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);
|
return self.protocol_thunk_map.contains(thunk_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Does `ty` conform to protocol `p_name` under SOME type-args? Used to
|
/// Does `ty` conform to protocol `p_name` (under SOME type-args for a
|
||||||
/// check protocol-pack elements (`..xs: P`), where each element's
|
/// parameterised protocol)? Used to check protocol-pack elements
|
||||||
/// protocol type-args are inferred from its impl rather than written out.
|
/// (`..xs: P`), where each element's protocol type-args are inferred from
|
||||||
/// Covers plain protocols (`protocol_thunk_map`) and parameterised ones
|
/// its impl rather than written out.
|
||||||
/// (any `param_impl_map` key `P\x00<args>\x00<mangle(ty)>`). An arg already
|
///
|
||||||
/// of the protocol's own (erased) type trivially conforms.
|
/// 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 {
|
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`).
|
// Arg already erased to the protocol struct itself (e.g. `xx a`).
|
||||||
if (!ty.isBuiltin()) {
|
if (!ty.isBuiltin()) {
|
||||||
const info = self.module.types.get(ty);
|
const info = self.module.types.get(ty);
|
||||||
if (info == .@"struct" and info.@"struct".is_protocol and
|
if (info == .@"struct" and info.@"struct".is_protocol and
|
||||||
std.mem.eql(u8, self.module.types.getString(info.@"struct".name), p_name)) return true;
|
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 pd = self.protocol_ast_map.get(p_name) orelse return false;
|
||||||
const suffix = std.fmt.allocPrint(self.alloc, "\x00{s}", .{self.mangleTypeName(ty)}) catch return false;
|
if (pd.type_params.len > 0) {
|
||||||
var it = self.param_impl_map.keyIterator();
|
const prefix = std.fmt.allocPrint(self.alloc, "{s}\x00", .{p_name}) catch return false;
|
||||||
while (it.next()) |k| {
|
const suffix = std.fmt.allocPrint(self.alloc, "\x00{s}", .{self.mangleTypeName(ty)}) catch return false;
|
||||||
if (std.mem.startsWith(u8, k.*, prefix) and std.mem.endsWith(u8, k.*, suffix)) return true;
|
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`.
|
/// 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