Files
scriptc/tests/corpus/2481-mutual-recursive-records.ts
Chris Tate b49052a389 Recursive record types compile statically as named recursive shapes
- mapType back-references mint placeholder shapes/unions finalized at frame exit; identity is per checker type, and context-sensitive (generic/mixin) knots stay fenced
- every type walk survives cyclic shape graphs: coinductive JSON/dyn-safety predicates, guarded formatIrType/jsvalLiftable, cycle headers via the existing collector fixpoint on both backends
- JSON.stringify throws V8's byte-exact circular TypeError (hop lines, ellipsis rule); cyclic values crossing into checked-dynamic slots trap instead of hanging
- console.log/util.inspect render <ref *N>/[Circular *N] exactly (SEMANTICS 129 resolved; depth:null lowers) and deepStrictEqual terminates through Node's pair memo
- corpus 2480-2488 green on C, LLVM, and the sanitized RC-audit lane, which proves child->parent cycles collect
2026-07-23 13:24:13 -05:00

28 lines
830 B
TypeScript

// Mutually recursive record types: the knot passes through TWO shapes and
// an optional-field union (A -> B[] -> a?: A). Both intern as named
// recursive shapes; the optional back edge is an undefined-armed union.
interface A { b: B[] }
interface B { a?: A }
const inner: A = { b: [{}] };
const outer: A = { b: [{ a: inner }, {}] };
function countB(a: A): number {
let n = 0;
for (const b of a.b) {
n += 1;
if (b.a !== undefined) n += countB(b.a);
}
return n;
}
console.log(countB(inner));
console.log(countB(outer));
// The back edge assigns after construction too — a genuine runtime
// reference cycle (collected by the cycle collector; see the RC-audit
// lane). Traversals here stay acyclic on purpose.
const b0: B = {};
const a0: A = { b: [b0] };
b0.a = a0;
console.log(b0.a === a0, a0.b[0] === b0);