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 $)
|
||||
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
|
||||
/// `..$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 {
|
||||
|
||||
@@ -264,6 +264,7 @@ pub const Lowering = struct {
|
||||
const TemplateParam = struct {
|
||||
name: []const u8,
|
||||
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 };
|
||||
@@ -11116,6 +11117,36 @@ pub const Lowering = struct {
|
||||
.tuple_type_expr => |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 => {},
|
||||
}
|
||||
// Alias resolution (`ShaderHandle :: u32`, `Vec4 ::
|
||||
@@ -11367,11 +11398,28 @@ pub const Lowering = struct {
|
||||
// Bind type params to args and build name suffix
|
||||
const saved_type_bindings = self.type_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 cvb = std.StringHashMap(i64).init(self.alloc);
|
||||
var pb = std.StringHashMap([]const TypeId).init(self.alloc);
|
||||
|
||||
for (tmpl.type_params, 0..) |tp, i| {
|
||||
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 {};
|
||||
|
||||
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.comptime_value_bindings = cvb;
|
||||
self.pack_bindings = pb;
|
||||
self.pack_arg_types = pb;
|
||||
|
||||
var fields = std.ArrayList(types.TypeInfo.StructInfo.Field).empty;
|
||||
for (tmpl.field_names, tmpl.field_type_nodes) |fname, ftype_node| {
|
||||
@@ -11420,6 +11471,8 @@ pub const Lowering = struct {
|
||||
// Restore bindings
|
||||
self.type_bindings = saved_type_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
|
||||
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| {
|
||||
tps[i] = .{
|
||||
.name = self.alloc.dupe(u8, tp.name) catch return,
|
||||
// $T: Type, $T: Lerpable, $T: Type/Eq — all are type params
|
||||
// Only value params like $N: u32 are non-type
|
||||
.is_type_param = if (tp.constraint.data == .type_expr) blk: {
|
||||
// $T: Type, $T: Lerpable, $T: Type/Eq — all are type params.
|
||||
// `..$Ts: []Type` (variadic) is a type-pack param. Only value
|
||||
// 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;
|
||||
// "Type" or a protocol name → type param
|
||||
break :blk std.mem.eql(u8, cname, "Type") or
|
||||
self.protocol_decl_map.contains(cname) or
|
||||
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);
|
||||
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
|
||||
try self.expect(.dollar);
|
||||
if (self.current.tag != .identifier) {
|
||||
@@ -874,7 +881,7 @@ pub const Parser = struct {
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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