ir: dedicated TypeId.unresolved sentinel; kill inferred_type => .s64
An unannotated param resolving to a plausible .s64 was the classic silent-default trap (root of the 2.5 multi-param-closure bug). Replace it with a dedicated TypeId.unresolved at slot 0, so a zero-initialised or forgotten TypeId trips the sentinel instead of masquerading as a real type. - types.zig: TypeId.unresolved = 0 (void moves to 17); TypeInfo.unresolved; sizeOf/toLLVMType @panic on it (codegen tripwire); hash/eql/printer cover it. - type_bridge: inferred_type => .unresolved (was .s64). - resolveParamType: emit "parameter 'x' has no type annotation" for a genuinely-unannotated value param (comptime/variadic/pack params exempt -- they resolve via per-call substitution). - lowerLambda: resolve unannotated params from the target closure signature; otherwise emit "cannot infer type of lambda parameter". - CLAUDE.md: .void documented as an UNACCEPTABLE failed-type sentinel (it conflates with a real, heavily-checked type); prescribe a distinct .unresolved-style value + codegen tripwire. Snapshot churn: one .ir (ffi-objc-call-06) -- the runtime type-name table and typeof match arms renumber by the new builtin slot; program output unchanged.
This commit is contained in:
@@ -5,8 +5,16 @@ const Allocator = std.mem.Allocator;
|
||||
// Opaque handle into the TypeTable. First 16 slots are reserved for builtins.
|
||||
|
||||
pub const TypeId = enum(u32) {
|
||||
// Builtin slots 0–15
|
||||
void = 0,
|
||||
// Builtin slots 0–17.
|
||||
/// Resolution failed (e.g. an unannotated param whose type was never
|
||||
/// inferred from context). A dedicated sentinel — never a legitimate
|
||||
/// result — so downstream `== .void`/`== .s64` checks can't silently
|
||||
/// swallow it. Must never reach codegen; sizeOf/toLLVMType panic on it.
|
||||
///
|
||||
/// Deliberately slot 0: a zero-initialised or forgotten `TypeId` (the most
|
||||
/// common accidental value) thus reads as `.unresolved` and trips the
|
||||
/// tripwire, rather than silently masquerading as `.void`.
|
||||
unresolved = 0,
|
||||
bool = 1,
|
||||
s8 = 2,
|
||||
s16 = 3,
|
||||
@@ -23,9 +31,10 @@ pub const TypeId = enum(u32) {
|
||||
noreturn = 14,
|
||||
isize = 15,
|
||||
usize = 16,
|
||||
_, // user-defined types start at 17
|
||||
void = 17,
|
||||
_, // user-defined types start at 18
|
||||
|
||||
pub const first_user: u32 = 17;
|
||||
pub const first_user: u32 = 18;
|
||||
|
||||
pub fn index(self: TypeId) u32 {
|
||||
return @intFromEnum(self);
|
||||
@@ -73,6 +82,8 @@ pub const TypeInfo = union(enum) {
|
||||
noreturn,
|
||||
usize,
|
||||
isize,
|
||||
/// Resolution-failure sentinel (see `TypeId.unresolved`).
|
||||
unresolved,
|
||||
|
||||
pub const StructInfo = struct {
|
||||
name: StringId,
|
||||
@@ -280,9 +291,9 @@ pub const TypeTable = struct {
|
||||
.slice_arena = std.heap.ArenaAllocator.init(alloc),
|
||||
};
|
||||
|
||||
// Pre-populate builtin slots 0–15 (must match TypeId enum order)
|
||||
// Pre-populate builtin slots 0–17 (must match TypeId enum order)
|
||||
const builtins = [_]TypeInfo{
|
||||
.void, // 0
|
||||
.unresolved, // 0: resolution-failure sentinel
|
||||
.bool, // 1
|
||||
.{ .signed = 8 }, // 2: s8
|
||||
.{ .signed = 16 }, // 3: s16
|
||||
@@ -299,6 +310,7 @@ pub const TypeTable = struct {
|
||||
.noreturn, // 14
|
||||
.isize, // 15: isize (pointer-sized signed)
|
||||
.usize, // 16: usize (pointer-sized unsigned)
|
||||
.void, // 17
|
||||
};
|
||||
for (&builtins) |info| {
|
||||
table.infos.append(alloc, info) catch unreachable;
|
||||
@@ -477,6 +489,9 @@ pub const TypeTable = struct {
|
||||
// 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)"),
|
||||
// Tripwire: a failed type resolution must have surfaced a
|
||||
// diagnostic and aborted before any layout query.
|
||||
.unresolved => @panic("unresolved type reached sizeOf — a type resolution failure was not diagnosed/aborted"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -674,6 +689,7 @@ pub const TypeTable = struct {
|
||||
.noreturn => "noreturn",
|
||||
.isize => "isize",
|
||||
.usize => "usize",
|
||||
.unresolved => "<unresolved>",
|
||||
else => {
|
||||
// User types — format from TypeInfo
|
||||
const info = self.get(id);
|
||||
@@ -813,7 +829,7 @@ fn hashTypeInfo(h: *std.hash.Wyhash, info: TypeInfo) void {
|
||||
switch (info) {
|
||||
.signed => |w| h.update(&.{w}),
|
||||
.unsigned => |w| h.update(&.{w}),
|
||||
.f32, .f64, .void, .bool, .string, .any, .noreturn, .usize, .isize => {},
|
||||
.f32, .f64, .void, .bool, .string, .any, .noreturn, .usize, .isize, .unresolved => {},
|
||||
.pointer => |p| h.update(std.mem.asBytes(&p.pointee)),
|
||||
.many_pointer => |p| h.update(std.mem.asBytes(&p.element)),
|
||||
.slice => |s| h.update(std.mem.asBytes(&s.element)),
|
||||
@@ -866,7 +882,7 @@ fn typeInfoEql(a: TypeInfo, b: TypeInfo) bool {
|
||||
return switch (a) {
|
||||
.signed => |w| w == b.signed,
|
||||
.unsigned => |w| w == b.unsigned,
|
||||
.f32, .f64, .void, .bool, .string, .any, .noreturn, .usize, .isize => true,
|
||||
.f32, .f64, .void, .bool, .string, .any, .noreturn, .usize, .isize, .unresolved => true,
|
||||
.pointer => |p| p.pointee == b.pointer.pointee,
|
||||
.many_pointer => |p| p.element == b.many_pointer.element,
|
||||
.slice => |s| s.element == b.slice.element,
|
||||
|
||||
Reference in New Issue
Block a user