Files
scriptc/tests/corpus/2484-json-stringify-circular.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

61 lines
1.9 KiB
TypeScript

// JSON.stringify over recursive record types: acyclic values serialize
// exactly; CYCLIC values throw V8's exact "Converting circular structure
// to JSON" TypeError, message byte for byte (starting object, hop lines,
// the ellipsis rule, the closing edge).
interface TreeNode { label: string; children: TreeNode[] }
const root: TreeNode = { label: "root", children: [{ label: "kid", children: [] }] };
console.log(JSON.stringify(root));
function boom(v: TreeNode): void {
try {
JSON.stringify(v);
console.log("no throw");
} catch (e) {
if (e instanceof TypeError) {
console.log("TypeError");
console.log(e.message);
}
}
}
// The canonical parent<->child cycle: record -> array -> record closes.
const parent: TreeNode = { label: "p", children: [] };
const child: TreeNode = { label: "c", children: [] };
parent.children.push(child);
child.children.push(parent);
boom(parent);
// Self-loop through the array.
const solo: TreeNode = { label: "solo", children: [] };
solo.children.push(solo);
boom(solo);
// A long chain (the ellipsis path: more than three hops elide the middle).
interface ListNode { next: ListNode | null }
const head: ListNode = { next: null };
let cur = head;
for (let i = 0; i < 6; i++) {
const n: ListNode = { next: null };
cur.next = n;
cur = n;
}
cur.next = head;
try {
JSON.stringify(head);
} catch (e) {
if (e instanceof TypeError) console.log(e.message);
}
// Not-at-root detection: the cycle sits below an acyclic prefix.
const wrapper: TreeNode = { label: "w", children: [parent] };
boom(wrapper);
// A DAG is NOT a cycle: the shared subtree serializes twice, like Node.
const shared: TreeNode = { label: "s", children: [] };
const dag: TreeNode = { label: "d", children: [shared, shared] };
console.log(JSON.stringify(dag));
// Stringify still works after a circular throw (the buffer resets).
console.log(JSON.stringify(root, null, 2));