ffi M5.A.next.4.1: interp arms for reflection builtins on .type_tag

Second slice of the .type_tag activation. The reflection
intrinsics (`type_name`, `type_eq`, `has_impl`) now have
interp-time implementations that read `.type_tag` Values
directly. Today's lower-time fast path (folding to
`const_string`/`const_bool` when the type arg is statically
resolvable) stays — these interp arms are the fallback path
for when lowering emits a real `builtin_call` because the
arg is interp-time-only (e.g. `args[i]` inside a builder body
where the pack element is bound at interp execution).

Plumbing:
- New BuiltinId entries: `type_name`, `type_eq`, `has_impl`.
- Interp arms in `execBuiltinInner`:
  - `type_name(t)`: reads `.type_tag` via `asTypeId`, looks up
    via `module.types.typeName`, dupes the slice into the
    interp allocator, returns `.string`. Non-`.type_tag` arg
    → `bailDetail` ("argument is not a Type value").
  - `type_eq(a, b)`: both args must be `.type_tag`; compares
    TypeIds. Either side missing → `bailDetail`.
  - `has_impl(P, T)`: bails with a "not yet wired" message —
    interp-time has_impl needs a queryable snapshot of the
    host's `protocol_thunk_map` + `param_impl_map`, which is
    its own follow-up slice. Static-arg has_impl still works
    via the lower-time `tryConstBoolCondition` fast path.
- emit_llvm: explicit arms for the three new builtins that
  log + map to undef-i64 (Type values are comptime-only; if
  one of these reaches LLVM emit, lowering produced wrong
  IR — the LLVM verifier downstream surfaces the offending
  site).

Three new Zig unit tests in interp.test.zig:
- `type_name builtin on type_tag` — emits a `builtin_call`
  to `type_name` with a `const_type(s64)` operand, asserts
  the result is the string "s64".
- `type_eq builtin on type_tag values` — two equal Type
  operands compare equal.
- (Pre-existing) `const_type yields type_tag` + `type_tag
  comparison` from 4.0 still pass.

208/208 example tests + `zig build test` green. No source-
language path constructs `.type_tag` yet — the foundation is
ready for the `$args`-in-expression-position slice that
turns it on for users.
This commit is contained in:
agra
2026-05-27 18:43:10 +03:00
parent ac60d98f0e
commit 9600ba5cdc
4 changed files with 107 additions and 0 deletions

View File

@@ -1740,6 +1740,43 @@ pub const Interpreter = struct {
.type_of => return bailDetail("comptime #builtin type_of: handled at lowering, not the interp"),
.alloc => return bailDetail("comptime #builtin alloc unused (use context.allocator.alloc)"),
.dealloc => return bailDetail("comptime #builtin dealloc unused (use context.allocator.dealloc)"),
// ── Comptime reflection (Type-as-Value path) ─────────
// These are only reached when lower.zig emitted a real
// builtin_call — i.e. the type argument was NOT statically
// resolvable (e.g. inside a builder body where `args[i]` is
// a `.type_tag(TypeId)` Value bound at interp time). Static
// calls fold to `const_string` / `const_bool` at lower time
// and never hit this dispatch.
.type_name => {
if (bi.args.len < 1) return bailDetail("comptime type_name: missing argument");
const arg = frame.getRef(bi.args[0]);
const tid = arg.asTypeId() orelse return bailDetail("comptime type_name: argument is not a Type value (expected `.type_tag`, got a different Value kind)");
const name = self.module.types.typeName(tid);
// Copy the slice into the interp's allocator so it
// outlives any TypeTable churn during the rest of the
// interp execution. The TypeTable's strings are stable
// for now but copying is the safe pattern.
const owned = self.alloc.dupe(u8, name) catch return error.CannotEvalComptime;
return .{ .value = .{ .string = owned } };
},
.type_eq => {
if (bi.args.len < 2) return bailDetail("comptime type_eq: needs two Type arguments");
const a = frame.getRef(bi.args[0]).asTypeId() orelse return bailDetail("comptime type_eq: first argument is not a Type value");
const b = frame.getRef(bi.args[1]).asTypeId() orelse return bailDetail("comptime type_eq: second argument is not a Type value");
return .{ .value = .{ .boolean = a == b } };
},
.has_impl => {
// has_impl at interp time needs access to the host's
// protocol-registration maps (protocol_thunk_map +
// param_impl_map). These live on `Lowering`, not on
// the Interpreter. Plumbing a queryable snapshot is
// its own slice — until then, bail loudly so the user
// gets a clear "not yet wired" message instead of a
// silent false. Static-arg has_impl still works via
// `tryConstBoolCondition` in lower.zig.
return bailDetail("comptime has_impl: interp-time evaluation not yet wired (use static type args for now — they fold at lower time)");
},
}
}