fix(stdlib/E6a): adopt forward struct stub for recursive enum/union (E6A-1)

attempt-1's per-decl enum/union register path panicked on any valid
self- or mutually-referential top-level enum/union: a `*Name` field in
the body is resolved through the stateless `type_resolver.resolveNamed`,
which has no kind context and forward-stubs an as-yet-unregistered name
as a STRUCT. `internNamedTypeDecl` then `findByName`-adopted that struct
stub and called `updatePreservingKey`, whose kind-stability assert tripped
on struct -> enum/union (types.zig:446). The corpus had no recursive
enum/union, so the gate missed it.

Fix: when the slot `findByName` returns is a wrong-kind forward struct
placeholder (empty-fields struct) for an enum/union/tagged_union
registration, re-key it in place (`replaceKeyedInfo`) under the same
TypeId instead of `updatePreservingKey`. This mirrors how a self-ref
struct adopts its own (same-kind) forward stub; the new helper
`adoptsForwardStructStub` gates the re-key precisely to that case, so a
struct adopting a struct stub and every non-recursive enum/union stay on
the byte-identical `updatePreservingKey`/fresh-intern path.

Regression 0799 (single-author): self-ref union linked cells
(`next: *Node`), self-ref enum/tagged-union (`branch: *Tree`), and a
mutual-ref pair (A holds *B, B holds *A); builds and walks each recursive
link. Fail-before: panic at registerUnionDecl on eed2f99. Pass-after:
exit 0, "union=7 enum=42 mutual=99".

Gate: zig build && zig build test && run_examples.sh all exit 0
(538 passed, 0 failed; 0795-0798 + 0752-0794 + FFI byte-identical);
m3te ios-sim build via the main binary exit 0.
This commit is contained in:
agra
2026-06-08 23:55:46 +03:00
parent eed2f99f76
commit b9a67d1042
5 changed files with 73 additions and 1 deletions

View File

@@ -0,0 +1,45 @@
// E6a (attempt-2) regression — RECURSIVE top-level enum/union via per-decl nominal
// identity. Three single-author shapes that reference a not-yet-interned name in a
// `*Name` field:
// * `Node` — a SELF-referential UNION (linked cells: `next: *Node`).
// * `Tree` — a SELF-referential ENUM/tagged-union (`branch: *Tree`).
// * `A`/`B` — a MUTUALLY-referential union pair (`A` holds `*B`, `B` holds `*A`).
//
// Pre-fix (eed2f99) the new per-decl register path built each enum/union body
// through the STATELESS `type_bridge` BEFORE a matching nominal slot existed, so a
// `*Name` field forward-created a STRUCT stub under `Name`; `internNamedTypeDecl`
// then refreshed that struct stub as an enum/union and tripped the kind-stability
// assert in `types.zig` `updatePreservingKey` — a hard panic (the corpus had no
// recursive enum/union, so the gate missed it). The fix adopts the forward struct
// stub IN PLACE (re-key to the real enum/union kind), mirroring how a self-ref
// struct adopts its own forward stub — so `*Node`/`*Tree`/`*B`/`*A` resolve to the
// genuine 8-byte-pointer nominal types and the recursive walks read through.
#import "modules/std.sx";
Node :: union { next: *Node; value: s32; }
Tree :: enum { leaf: s32; branch: *Tree; }
A :: union { b: *B; tag: s32; }
B :: union { a: *A; val: s32; }
main :: () -> s32 {
// Self-ref union: two-hop walk to the tail cell's value.
n2 : Node = ---;
n2.value = 7;
n1 : Node = ---;
n1.next = @n2;
n0 : Node = ---;
n0.next = @n1;
// Self-ref enum: a branch whose payload pointer derefs to a leaf.
leaf_node : Tree = .leaf(42);
root : Tree = .branch(@leaf_node);
// Mutual-ref pair: reach B's value through A's `*B`.
bv : B = ---;
bv.val = 99;
av : A = ---;
av.b = @bv;
print("union={} enum={} mutual={}\n", n0.next.*.next.*.value, root.branch.*.leaf, av.b.*.val);
0
}

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@
union=7 enum=42 mutual=99