ffi M5.A.next.1c.B: pack type rep — Closure(..$args) parses + interns
`parseTypeExpr`'s `Closure(...)` arm now accepts a trailing `..$name` (sigil optional) as a variadic-pack marker. Pack must be terminal — `)` is the only token accepted after the name. `ClosureTypeExpr` AST gains `pack_name: ?[]const u8` carrying the identifier so later slices can name the binding. `FunctionInfo` / `ClosureInfo` in src/ir/types.zig grow a `pack_start: ?u32 = null` field. `Closure(..$args) -> R` interns as `params = []`, `pack_start = Some(0)` — distinct from any concrete `Closure(...) -> R` shape thanks to updated hash/eql arms. New constructor pair `closureTypePack` / `functionTypePack` keeps the existing single-shape constructors unchanged. `type_bridge.resolveClosureType` calls `closureTypePack` when `pack_name != null`. The pack starts after the fixed prefix, so `Closure(Prefix, ..$args)` resolves with `params = [Prefix]`, `pack_start = Some(1)`. No semantic effect yet — the signature exists in the type table but no matching code reads `pack_start`. Step 1d wires impl matching: `Closure(..$args) -> $R` binds against any concrete closure source type in `tryUserConversion` / `registerParamImpl`. `examples/154-pack-type-rep.sx` flips from rejecting-with-error to positive parse smoke (prints "pack type rep ok"). 192/192 example tests + `zig build test` green.
This commit is contained in:
@@ -492,6 +492,10 @@ pub const ClosureTypeExpr = struct {
|
|||||||
param_types: []const *Node,
|
param_types: []const *Node,
|
||||||
param_names: ?[]const ?[]const u8 = null, // optional documentation names
|
param_names: ?[]const ?[]const u8 = null, // optional documentation names
|
||||||
return_type: ?*Node, // null = void return
|
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 {
|
pub const TupleTypeExpr = struct {
|
||||||
|
|||||||
@@ -277,6 +277,10 @@ fn resolveClosureType(ct: *const ast.ClosureTypeExpr, table: *TypeTable) TypeId
|
|||||||
param_ids.append(alloc, resolveAstType(pt, table)) catch unreachable;
|
param_ids.append(alloc, resolveAstType(pt, table)) catch unreachable;
|
||||||
}
|
}
|
||||||
const ret_id = if (ct.return_type) |rt| resolveAstType(rt, table) else TypeId.void;
|
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);
|
return table.closureType(param_ids.items, ret_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,11 @@ pub const TypeInfo = union(enum) {
|
|||||||
params: []const TypeId,
|
params: []const TypeId,
|
||||||
ret: TypeId,
|
ret: TypeId,
|
||||||
call_conv: CallConv = .default,
|
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 };
|
pub const CallConv = enum { default, c };
|
||||||
@@ -143,6 +148,9 @@ pub const TypeInfo = union(enum) {
|
|||||||
pub const ClosureInfo = struct {
|
pub const ClosureInfo = struct {
|
||||||
params: []const TypeId,
|
params: []const TypeId,
|
||||||
ret: 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 {
|
pub const OptionalInfo = struct {
|
||||||
@@ -354,11 +362,21 @@ pub const TypeTable = struct {
|
|||||||
return self.intern(.{ .function = .{ .params = owned_params, .ret = ret, .call_conv = cc } });
|
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 {
|
pub fn closureType(self: *TypeTable, params: []const TypeId, ret: TypeId) TypeId {
|
||||||
const owned_params = self.alloc.dupe(TypeId, params) catch unreachable;
|
const owned_params = self.alloc.dupe(TypeId, params) catch unreachable;
|
||||||
return self.intern(.{ .closure = .{ .params = owned_params, .ret = ret } });
|
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 {
|
pub fn vectorOf(self: *TypeTable, element: TypeId, length: u32) TypeId {
|
||||||
return self.intern(.{ .vector = .{ .element = element, .length = length } });
|
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));
|
h.update(std.mem.asBytes(&f.ret));
|
||||||
const cc_byte: u8 = @intFromEnum(f.call_conv);
|
const cc_byte: u8 = @intFromEnum(f.call_conv);
|
||||||
h.update(&.{cc_byte});
|
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| {
|
.closure => |c| {
|
||||||
for (c.params) |p| h.update(std.mem.asBytes(&p));
|
for (c.params) |p| h.update(std.mem.asBytes(&p));
|
||||||
h.update(std.mem.asBytes(&c.ret));
|
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)),
|
.@"struct" => |s| h.update(std.mem.asBytes(&s.name)),
|
||||||
.@"enum" => |e| h.update(std.mem.asBytes(&e.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 (fp != gp) return false;
|
||||||
}
|
}
|
||||||
if (f.call_conv != g.call_conv) 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;
|
return f.ret == g.ret;
|
||||||
},
|
},
|
||||||
.closure => |c| {
|
.closure => |c| {
|
||||||
@@ -727,6 +753,8 @@ fn typeInfoEql(a: TypeInfo, b: TypeInfo) bool {
|
|||||||
for (c.params, d.params) |cp, dp| {
|
for (c.params, d.params) |cp, dp| {
|
||||||
if (cp != dp) return false;
|
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;
|
return c.ret == d.ret;
|
||||||
},
|
},
|
||||||
.@"struct" => |s| s.name == b.@"struct".name,
|
.@"struct" => |s| s.name == b.@"struct".name,
|
||||||
|
|||||||
@@ -550,16 +550,35 @@ pub const Parser = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Closure type: Closure(params...) -> R
|
// 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) {
|
if (std.mem.eql(u8, name, "Closure") and self.current.tag == .l_paren) {
|
||||||
self.advance(); // skip '('
|
self.advance(); // skip '('
|
||||||
var param_types = std.ArrayList(*Node).empty;
|
var param_types = std.ArrayList(*Node).empty;
|
||||||
var param_names = std.ArrayList(?[]const u8).empty;
|
var param_names = std.ArrayList(?[]const u8).empty;
|
||||||
var has_names = false;
|
var has_names = false;
|
||||||
|
var pack_name: ?[]const u8 = null;
|
||||||
while (self.current.tag != .r_paren and self.current.tag != .eof) {
|
while (self.current.tag != .r_paren and self.current.tag != .eof) {
|
||||||
if (param_types.items.len > 0) {
|
if (param_types.items.len > 0) {
|
||||||
try self.expect(.comma);
|
try self.expect(.comma);
|
||||||
if (self.current.tag == .r_paren) break; // trailing comma ok
|
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`
|
// Check for optional param name: `name: Type`
|
||||||
if (self.current.tag == .identifier and self.peekNext() == .colon) {
|
if (self.current.tag == .identifier and self.peekNext() == .colon) {
|
||||||
const pname = self.tokenSlice(self.current);
|
const pname = self.tokenSlice(self.current);
|
||||||
@@ -582,6 +601,7 @@ pub const Parser = struct {
|
|||||||
.param_types = try param_types.toOwnedSlice(self.allocator),
|
.param_types = try param_types.toOwnedSlice(self.allocator),
|
||||||
.param_names = if (has_names) try param_names.toOwnedSlice(self.allocator) else null,
|
.param_names = if (has_names) try param_names.toOwnedSlice(self.allocator) else null,
|
||||||
.return_type = return_type,
|
.return_type = return_type,
|
||||||
|
.pack_name = pack_name,
|
||||||
} });
|
} });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1
|
0
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
/Users/agra/projects/sx/examples/154-pack-type-rep.sx:18:26: error: expected type name
|
pack type rep ok
|
||||||
|
|||||||
Reference in New Issue
Block a user