# CHECKPOINT-METATYPE — comptime type metaprogramming (`declare` / `define`) Companion to [PLAN-METATYPE.md](PLAN-METATYPE.md). Update after every step (one step at a time, per the cadence rule). ## Last completed step **Self-reference — recursive enums via `declare("Name")` + `*Name`.** The `declare`/`define` floor now supports self-referential types. - `declare(name) -> Type` mints an empty (undefined) nominal slot NAMED `name`; `define(handle, info) -> Type` decodes the `TypeInfo` value (variant names + payload Type-tags), fills the slot byte-identical to a source enum, and returns the handle (one-shot form chains: `T :: define(declare("T"), info)`). Interp executes both against a `mint` TypeTable handle; `defineEnum` + `decodeVariantElements` in `interp.zig`. - **Self-reference:** `evalComptimeType`'s `preregisterForwardTypes` scans the comptime expression (and a called ctor fn's body) for `declare("Name")` calls and, before the body lowers, registers each as an empty forward nominal type AND binds it as a type alias. The alias is essential — a `Name :: ctor()` decl makes `Name` a const_decl author, so a `*Name` self-reference resolves through the forward-ALIAS path (`type_aliases_by_source`), which a bare `findByName` registration alone does NOT satisfy (it returns a pending empty-struct stub). The interp's `declare` returns that same slot; `define` fills it. - A `::` binding or type-fn body calling a `Type`-returning fn is **comptime-evaluated** (`evalComptimeType`) — no constructor-name knowledge. `decl.zig` trigger = `fnReturnsTypeValue`; type-fn trigger = `returnExprMintsType`. - Nominal identity rides the type-fn instantiation cache (`renameNominalType`). - The type NAME is on `declare(name)` (compile-time string), not `EnumInfo`. Examples green: `0614` (one-shot), `0615` (type-fn identity), `0617` (channel results), **`0618` (recursive `*List`: construct, match through pointer, recursive traversal)**; `field_type` reflection `0616`. Full suite green (674 examples). ## Current state - `modules/std/meta.sx`: `EnumVariant` / `EnumInfo{ name, variants }` / `TypeInfo` data types; `declare` / `define` / `type_info` / `field_type` `#builtin`s; `RecvResult($T)` / `TryResult($T)` sx constructors over `define(declare(), …)`. - Compiler primitives only: `declare`/`define` (construction), `field_type` (reflection). No constructor-name knowledge anywhere in the compiler — every named constructor is sx. `declare(name)` carries the type name (compile-time string) for forward-type registration. - `type_info` still bails loudly (`call.zig:tryLowerReflectionCall`) — pending. ## Decision (kept) **Meta lives in `modules/std/meta.sx`, not the prelude.** Declaring its data types in the always-loaded prelude interns them into every module's type table and shifts every `.ir` snapshot. On-demand import keeps the prelude clean. ## Next step Pick any (independent): - **`make_enum(variants: []EnumVariant)`** sx helper over a COMPUTED (non-literal) variant list — exercises the interpreter decoding a value-arg slice in `define` (vs. the literal `.[ … ]` the current examples use). - **`type_info($T) -> TypeInfo`** — reflect a type INTO a value (inverse of `define`'s decode); widen `TypeInfo` past `` `enum `` + construct a `[]EnumVariant` value in the interp. Bails loudly today in `call.zig:tryLowerReflectionCall`. Its own focused effort. - **Validation + loud diagnostics** — duplicate variant names, by-VALUE self-reference (`payload = List` not `*List` → infinite size), a `declare()` never `define()`d (hard error), use-before-define. ## Known issues None. ## Log - **Self-reference done.** `declare(name)` + `preregisterForwardTypes` (forward type + alias before body lowers) → `*Name` resolves; recursive `*List` enum constructs, matches through the pointer, and traverses recursively. `0618` locks it. `declare` gained its `name` arg; `EnumInfo.name` dropped. Suite green (674). - **declare/define floor established.** The comptime type-construction surface is two primitives (`declare`/`define`); all named constructors are sx. A `::` binding or type-fn body that calls a `Type`-returning fn is comptime-evaluated (the builtins mint the type) — no syntactic constructor recognition in the compiler. Examples 0614 (one-shot) / 0615 (type-fn identity) / 0617 (channel results) on the floor; `field_type` reflection (0616) unchanged. - **Stream carved (earlier).** Selected as the first async-first foundation: gates channel result types (`RecvResult($T)`) and `race`'s synthesized union, fully validated, self-contained, testable in isolation (`06xx` comptime).