7.4 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
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).
Prior 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)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):
make_enum(variants: []EnumVariant)sx helper over a COMPUTED (non-literal) variant list — exercises the interpreter decoding a value-arg slice indefine(vs. the literal.[ … ]the current examples use).- 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 names, a
declare()neverdefine()d (hard error), use-before-define. (By-value self-reference already rejected — issue 0139.)
Known issues
None. (issue 0139 — by-value self-reference segfault — RESOLVED: checkInfiniteSize
Pass 1g emits a loud "infinitely sized" diagnostic + breaks the cycle; covers
source + comptime types; examples/1178 locks it.)
Log
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).