mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-04 08:04:37 +08:00
A reference edge that can only be stored while its owner is still under construction points to an object that existed before the owner, so a heap cycle made only of such edges is impossible. computeTraced now builds the shape/union graph with per-edge mutability, finds strongly connected components, and marks a component cycle-capable only if it holds an intrinsic reference (closure, dyn, class value, caught, promise) or a mutable internal edge; units that reach such a component stay traced. With every edge mutable this is exactly the previous fixpoint. Mutability comes from the final IR (cycle-mutability.ts), not from the TS readonly modifier: every fieldSet outside a constructor's own this, fieldIncDec, recordSet, recordKeySet and recordKeyDelete counts; a constructor where this escapes (anything but a field store target, a field read, or the receiver of the direct base constructor call, including closure/class captures) makes its hierarchy mutable, as does any other call of a constructor function; every type that can cross into checked-dynamic code (dynFrom operands including boxed closure signatures, thrown values, library/intrinsic/JSON/FFI arguments) is mutable with its whole type closure, because typed-ref capsules commit dynamic writes into native fields; runtime-rooted hierarchies stay mutable; library builds and dynamic-island programs disable the refinement. Union arms are immutable (boxes are), collection edges are always mutable. New corpus programs pin the acyclic case (DAG sharing, a 200k-node chain, derived constructors, records) and try to defeat the proof (constructor escapes, readonly bypass through casts/mapped types/any/Reflect/aliases, subclass rewrites, readonly arrays, untyped writes through capsules).