diff --git a/src/ast.zig b/src/ast.zig index 71c495a..9fe2ebd 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -492,6 +492,10 @@ pub const ClosureTypeExpr = struct { param_types: []const *Node, param_names: ?[]const ?[]const u8 = null, // optional documentation names return_type: ?*Node, // null = void return + /// Variadic heterogeneous type pack trailing the param list. + /// `Closure(..$args) -> R` ⇒ pack_name = "args", param_types = []. + /// `Closure(Prefix, ..$args)` ⇒ pack_name = "args", param_types = [Prefix]. + pack_name: ?[]const u8 = null, }; pub const TupleTypeExpr = struct { diff --git a/src/ir/type_bridge.zig b/src/ir/type_bridge.zig index e85c43f..aa2fe44 100644 --- a/src/ir/type_bridge.zig +++ b/src/ir/type_bridge.zig @@ -277,6 +277,10 @@ fn resolveClosureType(ct: *const ast.ClosureTypeExpr, table: *TypeTable) TypeId param_ids.append(alloc, resolveAstType(pt, table)) catch unreachable; } const ret_id = if (ct.return_type) |rt| resolveAstType(rt, table) else TypeId.void; + if (ct.pack_name != null) { + // Pack-variadic shape: fixed prefix in params, pack-start at end. + return table.closureTypePack(param_ids.items, ret_id, @intCast(param_ids.items.len)); + } return table.closureType(param_ids.items, ret_id); } diff --git a/src/ir/types.zig b/src/ir/types.zig index c696905..258d453 100644 --- a/src/ir/types.zig +++ b/src/ir/types.zig @@ -136,6 +136,11 @@ pub const TypeInfo = union(enum) { params: []const TypeId, ret: TypeId, call_conv: CallConv = .default, + /// Pack-variadic shape marker. When set, the signature represents a + /// heterogeneous type pack: `params` is the fixed prefix, and a + /// per-call-site type list binds the remainder. `pack_start == 0` + /// with `params.len == 0` denotes `fn(..$args)`. + pack_start: ?u32 = null, }; pub const CallConv = enum { default, c }; @@ -143,6 +148,9 @@ pub const TypeInfo = union(enum) { pub const ClosureInfo = struct { params: []const TypeId, ret: TypeId, + /// Pack-variadic shape marker — same semantics as FunctionInfo. + /// `Closure(..$args) -> $R` => params = [], pack_start = 0. + pack_start: ?u32 = null, }; pub const OptionalInfo = struct { @@ -354,11 +362,21 @@ pub const TypeTable = struct { return self.intern(.{ .function = .{ .params = owned_params, .ret = ret, .call_conv = cc } }); } + pub fn functionTypePack(self: *TypeTable, params: []const TypeId, ret: TypeId, cc: TypeInfo.CallConv, pack_start: u32) TypeId { + const owned_params = self.alloc.dupe(TypeId, params) catch unreachable; + return self.intern(.{ .function = .{ .params = owned_params, .ret = ret, .call_conv = cc, .pack_start = pack_start } }); + } + pub fn closureType(self: *TypeTable, params: []const TypeId, ret: TypeId) TypeId { const owned_params = self.alloc.dupe(TypeId, params) catch unreachable; return self.intern(.{ .closure = .{ .params = owned_params, .ret = ret } }); } + pub fn closureTypePack(self: *TypeTable, params: []const TypeId, ret: TypeId, pack_start: u32) TypeId { + const owned_params = self.alloc.dupe(TypeId, params) catch unreachable; + return self.intern(.{ .closure = .{ .params = owned_params, .ret = ret, .pack_start = pack_start } }); + } + pub fn vectorOf(self: *TypeTable, element: TypeId, length: u32) TypeId { return self.intern(.{ .vector = .{ .element = element, .length = length } }); } @@ -679,10 +697,16 @@ fn hashTypeInfo(h: *std.hash.Wyhash, info: TypeInfo) void { h.update(std.mem.asBytes(&f.ret)); const cc_byte: u8 = @intFromEnum(f.call_conv); h.update(&.{cc_byte}); + const pack_present: u8 = if (f.pack_start != null) 1 else 0; + h.update(&.{pack_present}); + if (f.pack_start) |ps| h.update(std.mem.asBytes(&ps)); }, .closure => |c| { for (c.params) |p| h.update(std.mem.asBytes(&p)); h.update(std.mem.asBytes(&c.ret)); + const pack_present: u8 = if (c.pack_start != null) 1 else 0; + h.update(&.{pack_present}); + if (c.pack_start) |ps| h.update(std.mem.asBytes(&ps)); }, .@"struct" => |s| h.update(std.mem.asBytes(&s.name)), .@"enum" => |e| h.update(std.mem.asBytes(&e.name)), @@ -719,6 +743,8 @@ fn typeInfoEql(a: TypeInfo, b: TypeInfo) bool { if (fp != gp) return false; } if (f.call_conv != g.call_conv) return false; + if ((f.pack_start == null) != (g.pack_start == null)) return false; + if (f.pack_start) |fp| if (fp != g.pack_start.?) return false; return f.ret == g.ret; }, .closure => |c| { @@ -727,6 +753,8 @@ fn typeInfoEql(a: TypeInfo, b: TypeInfo) bool { for (c.params, d.params) |cp, dp| { if (cp != dp) return false; } + if ((c.pack_start == null) != (d.pack_start == null)) return false; + if (c.pack_start) |cp| if (cp != d.pack_start.?) return false; return c.ret == d.ret; }, .@"struct" => |s| s.name == b.@"struct".name, diff --git a/src/parser.zig b/src/parser.zig index 7c42f0e..262de0d 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -550,16 +550,35 @@ pub const Parser = struct { } // Closure type: Closure(params...) -> R + // Variadic-pack trailing form: `Closure(Prefix..., ..$pack) -> R` + // binds `pack` to a heterogeneous comptime type list at impl + // match time (see plan: variadic heterogeneous type packs). if (std.mem.eql(u8, name, "Closure") and self.current.tag == .l_paren) { self.advance(); // skip '(' var param_types = std.ArrayList(*Node).empty; var param_names = std.ArrayList(?[]const u8).empty; var has_names = false; + var pack_name: ?[]const u8 = null; while (self.current.tag != .r_paren and self.current.tag != .eof) { if (param_types.items.len > 0) { try self.expect(.comma); if (self.current.tag == .r_paren) break; // trailing comma ok } + // Trailing pack marker: `..$name` (terminal only). + if (self.current.tag == .dot_dot) { + self.advance(); // skip '..' + if (self.current.tag == .dollar) self.advance(); // optional sigil + if (!self.isIdentLike()) { + return self.fail("expected pack name after '..' in Closure type"); + } + pack_name = self.tokenSlice(self.current); + self.advance(); + // Pack must be the LAST item — only `)` accepted next. + if (self.current.tag != .r_paren) { + return self.fail("variadic pack must be the last parameter in Closure type"); + } + break; + } // Check for optional param name: `name: Type` if (self.current.tag == .identifier and self.peekNext() == .colon) { const pname = self.tokenSlice(self.current); @@ -582,6 +601,7 @@ pub const Parser = struct { .param_types = try param_types.toOwnedSlice(self.allocator), .param_names = if (has_names) try param_names.toOwnedSlice(self.allocator) else null, .return_type = return_type, + .pack_name = pack_name, } }); } diff --git a/tests/expected/154-pack-type-rep.exit b/tests/expected/154-pack-type-rep.exit index d00491f..573541a 100644 --- a/tests/expected/154-pack-type-rep.exit +++ b/tests/expected/154-pack-type-rep.exit @@ -1 +1 @@ -1 +0 diff --git a/tests/expected/154-pack-type-rep.txt b/tests/expected/154-pack-type-rep.txt index a324bd9..8cda539 100644 --- a/tests/expected/154-pack-type-rep.txt +++ b/tests/expected/154-pack-type-rep.txt @@ -1 +1 @@ -/Users/agra/projects/sx/examples/154-pack-type-rep.sx:18:26: error: expected type name +pack type rep ok