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`.)
|
`List` growth; orthogonal, see `current/CHECKPOINT-METATYPE.md`.)
|
||||||
|
|
||||||
## Log
|
## 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).**
|
- **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
|
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
|
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;
|
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
|
--> examples/0149-types-int-numeric-limits-errors.sx:15:10
|
||||||
|
|
|
|
||||||
15 | s := MyStruct.min;
|
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;
|
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
|
--> examples/0160-types-float-numeric-limits-errors.sx:25:10
|
||||||
|
|
|
|
||||||
25 | f := MyStruct.epsilon;
|
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 {
|
pub fn emitConstType(self: Ops, tid: TypeId) void {
|
||||||
// Type values are Any-shaped pairs:
|
// A Type value is an 8-byte handle: a bare i64 carrying `tid.index()`
|
||||||
// { tag = .any.index() (the meta-marker),
|
// (the `.type_value` builtin TypeId), distinct from the 16-byte boxed
|
||||||
// value = tid.index() }
|
// `.any`. Flowing a Type into an `Any` slot boxes it via the standard
|
||||||
// Lets storage in Any slots, struct fields,
|
// box-any coercion (`{ tag = .any.index(), value = tid }`); `case type:`
|
||||||
// `Type`-typed vars, and slice elements all round-
|
// in `any_to_string` then matches tag == `.any.index()`, and runtime
|
||||||
// trip through the standard Any infrastructure.
|
// `type_name(t)` reads the TypeId through `reflectArgTypeId` (`.bare`
|
||||||
// `case type:` in `any_to_string` matches on
|
// when the arg is `.type_value`, `.boxed` when it is an Any).
|
||||||
// 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);
|
|
||||||
const val = c.LLVMConstInt(self.e.cached_i64, tid.index(), 0);
|
const val = c.LLVMConstInt(self.e.cached_i64, tid.index(), 0);
|
||||||
var result = c.LLVMGetUndef(any_ty);
|
self.e.mapRef(val);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Arithmetic ─────────────────────────────────────────
|
// ── Arithmetic ─────────────────────────────────────────
|
||||||
@@ -1341,11 +1333,11 @@ pub const Ops = struct {
|
|||||||
/// Resolve the `TypeId` (as a runtime `i64`) that a dynamic
|
/// Resolve the `TypeId` (as a runtime `i64`) that a dynamic
|
||||||
/// `type_name` / `type_is_unsigned` must operate on. A reflection
|
/// `type_name` / `type_is_unsigned` must operate on. A reflection
|
||||||
/// builtin reads an `Any`'s runtime TYPE-TAG, never its raw payload:
|
/// builtin reads an `Any`'s runtime TYPE-TAG, never its raw payload:
|
||||||
/// - `.bare`: a `Type` value already lowered to a bare i64 `TypeId`
|
/// - `.bare`: a `Type` value (a `.type_value` arg) — a bare i64 `TypeId`
|
||||||
/// index (an unboxed direct call site) → the value itself.
|
/// index (e.g. `type_of(x)` directly) → the value itself.
|
||||||
/// - `.boxed`: an `Any` aggregate `{ tag, value }`. When the tag is
|
/// - `.boxed`: an `Any` aggregate `{ tag, value }`. When the tag is
|
||||||
/// `.any`, the box carries a *Type value* (the `{ .any, tid }` shape
|
/// `.type_value`, the box carries a *Type value* (a `Type` boxed into an
|
||||||
/// `const_type` / `type_of` produce) → the TypeId is the payload.
|
/// `Any`, `{ .type_value, tid }`) → the TypeId is the payload.
|
||||||
/// Otherwise the box carries a *runtime value* whose type IS the tag
|
/// 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)`
|
/// → 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
|
/// for `av : Any = 6` report `i64` (the held value's type), while
|
||||||
@@ -1360,8 +1352,8 @@ pub const Ops = struct {
|
|||||||
.boxed => blk: {
|
.boxed => blk: {
|
||||||
const tag = c.LLVMBuildExtractValue(self.e.builder, arg_val, 0, "refl.tag");
|
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 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 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, any_tag, "refl.istype");
|
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");
|
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 = "error_tag_name", .want = .string },
|
||||||
.{ .name = "is_comptime", .want = .bool },
|
.{ .name = "is_comptime", .want = .bool },
|
||||||
.{ .name = "is_flags", .want = .bool },
|
.{ .name = "is_flags", .want = .bool },
|
||||||
.{ .name = "type_of", .want = .any },
|
.{ .name = "type_of", .want = .type_value },
|
||||||
.{ .name = "field_value", .want = .any },
|
.{ .name = "field_value", .want = .any },
|
||||||
.{ .name = "__interp_print_frames", .want = .void },
|
.{ .name = "__interp_print_frames", .want = .void },
|
||||||
// A math builtin with a non-`f32` argument widens to `f64` (the int
|
// 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);
|
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, "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_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);
|
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
|
// Plain bare same-name flat collision (R5 §C): route through the ONE
|
||||||
// author producer `selectedFreeAuthor` so `plan` types the call as the
|
// 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
|
// A bare type name (alias like `Vec4`, struct name, or
|
||||||
// builtin primitive) referenced in expression position
|
// builtin primitive) referenced in expression position
|
||||||
// is a Type value — IR type `.any`.
|
// is a Type value — IR type `.type_value` (8-byte handle). A
|
||||||
if (self.l.isKnownTypeName(id.name)) return .any;
|
// 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;
|
return .unresolved;
|
||||||
},
|
},
|
||||||
.type_expr => |te| {
|
.type_expr => |te| {
|
||||||
@@ -318,8 +321,8 @@ pub const ExprTyper = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// A bare type name in expression position (e.g. `i64`,
|
// A bare type name in expression position (e.g. `i64`,
|
||||||
// `Point`, `*u8`) is a Type value — IR type `.any`.
|
// `Point`, `*u8`) is a Type value — IR type `.type_value`.
|
||||||
if (self.l.isKnownTypeName(te.name)) return .any;
|
if (self.l.isKnownTypeName(te.name)) return .type_value;
|
||||||
return .unresolved;
|
return .unresolved;
|
||||||
},
|
},
|
||||||
.enum_literal => {
|
.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
|
// `av : Any = 6` (`{ tag = i64, value = 6 }`) must resolve to `i64`, NOT
|
||||||
// `types[6]` (`u8`).
|
// `types[6]` (`u8`).
|
||||||
test "reflect: reflectTypeId branches on the Any tag" {
|
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.
|
// Native first-class Type value → the held TypeId directly.
|
||||||
try std.testing.expectEqual(@as(?TypeId, .u64), (Value{ .type_tag = .u64 }).reflectTypeId());
|
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());
|
try std.testing.expectEqual(@as(?TypeId, .u32), (Value{ .aggregate = &held_u32 }).reflectTypeId());
|
||||||
|
|
||||||
// Any holding a TYPE value (the `type_of(x)` / `const_type` shape):
|
// 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) ...
|
// 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());
|
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.
|
// ... 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());
|
try std.testing.expectEqual(@as(?TypeId, .u64), (Value{ .aggregate = &held_type_tag }).reflectTypeId());
|
||||||
|
|
||||||
// Neither shape → null (the caller bails loudly, never guesses a TypeId).
|
// Neither shape → null (the caller bails loudly, never guesses a TypeId).
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ pub const Value = union(enum) {
|
|||||||
const fields = self.aggregate;
|
const fields = self.aggregate;
|
||||||
if (fields.len >= 2) {
|
if (fields.len >= 2) {
|
||||||
const tag = fields[0].asInt() orelse return null;
|
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].asTypeId()) |t| return t;
|
||||||
if (fields[1].asInt()) |iv| return TypeId.fromIndex(@intCast(iv));
|
if (fields[1].asInt()) |iv| return TypeId.fromIndex(@intCast(iv));
|
||||||
return null;
|
return null;
|
||||||
@@ -927,7 +927,16 @@ pub const Interpreter = struct {
|
|||||||
switch (base) {
|
switch (base) {
|
||||||
.aggregate => |fields| {
|
.aggregate => |fields| {
|
||||||
if (fa.field_index >= fields.len) return error.OutOfBounds;
|
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 => |s| {
|
||||||
// String as fat pointer: field 0 = ptr (string), field 1 = len
|
// String as fat pointer: field 0 = ptr (string), field 1 = len
|
||||||
@@ -942,12 +951,12 @@ pub const Interpreter = struct {
|
|||||||
},
|
},
|
||||||
.type_tag => |tid| {
|
.type_tag => |tid| {
|
||||||
// A first-class Type value is the comptime form of the
|
// 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).
|
// (see `const_type` lowering in buildPackSliceValue).
|
||||||
// `type_of(any_holding_a_Type)` lowers to struct_get
|
// `type_of(any_holding_a_Type)` lowers to struct_get
|
||||||
// field 0, expecting that runtime layout — mirror it so
|
// field 0, expecting that runtime layout — mirror it so
|
||||||
// field 0 reads the `.any` tag and field 1 the type id.
|
// field 0 reads the `.type_value` tag and field 1 the type id.
|
||||||
if (fa.field_index == 0) return .{ .value = .{ .int = @intCast(TypeId.any.index()) } };
|
if (fa.field_index == 0) return .{ .value = .{ .int = @intCast(TypeId.type_value.index()) } };
|
||||||
if (fa.field_index == 1) return .{ .value = .{ .type_tag = tid } };
|
if (fa.field_index == 1) return .{ .value = .{ .type_tag = tid } };
|
||||||
return error.OutOfBounds;
|
return error.OutOfBounds;
|
||||||
},
|
},
|
||||||
@@ -1080,7 +1089,12 @@ pub const Interpreter = struct {
|
|||||||
return .branch;
|
return .branch;
|
||||||
},
|
},
|
||||||
.switch_br => |sb| {
|
.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| {
|
for (sb.cases) |case| {
|
||||||
if (operand == case.value) {
|
if (operand == case.value) {
|
||||||
const args = self.alloc.alloc(Value, case.args.len) catch return error.CannotEvalComptime;
|
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.
|
// self-reference resolves); the interp's `declare` returns that slot.
|
||||||
const name_ref = self.lowerExpr(c.args[0]);
|
const name_ref = self.lowerExpr(c.args[0]);
|
||||||
const args_owned = self.alloc.dupe(Ref, &.{name_ref}) catch return Ref.none;
|
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")) {
|
if (std.mem.eql(u8, name, "define")) {
|
||||||
// Comptime type-construction primitive: complete a declare()'d slot
|
// 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;
|
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
|
// define returns the (now-completed) handle as a `Type` value, so the
|
||||||
// one-shot constructor form chains: `T :: define(declare(), info)`.
|
// 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")) {
|
if (std.mem.eql(u8, name, "type_info")) {
|
||||||
// Comptime reflection-into-data: reflect a type INTO a `TypeInfo`
|
// 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);
|
} }, .any);
|
||||||
}
|
}
|
||||||
if (std.mem.eql(u8, name, "type_of")) {
|
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);
|
if (c.args.len < 1) return self.builder.constType(.void);
|
||||||
const arg_ty = self.inferExprType(c.args[0]);
|
const arg_ty = self.inferExprType(c.args[0]);
|
||||||
if (arg_ty == .any) {
|
if (arg_ty == .any) {
|
||||||
// Runtime: extract tag, rebuild Any with `{.any, tag}` so
|
// Runtime: the held value's type is the Any's tag (field 0). Read it
|
||||||
// the returned value carries Type semantics (tag field
|
// out AS the 8-byte `.type_value` handle — type_of of a runtime Any
|
||||||
// says ".any" → the value field holds the type id).
|
// names the held value's type, so the tag IS the type id.
|
||||||
const val = self.lowerExpr(c.args[0]);
|
const val = self.lowerExpr(c.args[0]);
|
||||||
const tag_val = self.builder.structGet(val, 0, .i64);
|
return self.builder.structGet(val, 0, .type_value);
|
||||||
return self.builder.boxAny(tag_val, .any);
|
|
||||||
} else {
|
} else {
|
||||||
return self.builder.constType(arg_ty);
|
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
|
/// Strict `$T: Type` classification shared by the 7 type-introspection
|
||||||
/// builtins. An argument denotes a type iff it is a spelled /
|
/// builtins. An argument denotes a type iff it is a spelled /
|
||||||
/// compile-time type or generic type parameter (the `isStaticTypeArg`
|
/// 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
|
/// runtime (`type_of(x)`, a `[]Type` element `list[i]`, a `Type`-typed
|
||||||
/// local / field / param). Any other expression — a value of type
|
/// local / field / param). Any other expression — a value of type
|
||||||
/// i64 / f64 / bool / a struct — is NOT a type.
|
/// i64 / f64 / bool / a struct — is NOT a type.
|
||||||
pub fn reflectionArgIsType(self: *Lowering, arg: *const Node) bool {
|
pub fn reflectionArgIsType(self: *Lowering, arg: *const Node) bool {
|
||||||
if (self.isStaticTypeArg(arg)) return true;
|
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`,
|
/// 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});
|
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);
|
break :blk self.emitError(eff_fn_name, node.span);
|
||||||
}
|
}
|
||||||
// Type-as-value: if target is Any (Type variable), produce a type name string
|
// Type-as-value: a bare function name in a `Type` (`.type_value`)
|
||||||
if (self.target_type == .any) {
|
// 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: {
|
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.?)) {
|
switch (self.selectPlainCallableAuthor(id.name, self.current_source_file.?)) {
|
||||||
.func => |sf| break :fd_blk sf.decl,
|
.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 (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 fn_type_str = self.formatFnTypeString(fd);
|
||||||
const sid = self.module.types.internString(fn_type_str);
|
const sid = self.module.types.internString(fn_type_str);
|
||||||
const str = self.builder.constString(sid);
|
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 == .void) return "void";
|
||||||
if (ty == .string) return "string";
|
if (ty == .string) return "string";
|
||||||
if (ty == .any) return "Any";
|
if (ty == .any) return "Any";
|
||||||
|
if (ty == .type_value) return "Type";
|
||||||
if (ty == .usize) return "usize";
|
if (ty == .usize) return "usize";
|
||||||
if (ty == .isize) return "isize";
|
if (ty == .isize) return "isize";
|
||||||
|
|
||||||
@@ -665,7 +666,9 @@ pub fn resolveTypeCategoryTags(self: *Lowering, name: []const u8) []const u64 {
|
|||||||
return tags.items;
|
return tags.items;
|
||||||
}
|
}
|
||||||
if (std.mem.eql(u8, name, "type") or std.mem.eql(u8, name, "Type")) {
|
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;
|
return tags.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -454,11 +454,12 @@ pub const Builder = struct {
|
|||||||
/// fail loudly rather than silently materialise the TypeId as an
|
/// fail loudly rather than silently materialise the TypeId as an
|
||||||
/// int.
|
/// int.
|
||||||
pub fn constType(self: *Builder, tid: TypeId) Ref {
|
pub fn constType(self: *Builder, tid: TypeId) Ref {
|
||||||
// Type values are Any-shaped at runtime —
|
// A Type value is its own 8-byte builtin handle (`.type_value`), a bare
|
||||||
// `{ tag = .any.index() (the meta-marker), value = tid }`.
|
// i64 carrying `tid.index()` — distinct from the 16-byte boxed `.any`.
|
||||||
// Matches `Type → .any` in `type_bridge`. The interp keeps
|
// Flowing it into an `Any` slot boxes it (`{ tag = .any.index(), value =
|
||||||
// the high-fidelity `.type_tag` Value for comptime ops.
|
// tid }`) via the standard box-any coercion. The interp keeps the
|
||||||
return self.emit(.{ .const_type = tid }, .any);
|
// high-fidelity `.type_tag` Value for comptime ops.
|
||||||
|
return self.emit(.{ .const_type = tid }, .type_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Arithmetic ──────────────────────────────────────────────────
|
// ── 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, .f64), TypeResolver.resolvePrimitive("f64"));
|
||||||
try std.testing.expectEqual(@as(?TypeId, .void), TypeResolver.resolvePrimitive("void"));
|
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("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, .usize), TypeResolver.resolvePrimitive("usize"));
|
||||||
try std.testing.expectEqual(@as(?TypeId, .isize), TypeResolver.resolvePrimitive("isize"));
|
try std.testing.expectEqual(@as(?TypeId, .isize), TypeResolver.resolvePrimitive("isize"));
|
||||||
try std.testing.expectEqual(@as(?TypeId, .noreturn), TypeResolver.resolvePrimitive("noreturn"));
|
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, "cstring")) return .cstring;
|
||||||
if (std.mem.eql(u8, name, "void")) return .void;
|
if (std.mem.eql(u8, name, "void")) return .void;
|
||||||
if (std.mem.eql(u8, name, "Any")) return .any;
|
if (std.mem.eql(u8, name, "Any")) return .any;
|
||||||
// `Type` values are runtime-representable as Any-shaped pairs
|
// A `Type` value is its own 8-byte builtin handle (`.type_value`), DISTINCT
|
||||||
// `{ tag = .any.index(), value = TypeId.index() }`, so `Type` maps to
|
// from the 16-byte boxed `.any`. Flowing a `Type` into an `Any` slot boxes
|
||||||
// `.any` and routes through the existing Any infrastructure.
|
// it (`{ tag = .any.index(), value = TypeId.index() }`) via the standard
|
||||||
if (std.mem.eql(u8, name, "Type")) return .any;
|
// 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, "noreturn")) return .noreturn;
|
||||||
if (std.mem.eql(u8, name, "usize")) return .usize;
|
if (std.mem.eql(u8, name, "usize")) return .usize;
|
||||||
if (std.mem.eql(u8, name, "isize")) return .isize;
|
if (std.mem.eql(u8, name, "isize")) return .isize;
|
||||||
|
|||||||
Reference in New Issue
Block a user