diff --git a/src/ir/interp.zig b/src/ir/interp.zig index 9f6b93a..c89b6ca 100644 --- a/src/ir/interp.zig +++ b/src/ir/interp.zig @@ -663,6 +663,22 @@ pub const Interpreter = struct { }, .bitcast => |c| { const val = frame.getRef(c.operand); + // Loud-fail on `.type_tag → ` casts. A Type + // value can flow through bitcast only to .any (Any-boxing) + // or to itself; any other destination means the lowering + // emitted a coercion that silently pretends the TypeId is + // some other shape (e.g. an int, or a string). The most + // likely site that would trip this: the `case type:` arm + // of `any_to_string` in stdlib doing `xx val to string` — + // which expects the value field to already be a string, + // a leftover from the pre-`type_tag` era when Type values + // were string-shaped. + if (val == .type_tag) { + const allowed = c.to == .any or c.to == c.from; + if (!allowed) { + return bailDetail("comptime bitcast: Type value cast to a non-Type runtime kind — most likely a stale `xx val to string` from the pre-type_tag era; use `type_name(val)` instead"); + } + } return .{ .value = val }; }, .int_to_float => |c| {