12 KiB
CHECKPOINT-METATYPE — comptime type metaprogramming (declare / define)
Companion to PLAN-METATYPE.md. Update after every step (one step at a time, per the cadence rule).
Last completed step
make_enum(name, variants: []EnumVariant) -> Type — the general enum
constructor over declare/define, minting a nominal enum from a variant list
passed as a VALUE (not a hardcoded literal). Pure sx in meta.sx, no compiler
machinery — the open-ended form the channel-result constructors are special cases
of. Because variants is an ordinary comptime value, a non-generic builder
assembles it in a local before minting; examples/0620 (build_level fills a
local array → make_enum mints Level) exercises define decoding a value-arg
SLICE (decodeVariantElements' slice branch) vs. the inline .[ … ] array the
0614–0618 examples pass directly. No compiler change (locks existing capability).
Suite green (678). See "make_enum follow-ups" under Next step for the deferred
free-form-construction gaps (subslice/List at comptime, generic-type-fn locals).
Prior 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: newBuiltinId.type_info(comptime-only, alongsidedeclare/define).lower/call.zig:tryLowerReflectionCall: the old "not yet implemented" bail is gone. Resolve$Tat lower time, reject a non-enum/non-tagged_unionarg loudly (good span:"type_info: 'X' is not an enum …"), else emitcallBuiltin(.type_info, [const_type], TypeInfo).interp.zig:reflectTypeInfo: builds the exact nested-aggregate ValuedefineEnumdecodes — variant{name, payload}, slice{data, len}, EnumInfo{variants}, TypeInfo{tag0, EnumInfo}. Atagged_unionreflects eachfield.ty(tagless variants already carryvoid); a payloadless`enumreflectsvoidper variant. Round-trips both source enums AND constructed (declare/define) enums.- emit unchanged —
type_infois always comptime-evaluated; the existing comptime-onlyelsearm inemitCallBuiltin(shared with declare/define) never fires. - Scope: enum-only (the symmetric inverse of
define's current capability). Struct/tupleTypeInfowidening 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).
Earlier step
Self-reference — recursive enums via declare("Name") + *Name. The
declare/define floor now supports self-referential types.
declare(name) -> Typemints an empty (undefined) nominal slot NAMEDname;define(handle, info) -> Typedecodes theTypeInfovalue (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 amintTypeTable handle;defineEnum+decodeVariantElementsininterp.zig.- Self-reference:
evalComptimeType'spreregisterForwardTypesscans the comptime expression (and a called ctor fn's body) fordeclare("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 — aName :: ctor()decl makesNamea const_decl author, so a*Nameself-reference resolves through the forward-ALIAS path (type_aliases_by_source), which a barefindByNameregistration alone does NOT satisfy (it returns a pending empty-struct stub). The interp'sdeclarereturns that same slot;definefills it. - A
::binding or type-fn body calling aType-returning fn is comptime-evaluated (evalComptimeType) — no constructor-name knowledge.decl.zigtrigger =fnReturnsTypeValue; type-fn trigger =returnExprMintsType. - Nominal identity rides the type-fn instantiation cache (
renameNominalType). - The type NAME is on
declare(name)(compile-time string), notEnumInfo.
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 }/TypeInfodata types;declare/define/type_info/field_type#builtins;RecvResult($T)/TryResult($T)+ the generalmake_enum(name, variants)sx constructors overdefine(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 anenum/tagged_unionINTO aTypeInfovalue (call.zigemitscallBuiltin(.type_info);interp.zig:reflectTypeInfobuilds the Value). Enum-only; struct/tuple widening pending.examples/0619round-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
Pick any (independent):
- Widen
type_info/TypeInfopast`enum— struct/tuple variants: add.`struct/.`tupleto theTypeInfoenum inmeta.sx, teachreflectTypeInfoto build them, and teachdefineEnum(→ adefineType) to decode them. Round-trips a struct throughdefineonce it lands. - Validation + loud diagnostics (remaining) —
duplicate variant namesDONE (definebails naming the dup;examples/1180). Still pending: adeclare()neverdefine()d (hard error at end of comptime), use-before-define.
make_enum follow-ups (deferred capability gaps — NOT crashes; clean diagnostics)
make_enum itself is DONE (see Last completed step). Remaining adjacent
capabilities would let the variant list be built more freely; both error cleanly
(post-0140) rather than crash, so they're enhancements, not blockers:
Comptime slice over a non-string aggregate— DONE.arr[lo..hi]over a[]EnumVariantarray now yields a real slice value at comptime (was: bailed, string-only). Fix threadedbase_tyonto theSubsliceop so the interp tells an array from a{ptr,len}slice, folded open-endedhito a fixed array's static length at lower time (no runtime/.ir change), and addedinterp.zig:subsliceElements.examples/0621locks it.- Comptime
Listgrowth. Building variants viaList(EnumVariant).appendat comptime bails ("struct_get: base has no fields") — the allocator/List protocol path isn't fully interp-evaluable. Probe.sx-tmp/probe_makeenum.sx. - Generic type-fn body locals. A generic
($T) -> Typecomptime-evals only its return EXPRESSION (generic.zig:1760,findReturnTypeExpr), so a local before the return is unresolved. Workaround: build the list inline in the return, or use a non-generic() -> Typebuilder (whose whole body is evaluated — this is whatexamples/0620uses). Probe.sx-tmp/probe_me5.sx.
Known issues
- issue 0140 — comptime type-construction bail panicked instead of diagnosing —
RESOLVED.
evalComptimeTypenow clearslast_bail_detailbefore the interp call and, on thecatch, emits a build-gating.errat the construction span ("comptime type construction failed: {detail}") before returning the.unresolvedpoison — so the reason is shown and no unresolved type reaches emission unannounced.examples/1179locks it. - issue 0139 — by-value self-reference segfault — RESOLVED (
checkInfiniteSizePass 1g emits a loud "infinitely sized" diagnostic + breaks the cycle;examples/1178locks it).
Log
- Duplicate variant-name validation. Two same-named variants in a constructed
enum used to silently succeed (ambiguous construction/match).
defineEnumnow bails naming the duplicate;evalComptimeTyperenders it (post-0140).examples/1180locks it. Suite green (680). - Comptime subslice over non-string aggregates.
arr[lo..hi]at comptime used to bail (interp.subslicewas string-only) and the open-endedhicame from a.lengthop that misread a 2-elem array as a{ptr,len}fat pointer. Fix (interp-only; runtime already correct viaLLVMTypeOf): threadbase_tyonto theSubsliceop, fold open-endedhito a fixed array's static length at lower time (no IR/.ir change), addsubsliceElements.examples/0621mints an enum fromdirs[0..2]. Suite green (679). make_enumdone. General enum constructormake_enum(name, variants: []EnumVariant) -> Typeinmeta.sx(pure sx over declare/define). A non-generic builder assembles the variant list in a local, then mints from it —examples/0620exercisesdefine's value-arg SLICE decode. No compiler change. Suite green (678). Deferred free-form gaps (subslice/List at comptime, generic-type-fn locals) noted under Next step — all clean diagnostics now, not crashes (post-0140), so enhancements rather than blockers.- issue 0140 fixed. A comptime type-construction bail (
declare/define/ reflection) used to panic at LLVM emission ("unresolved type reached LLVM emission") or hide behind a cascade —evalComptimeTypeswallowed the interp'slast_bail_detail. Now it clears the detail before the call and renders a build-gating.errat the construction span on thecatch.examples/1179locks the empty-variants case. Suite green (677). Unblocks make_enum (its computed-slice decode failures now surface cleanly). type_info($T)reflection done (enum round-trip). NewBuiltinId.type_info;lower/call.zigresolves$T, rejects non-enum loudly, emits the builtin;interp.zig:reflectTypeInfoconstructs the exact nested-aggregate ValuedefineEnumdecodes (variant{name,payload}/ slice{data,len}/ EnumInfo / TypeInfo.enum).tagged_unionreflectsfield.ty; payloadless`enumreflectsvoid. Round-trips source AND constructed enums. Enum-only; struct/tuple widening deferred.examples/0619locks it. Suite green (676).- By-value self-reference rejected (issue 0139, F5 partial). New
checkInfiniteSizepass (Pass 1g) detects by-VALUE containment cycles (source + comptime types, direct + mutual), emits a loud "infinitely sized" diagnostic, and breaks the cycle (was atypeSizeBytesstack-overflow segfault).*Self(pointer) stays valid.examples/1178locks the message. Suite green (675). - Self-reference done.
declare(name)+preregisterForwardTypes(forward type + alias before body lowers) →*Nameresolves; recursive*Listenum constructs, matches through the pointer, and traverses recursively.0618locks it.declaregained itsnamearg;EnumInfo.namedropped. 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 aType-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_typereflection (0616) unchanged. - Stream carved (earlier). Selected as the first async-first foundation: gates
channel result types (
RecvResult($T)) andrace's synthesized union, fully validated, self-contained, testable in isolation (06xxcomptime).