comptime VM: switch_br + type_name (pure reflection ops); guard unresolved type reads

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 "<unresolved>") 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.
This commit is contained in:
agra
2026-06-18 19:26:59 +03:00
parent dcb1392255
commit 379ed05495

View File

@@ -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
// "<unresolved>". (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.