lang F1 4.2 (core): generic struct pack type-param + (..$Ts) tuple field
A generic struct can take a pack type-param ..$Ts: []Type that binds the remaining type args as a sequence, and a pack-shaped tuple field (..$Ts) resolves to a tuple of those per-position types. - parser/ast: accept a leading .. on a struct generic param; StructTypeParam gains is_variadic. - registration: TemplateParam carries is_variadic (and is a type param). - instantiateGenericStruct: a variadic type-param consumes the remaining args into pack_bindings + pack_arg_types (mangled into the name); restored after. - resolveTypeWithBindings: a tuple-literal-as-type containing a pack spread (e.g. (..$Ts)) expands via packTypeElems. Instantiate + correct per-position field types + whole-tuple store + element read all work (examples/205). Not yet: protocol-applied field (..F(Ts)) (the canonical (..VL(Ts)) shape) and nested element assignment b.pair.0 = v. 240 examples + unit green.
This commit is contained in:
25
examples/205-generic-struct-pack-field.sx
Normal file
25
examples/205-generic-struct-pack-field.sx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// Phase 4.2 (core) — a generic struct with a pack type-param `..$Ts: []Type`
|
||||||
|
// and a pack-shaped tuple field `(..$Ts)`. Each instantiation binds the
|
||||||
|
// remaining type args as the pack, so the field is a tuple of those per-position
|
||||||
|
// types. Storing the whole tuple field and reading its elements both work.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
Box :: struct($R: Type, ..$Ts: []Type) {
|
||||||
|
r: $R;
|
||||||
|
pair: (..$Ts); // tuple of the pack's element types
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
// Box(s64, s32, string): R=s64, Ts=[s32, string], pair: (s32, string).
|
||||||
|
a : Box(s64, s32, string) = ---;
|
||||||
|
a.r = 7;
|
||||||
|
a.pair = (42, "hi"); // whole-tuple field store
|
||||||
|
print("a: r={} 0={} 1={}\n", a.r, a.pair.0, a.pair.1);
|
||||||
|
|
||||||
|
// A different shape → a different per-position tuple field.
|
||||||
|
b : Box(bool, string, bool) = ---; // Ts=[string, bool], pair: (string, bool)
|
||||||
|
b.pair = ("x", true);
|
||||||
|
print("b: 0={} 1={}\n", b.pair.0, b.pair.1);
|
||||||
|
0;
|
||||||
|
}
|
||||||
@@ -328,6 +328,9 @@ pub const StructTypeParam = struct {
|
|||||||
name: []const u8, // e.g. "N" or "T" (without $)
|
name: []const u8, // e.g. "N" or "T" (without $)
|
||||||
constraint: *Node, // type_expr: "u32" for value param, "Type" for type param
|
constraint: *Node, // type_expr: "u32" for value param, "Type" for type param
|
||||||
protocol_constraints: []const []const u8 = &.{}, // e.g. ["Eq", "Hashable"] for $T/Eq/Hashable
|
protocol_constraints: []const []const u8 = &.{}, // e.g. ["Eq", "Hashable"] for $T/Eq/Hashable
|
||||||
|
/// `..$Ts: []Type` — a pack type-param binding the remaining type args as a
|
||||||
|
/// sequence (must be last). Field types reference it via `(..$Ts)` etc.
|
||||||
|
is_variadic: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const UsingEntry = struct {
|
pub const UsingEntry = struct {
|
||||||
|
|||||||
@@ -264,6 +264,7 @@ pub const Lowering = struct {
|
|||||||
const TemplateParam = struct {
|
const TemplateParam = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
is_type_param: bool, // true for $T: Type, false for $N: u32
|
is_type_param: bool, // true for $T: Type, false for $N: u32
|
||||||
|
is_variadic: bool = false, // `..$Ts: []Type` — binds remaining type args as a pack
|
||||||
};
|
};
|
||||||
|
|
||||||
const GlobalInfo = struct { id: inst_mod.GlobalId, ty: TypeId };
|
const GlobalInfo = struct { id: inst_mod.GlobalId, ty: TypeId };
|
||||||
@@ -11116,6 +11117,36 @@ pub const Lowering = struct {
|
|||||||
.tuple_type_expr => |tt| {
|
.tuple_type_expr => |tt| {
|
||||||
return self.resolveTupleTypeWithBindings(&tt);
|
return self.resolveTupleTypeWithBindings(&tt);
|
||||||
},
|
},
|
||||||
|
// `(..$Ts)` in a type position (e.g. a struct field) parses as a
|
||||||
|
// tuple LITERAL whose elements include a pack spread; expand it to
|
||||||
|
// the bound pack's element types, same as `resolveTupleTypeWithBindings`.
|
||||||
|
.tuple_literal => |tl| {
|
||||||
|
var any_spread = false;
|
||||||
|
for (tl.elements) |el| {
|
||||||
|
if (el.value.data == .spread_expr) {
|
||||||
|
any_spread = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (any_spread) {
|
||||||
|
var field_ids = std.ArrayList(TypeId).empty;
|
||||||
|
defer field_ids.deinit(self.alloc);
|
||||||
|
for (tl.elements) |el| {
|
||||||
|
if (el.value.data == .spread_expr) {
|
||||||
|
if (self.packTypeElems(el.value.data.spread_expr.operand)) |elems| {
|
||||||
|
defer self.alloc.free(elems);
|
||||||
|
for (elems) |e| field_ids.append(self.alloc, e) catch return .void;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
field_ids.append(self.alloc, self.resolveTypeWithBindings(el.value)) catch return .void;
|
||||||
|
}
|
||||||
|
return self.module.types.intern(.{ .tuple = .{
|
||||||
|
.fields = self.alloc.dupe(TypeId, field_ids.items) catch return .void,
|
||||||
|
.names = null,
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
// Alias resolution (`ShaderHandle :: u32`, `Vec4 ::
|
// Alias resolution (`ShaderHandle :: u32`, `Vec4 ::
|
||||||
@@ -11367,11 +11398,28 @@ pub const Lowering = struct {
|
|||||||
// Bind type params to args and build name suffix
|
// Bind type params to args and build name suffix
|
||||||
const saved_type_bindings = self.type_bindings;
|
const saved_type_bindings = self.type_bindings;
|
||||||
const saved_value_bindings = self.comptime_value_bindings;
|
const saved_value_bindings = self.comptime_value_bindings;
|
||||||
|
const saved_pack_bindings = self.pack_bindings;
|
||||||
|
const saved_pack_arg_types = self.pack_arg_types;
|
||||||
var tb = std.StringHashMap(TypeId).init(self.alloc);
|
var tb = std.StringHashMap(TypeId).init(self.alloc);
|
||||||
var cvb = std.StringHashMap(i64).init(self.alloc);
|
var cvb = std.StringHashMap(i64).init(self.alloc);
|
||||||
|
var pb = std.StringHashMap([]const TypeId).init(self.alloc);
|
||||||
|
|
||||||
for (tmpl.type_params, 0..) |tp, i| {
|
for (tmpl.type_params, 0..) |tp, i| {
|
||||||
if (i >= args.len) break;
|
if (i >= args.len) break;
|
||||||
|
|
||||||
|
// `..$Ts: []Type` — bind the REMAINING args as a type pack.
|
||||||
|
if (tp.is_variadic) {
|
||||||
|
var pack_tys = std.ArrayList(TypeId).empty;
|
||||||
|
for (args[i..]) |a| {
|
||||||
|
const ty = self.resolveTypeWithBindings(a);
|
||||||
|
pack_tys.append(self.alloc, ty) catch {};
|
||||||
|
name_parts.appendSlice(self.alloc, "__") catch {};
|
||||||
|
name_parts.appendSlice(self.alloc, self.formatTypeName(ty)) catch {};
|
||||||
|
}
|
||||||
|
pb.put(tp.name, pack_tys.toOwnedSlice(self.alloc) catch &.{}) catch {};
|
||||||
|
break; // a pack param is always last
|
||||||
|
}
|
||||||
|
|
||||||
name_parts.appendSlice(self.alloc, "__") catch {};
|
name_parts.appendSlice(self.alloc, "__") catch {};
|
||||||
|
|
||||||
if (tp.is_type_param) {
|
if (tp.is_type_param) {
|
||||||
@@ -11404,9 +11452,12 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up bindings and resolve fields
|
// Set up bindings and resolve fields. `pack_bindings` makes a
|
||||||
|
// pack-shaped field type like `(..$Ts)` resolve to the bound type list.
|
||||||
self.type_bindings = tb;
|
self.type_bindings = tb;
|
||||||
self.comptime_value_bindings = cvb;
|
self.comptime_value_bindings = cvb;
|
||||||
|
self.pack_bindings = pb;
|
||||||
|
self.pack_arg_types = pb;
|
||||||
|
|
||||||
var fields = std.ArrayList(types.TypeInfo.StructInfo.Field).empty;
|
var fields = std.ArrayList(types.TypeInfo.StructInfo.Field).empty;
|
||||||
for (tmpl.field_names, tmpl.field_type_nodes) |fname, ftype_node| {
|
for (tmpl.field_names, tmpl.field_type_nodes) |fname, ftype_node| {
|
||||||
@@ -11420,6 +11471,8 @@ pub const Lowering = struct {
|
|||||||
// Restore bindings
|
// Restore bindings
|
||||||
self.type_bindings = saved_type_bindings;
|
self.type_bindings = saved_type_bindings;
|
||||||
self.comptime_value_bindings = saved_value_bindings;
|
self.comptime_value_bindings = saved_value_bindings;
|
||||||
|
self.pack_bindings = saved_pack_bindings;
|
||||||
|
self.pack_arg_types = saved_pack_arg_types;
|
||||||
|
|
||||||
// Register the monomorphized struct
|
// Register the monomorphized struct
|
||||||
const info: types.TypeInfo = .{ .@"struct" = .{ .name = name_id, .fields = fields.items } };
|
const info: types.TypeInfo = .{ .@"struct" = .{ .name = name_id, .fields = fields.items } };
|
||||||
@@ -11646,15 +11699,17 @@ pub const Lowering = struct {
|
|||||||
for (sd.type_params, 0..) |tp, i| {
|
for (sd.type_params, 0..) |tp, i| {
|
||||||
tps[i] = .{
|
tps[i] = .{
|
||||||
.name = self.alloc.dupe(u8, tp.name) catch return,
|
.name = self.alloc.dupe(u8, tp.name) catch return,
|
||||||
// $T: Type, $T: Lerpable, $T: Type/Eq — all are type params
|
// $T: Type, $T: Lerpable, $T: Type/Eq — all are type params.
|
||||||
// Only value params like $N: u32 are non-type
|
// `..$Ts: []Type` (variadic) is a type-pack param. Only value
|
||||||
.is_type_param = if (tp.constraint.data == .type_expr) blk: {
|
// params like $N: u32 are non-type.
|
||||||
|
.is_type_param = tp.is_variadic or (if (tp.constraint.data == .type_expr) blk: {
|
||||||
const cname = tp.constraint.data.type_expr.name;
|
const cname = tp.constraint.data.type_expr.name;
|
||||||
// "Type" or a protocol name → type param
|
// "Type" or a protocol name → type param
|
||||||
break :blk std.mem.eql(u8, cname, "Type") or
|
break :blk std.mem.eql(u8, cname, "Type") or
|
||||||
self.protocol_decl_map.contains(cname) or
|
self.protocol_decl_map.contains(cname) or
|
||||||
self.protocol_ast_map.contains(cname);
|
self.protocol_ast_map.contains(cname);
|
||||||
} else false,
|
} else false),
|
||||||
|
.is_variadic = tp.is_variadic,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -852,6 +852,13 @@ pub const Parser = struct {
|
|||||||
try self.expect(.comma);
|
try self.expect(.comma);
|
||||||
if (self.current.tag == .r_paren) break;
|
if (self.current.tag == .r_paren) break;
|
||||||
}
|
}
|
||||||
|
// Optional leading `..` — a pack type-param `..$Ts: []Type`
|
||||||
|
// (must be the last param; binds the remaining type args).
|
||||||
|
var is_variadic = false;
|
||||||
|
if (self.current.tag == .dot_dot) {
|
||||||
|
is_variadic = true;
|
||||||
|
self.advance();
|
||||||
|
}
|
||||||
// Expect $name : constraint
|
// Expect $name : constraint
|
||||||
try self.expect(.dollar);
|
try self.expect(.dollar);
|
||||||
if (self.current.tag != .identifier) {
|
if (self.current.tag != .identifier) {
|
||||||
@@ -874,7 +881,7 @@ pub const Parser = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const pc = try pc_list.toOwnedSlice(self.allocator);
|
const pc = try pc_list.toOwnedSlice(self.allocator);
|
||||||
try type_params.append(self.allocator, .{ .name = param_name, .constraint = constraint, .protocol_constraints = pc });
|
try type_params.append(self.allocator, .{ .name = param_name, .constraint = constraint, .protocol_constraints = pc, .is_variadic = is_variadic });
|
||||||
}
|
}
|
||||||
try self.expect(.r_paren);
|
try self.expect(.r_paren);
|
||||||
}
|
}
|
||||||
|
|||||||
1
tests/expected/205-generic-struct-pack-field.exit
Normal file
1
tests/expected/205-generic-struct-pack-field.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
2
tests/expected/205-generic-struct-pack-field.txt
Normal file
2
tests/expected/205-generic-struct-pack-field.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
a: r=7 0=42 1=hi
|
||||||
|
b: 0=x 1=true
|
||||||
Reference in New Issue
Block a user