Type-checking gaps (segfault/corruption → compile errors):
- 0197: reject a store into an annotated slot whose value has no modeled
coercion AND a different byte width (a 16-byte string into a 4-byte i32
overran the slot and segfaulted). New checkAssignable / noneReinterpretIsUnsafe
(coerce.zig, width via the LLVM-accurate typeSizeBytes) wired into every store
site: var/const-decl, single + multi assignment (identifier/field/index/
element/deref), named-return defaults. Same-width reinterpretations (*T→[*]T,
i64→isize, fn-ref) and explicit xx/cast stay allowed; cascades suppressed via
externalErrorsExist. Examples 1205, 1206.
- 0198: an implicit `Any → T` unbox is now a compile error (it blindly
reinterpreted the boxed payload — silent garbage for a wrong scalar, a segfault
for an aggregate). xx and compiler-generated match/pack unboxes are unaffected.
Example 1207.
- 0199: `Any == <concrete>` (one operand Any) aborted the LLVM verifier — the
comparison arm now fires when either operand is Any, boxing the concrete side
first. Example 0654.
Multi-return deferrals (PLAN-MULTIRET #6 + named-order + D3 + generic):
- Reorder named return elements by name instead of requiring slot order; error on
unknown/duplicate/missing (value-only AND full-failable-tuple forms). Examples
0210, 0214.
- Reject a bare-paren (A, B) multi-return signature in generic-arg position
(return-position-only). Example 0215.
- Multi-return closure types / lambda literals work via the reused tuple
machinery (destructure, single-bind+field, lambda arg). Example 0216.
- Generic multi-return: positional works (0217); 0200: the named-slot
implicit-return form now works for generic free fns + struct methods —
monomorphizeFunction now calls bindNamedReturnSlots. Example 0218.
readme.md documents the annotated-store coercion rule; CHECKPOINT-MULTIRET.md
updated. Full corpus green (850/0).
The race (9099735e), Phase 0 (2f2d7f1d), Phase 1 (5c30bfe0) and tuple-store
(6a976287) commits added each example's .sx but their `git add NNNN-*` glob
missed the sibling expected/ snapshots (a different directory). The suite
passed locally because snapshots are read from disk, but the commits were
incomplete — a fresh checkout would have no goldens. Add them now.
`tup[i]` where `i` folds to a compile-time integer (an `inline for`
cursor or a literal) now reads the i-th tuple field with its CONCRETE
type instead of failing with "cannot index a value of type '(…)'".
A tuple's elements are heterogeneous, so there is no runtime
element-indexing op — a comptime index lowers exactly like the `.N`
field-access path (a `structGet` of the i-th field). A genuinely
runtime index into a tuple value still falls through to the existing
"cannot index a value of type" error (no single element type). A
comptime index out of range gets a dedicated loud diagnostic.
This is the read side `race` needs: pull the i-th `*Task(T_i)` handle
out of a named-tuple param keeping its real type so field/method access
on it resolves.
Locked by examples/comptime/0652-comptime-tuple-cursor-index.sx.
The metatype system can MINT tagged-union types (`make_enum`) and READ them
(`field_count`/`field_name`/`field_type`/`field_value`); `make_variant` is the
missing WRITE side — build a value of a minted tagged-union `E` whose active
variant is `idx`, carrying `payload`. Needed when the variant is selected at
RUNTIME but the union was synthesized at comptime so its labels can't be spelled
as a literal `.label(payload)` — e.g. the `race` result: an `inline for 0..N (i)`
arm constructs the i-th variant of a synthesized result with the winner's value.
Pure sx in modules/std/meta.sx (NOT a compiler builtin): a minted tagged-union is
laid out `{ i64 tag @0, payload @ size_of(i64) }` (the compiler fixes `tag_type`
to i64 for minted enums), so make_variant zeroes the value then writes the tag and
payload at those offsets — the same offsets the compiler's own enum_init/payload
ops use. The header comment documents the minted-only / i64-tag layout coupling.
Adversarially reviewed (SHIP): the offset-8 payload assumption is exactly the
compiler's enum layout with no alignment hazard (the payload is an alignment-1
`[N x i8]` array immediately after the i64 header); complex payloads (multi-field
struct, string fat pointer, 40-byte struct) round-trip. Locked by
examples/comptime/0650-comptime-make-variant.sx (heterogeneous + complex payloads,
runtime-index selection via the comptime `case` form). Suite green (822/0).
A `return` inside an `inline if` / comptime `case` branch — itself inside an
`inline for`, under a runtime `if` — made the compiler wrongly reject the
function as "body produces no value" and DROP its trailing statements (e.g. a
trailing `return -1`).
Root cause: the inline-if branch lowering sets the global `block_terminated` flag
when its taken arm returns (control_flow.zig / stmt.zig `lowerInlineBranch`),
unlike a bare `return` STATEMENT which deliberately never sets it (precisely to
avoid leaking past an `if cond { return }` merge — see the comment at
stmt.zig:37-42). The enclosing runtime-`if`'s merge never reset the flag, so it
leaked past the merge and `lowerBlock` skipped the following statements as dead.
Fix: after the runtime-`if` switches to its merge block, set
`block_terminated = then_diverged and has_else and else_diverged` — the `if`
leaves the block terminated ONLY when both arms diverged with an `else` covering
the cond-false edge (otherwise the merge is reachable and the flag must be
false). Adds `else_diverged` tracking alongside the existing `then_diverged`.
Adversarially reviewed (SHIP): the reachability predicate is correct across all
arm/else/divergence cases; the both-arms-diverge case still sets true (preserving
prior behavior); value-ternary and inline-for-unroll paths are unaffected.
Regression: examples/comptime/0651-comptime-inline-if-return.sx (nested inline-if
and comptime `case`, both with per-arm returns, asserting the trailing return is
emitted). Suite green (822/0).
A comptime-type-call's `Type` result (`field_type(T, i)`, `pointee(P)`) could only
be used in a type-arg slot — not as a `Type`-typed struct-field value, a generic
`$P: Type` argument, or a nested type-call arg — when the index was an `inline for`
loop variable. It routed through value / generic-fn lowering ("cannot infer generic
type parameter" / "unknown #builtin field_type") instead of the type-call fold. This
is what blocked the variable-arity `race` result synthesis: a `($T) -> Type` builder
looping `field_type(pointee(field_type(T, i)), 0)` to mint a tagged-union.
Three coordinated changes route these through the SAME type-call fold (which folds
the index, including a loop var), so type-arg and value positions never disagree:
- `isTypeShapedAstNode` (type_bridge.zig): a `.call` to a type-returning builtin
(`field_type`/`pointee`/`type_of`, via new `isTypeReturningBuiltinName`) is
type-shaped, so generic-arg inference (buildTypeBindings Strategy 1) resolves it
via `resolveTypeArg` rather than failing value inference.
- `tryLowerReflectionCall` (call.zig): value-position `field_type`/`pointee` fold
to `constType(resolveTypeCallWithBindings(c))` — the value twin of the existing
`type_of` fold (every failure path already diagnoses before `.unresolved`).
- `field_name` (call.zig): folds to a const STRING via `memberName` when the type
resolves and the index is a compile-time constant (matching the runtime
`field_name_get` array exactly — same `memberName`, same "" for nameless
members); a dynamic index still emits the `field_name_get` instruction.
Adversarially reviewed (SHIP): no over-broadening (only type-demanding slots consult
isTypeShapedAstNode; only `$T: Type` slots are affected), no silent defaults (every
fold failure is preceded by a diagnostic; "" is the runtime-matching value for a
nameless member). Locked by examples/comptime/0649-comptime-typecall-composition.sx
(reflect a named tuple of `*Box(..)` handles → mint a tagged-union with the tuple's
labels, projecting `*Box(A)` -> `A`). Suite green (821/0). Unblocks PLAN-RACE step 2.
The int-returning type-query builtins now fold to a compile-time constant in
const-required positions (`inline for` bound, array dimension), like a plain
`K :: 3` const — previously they evaluated only as runtime values, so
`[field_count(S)]T` and `inline for 0..field_count(S)` were rejected as "not a
compile-time integer". This is what lets a `($T) -> Type` builder loop
`inline for 0..field_count(T)` to assemble a member list from a type's fields
(the `race` result synthesis).
The shared comptime-int folder `evalConstIntExpr` (program_index.zig) gained a
`.call => ctx.evalConstCallInt(node)` arm. The body-lowering ctx (`Lowering`)
implements it — resolve the type arg via `resolveTypeArg`, return
`memberCount orelse 0` / `typeSizeBytes` / `typeAlignBytes`, matching the runtime
value path in lower/call.zig exactly. `SourceConstCtx` delegates to its wrapped
Lowering; the stateless ctxs (`ModuleConstCtx`, `StatelessInner`, test `DimCtx`)
stub null (they cannot resolve a type-expr arg). A non-type-query call / wrong
arg count / unresolved type arg folds to null (not a comptime integer).
Adversarially reviewed (SHIP): the fold matches the value path across every type
kind, ctx coverage is complete, recursion is AST-depth bounded, no speculative
spurious diagnostics, `orelse 0` is field_count's definitional value for
non-aggregates (not a silent default). Locked by
examples/comptime/0648-comptime-typequery-const-fold.sx. Suite green (820/0).
Project a pointer type to its target: `pointee(*X)` -> `X`. The one reflection
primitive missing for the `race` result synthesis (`*Task(A)` -> `A` via
`field_type(pointee(*Task(A)), 0)`) — reflection could read aggregate fields but
was blind to a pointer's target type.
Mirrors the `field_type` builtin: declared `#builtin` in std/core.sx, resolved as
a lower-time type-call fold in resolveTypeCallWithBindings (src/ir/lower/generic.zig)
so it composes in any type-arg slot. `.pointer` -> pointee, `.many_pointer` ->
element; a non-pointer arg is a loud diagnostic + `.unresolved` sentinel (no silent
fallback). Adversarially reviewed (SHIP). Locked by
examples/comptime/0647-comptime-pointee-reflection.sx. Suite green (819/0).
PLAN-RACE step 1 of 6.
`field_count` / `field_name` were broken on every non-struct/enum aggregate:
`field_count(Tuple(i64, bool))` silently returned 0 (a missing `.tuple` arm in
the count switches), and `field_name(tuple/array/vector, i)` SEGFAULTED — the
LLVM backend built a zero-length `[0 x string]` name array for those kinds while
sizing the runtime GEP at the (often non-zero) member count, so the indexed load
ran past the array.
Root cause was three+ parallel switches that each had to know how to count an
aggregate's members, and disagreed: `field_count` lowering and `memberCount` had
struct/union/tagged_union/enum/array/vector but no `.tuple`; the backend's
`field_name_get` build + GEP sizing had neither `.tuple` nor `.array`/`.vector`.
Fix:
- add the `.tuple` arm to `field_count` lowering (src/ir/lower/call.zig) and
`TypeTable.memberCount` (src/ir/types.zig; this also backs the COMPILER-API
`type_field_count` VM reader).
- unify the LLVM backend onto the single source of truth: both
`getOrBuildFieldNameArray` (reflection.zig) and `emitFieldNameGet`'s GEP sizing
(ops.zig) now derive from `memberCount` / `memberName`, so the name-array
length and the GEP array type can never diverge again — for any kind. A member
with no name (positional-tuple / array / vector element) reflects as "" (one
slot per member, always in-bounds); named-tuple elements recover their labels.
The array/vector clone was surfaced by adversarial review of the tuple-only fix.
Regression: examples/comptime/0646-comptime-field-reflect-tuple-array.sx exercises
field_count/field_name/field_type over struct, enum, positional + named tuple,
array, and vector. Full suite green (818/0). Unblocks the `race` synthesis, which
must reflect a named tuple's labels + element types.
The body-local #run fold in emitCall was effectively dead (gated on
args.len==0, but the __ct comptime wrapper always carries the implicit
*Context arg), so every body-local #run fell through to a RUNTIME call:
bridgeable shapes lucked into the right value; an unbridgeable shape
(e.g. [2][]i64) ran over --- storage -> garbage, exit 0, no diagnostic.
Fold any is_comptime callee (gated !enclosing.is_comptime so nested
metatype calls in a comptime wrapper's dead body aren't folded). On a
tryEval bail, distinguish a BRIDGE bail (result can't regToValue-
materialize -> error: comptime init of 'X' failed: <reason> +
comptime_failed, build fails, symmetric with the global #run path) from
an EXECUTION bail (VM can't run the body, e.g. NaN/extern -> runtime
fallthrough, preserving types/0150), via comptime_vm.last_bail_was_bridge
(reset at tryEval entry, set only at regToValue). The const name is
threaded onto the wrapper (comptime_display_name) so the diagnostic reads
the source name, not __ct_N.
Regressions: diagnostics/1204 (negative), comptime/0645 (positive).
Verified by 3 adversarial reviews, suite 801/0.
(C) regToValue (comptime_vm.zig) gained no array arm, so a #run returning
an aggregate containing an array bailed 'reg->value: aggregate shape not
bridged yet'. Add an .array arm: read N elements at stride
typeSizeBytes(elem) from the array address, bridge each recursively via
regToValue -> an .aggregate Value (serializeAggregateValue already emits
arrays). Composes with struct fields, nested arrays, array-of-structs,
and the ?Arr optional payload; unbridgeable elements bail loudly.
(E) A global failing #run proceeded into LLVM emission and panicked
'unresolved type reached LLVM emission' when the unresolved const was
used. Add 'if (self.comptime_failed) return;' in emit() after Pass 0 so
it aborts cleanly (exit 1, the comptime diagnostic) across run/ir/build.
Regression: examples/comptime/0644-comptime-run-array-aggregate.sx.
Verified by 3 adversarial reviews, suite 793/0. Filed separate bugs found
during review: 0181 (optional-chain ?. to array field + index panics),
0182 (body-local #run unbridged silently miscompiles).
Add an .optional arm to regToValue in comptime_vm.zig: read the
has_value flag at offset sizeof(child), bridge the payload recursively
into a { payload, i1=true } aggregate when set, yield .null_val (zero
{T,i1}) when clear or the bare null sentinel. Matching serialize arm in
serializeAggregateValue (emit_llvm.zig). Pointer/?Closure/?Protocol-child
optionals and array-payload aggregates bail loudly, not silently.
Regression: examples/comptime/0643-comptime-run-optional-aggregate.sx
(present ?T, present ?i64, null ?i64). Verified by 3 adversarial reviews.
Move examples/*.sx and their expected/ snapshots into per-category
subfolders (examples/<category>/...). Folder = leading filename token,
with ffi-objc/ffi-jni kept whole; filenames are unchanged. The corpus
runner and LSP sweep now discover each category's expected/ dir, while
issues/ stays flat. Example 1058's repo-root-relative companion import
is made file-relative. Path strings embedded in 164 snapshots were
regenerated (path-only changes). Test-layout docs in CLAUDE.md updated.