lang 2.1: Pack as a type-system value
Add a `pack` variant to IR `TypeInfo` — an ordered, interned sequence of
per-position element types (`PackInfo { elements: []const TypeId }`) — with
constructor (`packType`), structural equality + hashing, and a `pack(T0, …)`
printer. A pack is comptime-only: it lowers to flat positional args before
codegen and has no runtime layout, so `sizeOf` and `toLLVMType` bail loudly
rather than inventing a size. 5 unit tests (N=0/1/3, dedup, order/arity
distinctness, distinct-from-tuple, printer).
Also: give TypeTable an arena for the slices its constructors dupe (freed at
deinit), and add the missing `usize`/`isize` arms to `sizeOf` (a latent
non-exhaustive switch) so types.test.zig compiles and runs leak-free.
This commit is contained in:
@@ -4286,6 +4286,9 @@ pub const LLVMEmitter = struct {
|
|||||||
return self.cached_ptr;
|
return self.cached_ptr;
|
||||||
},
|
},
|
||||||
.usize, .isize => if (self.target_config.isWasm32()) self.cached_i32 else self.cached_i64,
|
.usize, .isize => if (self.target_config.isWasm32()) self.cached_i32 else self.cached_i64,
|
||||||
|
// Comptime-only: a pack is expanded to flat positional args before
|
||||||
|
// codegen, so it must never reach LLVM type emission.
|
||||||
|
.pack => @panic("pack type has no LLVM representation (comptime-only)"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -119,3 +119,78 @@ test "typeName for builtins" {
|
|||||||
try std.testing.expectEqualStrings("void", table.typeName(.void));
|
try std.testing.expectEqualStrings("void", table.typeName(.void));
|
||||||
try std.testing.expectEqualStrings("Any", table.typeName(.any));
|
try std.testing.expectEqualStrings("Any", table.typeName(.any));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Pack type (Feature 1, Step 2.1) ──────────────────────────────────
|
||||||
|
|
||||||
|
test "pack type: construct, element access, intern dedup (N=3)" {
|
||||||
|
const alloc = std.testing.allocator;
|
||||||
|
var table = TypeTable.init(alloc);
|
||||||
|
defer table.deinit();
|
||||||
|
|
||||||
|
const elems = &[_]TypeId{ .bool, .s32, .string };
|
||||||
|
const p1 = table.packType(elems);
|
||||||
|
const p2 = table.packType(elems);
|
||||||
|
try std.testing.expectEqual(p1, p2); // structural dedup
|
||||||
|
|
||||||
|
const info = table.get(p1);
|
||||||
|
try std.testing.expect(info == .pack);
|
||||||
|
try std.testing.expectEqual(@as(usize, 3), info.pack.elements.len);
|
||||||
|
try std.testing.expectEqual(TypeId.bool, info.pack.elements[0]);
|
||||||
|
try std.testing.expectEqual(TypeId.s32, info.pack.elements[1]);
|
||||||
|
try std.testing.expectEqual(TypeId.string, info.pack.elements[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "pack type: empty pack (N=0)" {
|
||||||
|
const alloc = std.testing.allocator;
|
||||||
|
var table = TypeTable.init(alloc);
|
||||||
|
defer table.deinit();
|
||||||
|
|
||||||
|
const empty1 = table.packType(&.{});
|
||||||
|
const empty2 = table.packType(&.{});
|
||||||
|
try std.testing.expectEqual(empty1, empty2);
|
||||||
|
const info = table.get(empty1);
|
||||||
|
try std.testing.expect(info == .pack);
|
||||||
|
try std.testing.expectEqual(@as(usize, 0), info.pack.elements.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "pack type: single element (N=1)" {
|
||||||
|
const alloc = std.testing.allocator;
|
||||||
|
var table = TypeTable.init(alloc);
|
||||||
|
defer table.deinit();
|
||||||
|
|
||||||
|
const p = table.packType(&[_]TypeId{.f64});
|
||||||
|
const info = table.get(p);
|
||||||
|
try std.testing.expectEqual(@as(usize, 1), info.pack.elements.len);
|
||||||
|
try std.testing.expectEqual(TypeId.f64, info.pack.elements[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "pack type: distinct element lists are distinct types" {
|
||||||
|
const alloc = std.testing.allocator;
|
||||||
|
var table = TypeTable.init(alloc);
|
||||||
|
defer table.deinit();
|
||||||
|
|
||||||
|
const a = table.packType(&[_]TypeId{ .bool, .s32 });
|
||||||
|
const b = table.packType(&[_]TypeId{ .s32, .bool }); // order matters
|
||||||
|
const c = table.packType(&[_]TypeId{.bool}); // arity matters
|
||||||
|
try std.testing.expect(a != b);
|
||||||
|
try std.testing.expect(a != c);
|
||||||
|
try std.testing.expect(b != c);
|
||||||
|
// A pack is distinct from the tuple of the same elements.
|
||||||
|
const tup = table.intern(.{ .tuple = .{ .fields = &[_]TypeId{ .bool, .s32 }, .names = null } });
|
||||||
|
try std.testing.expect(a != tup);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "pack type: formatTypeName" {
|
||||||
|
const alloc = std.testing.allocator;
|
||||||
|
var table = TypeTable.init(alloc);
|
||||||
|
defer table.deinit();
|
||||||
|
|
||||||
|
var arena = std.heap.ArenaAllocator.init(alloc);
|
||||||
|
defer arena.deinit();
|
||||||
|
|
||||||
|
const p = table.packType(&[_]TypeId{ .bool, .s32, .string });
|
||||||
|
try std.testing.expectEqualStrings("pack(bool, s32, string)", table.formatTypeName(arena.allocator(), p));
|
||||||
|
|
||||||
|
const empty = table.packType(&.{});
|
||||||
|
try std.testing.expectEqualStrings("pack()", table.formatTypeName(arena.allocator(), empty));
|
||||||
|
}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ pub const TypeInfo = union(enum) {
|
|||||||
closure: ClosureInfo,
|
closure: ClosureInfo,
|
||||||
optional: OptionalInfo,
|
optional: OptionalInfo,
|
||||||
tuple: TupleInfo,
|
tuple: TupleInfo,
|
||||||
|
pack: PackInfo,
|
||||||
any,
|
any,
|
||||||
protocol: ProtocolInfo,
|
protocol: ProtocolInfo,
|
||||||
noreturn,
|
noreturn,
|
||||||
@@ -162,6 +163,14 @@ pub const TypeInfo = union(enum) {
|
|||||||
names: ?[]const StringId,
|
names: ?[]const StringId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A heterogeneous variadic pack as a first-class type-system value: an
|
||||||
|
/// ordered sequence of per-position element types. Comptime-only — a pack
|
||||||
|
/// lowers to flat positional args before codegen and has NO runtime layout
|
||||||
|
/// (sizeOf panics). `elements.len == 0` is a valid empty pack.
|
||||||
|
pub const PackInfo = struct {
|
||||||
|
elements: []const TypeId,
|
||||||
|
};
|
||||||
|
|
||||||
pub const ProtocolInfo = struct {
|
pub const ProtocolInfo = struct {
|
||||||
name: StringId,
|
name: StringId,
|
||||||
methods: []const Method,
|
methods: []const Method,
|
||||||
@@ -245,6 +254,12 @@ pub const TypeTable = struct {
|
|||||||
/// Maps TypeInfo → TypeId for dedup of structural types
|
/// Maps TypeInfo → TypeId for dedup of structural types
|
||||||
intern_map: std.HashMap(TypeKey, TypeId, TypeKeyContext, 80),
|
intern_map: std.HashMap(TypeKey, TypeId, TypeKeyContext, 80),
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
|
/// Owns the element/param slices duped by the type constructors
|
||||||
|
/// (`functionType*`, `closureType*`, `packType`). Freed wholesale in
|
||||||
|
/// `deinit` — these slices live as long as the table, so an arena avoids
|
||||||
|
/// per-slice bookkeeping and the owned-vs-borrowed ambiguity that blocks
|
||||||
|
/// freeing them individually.
|
||||||
|
slice_arena: std.heap.ArenaAllocator,
|
||||||
/// Target pointer size in bytes (4 for wasm32, 8 for 64-bit targets).
|
/// Target pointer size in bytes (4 for wasm32, 8 for 64-bit targets).
|
||||||
pointer_size: u8 = 8,
|
pointer_size: u8 = 8,
|
||||||
/// Borrowed pointer to Lowering's `type_alias_map`. When set,
|
/// Borrowed pointer to Lowering's `type_alias_map`. When set,
|
||||||
@@ -262,6 +277,7 @@ pub const TypeTable = struct {
|
|||||||
.strings = StringPool.init(alloc),
|
.strings = StringPool.init(alloc),
|
||||||
.intern_map = std.HashMap(TypeKey, TypeId, TypeKeyContext, 80).init(alloc),
|
.intern_map = std.HashMap(TypeKey, TypeId, TypeKeyContext, 80).init(alloc),
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
|
.slice_arena = std.heap.ArenaAllocator.init(alloc),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pre-populate builtin slots 0–15 (must match TypeId enum order)
|
// Pre-populate builtin slots 0–15 (must match TypeId enum order)
|
||||||
@@ -295,6 +311,7 @@ pub const TypeTable = struct {
|
|||||||
self.infos.deinit(self.alloc);
|
self.infos.deinit(self.alloc);
|
||||||
self.strings.deinit(self.alloc);
|
self.strings.deinit(self.alloc);
|
||||||
self.intern_map.deinit();
|
self.intern_map.deinit();
|
||||||
|
self.slice_arena.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look up the TypeInfo for a given TypeId.
|
/// Look up the TypeInfo for a given TypeId.
|
||||||
@@ -366,22 +383,22 @@ pub const TypeTable = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn functionTypeCC(self: *TypeTable, params: []const TypeId, ret: TypeId, cc: TypeInfo.CallConv) TypeId {
|
pub fn functionTypeCC(self: *TypeTable, params: []const TypeId, ret: TypeId, cc: TypeInfo.CallConv) TypeId {
|
||||||
const owned_params = self.alloc.dupe(TypeId, params) catch unreachable;
|
const owned_params = self.slice_arena.allocator().dupe(TypeId, params) catch unreachable;
|
||||||
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 {
|
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;
|
const owned_params = self.slice_arena.allocator().dupe(TypeId, params) catch unreachable;
|
||||||
return self.intern(.{ .function = .{ .params = owned_params, .ret = ret, .call_conv = cc, .pack_start = pack_start } });
|
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.slice_arena.allocator().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 {
|
pub fn closureTypePack(self: *TypeTable, params: []const TypeId, ret: TypeId, pack_start: u32) TypeId {
|
||||||
const owned_params = self.alloc.dupe(TypeId, params) catch unreachable;
|
const owned_params = self.slice_arena.allocator().dupe(TypeId, params) catch unreachable;
|
||||||
return self.intern(.{ .closure = .{ .params = owned_params, .ret = ret, .pack_start = pack_start } });
|
return self.intern(.{ .closure = .{ .params = owned_params, .ret = ret, .pack_start = pack_start } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,6 +406,13 @@ pub const TypeTable = struct {
|
|||||||
return self.intern(.{ .vector = .{ .element = element, .length = length } });
|
return self.intern(.{ .vector = .{ .element = element, .length = length } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct (and intern) a heterogeneous pack type from an ordered
|
||||||
|
/// element-type list. `elements.len == 0` yields the empty pack.
|
||||||
|
pub fn packType(self: *TypeTable, elements: []const TypeId) TypeId {
|
||||||
|
const owned = self.slice_arena.allocator().dupe(TypeId, elements) catch unreachable;
|
||||||
|
return self.intern(.{ .pack = .{ .elements = owned } });
|
||||||
|
}
|
||||||
|
|
||||||
/// Size in bytes for a type (pointer-sized = 8 on 64-bit).
|
/// Size in bytes for a type (pointer-sized = 8 on 64-bit).
|
||||||
pub fn sizeOf(self: *const TypeTable, id: TypeId) u32 {
|
pub fn sizeOf(self: *const TypeTable, id: TypeId) u32 {
|
||||||
const info = self.get(id);
|
const info = self.get(id);
|
||||||
@@ -449,6 +473,10 @@ pub const TypeTable = struct {
|
|||||||
return if (total == 0) 8 else total;
|
return if (total == 0) 8 else total;
|
||||||
},
|
},
|
||||||
.protocol => 16, // {ctx, vtable}
|
.protocol => 16, // {ctx, vtable}
|
||||||
|
.usize, .isize => 8, // pointer-sized (this path is not target-aware; see typeSizeBytes)
|
||||||
|
// Comptime-only: a pack must be expanded to flat positional args
|
||||||
|
// before codegen. Reaching runtime layout means a pack leaked.
|
||||||
|
.pack => @panic("pack type has no runtime layout (comptime-only)"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -740,6 +768,17 @@ pub const TypeTable = struct {
|
|||||||
buf.append(alloc, ')') catch break :blk "(?)";
|
buf.append(alloc, ')') catch break :blk "(?)";
|
||||||
break :blk buf.toOwnedSlice(alloc) catch "(?)";
|
break :blk buf.toOwnedSlice(alloc) catch "(?)";
|
||||||
},
|
},
|
||||||
|
.pack => |pk| blk: {
|
||||||
|
var buf = std.ArrayList(u8).empty;
|
||||||
|
defer buf.deinit(alloc);
|
||||||
|
buf.appendSlice(alloc, "pack(") catch break :blk "pack(?)";
|
||||||
|
for (pk.elements, 0..) |e, i| {
|
||||||
|
if (i > 0) buf.appendSlice(alloc, ", ") catch break :blk "pack(?)";
|
||||||
|
buf.appendSlice(alloc, self.formatTypeName(alloc, e)) catch break :blk "pack(?)";
|
||||||
|
}
|
||||||
|
buf.append(alloc, ')') catch break :blk "pack(?)";
|
||||||
|
break :blk buf.toOwnedSlice(alloc) catch "pack(?)";
|
||||||
|
},
|
||||||
.signed => |w| std.fmt.allocPrint(alloc, "s{d}", .{w}) catch "s?",
|
.signed => |w| std.fmt.allocPrint(alloc, "s{d}", .{w}) catch "s?",
|
||||||
.unsigned => |w| std.fmt.allocPrint(alloc, "u{d}", .{w}) catch "u?",
|
.unsigned => |w| std.fmt.allocPrint(alloc, "u{d}", .{w}) catch "u?",
|
||||||
else => self.typeName(id),
|
else => self.typeName(id),
|
||||||
@@ -812,6 +851,9 @@ fn hashTypeInfo(h: *std.hash.Wyhash, info: TypeInfo) void {
|
|||||||
for (t.fields) |f| h.update(std.mem.asBytes(&f));
|
for (t.fields) |f| h.update(std.mem.asBytes(&f));
|
||||||
if (t.names) |ns| for (ns) |n| h.update(std.mem.asBytes(&n));
|
if (t.names) |ns| for (ns) |n| h.update(std.mem.asBytes(&n));
|
||||||
},
|
},
|
||||||
|
.pack => |p| {
|
||||||
|
for (p.elements) |e| h.update(std.mem.asBytes(&e));
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -871,5 +913,13 @@ fn typeInfoEql(a: TypeInfo, b: TypeInfo) bool {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
.pack => |p| {
|
||||||
|
const q = b.pack;
|
||||||
|
if (p.elements.len != q.elements.len) return false;
|
||||||
|
for (p.elements, q.elements) |pe, qe| {
|
||||||
|
if (pe != qe) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user