From 379ed0549563534a619ed4fdd1820003b86928f1 Mon Sep 17 00:00:00 2001 From: agra Date: Thu, 18 Jun 2026 19:26:59 +0300 Subject: [PATCH] comptime VM: switch_br + type_name (pure reflection ops); guard unresolved type reads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port two pure (side-effect-free) comptime ops toward the no-fallback gate: - switch_br: multi-way branch on an i64 discriminant (enum/error tag, or a .type_value whose word is its TypeId index) — mirrors the legacy asInt-orelse-asTypeId switch. - type_name(x): a Type value (.type_value word) or Any box ({tag,value}; tag == type_value means the boxed Type's id is in the value slot) -> table.typeName. Guards an .unresolved TypeId (bail, not "") to surface a bad slice/pack read instead of emitting garbage (see issue 0143). These are correct in isolation (0520-0524 run green under strict mode) but flip nothing yet because their examples also print via `out`, which can only land at the end state: under the legacy fallback a print-then-bail double-prints (no re-run rewind), so `out` is deferred until the fallback is removed. 699/0 both default gates. --- src/ir/comptime_vm.zig | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/ir/comptime_vm.zig b/src/ir/comptime_vm.zig index 1241749a..ac873062 100644 --- a/src/ir/comptime_vm.zig +++ b/src/ir/comptime_vm.zig @@ -859,6 +859,17 @@ pub const Vm = struct { if (frame.get(b.cond.index()) != 0) return .{ .jump = .{ .target = b.then_target, .args = b.then_args } }; return .{ .jump = .{ .target = b.else_target, .args = b.else_args } }; }, + // Multi-way branch on an integer discriminant: an enum/error tag, or a + // type-category match where the operand is a `.type_value` whose word IS + // its `TypeId` index (so the same i64 compare covers both, mirroring the + // legacy `switch_br`'s `asInt orelse asTypeId().index()`). + .switch_br => |sb| { + const operand: i64 = @bitCast(frame.get(sb.operand.index())); + for (sb.cases) |case| { + if (operand == case.value) return .{ .jump = .{ .target = case.target, .args = case.args } }; + } + return .{ .jump = .{ .target = sb.default, .args = sb.default_args } }; + }, .ret => |u| return .{ .ret = frame.get(u.operand.index()) }, .ret_void => return .ret_void, @@ -1470,6 +1481,33 @@ pub const Vm = struct { return self.failMsg("comptime define: TypeInfo payload is not a single-slice info struct"); return try self.defineFromInfo(table, handle, @intCast(tag), payload_ty, info_addr + tag_size); }, + // type_name(x) → the type's name as a string. The arg is a Type value + // (`.type_value` word = a TypeId) or an Any box (`{tag@0, value@8}` whose + // tag IS the boxed value's type, unless tag == type_value: then the boxed + // Type's id is in the value slot). Mirrors the legacy `reflectTypeId`. + .type_name => { + const table = try self.requireTable(); + if (bi.args.len < 1) return self.failMsg("comptime type_name: missing argument"); + const aty = try self.refTy(ref_types, bi.args[0]); + const w = frame.get(bi.args[0].index()); + const tid: TypeId = blk: { + if (aty == .type_value) break :blk TypeId.fromIndex(@intCast(w)); + if (aty == .any) { + const tag = try self.machine.readWord(w, 8); + if (tag == @as(u64, TypeId.type_value.index())) + break :blk TypeId.fromIndex(@intCast(try self.machine.readWord(w + 8, 8))); + break :blk TypeId.fromIndex(@intCast(tag)); + } + return self.failMsg("comptime type_name: arg is not a Type value or an Any box"); + }; + // An `.unresolved` TypeId means the read produced a bad type (e.g. a + // mis-strided `[]Type` slice over an Any-boxed pack — see issue 0143): + // the VM can't faithfully name it, so BAIL rather than emit + // "". (The legacy reads Values, not bytes, so it gets the + // real type; the fallback then handles this correctly.) + if (tid == .unresolved) return self.failMsg("comptime type_name: unresolved type (bad slice/pack read — see issue 0143)"); + return try self.makeStringValue(table, table.typeName(tid)); + }, // type_info($T) → reflect a type INTO a TypeInfo VALUE (the inverse of // define's decode). The arg folded to a `const_type` (a `.type_value` // word = the source TypeId); build the value in flat memory.