Files
sx/current/CHECKPOINT-METATYPE.md

5.2 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

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 #builtins; 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 (remaining) — duplicate variant names, a declare() never define()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

  • 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).