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:
@@ -15195,11 +15195,35 @@ pub const Lowering = struct {
|
||||
(table.findByName(name_id) orelse table.internNominal(info, 0))
|
||||
else
|
||||
table.internNominal(info, nominal_id);
|
||||
table.updatePreservingKey(id, stampNominalId(info, nominal_id));
|
||||
const stamped = stampNominalId(info, nominal_id);
|
||||
// A self / mutual `*Name` field in an enum/union body forward-creates a
|
||||
// STRUCT placeholder under `Name` (the stateless resolver has no kind
|
||||
// context — `type_resolver.resolveNamed` always stubs a struct), which the
|
||||
// `findByName` above then returns. Adopting a wrong-kind stub needs a
|
||||
// re-key, NOT the in-place `updatePreservingKey` body-fill — whose
|
||||
// kind-stability assert trips on struct→enum/union.
|
||||
if (adoptsForwardStructStub(table.get(id), stamped))
|
||||
table.replaceKeyedInfo(id, stamped)
|
||||
else
|
||||
table.updatePreservingKey(id, stamped);
|
||||
table.type_decl_tids.put(decl_key, id) catch {};
|
||||
return id;
|
||||
}
|
||||
|
||||
/// TRUE when `existing` is a forward-reference STRUCT placeholder (empty
|
||||
/// fields — the stateless resolver's stub for an as-yet-unregistered name) and
|
||||
/// `incoming` is a NON-struct nominal (enum / union / tagged_union): the one
|
||||
/// case where `internNamedTypeDecl` must re-key the slot rather than fill its
|
||||
/// body in place. A struct adopting its own struct stub is same-kind and stays
|
||||
/// on `updatePreservingKey`; a fresh-interned slot has no stub to adopt.
|
||||
fn adoptsForwardStructStub(existing: types.TypeInfo, incoming: types.TypeInfo) bool {
|
||||
if (existing != .@"struct" or existing.@"struct".fields.len != 0) return false;
|
||||
return switch (incoming) {
|
||||
.@"enum", .@"union", .tagged_union => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// The `nominal_id` to register a NAMED type author of `name_id` under. 0
|
||||
/// unless `name_id` is authored as a named type by ≥2 distinct modules (a real
|
||||
/// same-name shadow per the import facts): the FIRST source to register keeps
|
||||
|
||||
Reference in New Issue
Block a user