From 55c72af68a7307f374d1c819afe8563e7724c6b8 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 27 May 2026 18:47:32 +0300 Subject: [PATCH] ffi M5.A.next.4.2: audit box_any/unbox_any/display, guard bitcast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 6 + 7 of the .type_tag activation plan. Audit pass on the Any-boxing and value-display paths to confirm `.type_tag` flows cleanly OR fails loudly. Audit findings: - `box_any` (interp.zig:1168) stores fields[0] as `.int(TypeId)` for the Any-tag, fields[1] as the raw operand Value. A `.type_tag` operand becomes the value field — correct. Tag-field stays int-shaped across all Any boxes; value field can be any Value kind including type_tag. - `unbox_any` (interp.zig:1176) returns fields[1] as-is — preserves whatever was stored. Correct for `.type_tag`. - `any_to_string` (std.sx:316) has a `case type:` arm: case type: { s : string = xx val; result = s; } KNOWN GAP. Pre-`.type_tag`, the Any's value field was string-shaped (lower-time type_name folding to const_string). Now the value field will be `.type_tag(TypeId)`. The `xx val to string` cast becomes a shape mismatch. Deferred until source construction wires a path that surfaces this — the loud bitcast guard below catches the silent-fall-through case. New guard: - `bitcast` interp arm (interp.zig:664) now explicitly bails when source is `.type_tag` and target is anything OTHER than `.any` (boxing into Any) or the identity Type. Catches the case-type-arm scenario above + any other stale "xx val to string" path that would silently misinterpret a Type value. Diagnostic suggests using `type_name(val)` as the replacement. No code changes in box_any / unbox_any (already correct). 208/208 example tests + `zig build test` green. No `.type_tag` constructions exercised yet — the guards are dormant infrastructure ready for when source construction surfaces them. --- src/ir/interp.zig | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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| {