# 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 **`type_info($T)` reflection — enum round-trip.** Reflect a type INTO a `TypeInfo` value (the inverse of `define`'s decode), so `define(declare(n), type_info(T))` mints a byte-identical copy with NO literal variant list. - `inst.zig`: new `BuiltinId.type_info` (comptime-only, alongside `declare`/`define`). - `lower/call.zig:tryLowerReflectionCall`: the old "not yet implemented" bail is gone. Resolve `$T` at lower time, reject a non-`enum`/non-`tagged_union` arg loudly (good span: `"type_info: 'X' is not an enum …"`), else emit `callBuiltin(.type_info, [const_type], TypeInfo)`. - `interp.zig:reflectTypeInfo`: builds the exact nested-aggregate Value `defineEnum` decodes — variant `{name, payload}`, slice `{data, len}`, EnumInfo `{variants}`, TypeInfo `{tag0, EnumInfo}`. A `tagged_union` reflects each `field.ty` (tagless variants already carry `void`); a payloadless `` `enum `` reflects `void` per variant. Round-trips both source enums AND constructed (declare/define) enums. - emit unchanged — `type_info` is always comptime-evaluated; the existing comptime-only `else` arm in `emitCallBuiltin` (shared with declare/define) never fires. - Scope: **enum-only** (the symmetric inverse of `define`'s current capability). Struct/tuple `TypeInfo` widening is a separate later step. `examples/0619` locks it (source enum `circle:f64 / rect:i64 / empty` reflected → reconstructed → constructs + matches). Full suite green (676 examples + units). ## Prior 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($T)` reflects an `enum`/`tagged_union` INTO a `TypeInfo` value (`call.zig` emits `callBuiltin(.type_info)`; `interp.zig:reflectTypeInfo` builds the Value). Enum-only; struct/tuple widening pending. `examples/0619` round-trip. ## 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 **BLOCKED on issue 0140** for the make_enum path (see Known issues). The other two are unblocked: - **`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). **BLOCKED:** a computed (pointer-backed) `[]EnumVariant` slice from a local var / `List` does not decode in `defineEnum`/`decodeVariantElements`, and the resulting interp bail is SWALLOWED (panic / misleading cascade, no root reason) — issue 0140. Fix 0140 first so the decode failure surfaces cleanly, THEN implement the slice decode. - **Widen `type_info` / `TypeInfo` past `` `enum ``** — struct/tuple variants: add `` .`struct ``/`` .`tuple `` to the `TypeInfo` enum in `meta.sx`, teach `reflectTypeInfo` to build them, and teach `defineEnum` (→ a `defineType`) to decode them. Round-trips a struct through `define` once it lands. - **Validation + loud diagnostics** (remaining) — duplicate variant names, a `declare()` never `define()`d (hard error), use-before-define. (By-value self-reference already rejected — issue 0139.) ## Known issues - **issue 0140 (OPEN) — a failing comptime type construction panics instead of diagnosing.** `evalComptimeType` (`comptime.zig:~457`) swallows the interp's `last_bail_detail` via `catch return null`; callers poison to `.unresolved` with no diagnostic, and `.unresolved` reaches LLVM emission → panic ("unresolved type reached LLVM emission"), or rides behind a misleading downstream cascade. Pre-existing (plain `define` path; surfaced while starting make_enum). Repro: `issues/0140-*.sx`. Blocks the make_enum computed-slice step. **Session paused pending the fix** per the CLAUDE.md IMPASSABLE rule. - issue 0139 — by-value self-reference segfault — RESOLVED (`checkInfiniteSize` Pass 1g emits a loud "infinitely sized" diagnostic + breaks the cycle; `examples/1178` locks it). ## Log - **`type_info($T)` reflection done (enum round-trip).** New `BuiltinId.type_info`; `lower/call.zig` resolves `$T`, rejects non-enum loudly, emits the builtin; `interp.zig:reflectTypeInfo` constructs the exact nested-aggregate Value `defineEnum` decodes (variant `{name,payload}` / slice `{data,len}` / EnumInfo / TypeInfo `.enum`). `tagged_union` reflects `field.ty`; payloadless `` `enum `` reflects `void`. Round-trips source AND constructed enums. Enum-only; struct/tuple widening deferred. `examples/0619` locks it. Suite green (676). - **By-value self-reference rejected (issue 0139, F5 partial).** New `checkInfiniteSize` pass (Pass 1g) detects by-VALUE containment cycles (source + comptime types, direct + mutual), emits a loud "infinitely sized" diagnostic, and breaks the cycle (was a `typeSizeBytes` stack-overflow segfault). `*Self` (pointer) stays valid. `examples/1178` locks the message. Suite green (675). - **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).