comptime VM: flip Type to .type_value; migrate the .any refs that mean a Type value
type_resolver "Type" -> .type_value; const_type result + emitConstType now a bare 8-byte i64 handle (not a 16-byte Any box). Migrated every .any ref meaning "a Type value", leaving real boxed-Any refs: - "Any holds a Type" meta-marker tag .any -> .type_value at all 4 consumers (reflectArgTypeId, reflectTypeId, the comptime type_tag-as-struct path, resolveTypeCategoryTags "type"). - reflection-builtin return types (type_of/declare/define) -> .type_value; runtime type_of(any) reads the tag as a .type_value (no re-box). - expr_typer: a bare type-name expr is .type_value (backtick is_raw exempt). - reflectionArgIsType accepts .type_value OR .any (a reflection arg can be a bare Type or a boxed Any). - comptime switch_br accepts a .type_tag discriminant (type-category match). - a bare function name in a Type slot -> const_type(its function type), not a func-ref (fixes a JIT crash); old string-box kept only for genuine Any params. - field-not-found diagnostic + formatTypeName render .type_value as "Type". Fixed 3 unit tests asserting the old .any behavior. 697/0 both gates (gate ON bails cleanly to legacy since the VM doesn't model Type values yet) + 494 unit tests. 24 snapshots regenerated (22 .ir const_type shape; 2 .stderr Any->Type).
This commit is contained in:
@@ -335,6 +335,31 @@ when reached (sentinels or accessor fns; see the design doc Risks).
|
||||
`List` growth; orthogonal, see `current/CHECKPOINT-METATYPE.md`.)
|
||||
|
||||
## Log
|
||||
- **Phase 3 P3.4 step 3 (VM plan) — dedicated `Type` builtin TypeId: RESOLVER FLIPPED + `.any` migration (2026-06-18).**
|
||||
Flipped `type_resolver:64` (`"Type"` → `.type_value`), `module.zig` `constType` (result type
|
||||
→ `.type_value`), and `emitConstType` (a bare i64 carrying `tid.index()`, NOT a 16-byte Any
|
||||
box). Then migrated every `.any` reference that means "a Type value", classified per CLAUDE.md
|
||||
(leave the real boxed-Any refs): (a) the "Any holds a Type" **meta-marker tag** moved `.any` →
|
||||
`.type_value` at all 4 consumers — `reflectArgTypeId` (LLVM), `reflectTypeId` + the
|
||||
`.type_tag`-as-struct-field comptime path (interp), and `resolveTypeCategoryTags("type")`
|
||||
(generic.zig); (b) reflection-builtin RETURN types `.any` → `.type_value` (`type_of`/`declare`/
|
||||
`define`); the runtime `type_of(any)` now reads the tag AS a `.type_value` (no re-box); (c)
|
||||
expr_typer infers a bare type-name expr as `.type_value` (with a `is_raw` backtick exemption —
|
||||
`` `string `` is a value, never the reserved type); (d) `reflectionArgIsType` accepts
|
||||
`.type_value` OR `.any` (a reflection arg can be a bare Type OR a boxed Any — the over-narrow
|
||||
`==.type_value` was the catastrophic-regression cause, caught + fixed); (e) the comptime
|
||||
`switch_br` accepts a `.type_tag` discriminant (type-category match); (f) a bare function name
|
||||
in a `Type` slot now lowers to `const_type(its real function type)` instead of a func-ref
|
||||
(fixed a JIT crash — was a func-ref word read as a TypeId), keeping the old string-box path only
|
||||
for genuine `Any` params; (g) the field-not-found diagnostic + `formatTypeName` render
|
||||
`.type_value` as "Type". Fixed 3 unit tests asserting the old `.any` Type behavior.
|
||||
**697/0 BOTH gates** + all 494 unit tests (EXIT=0). Gate ON stays green because the VM's
|
||||
`kindOf(.type_value)` → `.unsupported` → bails CLEANLY to legacy (no silent-wrong) — the VM
|
||||
doesn't model `Type` values YET (next step), but parity holds. Regenerated 24 snapshots (22
|
||||
`.ir` const_type-shape; 2 `.stderr` Any→Type — diff reviewed, only the intended changes). On
|
||||
`reify`. **Next:** model `.type_value` natively in the VM (`kindOf` → word, `const_type` → word
|
||||
= `TypeId.index()`, `regToValue` word → `.type_tag`) for COVERAGE, then port the WRITE side into
|
||||
`callCompilerFn` + a real lowering-time Context → the first HANDLED lowering-time type-fn.
|
||||
- **Phase 3 P3.4 step 2 (VM plan) — dedicated `Type` builtin TypeId: FOUNDATION landed (dead/additive) (2026-06-18).**
|
||||
Added `TypeId.type_value` (slot 19) + a matching `TypeInfo.type_value` variant + the builtins
|
||||
init entry — an **8-byte type handle distinct from the 16-byte boxed `.any`** (THE WALL). All
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -4,7 +4,7 @@ error: type 'bool' has no '.max' — numeric limits apply only to integer and fl
|
||||
14 | b := bool.max;
|
||||
| ^^^^^^^^
|
||||
|
||||
error: field 'min' not found on type 'Any'
|
||||
error: field 'min' not found on type 'Type'
|
||||
--> examples/0149-types-int-numeric-limits-errors.sx:15:10
|
||||
|
|
||||
15 | s := MyStruct.min;
|
||||
|
||||
@@ -28,7 +28,7 @@ error: type 'string' has no '.max' — numeric limits apply only to integer and
|
||||
24 | e := string.max;
|
||||
| ^^^^^^^^^^
|
||||
|
||||
error: field 'epsilon' not found on type 'Any'
|
||||
error: field 'epsilon' not found on type 'Type'
|
||||
--> examples/0160-types-float-numeric-limits-errors.sx:25:10
|
||||
|
|
||||
25 | f := MyStruct.epsilon;
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -130,23 +130,15 @@ pub const Ops = struct {
|
||||
}
|
||||
|
||||
pub fn emitConstType(self: Ops, tid: TypeId) void {
|
||||
// Type values are Any-shaped pairs:
|
||||
// { tag = .any.index() (the meta-marker),
|
||||
// value = tid.index() }
|
||||
// Lets storage in Any slots, struct fields,
|
||||
// `Type`-typed vars, and slice elements all round-
|
||||
// trip through the standard Any infrastructure.
|
||||
// `case type:` in `any_to_string` matches on
|
||||
// tag == `.any.index()`. Runtime `type_name(t)`
|
||||
// extracts the value field and indexes into the
|
||||
// type-name lookup table.
|
||||
const any_ty = self.e.getAnyStructType();
|
||||
const tag = c.LLVMConstInt(self.e.cached_i64, TypeId.any.index(), 0);
|
||||
// A Type value is an 8-byte handle: a bare i64 carrying `tid.index()`
|
||||
// (the `.type_value` builtin TypeId), distinct from the 16-byte boxed
|
||||
// `.any`. Flowing a Type into an `Any` slot boxes it via the standard
|
||||
// box-any coercion (`{ tag = .any.index(), value = tid }`); `case type:`
|
||||
// in `any_to_string` then matches tag == `.any.index()`, and runtime
|
||||
// `type_name(t)` reads the TypeId through `reflectArgTypeId` (`.bare`
|
||||
// when the arg is `.type_value`, `.boxed` when it is an Any).
|
||||
const val = c.LLVMConstInt(self.e.cached_i64, tid.index(), 0);
|
||||
var result = c.LLVMGetUndef(any_ty);
|
||||
result = c.LLVMBuildInsertValue(self.e.builder, result, tag, 0, "ct.tag");
|
||||
result = c.LLVMBuildInsertValue(self.e.builder, result, val, 1, "ct.val");
|
||||
self.e.mapRef(result);
|
||||
self.e.mapRef(val);
|
||||
}
|
||||
|
||||
// ── Arithmetic ─────────────────────────────────────────
|
||||
@@ -1341,11 +1333,11 @@ pub const Ops = struct {
|
||||
/// Resolve the `TypeId` (as a runtime `i64`) that a dynamic
|
||||
/// `type_name` / `type_is_unsigned` must operate on. A reflection
|
||||
/// builtin reads an `Any`'s runtime TYPE-TAG, never its raw payload:
|
||||
/// - `.bare`: a `Type` value already lowered to a bare i64 `TypeId`
|
||||
/// index (an unboxed direct call site) → the value itself.
|
||||
/// - `.bare`: a `Type` value (a `.type_value` arg) — a bare i64 `TypeId`
|
||||
/// index (e.g. `type_of(x)` directly) → the value itself.
|
||||
/// - `.boxed`: an `Any` aggregate `{ tag, value }`. When the tag is
|
||||
/// `.any`, the box carries a *Type value* (the `{ .any, tid }` shape
|
||||
/// `const_type` / `type_of` produce) → the TypeId is the payload.
|
||||
/// `.type_value`, the box carries a *Type value* (a `Type` boxed into an
|
||||
/// `Any`, `{ .type_value, tid }`) → the TypeId is the payload.
|
||||
/// Otherwise the box carries a *runtime value* whose type IS the tag
|
||||
/// → use the tag as the TypeId. This is what makes `type_name(av)`
|
||||
/// for `av : Any = 6` report `i64` (the held value's type), while
|
||||
@@ -1360,8 +1352,8 @@ pub const Ops = struct {
|
||||
.boxed => blk: {
|
||||
const tag = c.LLVMBuildExtractValue(self.e.builder, arg_val, 0, "refl.tag");
|
||||
const payload = c.LLVMBuildExtractValue(self.e.builder, arg_val, 1, "refl.val");
|
||||
const any_tag = c.LLVMConstInt(self.e.cached_i64, @intCast(TypeId.any.index()), 0);
|
||||
const holds_type = c.LLVMBuildICmp(self.e.builder, c.LLVMIntEQ, tag, any_tag, "refl.istype");
|
||||
const type_tag = c.LLVMConstInt(self.e.cached_i64, @intCast(TypeId.type_value.index()), 0);
|
||||
const holds_type = c.LLVMBuildICmp(self.e.builder, c.LLVMIntEQ, tag, type_tag, "refl.istype");
|
||||
break :blk c.LLVMBuildSelect(self.e.builder, holds_type, payload, tag, "refl.tid");
|
||||
},
|
||||
};
|
||||
|
||||
@@ -86,7 +86,7 @@ test "calls: builtin and reflection result types, unknown fallthrough" {
|
||||
.{ .name = "error_tag_name", .want = .string },
|
||||
.{ .name = "is_comptime", .want = .bool },
|
||||
.{ .name = "is_flags", .want = .bool },
|
||||
.{ .name = "type_of", .want = .any },
|
||||
.{ .name = "type_of", .want = .type_value },
|
||||
.{ .name = "field_value", .want = .any },
|
||||
.{ .name = "__interp_print_frames", .want = .void },
|
||||
// A math builtin with a non-`f32` argument widens to `f64` (the int
|
||||
|
||||
@@ -154,7 +154,7 @@ pub const CallResolver = struct {
|
||||
return refl(bare_name, self.l.module.types.findByName(self.l.module.types.internString("TraceFrame")) orelse .unresolved);
|
||||
if (std.mem.eql(u8, bare_name, "is_flags")) return refl(bare_name, .bool);
|
||||
if (std.mem.eql(u8, bare_name, "type_is_unsigned")) return refl(bare_name, .bool);
|
||||
if (std.mem.eql(u8, bare_name, "type_of")) return refl(bare_name, .any);
|
||||
if (std.mem.eql(u8, bare_name, "type_of")) return refl(bare_name, .type_value);
|
||||
if (std.mem.eql(u8, bare_name, "field_value")) return refl(bare_name, .any);
|
||||
// Plain bare same-name flat collision (R5 §C): route through the ONE
|
||||
// author producer `selectedFreeAuthor` so `plan` types the call as the
|
||||
|
||||
@@ -306,8 +306,11 @@ pub const ExprTyper = struct {
|
||||
}
|
||||
// A bare type name (alias like `Vec4`, struct name, or
|
||||
// builtin primitive) referenced in expression position
|
||||
// is a Type value — IR type `.any`.
|
||||
if (self.l.isKnownTypeName(id.name)) return .any;
|
||||
// is a Type value — IR type `.type_value` (8-byte handle). A
|
||||
// BACKTICK raw identifier (`` `string ``) is explicitly a value
|
||||
// binding, never the reserved type — so it never resolves here as a
|
||||
// Type (it was found in scope above, or is genuinely unresolved).
|
||||
if (!id.is_raw and self.l.isKnownTypeName(id.name)) return .type_value;
|
||||
return .unresolved;
|
||||
},
|
||||
.type_expr => |te| {
|
||||
@@ -318,8 +321,8 @@ pub const ExprTyper = struct {
|
||||
}
|
||||
}
|
||||
// A bare type name in expression position (e.g. `i64`,
|
||||
// `Point`, `*u8`) is a Type value — IR type `.any`.
|
||||
if (self.l.isKnownTypeName(te.name)) return .any;
|
||||
// `Point`, `*u8`) is a Type value — IR type `.type_value`.
|
||||
if (self.l.isKnownTypeName(te.name)) return .type_value;
|
||||
return .unresolved;
|
||||
},
|
||||
.enum_literal => {
|
||||
|
||||
@@ -812,7 +812,10 @@ test "comptime: type_eq builtin on type_tag values" {
|
||||
// `av : Any = 6` (`{ tag = i64, value = 6 }`) must resolve to `i64`, NOT
|
||||
// `types[6]` (`u8`).
|
||||
test "reflect: reflectTypeId branches on the Any tag" {
|
||||
const any_idx: i64 = @intCast(TypeId.any.index());
|
||||
// The "Any holds a Type" meta-marker tag is `.type_value` (an Any boxing a
|
||||
// Type value carries `{ tag = .type_value, value = tid }`), distinct from a
|
||||
// boxed runtime value whose tag is the held value's own type.
|
||||
const type_marker: i64 = @intCast(TypeId.type_value.index());
|
||||
|
||||
// Native first-class Type value → the held TypeId directly.
|
||||
try std.testing.expectEqual(@as(?TypeId, .u64), (Value{ .type_tag = .u64 }).reflectTypeId());
|
||||
@@ -827,13 +830,13 @@ test "reflect: reflectTypeId branches on the Any tag" {
|
||||
try std.testing.expectEqual(@as(?TypeId, .u32), (Value{ .aggregate = &held_u32 }).reflectTypeId());
|
||||
|
||||
// Any holding a TYPE value (the `type_of(x)` / `const_type` shape):
|
||||
// `{ tag = .any, value = u64 }` → u64 (the payload). Payload as a plain
|
||||
// `{ tag = .type_value, value = u64 }` → u64 (the payload). Payload as a plain
|
||||
// int (the runtime box shape) ...
|
||||
var held_type_int = [_]Value{ .{ .int = any_idx }, .{ .int = @intCast(TypeId.u64.index()) } };
|
||||
var held_type_int = [_]Value{ .{ .int = type_marker }, .{ .int = @intCast(TypeId.u64.index()) } };
|
||||
try std.testing.expectEqual(@as(?TypeId, .u64), (Value{ .aggregate = &held_type_int }).reflectTypeId());
|
||||
|
||||
// ... and payload as a `.type_tag` (the comptime box shape) → same result.
|
||||
var held_type_tag = [_]Value{ .{ .int = any_idx }, .{ .type_tag = .u64 } };
|
||||
var held_type_tag = [_]Value{ .{ .int = type_marker }, .{ .type_tag = .u64 } };
|
||||
try std.testing.expectEqual(@as(?TypeId, .u64), (Value{ .aggregate = &held_type_tag }).reflectTypeId());
|
||||
|
||||
// Neither shape → null (the caller bails loudly, never guesses a TypeId).
|
||||
|
||||
@@ -107,7 +107,7 @@ pub const Value = union(enum) {
|
||||
const fields = self.aggregate;
|
||||
if (fields.len >= 2) {
|
||||
const tag = fields[0].asInt() orelse return null;
|
||||
if (tag == @as(i64, @intCast(TypeId.any.index()))) {
|
||||
if (tag == @as(i64, @intCast(TypeId.type_value.index()))) {
|
||||
if (fields[1].asTypeId()) |t| return t;
|
||||
if (fields[1].asInt()) |iv| return TypeId.fromIndex(@intCast(iv));
|
||||
return null;
|
||||
@@ -927,7 +927,16 @@ pub const Interpreter = struct {
|
||||
switch (base) {
|
||||
.aggregate => |fields| {
|
||||
if (fa.field_index >= fields.len) return error.OutOfBounds;
|
||||
return .{ .value = fields[fa.field_index] };
|
||||
const field_val = fields[fa.field_index];
|
||||
// `type_of(an_any)` lowers to `struct_get(any, 0, .type_value)`:
|
||||
// the Any's field 0 is the held value's type id (a plain
|
||||
// `.int`), but the SSA result type is `.type_value`, so yield a
|
||||
// first-class `.type_tag` Value (a `.type_value`-typed value is
|
||||
// always a `.type_tag` in the interp — mirrors `const_type`).
|
||||
if (instruction.ty == .type_value) {
|
||||
if (field_val.asInt()) |iv| return .{ .value = .{ .type_tag = TypeId.fromIndex(@intCast(iv)) } };
|
||||
}
|
||||
return .{ .value = field_val };
|
||||
},
|
||||
.string => |s| {
|
||||
// String as fat pointer: field 0 = ptr (string), field 1 = len
|
||||
@@ -942,12 +951,12 @@ pub const Interpreter = struct {
|
||||
},
|
||||
.type_tag => |tid| {
|
||||
// A first-class Type value is the comptime form of the
|
||||
// runtime Any-Type aggregate `{ tag=.any, value=tid }`
|
||||
// runtime Any-Type aggregate `{ tag=.type_value, value=tid }`
|
||||
// (see `const_type` lowering in buildPackSliceValue).
|
||||
// `type_of(any_holding_a_Type)` lowers to struct_get
|
||||
// field 0, expecting that runtime layout — mirror it so
|
||||
// field 0 reads the `.any` tag and field 1 the type id.
|
||||
if (fa.field_index == 0) return .{ .value = .{ .int = @intCast(TypeId.any.index()) } };
|
||||
// field 0 reads the `.type_value` tag and field 1 the type id.
|
||||
if (fa.field_index == 0) return .{ .value = .{ .int = @intCast(TypeId.type_value.index()) } };
|
||||
if (fa.field_index == 1) return .{ .value = .{ .type_tag = tid } };
|
||||
return error.OutOfBounds;
|
||||
},
|
||||
@@ -1080,7 +1089,12 @@ pub const Interpreter = struct {
|
||||
return .branch;
|
||||
},
|
||||
.switch_br => |sb| {
|
||||
const operand = frame.getRef(sb.operand).asInt() orelse return error.TypeError;
|
||||
// A type-category match (`type_of(x) == { case int: … }`)
|
||||
// switches on a Type value — a `.type_tag` Value whose discriminant
|
||||
// is its TypeId index; an enum/error switch uses a plain int.
|
||||
const sb_val = frame.getRef(sb.operand);
|
||||
const operand = sb_val.asInt() orelse
|
||||
(if (sb_val.asTypeId()) |t| @as(i64, @intCast(t.index())) else return error.TypeError);
|
||||
for (sb.cases) |case| {
|
||||
if (operand == case.value) {
|
||||
const args = self.alloc.alloc(Value, case.args.len) catch return error.CannotEvalComptime;
|
||||
|
||||
@@ -1690,7 +1690,7 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C
|
||||
// self-reference resolves); the interp's `declare` returns that slot.
|
||||
const name_ref = self.lowerExpr(c.args[0]);
|
||||
const args_owned = self.alloc.dupe(Ref, &.{name_ref}) catch return Ref.none;
|
||||
return self.builder.callBuiltin(.declare, args_owned, .any);
|
||||
return self.builder.callBuiltin(.declare, args_owned, .type_value);
|
||||
}
|
||||
if (std.mem.eql(u8, name, "define")) {
|
||||
// Comptime type-construction primitive: complete a declare()'d slot
|
||||
@@ -1711,7 +1711,7 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C
|
||||
const args_owned = self.alloc.dupe(Ref, &.{ handle_ref, info_ref }) catch return Ref.none;
|
||||
// define returns the (now-completed) handle as a `Type` value, so the
|
||||
// one-shot constructor form chains: `T :: define(declare(), info)`.
|
||||
return self.builder.callBuiltin(.define, args_owned, .any);
|
||||
return self.builder.callBuiltin(.define, args_owned, .type_value);
|
||||
}
|
||||
if (std.mem.eql(u8, name, "type_info")) {
|
||||
// Comptime reflection-into-data: reflect a type INTO a `TypeInfo`
|
||||
@@ -1935,16 +1935,15 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C
|
||||
} }, .any);
|
||||
}
|
||||
if (std.mem.eql(u8, name, "type_of")) {
|
||||
// type_of(val) — produce a Type value (.any-typed aggregate).
|
||||
// type_of(val) — produce a Type value (`.type_value`, a bare i64 handle).
|
||||
if (c.args.len < 1) return self.builder.constType(.void);
|
||||
const arg_ty = self.inferExprType(c.args[0]);
|
||||
if (arg_ty == .any) {
|
||||
// Runtime: extract tag, rebuild Any with `{.any, tag}` so
|
||||
// the returned value carries Type semantics (tag field
|
||||
// says ".any" → the value field holds the type id).
|
||||
// Runtime: the held value's type is the Any's tag (field 0). Read it
|
||||
// out AS the 8-byte `.type_value` handle — type_of of a runtime Any
|
||||
// names the held value's type, so the tag IS the type id.
|
||||
const val = self.lowerExpr(c.args[0]);
|
||||
const tag_val = self.builder.structGet(val, 0, .i64);
|
||||
return self.builder.boxAny(tag_val, .any);
|
||||
return self.builder.structGet(val, 0, .type_value);
|
||||
} else {
|
||||
return self.builder.constType(arg_ty);
|
||||
}
|
||||
@@ -1988,13 +1987,17 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C
|
||||
/// Strict `$T: Type` classification shared by the 7 type-introspection
|
||||
/// builtins. An argument denotes a type iff it is a spelled /
|
||||
/// compile-time type or generic type parameter (the `isStaticTypeArg`
|
||||
/// shapes), or a runtime `Type` value — which is `.any`-typed at
|
||||
/// shapes), or a runtime `Type` value — which is `.type_value`-typed at
|
||||
/// runtime (`type_of(x)`, a `[]Type` element `list[i]`, a `Type`-typed
|
||||
/// local / field / param). Any other expression — a value of type
|
||||
/// i64 / f64 / bool / a struct — is NOT a type.
|
||||
pub fn reflectionArgIsType(self: *Lowering, arg: *const Node) bool {
|
||||
if (self.isStaticTypeArg(arg)) return true;
|
||||
return self.inferExprType(arg) == .any;
|
||||
// Either a bare `Type` value (`.type_value`) or an `Any` that may hold a Type
|
||||
// — the boxed reflection path (`case type: type_name(val)` where `val: Any`,
|
||||
// the runtime tag deciding). Both are valid reflection arguments.
|
||||
const ty = self.inferExprType(arg);
|
||||
return ty == .type_value or ty == .any;
|
||||
}
|
||||
|
||||
/// Guard for the type-introspection builtins (`size_of`, `align_of`,
|
||||
|
||||
@@ -1881,8 +1881,12 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
||||
d.addFmt(.err, node.span, "'{s}' is not visible; #import the module that declares it", .{eff_fn_name});
|
||||
break :blk self.emitError(eff_fn_name, node.span);
|
||||
}
|
||||
// Type-as-value: if target is Any (Type variable), produce a type name string
|
||||
if (self.target_type == .any) {
|
||||
// Type-as-value: a bare function name in a `Type` (`.type_value`)
|
||||
// slot is its FUNCTION TYPE — `const_type(() -> R)` — so it prints
|
||||
// / reflects as the real function type, not a func-ref. For a
|
||||
// genuine `Any` param the old behavior is kept (a formatted
|
||||
// type-name string boxed as Any).
|
||||
if (self.target_type == .any or self.target_type == .type_value) {
|
||||
const fd_any: ?*const ast.FnDecl = self.program_index.fn_ast_map.get(eff_fn_name) orelse fd_blk: {
|
||||
switch (self.selectPlainCallableAuthor(id.name, self.current_source_file.?)) {
|
||||
.func => |sf| break :fd_blk sf.decl,
|
||||
@@ -1890,6 +1894,13 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
||||
}
|
||||
};
|
||||
if (fd_any) |fd| {
|
||||
if (self.target_type == .type_value) {
|
||||
var param_ids = std.ArrayList(TypeId).empty;
|
||||
defer param_ids.deinit(self.alloc);
|
||||
for (fd.params) |p| param_ids.append(self.alloc, self.resolveParamType(&p)) catch {};
|
||||
const fn_tid = self.module.types.functionType(param_ids.items, self.resolveReturnType(fd));
|
||||
break :blk self.builder.constType(fn_tid);
|
||||
}
|
||||
const fn_type_str = self.formatFnTypeString(fd);
|
||||
const sid = self.module.types.internString(fn_type_str);
|
||||
const str = self.builder.constString(sid);
|
||||
|
||||
@@ -438,6 +438,7 @@ pub fn formatTypeName(self: *Lowering, ty: TypeId) []const u8 {
|
||||
if (ty == .void) return "void";
|
||||
if (ty == .string) return "string";
|
||||
if (ty == .any) return "Any";
|
||||
if (ty == .type_value) return "Type";
|
||||
if (ty == .usize) return "usize";
|
||||
if (ty == .isize) return "isize";
|
||||
|
||||
@@ -665,7 +666,9 @@ pub fn resolveTypeCategoryTags(self: *Lowering, name: []const u8) []const u64 {
|
||||
return tags.items;
|
||||
}
|
||||
if (std.mem.eql(u8, name, "type") or std.mem.eql(u8, name, "Type")) {
|
||||
tags.append(self.alloc, TypeId.any.index()) catch {};
|
||||
// A Type value's runtime tag is `.type_value` (was `.any` when Type and
|
||||
// Any shared a TypeId) — so `case type:` matches an Any holding a Type.
|
||||
tags.append(self.alloc, TypeId.type_value.index()) catch {};
|
||||
return tags.items;
|
||||
}
|
||||
|
||||
|
||||
@@ -454,11 +454,12 @@ pub const Builder = struct {
|
||||
/// fail loudly rather than silently materialise the TypeId as an
|
||||
/// int.
|
||||
pub fn constType(self: *Builder, tid: TypeId) Ref {
|
||||
// Type values are Any-shaped at runtime —
|
||||
// `{ tag = .any.index() (the meta-marker), value = tid }`.
|
||||
// Matches `Type → .any` in `type_bridge`. The interp keeps
|
||||
// the high-fidelity `.type_tag` Value for comptime ops.
|
||||
return self.emit(.{ .const_type = tid }, .any);
|
||||
// A Type value is its own 8-byte builtin handle (`.type_value`), a bare
|
||||
// i64 carrying `tid.index()` — distinct from the 16-byte boxed `.any`.
|
||||
// Flowing it into an `Any` slot boxes it (`{ tag = .any.index(), value =
|
||||
// tid }`) via the standard box-any coercion. The interp keeps the
|
||||
// high-fidelity `.type_tag` Value for comptime ops.
|
||||
return self.emit(.{ .const_type = tid }, .type_value);
|
||||
}
|
||||
|
||||
// ── Arithmetic ──────────────────────────────────────────────────
|
||||
|
||||
@@ -37,7 +37,7 @@ test "TypeResolver.resolvePrimitive maps builtin keywords, null otherwise" {
|
||||
try std.testing.expectEqual(@as(?TypeId, .f64), TypeResolver.resolvePrimitive("f64"));
|
||||
try std.testing.expectEqual(@as(?TypeId, .void), TypeResolver.resolvePrimitive("void"));
|
||||
try std.testing.expectEqual(@as(?TypeId, .any), TypeResolver.resolvePrimitive("Any"));
|
||||
try std.testing.expectEqual(@as(?TypeId, .any), TypeResolver.resolvePrimitive("Type"));
|
||||
try std.testing.expectEqual(@as(?TypeId, .type_value), TypeResolver.resolvePrimitive("Type"));
|
||||
try std.testing.expectEqual(@as(?TypeId, .usize), TypeResolver.resolvePrimitive("usize"));
|
||||
try std.testing.expectEqual(@as(?TypeId, .isize), TypeResolver.resolvePrimitive("isize"));
|
||||
try std.testing.expectEqual(@as(?TypeId, .noreturn), TypeResolver.resolvePrimitive("noreturn"));
|
||||
|
||||
@@ -58,10 +58,11 @@ pub const TypeResolver = struct {
|
||||
if (std.mem.eql(u8, name, "cstring")) return .cstring;
|
||||
if (std.mem.eql(u8, name, "void")) return .void;
|
||||
if (std.mem.eql(u8, name, "Any")) return .any;
|
||||
// `Type` values are runtime-representable as Any-shaped pairs
|
||||
// `{ tag = .any.index(), value = TypeId.index() }`, so `Type` maps to
|
||||
// `.any` and routes through the existing Any infrastructure.
|
||||
if (std.mem.eql(u8, name, "Type")) return .any;
|
||||
// A `Type` value is its own 8-byte builtin handle (`.type_value`), DISTINCT
|
||||
// from the 16-byte boxed `.any`. Flowing a `Type` into an `Any` slot boxes
|
||||
// it (`{ tag = .any.index(), value = TypeId.index() }`) via the standard
|
||||
// box-any coercion; reflection reads it back through `reflectArgRepr`.
|
||||
if (std.mem.eql(u8, name, "Type")) return .type_value;
|
||||
if (std.mem.eql(u8, name, "noreturn")) return .noreturn;
|
||||
if (std.mem.eql(u8, name, "usize")) return .usize;
|
||||
if (std.mem.eql(u8, name, "isize")) return .isize;
|
||||
|
||||
Reference in New Issue
Block a user