feat: materialize address-free compile-time untagged unions

Snapshot full union storage rather than choosing an overlapping field. Preserve structural union source syntax independently of nominal identity so direct literal arguments materialize and share normalized specialization keys. Reject uninitialized bytes and address-bearing member layouts.
This commit is contained in:
2026-10-30 15:54:32 +01:00
parent c8890b4355
commit cc39c96fc0
9 changed files with 420 additions and 25 deletions
+3 -1
View File
@@ -138,7 +138,9 @@ computed callbacks materialize as references to their original source declaratio
readonly byte-slice results (`[]u8` and `[;sentinel]u8` for any byte sentinel) snapshot their bytes into owned literals before evaluation storage is released. ordinary compile-time helpers can return string subviews, views of locally filled arrays or immutable global arrays, including nested records/arrays, tagged payloads, optionals and both fallible channels. explicit `$()` expressions, generic arguments and generated defaults preserve embedded NULs, arbitrary byte values, exact declared slice types and content-based specialization-cache identity. nonzero-sentinel snapshots retain an owned trailing sentinel outside the logical length; empty views also retain it. snapshots survive evaluation/session disposal and repeated compilation; uninitialized source bytes diagnose read-before-initialization, and sentinel bytes are rechecked after evaluation. mutable slices and nonbyte or nominal-byte slices remain unsupported.
`$(expression)` explicitly evaluates a constant expression, including an ordinary procedure call, even in a package without templates. use a typed expression when its shape cannot be inferred independently, such as `$(Point { x = 40 })`; integer literals retain contextual typing. the resulting value works in runtime expressions, bindings, expression statements and array lengths, while the erased expression still receives ordinary checking. each interpreter evaluation is isolated and limited to 100,000 IR instructions/terminators and 128 active frames; failures retain their originating source locations. runtime parameters and mutable local/global dependencies cannot supply compile-time inputs. nonnull data pointers, unsupported slice types and untagged unions cannot escape temporary evaluation as generated constants; active payloads or aggregate fields containing unsupported values are diagnosed rather than retaining interpreter handles. untagged unions lack an active-member identity, so selecting a source variant could discard observable bytes. executed foreign calls remain interpreter traps. general type-valued execution, broader storage-bearing materialization and remaining existing-library acceptance, including broader `meta` operations and `io.print`, remain open.
untagged union constants snapshot every byte of their native-layout storage, including padding and bytes beyond a smaller overlapping member. scalar reinterpretation, nested records/arrays, optionals, tagged payloads, fallible channels and generated defaults preserve exact nominal types and by-value copies through `$()` and compile-time arguments. specialization keys compare the full snapshot, not one selected field. every storage byte must be initialized. because untagged storage has no active-member identity, all member layouts must be free of data pointers, callbacks, strings and slices, recursively; otherwise materialization diagnoses an escape rather than retaining interpreter handles, even if a numeric member was initialized.
`$(expression)` explicitly evaluates a constant expression, including an ordinary procedure call, even in a package without templates. use a typed expression when its shape cannot be inferred independently, such as `$(Point { x = 40 })`; integer literals retain contextual typing. the resulting value works in runtime expressions, bindings, expression statements and array lengths, while the erased expression still receives ordinary checking. each interpreter evaluation is isolated and limited to 100,000 IR instructions/terminators and 128 active frames; failures retain their originating source locations. runtime parameters and mutable local/global dependencies cannot supply compile-time inputs. nonnull data pointers, unsupported slice types and address-bearing untagged union layouts cannot escape temporary evaluation as generated constants; active payloads or aggregate fields containing unsupported values are diagnosed rather than retaining interpreter handles. executed foreign calls remain interpreter traps. general type-valued execution, broader storage-bearing materialization and remaining existing-library acceptance, including broader `meta` operations and `io.print`, remain open.
`if $(condition) { ... } else { ... }` evaluates a boolean guard before expanding or checking either body, including calls to ordinary constant-input procedures. concrete generic specializations also select branches whose guards use constants, immutable constant bindings, type comparisons or substituted compile-time parameters. only the selected body is specialized and checked; both bodies are still parsed and name-resolved. selected bodies retain their scope, cleanup and return behavior, and erased guards retain ordinary type checking and source diagnostics. ordinary calls in an implicit guard stay runtime; use `$()` to request their evaluation. optional captures remain runtime, and ordinary nongeneric `if (true)` still checks both bodies.