Commit Graph

1213 Commits

Author SHA1 Message Date
agra
34734d415b comptime VM: harden against malformed lowering-time IR (P3.4-prep)
Prerequisite for wiring the VM at the lowering-time comptime site
(runComptimeTypeFunc), where IR can be malformed (an unresolved name lowers to a
dangling / Ref.none operand — the 0737 crash). Close the remaining panic vectors
so the VM bails (-> legacy fallback) instead of aborting:

- Vm.refTy(ref_types, r): a bounds-checked accessor replacing every raw
  ref_types[ref.index()] in exec — the type-side companion to Frame.get's
  bad_ref value-side guard.
- aggType is now a bailing method (Error!TypeId) routed through refTy.
- the block-dispatch loop bounds-checks the branch target before indexing
  func.blocks.items (a malformed br target). global_get was already guarded.

No behavior change: gate OFF and -Dcomptime-flat both 697/0. Unit test added
(a cmp_lt with a Ref.none operand bails, not panics).
2026-06-18 11:45:40 +03:00
agra
9ae3934f0f PLAN-COMPILER-VM: record non-negotiable end state — ONE evaluator, legacy deleted
Dual-path + emit-time legacy fallback are transitional scaffolding only; the VM
must reach parity at BOTH comptime sites (emit time AND lowering time), after
which the -Dcomptime-flat flag, the fallback, and interp.zig are all removed.
We do not ship both evaluators permanently.
2026-06-18 11:35:59 +03:00
agra
9e3aabcf76 comptime VM: Phase 3 — register_type write side + payloadless-enum fixes
The mutating compiler-API, minting types LAZILY at lowering time (single pass,
the existing runComptimeTypeFunc path — so the write side is legacy-only; the
VM isn't wired at lowering time, and the read-side readers stay dual-path):

  declare_type(name) -> Type            forward nominal handle (≈ declare)
  pointer_to(t) -> Type                 build *T references
  register_type(handle, kind, members)  ONE kind-branching fill (≈ unified define)

register_type branches on kind IN THE COMPILER (subsuming define's per-kind
dispatch); codes match type_kind: 1 struct, 2 actual .@"enum", 3 tagged_union,
4 tuple. Members are {name: string, ty: Type}. A non-generic `-> Type` builder is
now flagged is_comptime (decl.zig) so its dead body permits the welded calls.

Graph support: forward declare_type handles + pointer_to express a mutually-
recursive A<->B graph (*A, *B, B-by-value) before bodies are filled. register_type
is idempotent — re-filling a nominal slot (a minting module reached via two import
edges) re-mints identically rather than erroring (nominalIdent reads identity from
any nominal kind).

Fixes (issue 0142):
- A fully payloadless comptime-minted enum was minted as an all-void tagged_union,
  whose IR size disagrees with its LLVM size -> verifySizes panic. Now mints a real
  .@"enum" (register_type kind 2 AND the metatype defineEnum).
- Bare `EnumType.variant` qualified construction of a payloadless variant wasn't
  supported (failed for hand-written enums too — the type name lowered to a Type
  value). Added in lowerFieldAccess via isPayloadlessVariant; payload-carrying
  variants keep their call form.

Examples: 0631 (graph + actual enum + reflection), 0632 (make_enum all-void),
0633/0634/0635 (namespaced / bare / multi-edge import of a minted type), 0187
(qualified variant construction). Unit tests added.

Parity 697/697 (gate OFF and -Dcomptime-flat).
2026-06-18 10:47:36 +03:00
agra
27bc301651 comptime VM: Phase 3 — type_kind + type_field_value readers (read side complete)
The last two read-only readers the metatype's type_info(T) needs, each backed by
a TypeTable query both the legacy handler and the VM call (no drift):

  type_kind(t: TypeId) -> i64            (kindCode; stable discriminant, total — never bails)
  type_field_value(t: TypeId, idx) -> i64 (memberValue; enum explicit value or ordinal)

kindCode codes (compiler-owned, stable): 0 other / 1 struct / 2 enum /
3 tagged_union / 4 tuple / 5 union / 6 array / 7 vector / 8 error_set.

With these, the READ side is complete: find_type + type_kind + type_field_count +
type_field_{name,type} + type_nominal_name + type_field_value cover everything
reflectTypeInfo reads — a comptime sx fn can fully reflect a struct/enum/tuple
into data with no #builtin.

Example 0630 reflects Color / WindowFlags(flags) / Point. VM unit test added.

Revised forward direction: the write side will be ONE register_type(info) fn that
branches on the kind in the compiler (subsuming define's per-kind dispatch), not a
per-kind register_struct.

Parity 691/691 (gate OFF and -Dcomptime-flat).
2026-06-18 09:47:23 +03:00
agra
d23e208430 comptime VM: Phase 3 — field-level reflection readers
Three more read-only compiler-API readers on the TypeId-handle shape, each backed
by a new TypeTable query that both the legacy handler and the VM call (no drift):

  type_nominal_name(t: TypeId) -> StringId     (nominalName; loud-bail for unnamed types)
  type_field_name(t: TypeId, idx: i64) -> StringId   (memberName)
  type_field_type(t: TypeId, idx: i64) -> TypeId     (memberType)

All loud-bail on out-of-range idx / no-member — no silent default. First multi-arg
compiler fns (callCompilerFn now reads arg 1 = idx); added Vm.argHandle/argTypeId
range-checked arg readers and moved find_type/type_field_count onto them. Names use
the type_* family to avoid colliding with the std metatype builtins (field_name /
type_name in core.sx); the new TypeTable.nominalName is distinct from the existing
typeName(id) display-string renderer.

Example 0629 reflects Pair { lo: Point; hi: Point } — each field name + the nominal
name of a field's type, #run-folded, VM-HANDLED natively. VM unit test added.

Parity 690/690 (gate OFF and -Dcomptime-flat).
2026-06-18 09:34:36 +03:00
agra
a9302a8b50 comptime VM: Phase 3 — find_type + type_field_count reflection readers
First read-only compiler-API reflection readers, bound the same way as the
intern/text_of seed (compiler_lib.bound_fns + Vm.callCompilerFn, native on flat
memory, no marshaling). A type handle is a plain u32 TypeId (like StringId), so
both stay clean scalar host-calls:

  find_type(name: StringId) -> TypeId          (TypeTable.findByName; unresolved/0 if absent)
  type_field_count(t: TypeId) -> i64           (new TypeTable.memberCount; loud-bail, no silent 0)

memberCount is the single source both the legacy handler and the VM read, so the
two paths can't drift. find_type returns a non-optional TypeId using the
unresolved(0) sentinel for not-found rather than ?Type — a Type value is
.any-typed (which the flat-memory VM does not represent) and an optional can't
cross the legacy<->VM eval boundary; unresolved is the project-blessed "no type"
marker.

Example 0628 chains intern -> find_type -> type_field_count (+ a not-found
lookup), folded at #run, VM-HANDLED natively. VM unit test added.

Parity 689/689 (gate OFF and -Dcomptime-flat).
2026-06-18 09:25:26 +03:00
agra
0367d96d9b comptime VM: host wiring, full corpus parity, build flag, Phase 3 seed
Phase 1.final of the flat-memory comptime VM — wire the host through it,
reach corpus parity, and gate it behind a build flag — plus the first
Phase 3 (compiler-API) step. Default OFF; legacy interpreter unchanged.

Host wiring + hardening:
- Machine accessors return error.OutOfBounds (no debug panic) on bad
  addresses; Frame.get/set bounds-check and bail (no panic) on a malformed
  operand ref (e.g. a ret Ref.none from an unresolved name).
- tryEval routed at both comptime call sites in emit_llvm — the const-init
  fold and the #run side-effect path — with per-eval legacy fallback;
  yields .void_val for void/noreturn entries. Both sites sx_trace_clear()
  before the legacy fallback so a partial VM run that pushed trace frames
  doesn't double-push on re-run.

VM coverage (all corpus const-inits except the inline-asm global):
- Implicit context materialized from the __sx_default_context global; the
  full allocator protocol runs on the VM (context.allocator.alloc ->
  call_indirect -> CAllocator thunk -> libc_malloc -> native flat malloc).
- Native libc memory builtins (malloc/calloc/free/memcpy/memmove/memset)
  on flat memory; f32 stored/loaded as the 4-byte single; signed sub-64-bit
  loads sign-extended; global_get (lazy + memoized); func_ref/call_indirect
  (func-ref encoded fid+1, 0 reserved for null); string/slice fat-pointer
  field access; is_comptime; the failable/error cluster (error_set tuples,
  trace_frame + native sx_trace_push/clear -> raise/catch/or + return traces).

Build flag + Phase 3 seed:
- -Dcomptime-flat (build_opts module) OR SX_COMPTIME_FLAT env enables the VM;
  zig build test -Dcomptime-flat runs the full corpus on the VM (688/0).
- intern/text_of serviced natively on flat memory via Vm.callCompilerFn
  (compiler_welded boundary) — the seed the rest of the compiler-API grows on.

Parity 688/688 gate ON and OFF. Unit tests added throughout. The
lowering-time #insert wiring was explored and reverted (lowering-time IR can
be malformed; full malformed-IR hardening is a prerequisite, deferred).
2026-06-18 08:27:58 +03:00
agra
b8f3d6fd78 comptime VM: flat-memory machine + executor + Reg<->Value bridge + tryEval
Phase 1 of the flat-memory comptime VM (current/PLAN-COMPILER-VM.md),
built standalone + unit-tested with the legacy interpreter still live and
the corpus untouched (688 green).

src/ir/comptime_vm.zig:
- Machine: one linear byte memory (comptime stack+heap) with a bump/stack
  allocator (mark/reset), scalar readWord/writeWord (1/2/4/8 LE) + byte
  views; addr 0 reserved as null_addr. Frame: a Ref-indexed register file
  (Reg = raw u64: immediate scalar bits OR an Addr). Target-aware layout
  comes from the type table, so cross-compilation stays correct.
- Vm executor over the SAME SSA IR, mirroring the legacy interp's scalar
  semantics (i64 wrapping/signed, f64). Ported: constants, arithmetic,
  comparison, logical, conversions, control flow (br/cond_br/ret + block
  params); structs (alloca/load/store/struct_init/get/gep at target
  offsets); tuples; arrays (index_get/gep, length); slices+strings as
  {ptr,len} fat pointers (const_string, data_ptr, subslice,
  array_to_slice, str_eq/ne, index-through-slice); optionals (pointer and
  {T,i1} shapes); payloadless enums; deref/addr_of; direct + recursive
  call over the shared flat memory (depth-guarded). The value model: a
  word for scalars/pointers, by-address for aggregates (a struct's value
  IS its Addr). Any unported op bails loudly (error.Unsupported + detail).
- Reg<->Value boundary bridge (valueToReg / regToValue) + tryEval, the
  hybrid-wiring entry point: run a comptime fn on the VM, return a legacy
  Value or null to fall back. Transitional, for the legacy interop edge.

Registered in the ir.zig barrel.
2026-06-17 19:29:50 +03:00
agra
18af8eb845 comptime-API: strip the byte-weld; pivot to a flat-memory comptime VM
The byte-weld (sx structs whose layout was validated to mirror the
compiler's Zig records) plus the serialization/marshaling bridge was the
wrong direction: it bolted a parallel layout regime and hand-built
byte-copies onto a comptime value model that fundamentally isn't bytes.

Strip the struct-weld machinery:
- compiler_lib.zig loses the type registry (weldStruct / bound_types /
  BoundType / FieldLayout / findType / SxField / LayoutMismatch /
  validateStructLayout); it is now just the intern/text_of function
  host-call bridge (kept as the Phase-3 compiler-call seed).
- nominal.zig loses validateWeldedStruct / weldedFieldOrderStr + the
  sd.abi == .zig validation call.
- Remove the struct-weld unit tests and examples 0625/0627 (welded
  structs) + 1183/1186 (weld-layout diagnostics).
- The #library / abi / extern syntax stays.

Record the new direction: a bytecode VM over flat, byte-addressable
memory so comptime values are native bytes (no weld/validation/marshal),
target-aware (preserves cross-compilation) and sandboxed. See
current/PLAN-COMPILER-VM.md (Phase 0 strip -> Phase 1 flat-memory value
model -> Phase 2 bytecode -> Phase 3 compiler-API on flat memory).
design/comptime-compiler-api.md gets a SUPERSEDED banner. Also drop the
"~500 lines / split the step" rule from CLAUDE.md.
2026-06-17 19:29:36 +03:00
agra
40d075ca98 compiler-API: welded structs by reflection + memory-order validation
Replace the explored byte-layout-override engine (offset-ordered LLVM structs /
weld plans / byte-blobs — all unnecessary) with a much simpler design: a welded
`struct abi(.zig) extern compiler { … }` is a bodied header declaring its fields
in the bound compiler type's MEMORY order. The compiler reflects the real Zig
type (field names via @typeInfo, offsets via @offsetOf, size via @sizeOf —
nothing hand-maintained) and validates the header matches, with loud diagnostics.

On pass it is an ordinary struct whose natural layout already equals the Zig
layout — no reorder, no padding, no index/remap tables, no special LLVM path — so
@ptrCast'ing it to the compiler's own type and dereferencing is byte-identical.
When types.zig shifts, the header stops matching and the developer gets a specific
message to fix it.

- compiler_lib.zig: weldStruct reflects field names and bakes bound_types fields
  in ascending-offset (memory) order; deleted computeWeldPlan/WeldPlan/WeldElement.
- nominal.zig validateWeldedStruct: precise diagnostics — field-not-found,
  wrong-field-order (+ expected memory order), type-layout (size) mismatch,
  total-size mismatch.
- Examples: 0627 (StructInfo in memory order, byte-identical, usable),
  1186 (source-order StructInfo -> wrong-field-order diagnostic); 1183 refreshed.
- Design doc + checkpoint updated.
2026-06-17 15:45:23 +03:00
agra
88c4cbcfa5 test harness: add -Dname to scope the corpus to specific examples
`zig build test -Dname=examples/0625-foo.sx[,examples/0626-bar.sx]` runs ONLY the
named example(s) — full repo-relative .sx paths, comma-separated (a leading `./`
is tolerated). Empty = run everything (unchanged default).

Why: a full `-Dupdate-goldens` re-runs and rewrites all ~690 snapshots, so one
flaky/host-divergent example (AOT links, cross-arch `target` examples) can clobber
a good snapshot. `-Dname` regenerates only the named example(s) and touches
nothing else. It also busts the cached test-run result — the corpus enumerates
.sx/expected files at runtime, so a bare snapshot edit alone is otherwise served
from cache.

- build.zig: new `name` option threaded onto corpus_paths.
- corpus_run.test.zig: `nameMatchesFilter` + a per-example skip in the run loop.
- CLAUDE.md: document the targeted-regen workflow under Snapshot integrity.
2026-06-17 14:55:06 +03:00
agra
0b4c50b187 compiler-API: resume scaffolding for a fresh session
Add the COMPILER-API stream to CLAUDE.md's session-start router and a
`## ⏯ Resume` block to CHECKPOINT-COMPILER-API.md (next action = sub-step 2.2,
read order, build/verify, and the cross-arch snapshot-regen gotcha).
2026-06-17 13:32:33 +03:00
agra
cd5b958d19 comptime compiler-API: Phase 1 foundation + Phase 2.1 weld plan
Introduce the welded comptime `compiler` library (`#library "compiler"` +
`abi(.zig) extern compiler`), per design/comptime-compiler-api.md, and unify
`callconv(...)` into the new `abi(...)` annotation.

abi(...) replaces callconv(...):
- New ABI enum { default, c, zig, pure }; `abi(.c|.zig|.pure)` parses in the
  postfix slot before extern/export (and standalone). `kw_callconv` -> `kw_abi`.
- Migrated 52 sx files, the call-convention-mismatch diagnostic, and docs
  (readme/specs) from `callconv(.c)` to `abi(.c)`.

Phase 1 — welded compiler library (parse -> registry -> validation -> bridge):
- `abi(.zig) extern compiler` parses on fn decls (carries abi/extern_lib) and
  struct decls (StructDecl.abi/extern_lib).
- `#library "compiler"` is the comptime-only internal surface — never dlopen'd.
- src/ir/compiler_lib.zig: the binding registry (the safety boundary). `Field`
  welded to StructInfo.Field with layout baked from the real Zig type
  (@offsetOf/@sizeOf); `findType`/`findFn`. Welded structs are layout-validated
  at registration (field set + total size) as a header checked against the impl.
- Host-call bridge: a `fn abi(.zig) extern compiler` dispatches under the
  comptime interp to its registered Zig handler (intern/text_of round-trip),
  never dlsym. IR Function.compiler_welded; validated in declareFunction.
- Comptime-only enforcement: a runtime call to a welded fn is a clean
  build-gating error (emitCall), not an undefined-symbol link failure.

Phase 2.1 — byte-layout weld foundation:
- Decision: full byte-layout weld (sx struct laid out byte-identically to the
  bound Zig type). Registered StructInfo (first non-natural / Zig-reordered
  layout). `computeWeldPlan` — pure offset-ordered element plan + padding +
  sx-field->LLVM-element remap; unit-tested. Emit/interp wiring is the next
  sub-step (2.2+, see current/CHECKPOINT-COMPILER-API.md).

Examples: 0625/0626 (welded struct + fn round-trip), 1183/1184/1185
(layout-mismatch, unexported-fn, runtime-call diagnostics).
2026-06-17 13:31:11 +03:00
agra
3a9b508502 design: drop 'Co-designed' attribution line 2026-06-17 10:11:38 +03:00
agra
7a37fe33ce design: ground compiler-API build order in code anchors
#library already lexes/parses (library_decl node); extern/export are
keywords. Phase 1 new work pinned to concrete sites: parser (extern(.zig)
postfix at the #builtin/#compiler positions), AST binding field,
compiler_hooks.zig as the registry, types/llvm layout emission, host_ffi
comptime bridge. First testable sub-step: extern(.zig) <lib> parses on a
fn decl.
2026-06-17 10:04:10 +03:00
agra
2b43af4f8a upgrade llvm@22 2026-06-17 09:58:43 +03:00
agra
08b0a35758 design: comptime compiler API — #library "compiler" + extern(.zig)
Unified sx<->compiler binding that subsumes the metatype declare/define
primitives AND the #compiler struct attribute. A named 'compiler' library
exposes the compiler's real types (layout-welded via extern(.zig), offsets
queried from the Zig type at compiler-build time + a build-time equality
assertion) and functions (comptime-only, host-call bridged). declare/
define/type_info become sx library code over register_*/find_type; the
projected meta.sx TypeInfo + hand marshaling are deleted; BuildOptions
migrates onto it and #compiler is removed. Includes the safety boundary
(curated export list, guarded mutators, comptime-only), the honest limit
(the ordering law stays, but stops leaking as 'weird stages' — dissolving
the 0141 class), a phased suite-green build order, and the open risks
(union(enum) welding, optional fields, LLVM offset emission).
2026-06-17 09:38:00 +03:00
agra
e2b2e22fa7 issue(0141): Direction 2 (defer eval) ruled out by experiment; Direction 1 is the path
Wired a minimal deferral (eval at a new Pass 1c' after the CAllocator
thunks exist) — the List repro STILL bailed with struct_get, and it
destabilized examples/0620. So deferring past the thunks isn't the cause
of the wrong IR; the field-access lowering only emits struct_gep at
body-lowering/emit time. No single pass slot satisfies both 'body lowers
correctly' and 'layout ready before use'. Pivot to Direction 1 (robust
*Struct field-access lowering). Experiment reverted; tree clean.
2026-06-17 08:34:50 +03:00
agra
a448f50f7f issue(0141): refine root cause — wrong IR (struct_get vs struct_gep) at scanDecls
Instrumentation shows List.append lowers list.len/list.cap to struct_gep
(correct) at #run/emit time but struct_get (wrong, value access on a *T
receiver) at scanDecls/metatype time — same source, different IR. The
function IS lowered both ways, just to wrong IR at scanDecls due to
incomplete generic-instantiation context. So an interp-side lazy-lower
hook can't fix it (IR is wrong before the interp runs); the fix is either
robust field-access lowering or deferring the comptime type-construction
eval to a complete-world pass (like #run). Supersedes the two-layer framing.
2026-06-17 08:21:55 +03:00
agra
86feced560 docs(metatype): refresh PLAN Status — surface complete, 0141 deferred 2026-06-17 08:08:56 +03:00
agra
0f88525884 issue(0141): comptime List growth in type construction (two-layer)
File the last METATYPE deferred enhancement: List(T).append at comptime
bails ('struct_get: base has no fields') in a type-construction ::.
Standalone repro + two-layer root cause (null comptime allocator at
scanDecls; *T slot_ptr struct_get) + investigation prompt. Non-blocking:
array-literal locals already build variant lists (examples/0620/0624).
Checkpoint + Known issues reference 0141.
2026-06-17 08:07:11 +03:00
agra
85c1b85f8b 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).
2026-06-17 07:58:34 +03:00
agra
c7e997043f docs(metatype): generic type-fn body locals done; only List growth remains 2026-06-17 07:45:08 +03:00
agra
32bbfdecc1 test(metatype): generic type-fn body local (examples/0624)
Locks the generic-type-fn prelude eval (d87d86d): make_status($T)
assembles a variant list in a local then mints, with the ok payload = T.
2026-06-17 07:44:15 +03:00
agra
d87d86df8a feat(metatype): comptime-eval generic type-fn body locals
A generic ($T) -> Type type-fn comptime-evaluated only its return
EXPRESSION, so a local declared before the return ('vs := …; return
make_enum(…, vs)') was unresolved. Now a body with a prelude (statements
before the return) has its full body evaluated: createComptimeFunction-
WithPrelude lowers the pre-return statements into the comptime function's
scope before the return expr, so the locals resolve.

- comptime.zig: createComptimeFunctionWithPrelude (prelude stmts +
  expr); evalComptimeTypeBody (extract prelude + return expr, scan the
  whole body for declare() forward types); runComptimeTypeFunc factored
  out of evalComptimeType (shared bail/declare-never-defined handling).
- generic.zig: route a type-fn body WITH a prelude through
  evalComptimeTypeBody; no-prelude bodies stay on evalComptimeType (zero
  change for RecvResult/TryResult etc.).

Non-generic builders (whole body already evaluated) and the List-growth
path are unaffected. Suite green (684).
2026-06-17 07:40:09 +03:00
agra
60293bf5dd docs(metatype): tuple done; reflect/construct triad complete 2026-06-17 07:10:45 +03:00
agra
14cfb64874 test(metatype): tuple construct + round-trip (examples/0623)
Locks the tuple widening (9f3f746): programmatic Pair build via
.tuple(.{elements}) + a source-tuple round-trip via type_info. Completes
the reflect/construct triad (enum 0619, struct 0622, tuple 0623).
2026-06-17 07:10:01 +03:00
agra
9f3f746c4b feat(metatype): widen type_info/define to tuple types
TypeInfo gains a `tuple(TupleInfo) variant (TupleInfo{elements: []Type},
positional/unnamed) — completing the reflect/construct triad with enum
and struct.

- meta.sx: TupleInfo + `tuple TypeInfo variant.
- interp: reflectTypeInfo builds .tuple (tag 2) as bare type_tag elements
  (no name pairs); defineType dispatches tag 2 -> defineTuple, which
  decodes []Type and completes the declare slot as a structural .tuple
  via replaceKeyedInfo (kind change). Tuples are structural so the
  declared name is vestigial, but the slot is still completed in place so
  define returns the handle (consistent with enum/struct).
- call.zig: the lower-time type_info guard now admits .tuple.

define(declare("P"), .tuple(.{elements=.[i64,f64]})) builds a tuple, and
define(declare("T"), type_info((i64,bool,f64))) round-trips one. Suite
green (683).
2026-06-17 07:05:55 +03:00
agra
d83f5fa90d docs(metatype): struct widening done; tuple is the last shape 2026-06-17 06:59:17 +03:00
agra
8f03349279 test(metatype): struct construct + round-trip (examples/0622)
Locks the struct widening (aaac019): programmatic Vec2 build via
.struct(.{fields}) and a source-struct round-trip via type_info.
2026-06-17 06:58:28 +03:00
agra
aaac019715 feat(metatype): widen type_info/define to struct types
TypeInfo gains a `struct(StructInfo) variant (StructField{name,type});
the metatype system now reflects AND constructs structs, not just enums.

- meta.sx: StructField / StructInfo / `struct TypeInfo variant.
- interp: reflectTypeInfo builds .struct (tag 1) for a source @"struct";
  define dispatches on the TypeInfo tag (defineType) -> defineEnum (0) /
  defineStruct (1). defineStruct mirrors defineEnum (dup-field-name check
  included) but completes the declare slot AS a struct via replaceKeyedInfo
  (a kind change re-keys the intern map; updatePreservingKey asserts no
  key change, true only for the enum path).
- call.zig: the lower-time type_info guard now admits @"struct".

define(declare("P"), .struct(.{ fields = .[ … ] })) builds a struct, and
define(declare("C"), type_info(SrcStruct)) round-trips one. Suite green
(682); enum path (0619) unchanged.
2026-06-17 06:54:17 +03:00
agra
afb1fee252 docs(metatype): validation story complete (use-before-define subsumed) 2026-06-17 06:42:27 +03:00
agra
dcdf1dd318 test(metatype): lock by-value self-ref rejection for constructed enums (1182)
Constructed-type companion to examples/1178 (source form): a declare/
define enum whose variant references itself BY VALUE is rejected by the
same checkInfiniteSize guard ('infinitely sized'). Pins the use-before-
define corner of the validation story — by-value self-reference is the
one self-ref shape that isn't legal; *L (pointer) is fine (see 0618).
No compiler change (locks existing behavior).
2026-06-17 06:42:02 +03:00
agra
fe6799545a docs(metatype): declare()-never-defined validation done 2026-06-17 06:34:26 +03:00
agra
c185dbdd13 test(metatype): lock declare()-never-defined rejection (examples/1181)
Diagnostics example for the bare-declare guard (14f30f3): an unfinished
declare("Undef") -> build-gating error naming the type, exit 1.
2026-06-17 06:33:31 +03:00
agra
14f30f341c fix(metatype): reject declare() never completed by define()
A bare declare("X") with no define left a zero-field nominal slot that
panicked at codegen (verifySizes: llvm_size != ir_size). evalComptimeType
now detects a zero-variant tagged_union result and emits a clean
build-gating diagnostic naming the type — a zero-variant enum is never a
legitimate construction result (defineEnum rejects empty variant lists
too). Self-reference (a declared slot completed by define) is unaffected.
2026-06-17 06:29:23 +03:00
agra
c573e4befb docs(metatype): duplicate-variant validation done; tidy Next step list 2026-06-17 05:27:17 +03:00
agra
e291034e46 test(metatype): lock duplicate-variant-name rejection (examples/1180)
Diagnostics example for the define duplicate-name guard (b2db2c5): two
'value' variants -> build-gating error naming the duplicate, exit 1.
2026-06-17 05:26:35 +03:00
agra
b2db2c54ed fix(metatype): reject duplicate variant names in define
Two same-named variants in a constructed enum silently succeeded —
construction (.a) and matching would ambiguously pick one. defineEnum
now bails when a variant name repeats, naming it. The name is dynamic so
it sets last_bail_detail directly (bailDetail takes a comptime string);
evalComptimeType renders it as a build-gating diagnostic.
2026-06-17 05:22:23 +03:00
agra
964ddeb73a docs(metatype): comptime aggregate subslice gap resolved 2026-06-17 05:16:08 +03:00
agra
60471b3a2c test(metatype): comptime subslice over an aggregate (examples/0621)
make_enum from dirs[0..2] — mints Axis from a comptime SUBSLICE of a
local EnumVariant array. Locks the interp subslice-over-non-string-
aggregate fix (d22037c); previously bailed.
2026-06-17 05:15:43 +03:00
agra
d22037c4a7 fix(interp): comptime subslice over non-string aggregates
`arr[lo..hi]` at comptime bailed for any non-string base — the interp's
.subslice op only handled string-backed values. Worse, the open-ended
`hi` came from a .length op that misread a 2-element array as a {ptr,len}
fat pointer (returning the 2nd element, not the count), so even lo/hi
weren't valid ints.

Fix, interp-only (runtime already handles arrays via LLVMTypeOf):
- Thread the base operand's IR type onto the Subslice op (base_ty); the
  interp uses it to tell a bare array (elements = aggregate fields) from a
  {data,len} slice (elements in the data field) — indistinguishable by
  Value shape alone.
- Fold an open-ended slice's hi to the array's static length for fixed
  arrays at lower time (runtime emitLength folds the same constant, so the
  IR result is unchanged — no snapshot churn — but the comptime interp no
  longer hits the ambiguous .length op).
- subsliceElements() resolves the element list (array/slice, inline or
  slot_ptr-backed) and subslice returns a proper {data,len} slice value.

Suite green (678), no .ir changes.
2026-06-17 05:11:33 +03:00
agra
4e8075491d docs(metatype): make_enum done; note deferred free-form-construction gaps 2026-06-17 04:56:56 +03:00
agra
2250652ba5 feat(metatype): make_enum — general enum constructor over a []EnumVariant value
make_enum(name, variants: []EnumVariant) -> Type mints a nominal enum
from a variant list passed as a VALUE, not a hardcoded literal — the
open-ended form the channel-result constructors are special cases of.
Pure sx over declare/define; no compiler machinery.

Because variants is an ordinary comptime value, a non-generic builder
can ASSEMBLE it in a local before minting. examples/0620: build_level
fills a local array, then make_enum mints Level from it — exercising
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).
2026-06-17 04:55:48 +03:00
agra
0cb1aa270e docs(metatype): issue 0140 resolved; make_enum unblocked 2026-06-17 04:36:51 +03:00
agra
4da6add334 test(0140): pin comptime type-construction bail diagnostic (examples/1179)
Move the issue 0140 repro into the feature suite as a regression test.
Asserts the build-gating diagnostic 'comptime type construction failed:
comptime define(): enum has no variants' at the construction site, exit
1 — locking out the prior 'unresolved type reached LLVM emission' panic.
2026-06-17 04:36:09 +03:00
agra
37ec3da8cb fix(0140): surface comptime type-construction bail as a diagnostic
evalComptimeType did `interp.call(...) catch return null`, dropping the
interp's last_bail_detail; callers poisoned to .unresolved with no
diagnostic, so the sentinel reached LLVM emission and panicked
("unresolved type reached LLVM emission"), or hid behind a downstream
cascade.

Clear last_bail_detail before the call; on the catch emit a build-gating
.err at the construction expr's span ("comptime type construction
failed: {detail}", mirroring the #run surfacing in emit_llvm.zig), then
return null to keep the .unresolved poison — now gated by a real message
so no unresolved type reaches emission unannounced.

Empty-variant define now prints 'comptime define(): enum has no
variants' and exits 1 (no panic); make_enum-style computed-slice
failures show their root reason at the construction site.
2026-06-17 04:31:38 +03:00
agra
3a062780f7 issue(0140): comptime type-construction bail panics instead of diagnosing
A failing declare/define (e.g. empty variant list) bails correctly in
the interp, but evalComptimeType swallows last_bail_detail via
`catch return null`; the decl poisons to .unresolved with no diagnostic
and reaches LLVM emission -> panic ("unresolved type reached LLVM
emission"), or hides behind a misleading downstream cascade.

Pre-existing (plain define path), surfaced while starting the make_enum
step. Blocks make_enum's computed (pointer-backed) []EnumVariant slice
decode. Repro + investigation prompt filed; CHECKPOINT-METATYPE marked
BLOCKED. Session paused pending fix per CLAUDE.md IMPASSABLE rule.
2026-06-16 22:59:49 +03:00
agra
52b0dc2a9a docs(metatype): type_info($T) enum reflection done; update plan/checkpoint 2026-06-16 22:53:52 +03:00
agra
1ffda415c2 feat(metatype): implement type_info($T) reflection (enum round-trip)
type_info reflects an enum / tagged-union 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: new BuiltinId.type_info (comptime-only, like declare/define).
- lower/call.zig: replace the 'not yet implemented' bail. Resolve $T at
  lower time, reject non-enum/non-tagged-union loudly with a good span,
  emit callBuiltin(.type_info, [const_type], TypeInfo).
- interp.zig: reflectTypeInfo builds the exact nested-aggregate Value
  defineEnum decodes — variant {name,payload}, slice {data,len}, EnumInfo
  {variants}, TypeInfo {tag0, EnumInfo}. tagged_union reflects field.ty
  (tagless already void); payloadless `enum` reflects void per variant.
- emit: unchanged — type_info is always comptime-evaluated, the existing
  comptime-only else arm (shared with declare/define) never fires.

0619 turns green: a source enum (circle:f64 / rect:i64 / empty) reflected
and reconstructed, constructs and matches like the original.
2026-06-16 22:52:53 +03:00