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:
agra
2026-05-30 02:30:49 +03:00
parent 82b46bc412
commit b48766d153
6 changed files with 99 additions and 6 deletions

View File

@@ -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,
};
}