docs+rename: erase the reify name everywhere — stream is METATYPE
The compiler concept is declare/define (comptime type construction); the old "reify" framing is gone from the entire repo. - Rename: PLAN-REIFY → PLAN-METATYPE, CHECKPOINT-REIFY → CHECKPOINT-METATYPE, PLAN-POST-REIFY → PLAN-POST-METATYPE (both rewritten around declare/define); examples 0614/0615/0617 → comptime-metatype-* (+ their expected/ triplets), headers rewritten. - Scrub reify from design/execution-evolution-roadmap.md (§7 step 3 contracts, §8.1, §9 decisions, §10 gates) → declare/define / comptime type construction. - core.sx prelude pointer + parser.test.zig surface lock updated to the declare/define builtins (define(handle, info) -> Type; EnumInfo.name). No behavior change; renamed examples match their renamed snapshots. Full suite green (673), all unit tests pass. Zero `reify` tokens remain in src/docs/sx/examples.
This commit is contained in:
115
current/PLAN-METATYPE.md
Normal file
115
current/PLAN-METATYPE.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# PLAN-METATYPE — comptime type metaprogramming (`declare` / `define` + reflection)
|
||||
|
||||
## Goal
|
||||
|
||||
Comptime type metaprogramming with the smallest possible compiler surface:
|
||||
|
||||
- **`declare() -> Type`** — mint a NEW empty (undefined) nominal type, returned as
|
||||
a first-class `Type` handle.
|
||||
- **`define(handle, info) -> Type`** — fill a declared handle's body from a
|
||||
`TypeInfo` *value* (which carries the type's name), and return the handle.
|
||||
- **`type_info($T) -> TypeInfo`** — reflect a type INTO data (the inverse of
|
||||
`define`'s decode). *Pending.*
|
||||
- **`field_type($T, i) -> Type`** — the i-th field / variant-payload / element
|
||||
type of `$T`. *Done.*
|
||||
|
||||
These four `#builtin`s in `library/modules/std/meta.sx` are the **entire**
|
||||
compiler surface. Every higher-level constructor is **plain sx built over
|
||||
`declare`/`define`** — the compiler knows none of them by name:
|
||||
|
||||
```sx
|
||||
// one-shot (non-recursive): declare + define chained, define returns the handle
|
||||
T :: define(declare(), .enum(.{ name = "T", variants = .[ … ] }));
|
||||
|
||||
// recursive / mutually-recursive: keep the handle, reference it in its own body
|
||||
List :: declare();
|
||||
define(List, .enum(.{ name = "List", variants = .[
|
||||
EnumVariant.{ name = "cons", payload = *List },
|
||||
EnumVariant.{ name = "nil", payload = void } ] }));
|
||||
|
||||
// type-fns are ordinary sx (channel result types, etc.)
|
||||
RecvResult :: ($T: Type) -> Type {
|
||||
return define(declare(), .enum(.{ name = "RecvResult", variants = .[
|
||||
EnumVariant.{ name = "value", payload = T },
|
||||
EnumVariant.{ name = "closed", payload = void } ] }));
|
||||
}
|
||||
```
|
||||
|
||||
This gates channel result types (`RecvResult($T)`) and `race`'s synthesized
|
||||
tagged-union (design [../design/execution-evolution-roadmap.md](../design/execution-evolution-roadmap.md) §7 step 3), and replaces a would-be `enum($T)` language feature.
|
||||
|
||||
## How it works (the locked design)
|
||||
|
||||
1. **Two comptime interp builtins.** `declare` mints an empty `tagged_union` slot
|
||||
in the type table; `define` decodes the `TypeInfo` value (variant-name strings +
|
||||
payload `Type`-tags) and completes the slot byte-identical to a source enum's
|
||||
`buildEnumInfo` output, so it flows through enum codegen unmodified. The interp
|
||||
mutates the type table via a `mint` handle the host sets (`setMintTable`).
|
||||
2. **No syntactic constructor recognition.** A `::` binding or type-fn body that
|
||||
calls a `Type`-returning fn is **comptime-evaluated** (`evalComptimeType`): the
|
||||
expression runs through the interpreter, the `declare`/`define` builtins mint the
|
||||
type, and the result `type_tag` is bound. `decl.zig` triggers on a non-generic
|
||||
`-> Type` fn call; `instantiateTypeFunction` triggers on a type-fn body that
|
||||
returns a `define(…)` call (or a bodied `-> Type` helper) — see
|
||||
`generic.zig:returnExprMintsType`.
|
||||
3. **Name travels in the data.** `define` names the slot from `EnumInfo.name`; the
|
||||
compiler derives no name from a binding LHS. `type_info` round-trips it.
|
||||
4. **Nominal identity** rides the existing type-fn mangled-name instantiation cache:
|
||||
`RecvResult(i64)` at two sites memoizes to ONE `TypeId` (the body runs once;
|
||||
`renameNominalType` re-keys the minted type to the mangled name).
|
||||
5. **Comptime-only, JIT-free.** `declare`/`define` are interp ops; reaching them at
|
||||
runtime / emit is a hard error.
|
||||
6. **Undefined-until-defined.** `declare()` mints an undefined slot; *using* it
|
||||
(construct / match / size) before its `define` is a loud diagnostic. A *pointer*
|
||||
to an undefined slot (`*Self`) is fine — that's what self-reference needs.
|
||||
|
||||
## Key code anchors
|
||||
|
||||
- Builtins: `BuiltinId.declare` / `.define` (`src/ir/inst.zig`); lowering to
|
||||
`callBuiltin` (`src/ir/lower/call.zig:tryLowerReflectionCall`); interp exec +
|
||||
`defineEnum` + `decodeVariantElements` (`src/ir/interp.zig`); `mint` field +
|
||||
`setMintTable`.
|
||||
- Comptime evaluation: `evalComptimeType` / `renameNominalType`
|
||||
(`src/ir/lower/comptime.zig`); decl trigger `fnReturnsTypeValue`
|
||||
(`src/ir/lower/decl.zig`); type-fn trigger `returnExprMintsType` +
|
||||
`instantiateTypeFunction` (`src/ir/lower/generic.zig`).
|
||||
- Reflection: `field_type` → `fieldTypeOf` (`src/ir/lower/generic.zig`).
|
||||
- Surface: `library/modules/std/meta.sx` (on-demand import — NOT the prelude, to
|
||||
avoid shifting every `.ir` snapshot).
|
||||
|
||||
## Cadence (IMPASSIBLE)
|
||||
|
||||
No commit may both add a test AND make it pass (xfail-then-green, or a behavior
|
||||
lock). `zig build && zig build test` after every step. Never regenerate snapshots
|
||||
while red. Examples: `06xx` (comptime), `11xx` (diagnostics).
|
||||
|
||||
## Status
|
||||
|
||||
- [x] `declare` / `define` comptime builtins + the `mint` plumbing.
|
||||
- [x] Comptime evaluation of a `Type`-returning `::` RHS and type-fn body
|
||||
(the only triggers; no constructor-name knowledge in the compiler).
|
||||
- [x] Name-in-`TypeInfo`; nominal identity via the instantiation cache.
|
||||
- [x] `field_type` reflection (`examples/0616`).
|
||||
- [x] Examples green on the floor: `0614` (one-shot), `0615` (type-fn identity),
|
||||
`0617` (channel result types).
|
||||
- [ ] **Self-reference** — the top-level `List :: declare(); define(List, …)`
|
||||
two-statement form with a `*Self` payload (recursive / mutually-recursive
|
||||
enums). Needs the `define(…)` top-level statement form to run at comptime in
|
||||
the right window. Pairs with a `make_enum(variants: []EnumVariant)` sx helper
|
||||
(computed variant list).
|
||||
- [ ] **`type_info($T) -> TypeInfo`** — reflect a type INTO a value (inverse of
|
||||
`define`'s decode): widen `TypeInfo` past `` `enum `` (struct/tuple) AND
|
||||
construct a `[]EnumVariant`-style value in the interpreter. Bails loudly today
|
||||
in `call.zig:tryLowerReflectionCall`. Round-trips through `define` once it lands.
|
||||
- [ ] **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. Emit at the
|
||||
`intern`/`internNominal` choke point; never a broken type.
|
||||
|
||||
## Risks / watch
|
||||
|
||||
- **Self-ref timing** — `define` for the two-statement form must complete before any
|
||||
code uses the type's layout; a use-before-define must be a loud diagnostic, not a
|
||||
silent empty enum.
|
||||
- Keep `declare`/`define` **comptime-only**: reaching them at runtime is a hard error
|
||||
(emit should bail loudly if one ever leaks into codegen).
|
||||
Reference in New Issue
Block a user