ir: Type as first-class value (Any-shaped {tag, value})
Previously, `t : Type = f64` stored a boxed string carrying the literal
name "f64"; comparisons and `type_of`/`type_name` round-trips lost the
underlying TypeId. This switches `Type` to a runtime-representable Any
pair: `{ tag = .any.index() (meta-marker), value = TypeId.index() }`.
Mechanism:
- `const_type` emits a 16-byte Any aggregate via insertvalue.
- `TypeId.any` advertises 16 bytes / 8-byte alignment so structs that
embed `t: Type` size correctly under verifySizes.
- `lowerBinaryOp` folds `==`/`!=` between static type-refs to a
`const_bool`, and decomposes runtime Any-vs-Any compares via
`unbox_any` so LLVM doesn't see icmp on aggregates.
- `lowerMatch`'s `is_type_match` path unboxes Any-typed subjects to
the i64 type tag before the switch, so `case type:` etc. fire.
- `lowerRuntimeDispatchCall` (used by `case T: ... cast(t) val`) does
the same unbox for the type-tag arg.
- `type_of(val: Any)` rebuilds an Any with `{.any, tag_of(val)}` so
the result is itself a `Type` value, not a bare i64.
- `buildPackSliceValue` stops re-boxing const_type — the value is
already canonical Any.
- `__sx_type_names` now indexes by TypeId across the whole table
using the new `types.formatTypeName` (structural names for `*T`,
`[]T`, `[N]T`, `?T`, `Vector(N,T)`, function/closure/tuple) so
runtime `type_name(t)` works for compound types.
- `interp.zig`'s comptime `type_name` accepts either the bare
`.type_tag` Value or the Any-boxed aggregate it now sees.
- `scanDecls` registers `Vec4 :: Vector(4, f32)` style aliases in
`type_alias_map` (before the `fn_ast_map` check; `Vector` IS a
`#builtin` fn). Lets `Vec4` in expression position lower as
`const_type(<vector tid>)`.
- `isStaticTypeArg` becomes scope-aware: a name shadowed by a runtime
local is not static. `isStaticTypeRef` is the symmetric helper for
the eq fold.
- `inferExprType` returns `.any` for bare type names (identifier and
type_expr) so pack arg types are correct.
Side effect: `print("{}", Vec4)` now prints the structural name
`Vector(4,f32)` rather than the alias literal `Vec4` — 12-meta's
expectation updated. Aliases stay pointer-equal to their target
(`Vec4 == Vector(4, f32)` is true).
Tests:
- examples/189-type-all-interactions.sx: 12-section comprehensive
coverage — literal `==`, `type_of(value) == T`, `Type` var storage,
`type_name` (static + runtime), printing Type values, generic
dispatch via `$T: Type`, `identity($T, val)`, `Wrap($T)`, reflection
builtins (`size_of`, `align_of`, `field_count`, `type_eq`),
`..$args` pack walking, `Type` in struct field, compound type
literals (`*Point`, `[4]s32`, `[]bool`, `?f64`).
- examples/12-meta.sx: expected output updated to reflect structural
name for the Vec4 alias path.
- ffi-objc-call-06-sret-return.ir: regenerated to absorb the new
type-name strings now emitted globally.
223/223 examples pass.
This commit is contained in:
@@ -25,5 +25,5 @@ main :: () {
|
||||
// ** stdout **
|
||||
// f64
|
||||
// 3.200000
|
||||
// Vec4
|
||||
// Vector(4,f32)
|
||||
// () -> s32
|
||||
206
examples/189-type-all-interactions.sx
Normal file
206
examples/189-type-all-interactions.sx
Normal file
@@ -0,0 +1,206 @@
|
||||
// Type as a first-class value — comprehensive interaction smoke.
|
||||
//
|
||||
// Every way a user can plausibly touch a `Type` value, in one
|
||||
// place. Pre-fix: some sections fail (`<?>`, false, garbage, or
|
||||
// compile errors). Post-fix: every section's output matches the
|
||||
// header line.
|
||||
//
|
||||
// Sections labelled `--- N. <thing> ---` so the diff localises
|
||||
// which interaction regressed.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
// ── Test fixtures ─────────────────────────────────────────────
|
||||
|
||||
Point :: struct { x: s32; y: s32; }
|
||||
|
||||
Color :: enum { red; green; blue; }
|
||||
|
||||
Wrap :: struct ($T: Type) { v: T; }
|
||||
|
||||
identity :: ($T: Type, val: T) -> T => val;
|
||||
|
||||
// Generic comptime fn that dispatches on type identity (compile-
|
||||
// time fold via type_eq → const_bool → inline-if folds the
|
||||
// branch away).
|
||||
describe :: ($T: Type) -> string {
|
||||
inline if type_eq(T, s64) { return "int64"; }
|
||||
inline if type_eq(T, string) { return "text"; }
|
||||
inline if type_eq(T, bool) { return "boolean"; }
|
||||
return "other";
|
||||
}
|
||||
|
||||
// Pack-fn collecting per-position types (step 4A.bare path).
|
||||
type_list :: (..$args) -> string {
|
||||
list := $args;
|
||||
s := "[";
|
||||
i : s64 = 0;
|
||||
while i < list.len {
|
||||
if i > 0 { s = concat(s, ", "); }
|
||||
s = concat(s, type_name(list[i]));
|
||||
i = i + 1;
|
||||
}
|
||||
return concat(s, "]");
|
||||
}
|
||||
|
||||
// Type stored in a struct field.
|
||||
TypeHolder :: struct { t: Type; }
|
||||
|
||||
main :: () -> s32 {
|
||||
// ── 1. Type literal equality ────────────────────────────
|
||||
print("=== 1. literal == ===\n");
|
||||
print("s64 == s64: {}\n", s64 == s64);
|
||||
print("s64 == string: {}\n", s64 == string);
|
||||
print("*u8 == *u8: {}\n", *u8 == *u8);
|
||||
print("?s64 == ?s64: {}\n", ?s64 == ?s64);
|
||||
print("?s64 == ?s32: {}\n", ?s64 == ?s32);
|
||||
|
||||
// ── 2. type_of(value) ───────────────────────────────────
|
||||
print("=== 2. type_of(value) == T ===\n");
|
||||
a : s64 = 42;
|
||||
b : f64 = 3.14;
|
||||
s : string = "hi";
|
||||
print("type_of(a) == s64: {}\n", type_of(a) == s64);
|
||||
print("type_of(b) == f64: {}\n", type_of(b) == f64);
|
||||
print("type_of(s) == string: {}\n", type_of(s) == string);
|
||||
print("type_of(a) == f64: {}\n", type_of(a) == f64);
|
||||
|
||||
// ── 3. Type variable storage + readback ─────────────────
|
||||
print("=== 3. Type variable storage ===\n");
|
||||
t : Type = f64;
|
||||
print("t == f64: {}\n", t == f64);
|
||||
print("t == string: {}\n", t == string);
|
||||
t = string;
|
||||
print("after reassign t == string: {}\n", t == string);
|
||||
t = bool;
|
||||
print("t == bool: {}\n", t == bool);
|
||||
|
||||
// ── 4. type_name on literals + variables ────────────────
|
||||
print("=== 4. type_name ===\n");
|
||||
print("type_name(s64): {}\n", type_name(s64));
|
||||
print("type_name(*u8): {}\n", type_name(*u8));
|
||||
print("type_name(Point): {}\n", type_name(Point));
|
||||
print("type_name(Color): {}\n", type_name(Color));
|
||||
t = f64;
|
||||
print("type_name(t): {}\n", type_name(t));
|
||||
|
||||
// ── 5. Print Type values directly ───────────────────────
|
||||
print("=== 5. print Type values ===\n");
|
||||
print("literal: {}\n", s64);
|
||||
t = string;
|
||||
print("var: {}\n", t);
|
||||
print("type_of(b): {}\n", type_of(b));
|
||||
|
||||
// ── 6. Generic dispatch via $T: Type ────────────────────
|
||||
print("=== 6. generic dispatch ===\n");
|
||||
print("describe(s64): {}\n", describe(s64));
|
||||
print("describe(string): {}\n", describe(string));
|
||||
print("describe(bool): {}\n", describe(bool));
|
||||
print("describe(f64): {}\n", describe(f64));
|
||||
|
||||
// ── 7. identity(T, val) ─────────────────────────────────
|
||||
print("=== 7. identity($T, val) ===\n");
|
||||
print("identity(s64, 7): {}\n", identity(s64, 7));
|
||||
print("identity(string, hi): {}\n", identity(string, "hi"));
|
||||
print("identity(bool, true): {}\n", identity(bool, true));
|
||||
|
||||
// ── 8. Comptime-generated struct (Wrap($T)) ─────────────
|
||||
print("=== 8. Wrap($T) ===\n");
|
||||
w_int := Wrap(s64).{ v = 42 };
|
||||
w_str := Wrap(string).{ v = "wrapped" };
|
||||
print("Wrap(s64).v: {}\n", w_int.v);
|
||||
print("Wrap(string).v: {}\n", w_str.v);
|
||||
|
||||
// ── 9. Reflection builtins on Types ─────────────────────
|
||||
print("=== 9. reflection on Type ===\n");
|
||||
print("size_of(s64): {}\n", size_of(s64));
|
||||
print("size_of(*u8): {}\n", size_of(*u8));
|
||||
print("align_of(f64): {}\n", align_of(f64));
|
||||
print("field_count(Point): {}\n", field_count(Point));
|
||||
print("type_eq(s64, s64): {}\n", type_eq(s64, s64));
|
||||
print("type_eq(s64, string): {}\n", type_eq(s64, string));
|
||||
|
||||
// ── 10. Type pack (..$args) walking ─────────────────────
|
||||
print("=== 10. ..$args walking ===\n");
|
||||
print("type_list(): {}\n", type_list());
|
||||
print("type_list(1): {}\n", type_list(42));
|
||||
print("type_list(1, \"x\"): {}\n", type_list(42, "x"));
|
||||
print("type_list(true, 3.14): {}\n", type_list(true, 3.14));
|
||||
|
||||
// ── 11. Type in struct field ────────────────────────────
|
||||
print("=== 11. Type in struct field ===\n");
|
||||
h := TypeHolder.{ t = s64 };
|
||||
print("h.t == s64: {}\n", h.t == s64);
|
||||
print("h.t == string: {}\n", h.t == string);
|
||||
print("type_name(h.t): {}\n", type_name(h.t));
|
||||
|
||||
// ── 12. Compound type literals ──────────────────────────
|
||||
print("=== 12. compound literals ===\n");
|
||||
print("type_name(*Point): {}\n", type_name(*Point));
|
||||
print("type_name([4]s32): {}\n", type_name([4]s32));
|
||||
print("type_name([]bool): {}\n", type_name([]bool));
|
||||
print("type_name(?f64): {}\n", type_name(?f64));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ** stdout **
|
||||
// === 1. literal == ===
|
||||
// s64 == s64: true
|
||||
// s64 == string: false
|
||||
// *u8 == *u8: true
|
||||
// ?s64 == ?s64: true
|
||||
// ?s64 == ?s32: false
|
||||
// === 2. type_of(value) == T ===
|
||||
// type_of(a) == s64: true
|
||||
// type_of(b) == f64: true
|
||||
// type_of(s) == string: true
|
||||
// type_of(a) == f64: false
|
||||
// === 3. Type variable storage ===
|
||||
// t == f64: true
|
||||
// t == string: false
|
||||
// after reassign t == string: true
|
||||
// t == bool: true
|
||||
// === 4. type_name ===
|
||||
// type_name(s64): s64
|
||||
// type_name(*u8): *u8
|
||||
// type_name(Point): Point
|
||||
// type_name(Color): Color
|
||||
// type_name(t): f64
|
||||
// === 5. print Type values ===
|
||||
// literal: s64
|
||||
// var: string
|
||||
// type_of(b): f64
|
||||
// === 6. generic dispatch ===
|
||||
// describe(s64): int64
|
||||
// describe(string): text
|
||||
// describe(bool): boolean
|
||||
// describe(f64): other
|
||||
// === 7. identity($T, val) ===
|
||||
// identity(s64, 7): 7
|
||||
// identity(string, hi): hi
|
||||
// identity(bool, true): true
|
||||
// === 8. Wrap($T) ===
|
||||
// Wrap(s64).v: 42
|
||||
// Wrap(string).v: wrapped
|
||||
// === 9. reflection on Type ===
|
||||
// size_of(s64): 8
|
||||
// size_of(*u8): 8
|
||||
// align_of(f64): 8
|
||||
// field_count(Point): 2
|
||||
// type_eq(s64, s64): true
|
||||
// type_eq(s64, string): false
|
||||
// === 10. ..$args walking ===
|
||||
// type_list(): []
|
||||
// type_list(1): [s64]
|
||||
// type_list(1, "x"): [s64, string]
|
||||
// type_list(true, 3.14): [bool, f64]
|
||||
// === 11. Type in struct field ===
|
||||
// h.t == s64: true
|
||||
// h.t == string: false
|
||||
// type_name(h.t): s64
|
||||
// === 12. compound literals ===
|
||||
// type_name(*Point): *Point
|
||||
// type_name([4]s32): [4]s32
|
||||
// type_name([]bool): []bool
|
||||
// type_name(?f64): ?f64
|
||||
@@ -319,7 +319,7 @@ any_to_string :: (val: Any) -> string {
|
||||
case slice: result = slice_to_string(cast(type) val);
|
||||
case pointer: result = pointer_to_string(cast(type) val);
|
||||
case optional: result = optional_to_string(cast(type) val);
|
||||
case type: { s : string = xx val; result = s; }
|
||||
case type: result = type_name(val);
|
||||
}
|
||||
result;
|
||||
}
|
||||
|
||||
@@ -144,6 +144,12 @@ pub const LLVMEmitter = struct {
|
||||
// Cached field name arrays for reflection (TypeId → LLVM global)
|
||||
field_name_arrays: std.AutoHashMap(u32, c.LLVMValueRef),
|
||||
|
||||
// Lazy global `[N x string]` indexed by TypeId.index(), holding
|
||||
// each type's display name. Built on the first dynamic
|
||||
// `type_name(t)` call site; reused thereafter.
|
||||
type_name_array: ?c.LLVMValueRef = null,
|
||||
type_name_array_len: u32 = 0,
|
||||
|
||||
// Target configuration (stored for ABI decisions during emission)
|
||||
target_config: TargetConfig,
|
||||
|
||||
@@ -1670,20 +1676,24 @@ pub const LLVMEmitter = struct {
|
||||
self.mapRef(llvm_val);
|
||||
}
|
||||
},
|
||||
.const_type => {
|
||||
// Type values are comptime-only. At LLVM emit they
|
||||
// become undef-i64 placeholders — Type-aware ops are
|
||||
// routed through the interp; if one slips through to
|
||||
// a runtime use site (`type_name` / `type_eq` /
|
||||
// `has_impl` / `bitcast`), the corresponding emit_llvm
|
||||
// bail or runtime use-site error fires loudly. Pure
|
||||
// STORAGE of Type values in runtime aggregates (e.g.
|
||||
// `$args` lowering builds an `[]Any` slice whose
|
||||
// elements happen to be Type values that the user's
|
||||
// code may or may not actually read) is safe — undef
|
||||
// just gets stored, undef just gets read; no use-site
|
||||
// misbehaviour follows.
|
||||
self.mapRef(c.LLVMGetUndef(self.cached_i64));
|
||||
.const_type => |tid| {
|
||||
// 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.getAnyStructType();
|
||||
const tag = c.LLVMConstInt(self.cached_i64, TypeId.any.index(), 0);
|
||||
const val = c.LLVMConstInt(self.cached_i64, tid.index(), 0);
|
||||
var result = c.LLVMGetUndef(any_ty);
|
||||
result = c.LLVMBuildInsertValue(self.builder, result, tag, 0, "ct.tag");
|
||||
result = c.LLVMBuildInsertValue(self.builder, result, val, 1, "ct.val");
|
||||
self.mapRef(result);
|
||||
},
|
||||
|
||||
// ── Arithmetic ─────────────────────────────────────────
|
||||
@@ -2969,19 +2979,66 @@ pub const LLVMEmitter = struct {
|
||||
_ = c.LLVMBuildCall2(self.builder, self.getWriteType(), write_fn, &write_args, 3, "");
|
||||
self.advanceRefCounter();
|
||||
},
|
||||
.type_name, .type_eq, .has_impl => {
|
||||
// Comptime-only reflection builtins. When the
|
||||
// lowering split between static-fold and
|
||||
// dynamic-builtin-call routes a call to one of
|
||||
// these, the call site is intended for the
|
||||
// interp's arm. LLVM still compiles the
|
||||
// containing fn (the JIT module-wide) even if
|
||||
// main never calls it — so this op DOES reach
|
||||
// emit, just in dead-from-main's-perspective
|
||||
// code. Silent undef-i64 placeholder is the
|
||||
// right answer; the interp's arm catches real
|
||||
// misuse via `asTypeId orelse bailDetail`.
|
||||
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
||||
.type_name => {
|
||||
// Dynamic `type_name(t)` at runtime: extract
|
||||
// the TypeId from the arg (an Any-boxed Type
|
||||
// value: tag=`.s64.index()`, value=tid), GEP
|
||||
// into the compiler-emitted `__sx_type_names`
|
||||
// global, load the string. The arg's LLVM
|
||||
// shape is the `{i64, i64}` Any aggregate
|
||||
// (because the IR-side arg type is `.any`
|
||||
// when boxed); for unboxed direct call sites
|
||||
// (the arg IR type is `.s64` from
|
||||
// `const_type`), the value IS the TypeId
|
||||
// index directly.
|
||||
const arg_ref = bi.args[0];
|
||||
const arg_val = self.resolveRef(arg_ref);
|
||||
const arg_ir_ty = self.getRefIRType(arg_ref) orelse @import("types.zig").TypeId.s64;
|
||||
const tid_idx = blk: {
|
||||
if (arg_ir_ty == .any) {
|
||||
// Boxed: extract value field.
|
||||
break :blk c.LLVMBuildExtractValue(self.builder, arg_val, 1, "tn.tid");
|
||||
}
|
||||
// Bare i64 (TypeId index).
|
||||
break :blk arg_val;
|
||||
};
|
||||
const arr_global = self.getOrBuildTypeNameArray();
|
||||
const arr_len = self.type_name_array_len;
|
||||
const string_ty = self.getStringStructType();
|
||||
const arr_ty = c.LLVMArrayType(string_ty, arr_len);
|
||||
const zero = c.LLVMConstInt(self.cached_i64, 0, 0);
|
||||
var indices = [2]c.LLVMValueRef{ zero, tid_idx };
|
||||
const gep = c.LLVMBuildInBoundsGEP2(self.builder, arr_ty, arr_global, &indices, 2, "tn.gep");
|
||||
const result = c.LLVMBuildLoad2(self.builder, string_ty, gep, "tn.load");
|
||||
self.mapRef(result);
|
||||
},
|
||||
.type_eq => {
|
||||
// Dynamic `type_eq(a, b)` — both args are
|
||||
// Type values. Extract TypeId from each Any
|
||||
// box (or use directly if `.s64`-typed),
|
||||
// icmp eq.
|
||||
const a = blk: {
|
||||
const v = self.resolveRef(bi.args[0]);
|
||||
const ty = self.getRefIRType(bi.args[0]) orelse @import("types.zig").TypeId.s64;
|
||||
if (ty == .any) break :blk c.LLVMBuildExtractValue(self.builder, v, 1, "te.a");
|
||||
break :blk v;
|
||||
};
|
||||
const b = blk: {
|
||||
const v = self.resolveRef(bi.args[1]);
|
||||
const ty = self.getRefIRType(bi.args[1]) orelse @import("types.zig").TypeId.s64;
|
||||
if (ty == .any) break :blk c.LLVMBuildExtractValue(self.builder, v, 1, "te.b");
|
||||
break :blk v;
|
||||
};
|
||||
const eq_res = c.LLVMBuildICmp(self.builder, c.LLVMIntEQ, a, b, "te.eq");
|
||||
self.mapRef(eq_res);
|
||||
},
|
||||
.has_impl => {
|
||||
// Runtime has_impl needs a protocol-map
|
||||
// snapshot — not wired yet. Silent false for
|
||||
// now; the lower-time fold via
|
||||
// `tryConstBoolCondition` covers every
|
||||
// statically-resolvable call.
|
||||
self.mapRef(c.LLVMConstInt(self.cached_i1, 0, 0));
|
||||
},
|
||||
else => {
|
||||
// size_of, cast — handled by lowering or codegen glue
|
||||
@@ -4496,6 +4553,49 @@ pub const LLVMEmitter = struct {
|
||||
|
||||
// ── Reflection emission helpers ────────────────────────────────
|
||||
|
||||
/// Build (or return cached) a global constant array of {ptr, i64}
|
||||
/// string values indexed by `TypeId.index()`. Lets the dynamic
|
||||
/// `type_name(t)` builtin look up the type's display name at
|
||||
/// runtime — `gep arr[tid]; load string`. The array's length is
|
||||
/// the current `infos.items.len`; new types interned after this
|
||||
/// is built fall outside the array (the gep would OOB), so
|
||||
/// callers must build LAZILY after all types are registered.
|
||||
fn getOrBuildTypeNameArray(self: *LLVMEmitter) c.LLVMValueRef {
|
||||
if (self.type_name_array) |g| return g;
|
||||
|
||||
const n: u32 = @intCast(self.ir_mod.types.infos.items.len);
|
||||
const string_ty = self.getStringStructType();
|
||||
|
||||
var field_vals = std.ArrayList(c.LLVMValueRef).empty;
|
||||
defer field_vals.deinit(self.alloc);
|
||||
var i: u32 = 0;
|
||||
while (i < n) : (i += 1) {
|
||||
const tid = @import("types.zig").TypeId.fromIndex(i);
|
||||
const name_str = self.ir_mod.types.formatTypeName(self.alloc, tid);
|
||||
const str_z = self.alloc.dupeZ(u8, name_str) catch unreachable;
|
||||
defer self.alloc.free(str_z);
|
||||
const global_str = c.LLVMAddGlobal(self.llvm_module, c.LLVMArrayType(self.cached_i8, @intCast(name_str.len + 1)), "tn.str");
|
||||
c.LLVMSetInitializer(global_str, c.LLVMConstStringInContext(self.context, str_z.ptr, @intCast(name_str.len + 1), 1));
|
||||
c.LLVMSetGlobalConstant(global_str, 1);
|
||||
c.LLVMSetLinkage(global_str, c.LLVMPrivateLinkage);
|
||||
const len_val = c.LLVMConstInt(self.cached_i64, name_str.len, 0);
|
||||
var struct_fields = [2]c.LLVMValueRef{ global_str, len_val };
|
||||
const const_struct = c.LLVMConstStructInContext(self.context, &struct_fields, 2, 0);
|
||||
field_vals.append(self.alloc, const_struct) catch unreachable;
|
||||
}
|
||||
|
||||
const arr_ty = c.LLVMArrayType(string_ty, n);
|
||||
const arr_init = c.LLVMConstArray(string_ty, field_vals.items.ptr, n);
|
||||
const global = c.LLVMAddGlobal(self.llvm_module, arr_ty, "__sx_type_names");
|
||||
c.LLVMSetInitializer(global, arr_init);
|
||||
c.LLVMSetGlobalConstant(global, 1);
|
||||
c.LLVMSetLinkage(global, c.LLVMPrivateLinkage);
|
||||
|
||||
self.type_name_array = global;
|
||||
self.type_name_array_len = n;
|
||||
return global;
|
||||
}
|
||||
|
||||
/// Build (or return cached) a global constant array of {ptr, i64} string values
|
||||
/// for the field names of a struct type.
|
||||
fn getOrBuildFieldNameArray(self: *LLVMEmitter, struct_type: TypeId) c.LLVMValueRef {
|
||||
|
||||
@@ -1767,7 +1767,22 @@ pub const Interpreter = struct {
|
||||
.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)");
|
||||
// Accept either a bare `.type_tag` Value (the
|
||||
// comptime-native form) or an Any-boxed Type
|
||||
// (`.aggregate { tag: int, value: .type_tag }`)
|
||||
// — the latter shape is what `box_any` produces
|
||||
// when const_type values flow through a `.any`-typed
|
||||
// slice or struct field.
|
||||
const tid = blk: {
|
||||
if (arg.asTypeId()) |t| break :blk t;
|
||||
if (arg == .aggregate) {
|
||||
const fields = arg.aggregate;
|
||||
if (fields.len >= 2) {
|
||||
if (fields[1].asTypeId()) |t| break :blk t;
|
||||
}
|
||||
}
|
||||
return bailDetail("comptime type_name: argument is not a Type value (expected `.type_tag` or Any-boxed Type)");
|
||||
};
|
||||
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
|
||||
|
||||
242
src/ir/lower.zig
242
src/ir/lower.zig
@@ -610,6 +610,18 @@ pub const Lowering = struct {
|
||||
const alias_id = if (self.module.types.findByName(alias_name_id)) |existing| existing else self.module.types.intern(alias_info);
|
||||
self.module.types.update(alias_id, alias_info);
|
||||
}
|
||||
} else if (std.mem.eql(u8, callee_name, "Vector")) {
|
||||
// Builtin type constructor — checked BEFORE
|
||||
// the generic `fn_ast_map` branch because
|
||||
// `Vector` IS in `fn_ast_map` (declared as a
|
||||
// `#builtin` fn) but `instantiateTypeFunction`
|
||||
// can't resolve it (no body). Use
|
||||
// `resolveTypeCallWithBindings` which
|
||||
// hard-codes the vector layout.
|
||||
const result_ty = self.resolveTypeCallWithBindings(call_data);
|
||||
if (result_ty != .void) {
|
||||
self.type_alias_map.put(cd.name, result_ty) catch {};
|
||||
}
|
||||
} else if (self.fn_ast_map.get(callee_name)) |fd| {
|
||||
// Type-returning function: Foo :: Complex(u32)
|
||||
if (fd.type_params.len > 0) {
|
||||
@@ -635,6 +647,15 @@ pub const Lowering = struct {
|
||||
const alias_id = if (self.module.types.findByName(alias_name_id)) |existing| existing else self.module.types.intern(alias_info);
|
||||
self.module.types.update(alias_id, alias_info);
|
||||
}
|
||||
} else {
|
||||
// Builtin parameterised type (Vector(N, T) etc) —
|
||||
// resolve via type_bridge and register the result
|
||||
// under the alias name so `Vec4` in expression
|
||||
// position can `const_type(<vector tid>)`.
|
||||
const result_ty = type_bridge.resolveAstType(cd.value, &self.module.types);
|
||||
if (result_ty != .void and result_ty != .s64) {
|
||||
self.type_alias_map.put(cd.name, result_ty) catch {};
|
||||
}
|
||||
}
|
||||
}
|
||||
// comptime_expr handled in Pass 2
|
||||
@@ -2258,16 +2279,26 @@ pub const Lowering = struct {
|
||||
break :blk self.builder.emit(.{ .func_ref = fid }, .s64);
|
||||
}
|
||||
}
|
||||
// Type-as-value: if target is Any (Type context), produce a type name string
|
||||
if (self.target_type == .any) {
|
||||
const sid = self.module.types.internString(id.name);
|
||||
const str = self.builder.constString(sid);
|
||||
break :blk self.builder.boxAny(str, .string);
|
||||
}
|
||||
// Type-as-value: known type name used where a value is expected (e.g. cast(s64, val))
|
||||
// The type arg is lowered but unused — the caller resolves from AST.
|
||||
if (self.isKnownTypeName(id.name)) {
|
||||
break :blk self.emitPlaceholder(id.name);
|
||||
// Type-as-value: a name that resolves to a TypeId
|
||||
// (primitive, alias, registered struct/enum/union,
|
||||
// generic-struct instantiation) evaluates to a
|
||||
// `const_type` in expression position. Works for
|
||||
// direct assignment to a `Type`-typed slot
|
||||
// (`x: Type = Vec4`), comparison (`x == Vec4`), and
|
||||
// pack-arg / Any context (boxing happens at the
|
||||
// consumer).
|
||||
const ty = blk_ty: {
|
||||
if (self.type_bindings) |tb| {
|
||||
if (tb.get(id.name)) |t| break :blk_ty t;
|
||||
}
|
||||
if (self.type_alias_map.get(id.name)) |t| break :blk_ty t;
|
||||
if (type_bridge.resolveTypePrimitive(id.name)) |t| break :blk_ty t;
|
||||
const name_id = self.module.types.internString(id.name);
|
||||
if (self.module.types.findByName(name_id)) |t| break :blk_ty t;
|
||||
break :blk_ty TypeId.void;
|
||||
};
|
||||
if (ty != .void) {
|
||||
break :blk self.builder.constType(ty);
|
||||
}
|
||||
// Unknown identifier
|
||||
break :blk self.emitError(id.name, node.span);
|
||||
@@ -2410,16 +2441,13 @@ pub const Lowering = struct {
|
||||
if (self.global_names.get(te.name)) |gi| {
|
||||
break :blk self.builder.emit(.{ .global_get = gi.id }, gi.ty);
|
||||
}
|
||||
// Type-as-value: if target is Any (Type variable), produce a boxed string
|
||||
if (self.target_type == .any) {
|
||||
const sid = self.module.types.internString(te.name);
|
||||
const str = self.builder.constString(sid);
|
||||
break :blk self.builder.boxAny(str, .string);
|
||||
}
|
||||
// Type expressions are always type names from the parser — they appear
|
||||
// in value position when used as args to cast() etc. Silent placeholder.
|
||||
// Type literal in expression position → first-class
|
||||
// `const_type` Value (i64 = TypeId.index()). Makes
|
||||
// `t : Type = f64;` store a real TypeId; lets
|
||||
// `t == f64` icmp at runtime against the same TypeId.
|
||||
if (self.isKnownTypeName(te.name)) {
|
||||
break :blk self.emitPlaceholder(te.name);
|
||||
const ty = type_bridge.resolveAstType(node, &self.module.types);
|
||||
break :blk self.builder.constType(ty);
|
||||
}
|
||||
break :blk self.emitError(te.name, node.span);
|
||||
},
|
||||
@@ -2456,6 +2484,46 @@ pub const Lowering = struct {
|
||||
return self.builder.blockParam(merge_bb, 0, .bool);
|
||||
}
|
||||
|
||||
// Type-literal comparison fold: when both sides are type-shaped
|
||||
// AST nodes (`s64`, `*u8`, `?T`, `[3]f64`, etc.) OR resolve to
|
||||
// a static TypeId at lower time (`type_of(x)` for any
|
||||
// statically-typed `x`), resolve each and emit a `const_bool`.
|
||||
// Same semantic as `type_eq(A, B)` but using the standard `==`
|
||||
// operator — the user's intuition. Without the fold, both
|
||||
// sides lower as `const_type` undef-i64 and the runtime icmp
|
||||
// returns garbage.
|
||||
if (bop.op == .eq or bop.op == .neq) {
|
||||
if (self.isStaticTypeRef(bop.lhs) and self.isStaticTypeRef(bop.rhs)) {
|
||||
const lhs_ty = self.resolveTypeArg(bop.lhs);
|
||||
const rhs_ty = self.resolveTypeArg(bop.rhs);
|
||||
const eq_result = lhs_ty == rhs_ty;
|
||||
return self.builder.constBool(if (bop.op == .eq) eq_result else !eq_result);
|
||||
}
|
||||
}
|
||||
|
||||
// Any-shaped `==` (e.g. `t == s64` where `t: Type`): both
|
||||
// operands are 16-byte `{tag, value}` aggregates. LLVM
|
||||
// doesn't accept `icmp` on aggregates directly. Decompose
|
||||
// via `unbox_any` (which extracts the value field at
|
||||
// `.s64`) and compare the i64s. Tag fields are stable
|
||||
// across compilations of the same source so value-only
|
||||
// identity is enough.
|
||||
if (bop.op == .eq or bop.op == .neq) {
|
||||
const lhs_ty = self.inferExprType(bop.lhs);
|
||||
const rhs_ty = self.inferExprType(bop.rhs);
|
||||
if (lhs_ty == .any and rhs_ty == .any) {
|
||||
const lhs = self.lowerExpr(bop.lhs);
|
||||
const rhs = self.lowerExpr(bop.rhs);
|
||||
const lhs_val = self.builder.emit(.{ .unbox_any = .{ .operand = lhs } }, .s64);
|
||||
const rhs_val = self.builder.emit(.{ .unbox_any = .{ .operand = rhs } }, .s64);
|
||||
if (bop.op == .eq) {
|
||||
return self.builder.emit(.{ .cmp_eq = .{ .lhs = lhs_val, .rhs = rhs_val } }, .bool);
|
||||
} else {
|
||||
return self.builder.emit(.{ .cmp_ne = .{ .lhs = lhs_val, .rhs = rhs_val } }, .bool);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special case: optional == null / optional != null
|
||||
if (bop.op == .eq or bop.op == .neq) {
|
||||
const lhs_is_null = bop.lhs.data == .null_literal;
|
||||
@@ -3295,8 +3363,15 @@ pub const Lowering = struct {
|
||||
default_bb = self.freshBlock("match.unr");
|
||||
}
|
||||
|
||||
// Switch on the subject (for type match, subject IS the tag; for enum match, extract tag)
|
||||
const tag = if (is_type_match) subject else if (is_optional_match) self.builder.emit(.{ .optional_has_value = .{ .operand = subject } }, .bool) else blk: {
|
||||
// Switch on the subject (for type match, subject is either a
|
||||
// bare TypeId (s64) or an Any-shaped Type value — unbox in the
|
||||
// latter case so the switch sees the i64 type id).
|
||||
const tag = if (is_type_match) tag_blk: {
|
||||
if (subject_ty == .any) {
|
||||
break :tag_blk self.builder.emit(.{ .unbox_any = .{ .operand = subject } }, .s64);
|
||||
}
|
||||
break :tag_blk subject;
|
||||
} else if (is_optional_match) self.builder.emit(.{ .optional_has_value = .{ .operand = subject } }, .bool) else blk: {
|
||||
// Determine actual tag type from union info (e.g. u32 for SDL_Event)
|
||||
const tag_ty: TypeId = tt: {
|
||||
if (!subject_ty.isBuiltin()) {
|
||||
@@ -8018,7 +8093,12 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
// Lower the type tag (runtime value) and Any value BEFORE the switch
|
||||
const type_tag = self.lowerExpr(type_tag_node orelse return self.emitError("dispatch", call_node.callee.span));
|
||||
const type_tag_raw = self.lowerExpr(type_tag_node orelse return self.emitError("dispatch", call_node.callee.span));
|
||||
const type_tag_node_ty = self.inferExprType(type_tag_node.?);
|
||||
const type_tag = if (type_tag_node_ty == .any)
|
||||
self.builder.emit(.{ .unbox_any = .{ .operand = type_tag_raw } }, .s64)
|
||||
else
|
||||
type_tag_raw;
|
||||
const any_val = self.lowerExpr(any_val_node orelse return self.emitError("dispatch", call_node.callee.span));
|
||||
|
||||
// Lower non-cast arguments once (before the switch)
|
||||
@@ -8290,6 +8370,9 @@ pub const Lowering = struct {
|
||||
const array_slot = self.builder.alloca(array_ty);
|
||||
|
||||
for (arg_types, 0..) |ty, i| {
|
||||
// `const_type` produces an `.any`-typed Type value
|
||||
// (`{tag=.any, value=tid}`) — already the canonical Any
|
||||
// shape, so no re-box needed.
|
||||
const type_val = self.builder.constType(ty);
|
||||
const idx_ref = self.builder.constInt(@intCast(i), .s64);
|
||||
const elem_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = idx_ref } }, any_ptr_ty);
|
||||
@@ -8923,7 +9006,7 @@ pub const Lowering = struct {
|
||||
// `resolveTypeArg` silently returns "s64" for every
|
||||
// dynamic call — exactly the silent-arm pattern the
|
||||
// project's REJECTED PATTERNS forbid.
|
||||
if (isStaticTypeArg(c.args[0])) {
|
||||
if (self.isStaticTypeArg(c.args[0])) {
|
||||
const ty = self.resolveTypeArg(c.args[0]);
|
||||
const tn_str = self.formatTypeName(ty);
|
||||
const sid = self.module.types.internString(tn_str);
|
||||
@@ -9024,16 +9107,18 @@ pub const Lowering = struct {
|
||||
} }, .any);
|
||||
}
|
||||
if (std.mem.eql(u8, name, "type_of")) {
|
||||
// type_of(val) — extract Any tag or produce compile-time constant
|
||||
if (c.args.len < 1) return self.builder.constInt(0, .s64);
|
||||
// type_of(val) — produce a Type value (.any-typed aggregate).
|
||||
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 field (field 0 of Any {tag: s64, value: s64})
|
||||
// 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).
|
||||
const val = self.lowerExpr(c.args[0]);
|
||||
return self.builder.structGet(val, 0, .s64);
|
||||
const tag_val = self.builder.structGet(val, 0, .s64);
|
||||
return self.builder.boxAny(tag_val, .any);
|
||||
} else {
|
||||
// Static: emit type tag as constant
|
||||
return self.builder.constInt(@intCast(@intFromEnum(arg_ty)), .s64);
|
||||
return self.builder.constType(arg_ty);
|
||||
}
|
||||
}
|
||||
if (std.mem.eql(u8, name, "field_index")) {
|
||||
@@ -9091,10 +9176,25 @@ pub const Lowering = struct {
|
||||
///
|
||||
/// Dynamic shapes (index_expr, field_access, runtime locals,
|
||||
/// etc.) fall to the alternative path that emits a builtin_call.
|
||||
fn isStaticTypeArg(node: *const Node) bool {
|
||||
return switch (node.data) {
|
||||
.type_expr,
|
||||
.identifier,
|
||||
fn isStaticTypeArg(self: *Lowering, node: *const Node) bool {
|
||||
switch (node.data) {
|
||||
.type_expr => |te| {
|
||||
// A type-keyword name (e.g. `s64`) is always static.
|
||||
// A user-defined name that happens to be in scope as
|
||||
// a runtime variable (`x: Type = s64; type_name(x)`)
|
||||
// is NOT static — route through the dynamic builtin
|
||||
// call so the runtime lookup table fires.
|
||||
if (self.scope) |scope| {
|
||||
if (scope.lookup(te.name) != null) return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
.identifier => |id| {
|
||||
if (self.scope) |scope| {
|
||||
if (scope.lookup(id.name) != null) return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
.pack_index_type_expr,
|
||||
.pointer_type_expr,
|
||||
.many_pointer_type_expr,
|
||||
@@ -9104,9 +9204,59 @@ pub const Lowering = struct {
|
||||
.function_type_expr,
|
||||
.tuple_literal,
|
||||
.call,
|
||||
=> true,
|
||||
else => false,
|
||||
};
|
||||
=> return true,
|
||||
else => return false,
|
||||
}
|
||||
}
|
||||
|
||||
/// True iff `node` is a Type-shaped expression that resolves to a
|
||||
/// concrete TypeId at lower time WITHOUT being a runtime variable
|
||||
/// reference. Differs from `isStaticTypeArg` in that we exclude
|
||||
/// identifiers that are in scope as runtime locals/globals — those
|
||||
/// are runtime Type values (e.g. `t: Type = f64`) and the
|
||||
/// comparison fold can't statically resolve them.
|
||||
fn isStaticTypeRef(self: *Lowering, node: *const Node) bool {
|
||||
switch (node.data) {
|
||||
.type_expr => |te| {
|
||||
// Compound type names (`s64`, `Point`, `Vec4`) resolve
|
||||
// statically. If the name is also a runtime var in
|
||||
// scope, it's a value reference, not a type ref.
|
||||
if (self.scope) |scope| {
|
||||
if (scope.lookup(te.name) != null) return false;
|
||||
}
|
||||
return self.isKnownTypeName(te.name) or
|
||||
self.module.types.findByName(self.module.types.internString(te.name)) != null or
|
||||
self.type_alias_map.get(te.name) != null;
|
||||
},
|
||||
.identifier => |id| {
|
||||
if (self.scope) |scope| {
|
||||
if (scope.lookup(id.name) != null) return false;
|
||||
}
|
||||
return self.isKnownTypeName(id.name) or
|
||||
self.module.types.findByName(self.module.types.internString(id.name)) != null or
|
||||
self.type_alias_map.get(id.name) != null;
|
||||
},
|
||||
.pointer_type_expr,
|
||||
.many_pointer_type_expr,
|
||||
.array_type_expr,
|
||||
.slice_type_expr,
|
||||
.optional_type_expr,
|
||||
.function_type_expr,
|
||||
.pack_index_type_expr,
|
||||
=> return true,
|
||||
.call => |cl| {
|
||||
// `type_of(x)` resolves statically when `x`'s type is
|
||||
// known — which it always is for a typed expression.
|
||||
if (cl.callee.data == .identifier and
|
||||
std.mem.eql(u8, cl.callee.data.identifier.name, "type_of") and
|
||||
cl.args.len == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
else => return false,
|
||||
}
|
||||
}
|
||||
|
||||
fn resolveTypeArg(self: *Lowering, node: *const Node) TypeId {
|
||||
@@ -9170,6 +9320,18 @@ pub const Lowering = struct {
|
||||
return type_bridge.resolveAstType(node, &self.module.types);
|
||||
},
|
||||
.call => |cl| {
|
||||
// `type_of(x)` resolves to `inferExprType(x)` at lower
|
||||
// time when `x`'s type is statically known (which it
|
||||
// is for any expression — type inference always
|
||||
// produces a concrete TypeId). Lets
|
||||
// `type_of(a) == s64` fold the same as
|
||||
// `inferExprType(a) == s64`.
|
||||
if (cl.callee.data == .identifier and
|
||||
std.mem.eql(u8, cl.callee.data.identifier.name, "type_of") and
|
||||
cl.args.len == 1)
|
||||
{
|
||||
return self.inferExprType(cl.args[0]);
|
||||
}
|
||||
// Handle type constructor calls: size_of(Sx(f32)), size_of(Complex(u32))
|
||||
return self.resolveTypeCallWithBindings(&cl);
|
||||
},
|
||||
@@ -12148,6 +12310,10 @@ pub const Lowering = struct {
|
||||
if (self.module_const_map.get(id.name)) |ci| {
|
||||
return ci.ty;
|
||||
}
|
||||
// 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.isKnownTypeName(id.name)) return .any;
|
||||
return .s64;
|
||||
},
|
||||
.type_expr => |te| {
|
||||
@@ -12157,6 +12323,9 @@ pub const Lowering = struct {
|
||||
return binding.ty;
|
||||
}
|
||||
}
|
||||
// A bare type name in expression position (e.g. `s64`,
|
||||
// `Point`, `*u8`) is a Type value — IR type `.any`.
|
||||
if (self.isKnownTypeName(te.name)) return .any;
|
||||
return .s64;
|
||||
},
|
||||
.enum_literal => {
|
||||
@@ -12927,6 +13096,7 @@ pub const Lowering = struct {
|
||||
if (self.type_bindings) |bindings| {
|
||||
if (bindings.get(name) != null) return true;
|
||||
}
|
||||
if (self.type_alias_map.get(name) != null) return true;
|
||||
const name_id = self.module.types.internString(name);
|
||||
return self.module.types.findByName(name_id) != null;
|
||||
}
|
||||
|
||||
@@ -385,6 +385,10 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -232,7 +232,15 @@ pub fn resolveTypePrimitive(name: []const u8) ?TypeId {
|
||||
if (std.mem.eql(u8, name, "string")) return .string;
|
||||
if (std.mem.eql(u8, name, "void")) return .void;
|
||||
if (std.mem.eql(u8, name, "Any")) return .any;
|
||||
if (std.mem.eql(u8, name, "Type")) return .any; // Type-as-value: boxed string
|
||||
// Type values are runtime-representable as Any-shaped pairs:
|
||||
// `{ tag = .any.index() (the meta-marker), value = TypeId.index() }`.
|
||||
// Lets `t : Type = f64; t == s64; print(t)` all route through the
|
||||
// existing Any infrastructure — boxing/unboxing, `case type:`
|
||||
// dispatch, runtime `type_name(t)` via the type-name lookup
|
||||
// table. Comparison decomposes via the eq fold path
|
||||
// (`isStaticTypeRef`) for static literals; runtime-var vs
|
||||
// literal compares decompose at `lowerBinaryOp`.
|
||||
if (std.mem.eql(u8, name, "Type")) return .any;
|
||||
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;
|
||||
|
||||
@@ -466,6 +466,7 @@ pub const TypeTable = struct {
|
||||
if (ty == .s64 or ty == .u64 or ty == .f64) return 8;
|
||||
if (ty == .usize or ty == .isize) return ptr_size;
|
||||
if (ty == .string) return 16; // {ptr, i64} — always 16 (i64 alignment pads on wasm32)
|
||||
if (ty == .any) return 16; // {i64 tag, i64 value} — Any boxed layout
|
||||
if (ty.isBuiltin()) return ptr_size; // default for unknown builtins
|
||||
const info = self.get(ty);
|
||||
return switch (info) {
|
||||
@@ -564,6 +565,7 @@ pub const TypeTable = struct {
|
||||
if (ty == .s64 or ty == .u64 or ty == .f64) return 8;
|
||||
if (ty == .usize or ty == .isize) return ptr_align;
|
||||
if (ty == .string) return 8; // i64 drives alignment
|
||||
if (ty == .any) return 8; // {i64, i64} aligns to 8
|
||||
if (ty.isBuiltin()) return ptr_align;
|
||||
const info = self.get(ty);
|
||||
return switch (info) {
|
||||
@@ -650,6 +652,91 @@ pub const TypeTable = struct {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/// Like `typeName` but produces structural names for compound
|
||||
/// types (`*T`, `[]T`, `[N]T`, `?T`, `Vector(N,T)`, function and
|
||||
/// tuple types) instead of returning `"?"`. Compound names are
|
||||
/// freshly allocated via `alloc`; builtin and named user types
|
||||
/// return borrowed slices.
|
||||
pub fn formatTypeName(self: *const TypeTable, alloc: std.mem.Allocator, id: TypeId) []const u8 {
|
||||
if (id.isBuiltin()) return self.typeName(id);
|
||||
const info = self.get(id);
|
||||
return switch (info) {
|
||||
.@"struct" => |s| self.getString(s.name),
|
||||
.@"enum" => |e| self.getString(e.name),
|
||||
.@"union" => |u| self.getString(u.name),
|
||||
.tagged_union => |u| self.getString(u.name),
|
||||
.protocol => |p| self.getString(p.name),
|
||||
.pointer => |p| blk: {
|
||||
const inner = self.formatTypeName(alloc, p.pointee);
|
||||
break :blk std.fmt.allocPrint(alloc, "*{s}", .{inner}) catch "*?";
|
||||
},
|
||||
.many_pointer => |p| blk: {
|
||||
const inner = self.formatTypeName(alloc, p.element);
|
||||
break :blk std.fmt.allocPrint(alloc, "[*]{s}", .{inner}) catch "[*]?";
|
||||
},
|
||||
.slice => |s| blk: {
|
||||
const inner = self.formatTypeName(alloc, s.element);
|
||||
break :blk std.fmt.allocPrint(alloc, "[]{s}", .{inner}) catch "[]?";
|
||||
},
|
||||
.array => |a| blk: {
|
||||
const inner = self.formatTypeName(alloc, a.element);
|
||||
break :blk std.fmt.allocPrint(alloc, "[{d}]{s}", .{ a.length, inner }) catch "[N]?";
|
||||
},
|
||||
.vector => |v| blk: {
|
||||
const inner = self.formatTypeName(alloc, v.element);
|
||||
break :blk std.fmt.allocPrint(alloc, "Vector({d},{s})", .{ v.length, inner }) catch "Vector(?)";
|
||||
},
|
||||
.optional => |o| blk: {
|
||||
const inner = self.formatTypeName(alloc, o.child);
|
||||
break :blk std.fmt.allocPrint(alloc, "?{s}", .{inner}) catch "?_";
|
||||
},
|
||||
.function => |f| blk: {
|
||||
var buf = std.ArrayList(u8).empty;
|
||||
defer buf.deinit(alloc);
|
||||
buf.append(alloc, '(') catch break :blk "(?)";
|
||||
for (f.params, 0..) |p, i| {
|
||||
if (i > 0) buf.appendSlice(alloc, ", ") catch break :blk "(?)";
|
||||
buf.appendSlice(alloc, self.formatTypeName(alloc, p)) catch break :blk "(?)";
|
||||
}
|
||||
buf.append(alloc, ')') catch break :blk "(?)";
|
||||
if (f.ret != .void) {
|
||||
buf.appendSlice(alloc, " -> ") catch break :blk "(?)";
|
||||
buf.appendSlice(alloc, self.formatTypeName(alloc, f.ret)) catch break :blk "(?)";
|
||||
}
|
||||
break :blk buf.toOwnedSlice(alloc) catch "(?)";
|
||||
},
|
||||
.closure => |co| blk: {
|
||||
var buf = std.ArrayList(u8).empty;
|
||||
defer buf.deinit(alloc);
|
||||
buf.appendSlice(alloc, "Closure(") catch break :blk "Closure(?)";
|
||||
for (co.params, 0..) |p, i| {
|
||||
if (i > 0) buf.appendSlice(alloc, ", ") catch break :blk "Closure(?)";
|
||||
buf.appendSlice(alloc, self.formatTypeName(alloc, p)) catch break :blk "Closure(?)";
|
||||
}
|
||||
buf.append(alloc, ')') catch break :blk "Closure(?)";
|
||||
if (co.ret != .void) {
|
||||
buf.appendSlice(alloc, " -> ") catch break :blk "Closure(?)";
|
||||
buf.appendSlice(alloc, self.formatTypeName(alloc, co.ret)) catch break :blk "Closure(?)";
|
||||
}
|
||||
break :blk buf.toOwnedSlice(alloc) catch "Closure(?)";
|
||||
},
|
||||
.tuple => |tu| blk: {
|
||||
var buf = std.ArrayList(u8).empty;
|
||||
defer buf.deinit(alloc);
|
||||
buf.append(alloc, '(') catch break :blk "(?)";
|
||||
for (tu.fields, 0..) |f, i| {
|
||||
if (i > 0) buf.appendSlice(alloc, ", ") catch break :blk "(?)";
|
||||
buf.appendSlice(alloc, self.formatTypeName(alloc, f)) catch break :blk "(?)";
|
||||
}
|
||||
buf.append(alloc, ')') catch break :blk "(?)";
|
||||
break :blk buf.toOwnedSlice(alloc) catch "(?)";
|
||||
},
|
||||
.signed => |w| std.fmt.allocPrint(alloc, "s{d}", .{w}) catch "s?",
|
||||
.unsigned => |w| std.fmt.allocPrint(alloc, "u{d}", .{w}) catch "u?",
|
||||
else => self.typeName(id),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// ── Intern map support ──────────────────────────────────────────────────
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
f64
|
||||
3.200000
|
||||
Vec4
|
||||
Vector(4,f32)
|
||||
() -> s32
|
||||
|
||||
1
tests/expected/189-type-all-interactions.exit
Normal file
1
tests/expected/189-type-all-interactions.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
59
tests/expected/189-type-all-interactions.txt
Normal file
59
tests/expected/189-type-all-interactions.txt
Normal file
@@ -0,0 +1,59 @@
|
||||
=== 1. literal == ===
|
||||
s64 == s64: true
|
||||
s64 == string: false
|
||||
*u8 == *u8: true
|
||||
?s64 == ?s64: true
|
||||
?s64 == ?s32: false
|
||||
=== 2. type_of(value) == T ===
|
||||
type_of(a) == s64: true
|
||||
type_of(b) == f64: true
|
||||
type_of(s) == string: true
|
||||
type_of(a) == f64: false
|
||||
=== 3. Type variable storage ===
|
||||
t == f64: true
|
||||
t == string: false
|
||||
after reassign t == string: true
|
||||
t == bool: true
|
||||
=== 4. type_name ===
|
||||
type_name(s64): s64
|
||||
type_name(*u8): *u8
|
||||
type_name(Point): Point
|
||||
type_name(Color): Color
|
||||
type_name(t): f64
|
||||
=== 5. print Type values ===
|
||||
literal: s64
|
||||
var: string
|
||||
type_of(b): f64
|
||||
=== 6. generic dispatch ===
|
||||
describe(s64): int64
|
||||
describe(string): text
|
||||
describe(bool): boolean
|
||||
describe(f64): other
|
||||
=== 7. identity($T, val) ===
|
||||
identity(s64, 7): 7
|
||||
identity(string, hi): hi
|
||||
identity(bool, true): true
|
||||
=== 8. Wrap($T) ===
|
||||
Wrap(s64).v: 42
|
||||
Wrap(string).v: wrapped
|
||||
=== 9. reflection on Type ===
|
||||
size_of(s64): 8
|
||||
size_of(*u8): 8
|
||||
align_of(f64): 8
|
||||
field_count(Point): 2
|
||||
type_eq(s64, s64): true
|
||||
type_eq(s64, string): false
|
||||
=== 10. ..$args walking ===
|
||||
type_list(): []
|
||||
type_list(1): [s64]
|
||||
type_list(1, "x"): [s64, string]
|
||||
type_list(true, 3.14): [bool, f64]
|
||||
=== 11. Type in struct field ===
|
||||
h.t == s64: true
|
||||
h.t == string: false
|
||||
type_name(h.t): s64
|
||||
=== 12. compound literals ===
|
||||
type_name(*Point): *Point
|
||||
type_name([4]s32): [4]s32
|
||||
type_name([]bool): []bool
|
||||
type_name(?f64): ?f64
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user