docs(metatype): comptime List growth — two-layer root cause documented

Investigated the last deferred enhancement. List(T).append at comptime
fails in two independent layers (both reproduce with plain List(i64);
List works via #run because that evaluates at emit time, after lowering):
1. null comptime allocator — defaultContextValue looks up the
   CAllocator->Allocator thunks by name, but they aren't lowered at
   scanDecls time. Fixable by forcing getOrCreateThunks before the interp
   runs in runComptimeTypeFunc (tried, works for this layer).
2. struct_get through a *T slot_ptr chain (the *List receiver) — the
   deep part; comptime pointer/struct/slot resolution, its own session.
Speculative fixes reverted (no end-to-end win without layer 2).
This commit is contained in:
agra
2026-06-17 07:58:34 +03:00
parent c7e997043f
commit 85c1b85f8b

View File

@@ -115,12 +115,34 @@ shifts every `.ir` snapshot. On-demand import keeps the prelude clean.
The reflect/construct triad is COMPLETE — `` `enum `` (`0619`), `` `struct ``
(`0622`), `` `tuple `` (`0623`) all reflect AND construct + round-trip. Remaining
METATYPE work is ONE deferred enhancement, a clean diagnostic rather than a crash:
- **Comptime `List` growth** — `List(EnumVariant).append` at comptime bails
("struct_get: base has no fields"); the allocator/List protocol path isn't fully
interp-evaluable (heap alloc + protocol dispatch + List internals at comptime —
a deeper, multi-step interp effort). Probe `.sx-tmp/probe_makeenum.sx`. Doesn't
block anything: array-literal locals already work for building variant lists
(`examples/0620`/`0624`).
- **Comptime `List` growth** — `List(T).append` at comptime bails ("struct_get:
base has no fields"). Doesn't block anything: array-literal locals already build
variant lists (`examples/0620`/`0624`). Probe `.sx-tmp/probe_makeenum.sx` /
`probe_li64.sx`. **Investigated — it's TWO layers** (both reproduce with plain
`List(i64)`, not metatype-specific; List works via `#run` because that evaluates
at EMIT time, after everything is lowered, while a metatype `::` const evaluates
at `scanDecls` time):
1. **Null comptime allocator.** `interp.zig:defaultContextValue` builds the
comptime `context.allocator` by looking up `__thunk_CAllocator_Allocator_alloc_bytes`
by name in the module's functions — but at `scanDecls` time those protocol
thunks aren't lowered yet, so `alloc_fn`/`dealloc_fn` are `.null_val` and any
comptime allocation fails. FIX (tried, works for this layer): call
`self.getOrCreateThunks("Allocator", "CAllocator")` (guarded by the same
Context/Allocator/CAllocator-registered check `emitDefaultContextGlobal` uses)
before the interp runs in `comptime.zig:runComptimeTypeFunc`.
`createProtocolThunk` saves/restores builder state, so calling it mid-lowering
is safe. After this, `alloc_fn=func_ref` — but layer 2 still bails.
2. **`struct_get` through a `*T` slot_ptr chain.** A `*List` struct receiver
(`vs.append(…)` → `append(self: *List, …)`) lands in the interp as a slot
whose contents are a slot_ptr to the actual value — `self.field` does
`struct_get` on `base=slot_ptr field_index=1` and bails. The auto-deref in
`interp.zig:.struct_get` does a single `loadSlot`; a chain-resolve loop did
NOT fix it (the final loaded value is a field-pointer aggregate that
`resolveFieldLoad` turns back into a slot_ptr — List's comptime representation
uses field-pointers + slot_ptrs the struct_get path doesn't fully resolve).
This is the deep part: comptime pointer/struct/slot resolution for `*T`
receivers, its own focused effort. Both speculative fixes were REVERTED (no
end-to-end testable win without layer 2).
The metatype surface (declare/define/type_info/field_type + make_enum) is
feature-complete for the locked design; generic type-fn body locals now work too.
- ~~**Validation + loud diagnostics**~~ — COMPLETE. duplicate variant names
@@ -144,9 +166,11 @@ capabilities would let the variant list be built more freely; both error cleanly
an array from a `{ptr,len}` slice, folded open-ended `hi` to a fixed array's
static length at lower time (no runtime/.ir change), and added
`interp.zig:subsliceElements`. `examples/0621` locks it.
- **Comptime `List` growth.** Building variants via `List(EnumVariant).append` at
comptime bails ("struct_get: base has no fields") — the allocator/List protocol
path isn't fully interp-evaluable. Probe `.sx-tmp/probe_makeenum.sx`.
- **Comptime `List` growth.** `List(T).append` at comptime bails ("struct_get:
base has no fields"). Investigated — two layers (null comptime allocator at
scanDecls + `struct_get` through a `*T` slot_ptr chain); see the detailed writeup
under "Next step". Layer 1 has a known fix; layer 2 is deep. Probe
`.sx-tmp/probe_makeenum.sx`.
- ~~Generic type-fn body locals~~ — DONE. A generic `($T) -> Type` now
comptime-evaluates its FULL body (prelude statements + return), so a local
before the return resolves. `createComptimeFunctionWithPrelude` +