ffi M5.A.next.4.2: audit box_any/unbox_any/display, guard bitcast

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.
This commit is contained in:
agra
2026-05-27 18:47:32 +03:00
parent 9600ba5cdc
commit 55c72af68a

View File

@@ -663,6 +663,22 @@ pub const Interpreter = struct {
},
.bitcast => |c| {
const val = frame.getRef(c.operand);
// Loud-fail on `.type_tag → <runtime kind>` 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| {