ffi M5.A.next.4.0: activate Value.type_tag — opcode + helper + cmp

Wires the dormant `Value.type_tag(TypeId)` variant in interp.zig
so Type values flow through the comptime interpreter as
first-class kind-distinguished entities. No source-language
construction path yet — that's a follow-up. This commit is the
infrastructure foundation.

Audit findings (from interp.zig switch-walk):
- Every `else =>` arm over Value is either already loud
  (`bailDetail` / `error.TypeError`) or a pass-through helper
  (`materializeCtxArg`, `materializeForCall`, `resolveSlotChain`)
  where transit-unchanged is semantically correct for type_tag.
  No new silent paths introduced by activating the variant.
- The three pre-existing `.type_tag => return bailDetail(...)`
  arms (store-at-raw-ptr, deref-non-pointer, unbox-non-aggregate)
  already cover the disallowed paths cleanly.

New plumbing:
- `Op.const_type: TypeId` — dedicated opcode. Never piggybacks
  on `const_int`. Result IR-type is `.any` to signal "untyped
  at runtime" so downstream coercions fail loudly.
- `Builder.constType(tid)` constructor.
- Interp arm emits `Value{ .type_tag = tid }` for the op.
- emit_llvm arm bails loudly + emits an undef-i64 placeholder
  (Type is comptime-only — if a Type ever reached LLVM emit,
  some upstream builder leaked through; the diagnostic + LLVM
  verifier downstream surface the offending site).
- `print.zig` arm prints `const type(<typeName>)`.
- `Value.asTypeId() ?TypeId` helper — the kind-honest accessor
  for Type values. asInt/asFloat/asBool/asString continue to
  return null for `.type_tag` (no silent coercion).
- `evalCmp` arm for `.type_tag, .type_tag` — TypeId equality.
  Mixed `.type_tag` vs `.int` deliberately falls through to
  the typeErrorDetail bail (a Type is not an int).

Tests (src/ir/interp.test.zig):
- `const_type yields type_tag` — confirms the variant is
  produced and that asTypeId/asInt distinguish correctly.
- `type_tag comparison` — exercises cmp_eq on equal and
  unequal pairs, asserts the right bool comes back.

208/208 example tests + `zig build test` green. No user-visible
behaviour change yet — `.type_tag` is constructible from Zig-
side IR builders but no sx-level syntax produces it. Next slice
wires `$args` lowering (or `$args[i]` in expression position)
to emit `const_type` per pack element.
This commit is contained in:
agra
2026-05-27 18:30:17 +03:00
parent 8990edbec8
commit ac60d98f0e
6 changed files with 122 additions and 1 deletions

View File

@@ -77,6 +77,18 @@ pub const Value = union(enum) {
return self == .null_val;
}
/// Extract the TypeId from a first-class Type value. Returns null
/// for anything else — including `.int(N)` where N happens to be
/// a valid TypeId enum value. The kinds are distinct: a Type IS
/// NOT an int. Use this helper instead of `asInt` when reading a
/// TypeId out of a Value to keep the kind-distinction honest.
pub fn asTypeId(self: Value) ?TypeId {
return switch (self) {
.type_tag => |id| id,
else => null,
};
}
/// Get the string content, whether from a literal or a heap-backed string aggregate.
pub fn asString(self: Value, interp: *const Interpreter) ?[]const u8 {
return switch (self) {
@@ -587,6 +599,7 @@ pub const Interpreter = struct {
.const_string => |sid| return .{ .value = .{ .string = self.module.types.getString(sid) } },
.const_null => return .{ .value = .null_val },
.const_undef => return .{ .value = .undef },
.const_type => |tid| return .{ .value = .{ .type_tag = tid } },
// ── Arithmetic ──────────────────────────────────────
.add => |b| return .{ .value = try self.evalArith(frame, b, .add) },
@@ -1470,7 +1483,21 @@ pub const Interpreter = struct {
}
}
return typeErrorDetail("comptime comparison: operand pair has no shared comparable shape (int/float/bool/string)");
// Type-as-value equality. Compares TypeIds structurally.
// `.type_tag` vs `.int(N)` deliberately does NOT compare —
// a Type is not an int even if the underlying enum value
// matches; falls through to the typeErrorDetail below.
if (lhs.asTypeId()) |la| {
if (rhs.asTypeId()) |ra| {
return switch (cop) {
.eq => la == ra,
.ne => la != ra,
else => return error.TypeError,
};
}
}
return typeErrorDetail("comptime comparison: operand pair has no shared comparable shape (int/float/bool/string/type)");
}
// ── Slot chain resolution ────────────────────────────────────