lang F1 4.2: parameterized protocol as a runtime value type
VL(s64) used as a value/field type resolved to a 0-field stub (size 0); a
plain protocol was already a 16-byte {ctx,vtable} value. New
instantiateParamProtocol materializes a parameterized protocol per
instantiation: a 16-byte protocol value (is_protocol), protocol_decl_map
methods resolved under the type-arg binding (get -> T becomes get -> s64 for
VL(s64)), a vtable struct, and the type-arg binding recorded for projection.
Hooked into resolveParameterizedWithBindings before the empty-struct fallback.
xx-erasing a conforming struct into VL(s64)/VL(string) + method dispatch now
works (examples/206). This is the keystone for the canonical Combined field
(..VL(Ts)). 241 examples + unit green.
This commit is contained in:
22
examples/206-parameterized-protocol-value.sx
Normal file
22
examples/206-parameterized-protocol-value.sx
Normal file
@@ -0,0 +1,22 @@
|
||||
// Phase 4.2 — parameterized protocol as a runtime VALUE type. `VL(s64)` is a
|
||||
// 16-byte protocol value {ctx, vtable} (a plain protocol was already, but a
|
||||
// parameterized one used to resolve to a 0-field stub). A conforming struct
|
||||
// `xx`-erases into it, and method dispatch uses the bound type-arg
|
||||
// (`get -> T` becomes `get -> s64` for `VL(s64)`).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
VL :: protocol(T: Type) { get :: () -> T; }
|
||||
IntCell :: struct { v: s64; }
|
||||
StrCell :: struct { s: string; }
|
||||
impl VL(s64) for IntCell { get :: (self: *IntCell) -> s64 => self.v; }
|
||||
impl VL(string) for StrCell { get :: (self: *StrCell) -> string => self.s; }
|
||||
|
||||
main :: () -> s32 {
|
||||
a : VL(s64) = xx IntCell.{ v = 42 };
|
||||
print("a.get={}\n", a.get()); // 42 (T = s64)
|
||||
|
||||
b : VL(string) = xx StrCell.{ s = "hi" };
|
||||
print("b.get={}\n", b.get()); // hi (T = string)
|
||||
0;
|
||||
}
|
||||
@@ -11381,6 +11381,14 @@ pub const Lowering = struct {
|
||||
return self.instantiateGenericStruct(tmpl, pt.args);
|
||||
}
|
||||
|
||||
// Parameterized protocol used as a value type (`VL(s64)`): materialize a
|
||||
// 16-byte protocol value with the type-arg bound (not a 0-field stub).
|
||||
if (self.protocol_ast_map.get(base_name)) |pd| {
|
||||
if (pd.type_params.len > 0) {
|
||||
return self.instantiateParamProtocol(pd, pt.args);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: register as named type placeholder
|
||||
const name_id = table.internString(pt.name);
|
||||
return table.intern(.{ .@"struct" = .{ .name = name_id, .fields = &.{} } });
|
||||
@@ -12004,6 +12012,96 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Instantiate a parameterized protocol as a runtime VALUE type:
|
||||
/// `VL(s64)` → a 16-byte `{ctx, __vtable}` protocol value (`is_protocol`),
|
||||
/// with method infos resolved under the type-arg binding (so `get -> T`
|
||||
/// becomes `get -> s64`) and the binding recorded for projection. Cached by
|
||||
/// the mangled name `VL__s64`. Mirrors the non-parameterized path in
|
||||
/// `registerProtocolDecl`.
|
||||
fn instantiateParamProtocol(self: *Lowering, pd: *const ast.ProtocolDecl, args: []const *const Node) TypeId {
|
||||
const table = &self.module.types;
|
||||
const void_ptr_ty = table.ptrTo(.void);
|
||||
|
||||
var np = std.ArrayList(u8).empty;
|
||||
np.appendSlice(self.alloc, pd.name) catch {};
|
||||
var tb = std.StringHashMap(TypeId).init(self.alloc);
|
||||
for (pd.type_params, 0..) |tp, i| {
|
||||
if (i >= args.len) break;
|
||||
const ty = self.resolveTypeWithBindings(args[i]);
|
||||
tb.put(tp.name, ty) catch {};
|
||||
np.appendSlice(self.alloc, "__") catch {};
|
||||
np.appendSlice(self.alloc, self.formatTypeName(ty)) catch {};
|
||||
}
|
||||
const mangled = np.items;
|
||||
const name_id = table.internString(mangled);
|
||||
if (table.findByName(name_id)) |existing| {
|
||||
const info = table.get(existing);
|
||||
if (info == .@"struct" and info.@"struct".is_protocol) return existing;
|
||||
}
|
||||
|
||||
// Value struct: {ctx, __vtable} (or ctx + fn-ptrs for an inline protocol).
|
||||
var fields = std.ArrayList(types.TypeInfo.StructInfo.Field).empty;
|
||||
fields.append(self.alloc, .{ .name = table.internString("ctx"), .ty = void_ptr_ty }) catch unreachable;
|
||||
if (pd.is_inline) {
|
||||
for (pd.methods) |m| fields.append(self.alloc, .{ .name = table.internString(m.name), .ty = void_ptr_ty }) catch unreachable;
|
||||
} else {
|
||||
fields.append(self.alloc, .{ .name = table.internString("__vtable"), .ty = void_ptr_ty }) catch unreachable;
|
||||
}
|
||||
const struct_info: types.TypeInfo = .{ .@"struct" = .{ .name = name_id, .fields = fields.items, .is_protocol = true } };
|
||||
const id = if (table.findByName(name_id)) |existing| existing else table.intern(struct_info);
|
||||
table.update(id, struct_info);
|
||||
|
||||
// Method infos resolved with the type-arg binding (T → s64).
|
||||
const saved_tb = self.type_bindings;
|
||||
self.type_bindings = tb;
|
||||
var method_infos = std.ArrayList(ProtocolMethodInfo).empty;
|
||||
for (pd.methods) |method| {
|
||||
var ptypes = std.ArrayList(TypeId).empty;
|
||||
for (method.params) |p| {
|
||||
const pty = blk: {
|
||||
if (p.data == .type_expr and std.mem.eql(u8, p.data.type_expr.name, "Self")) break :blk void_ptr_ty;
|
||||
break :blk self.resolveTypeWithBindings(p);
|
||||
};
|
||||
ptypes.append(self.alloc, pty) catch unreachable;
|
||||
}
|
||||
var ret_is_self = false;
|
||||
const ret = if (method.return_type) |rt| blk: {
|
||||
if (rt.data == .type_expr and std.mem.eql(u8, rt.data.type_expr.name, "Self")) {
|
||||
ret_is_self = true;
|
||||
break :blk void_ptr_ty;
|
||||
}
|
||||
break :blk self.resolveTypeWithBindings(rt);
|
||||
} else .void;
|
||||
method_infos.append(self.alloc, .{
|
||||
.name = method.name,
|
||||
.param_types = self.alloc.dupe(TypeId, ptypes.items) catch unreachable,
|
||||
.ret_type = ret,
|
||||
.ret_is_self = ret_is_self,
|
||||
}) catch unreachable;
|
||||
}
|
||||
self.type_bindings = saved_tb;
|
||||
|
||||
const owned = self.alloc.dupe(u8, mangled) catch return id;
|
||||
self.protocol_decl_map.put(owned, .{
|
||||
.name = owned,
|
||||
.is_inline = pd.is_inline,
|
||||
.methods = self.alloc.dupe(ProtocolMethodInfo, method_infos.items) catch unreachable,
|
||||
}) catch {};
|
||||
// Record the type-arg binding so projection (`xs.T`, `.value`) and
|
||||
// method-arg resolution on this instance can recover it.
|
||||
self.struct_instance_bindings.put(owned, tb) catch {};
|
||||
|
||||
if (!pd.is_inline) {
|
||||
var vtable_fields = std.ArrayList(types.TypeInfo.StructInfo.Field).empty;
|
||||
for (pd.methods) |m| vtable_fields.append(self.alloc, .{ .name = table.internString(m.name), .ty = void_ptr_ty }) catch unreachable;
|
||||
var vtable_name_buf: [192]u8 = undefined;
|
||||
const vtable_name = std.fmt.bufPrint(&vtable_name_buf, "__{s}__Vtable", .{mangled}) catch "__Vtable";
|
||||
const vtable_ty = table.intern(.{ .@"struct" = .{ .name = table.internString(vtable_name), .fields = vtable_fields.items } });
|
||||
self.protocol_vtable_type_map.put(owned, vtable_ty) catch {};
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
// ── Pack projection name resolution (Feature 1, Decision 4) ──────────
|
||||
//
|
||||
// A `..pack.<name>` projection can target two protocol namespaces:
|
||||
|
||||
1
tests/expected/206-parameterized-protocol-value.exit
Normal file
1
tests/expected/206-parameterized-protocol-value.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
2
tests/expected/206-parameterized-protocol-value.txt
Normal file
2
tests/expected/206-parameterized-protocol-value.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
a.get=42
|
||||
b.get=hi
|
||||
Reference in New Issue
Block a user