mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-02 08:35:07 +08:00
- 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
28 lines
830 B
TypeScript
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);
|