The cycle analysis gave a header to every type that could reach a cycle,
and its construction-only facts gave up on a whole hierarchy as soon as a
constructor let `this` escape or a type could cross into checked-dynamic
code. Four refinements, all derived from the final IR:
- A constructor's stores stay construction-only up to the first top-level
statement in which `this` escapes. Statements run in order, so earlier
stores complete while the object is private; stores from the escaping
statement on, and every store of a constructor whose ancestor let `this`
escape, are writes. Compound-assignment receiver temporaries count as
`this`. An all-immutable cycle is still impossible: each immutable edge
a -> b is stored before a escapes while b has escaped already.
- Crossing into checked-dynamic code only makes types mutable when the
program contains a runtime store into a dynamic value (int32-slots'
audit, now shared as dynamicFieldStores): typed-ref capsules write native
fields only through such a commit. Without one, hidden property bags
stay empty and add no edges. FFI operands and intrinsics other than
publish stay conservative.
- A type is traced iff it lies in a cycle-capable component or reaches an
intrinsic reference. A type that merely holds a cycle cannot be reached
back from it: its references are external counts.
- Reachability is per class: a slot of static type C reaches C's subtree,
not the whole hierarchy; header presence stays uniform per hierarchy.
tsc-ts (e469e33): traced classes 603 -> 469 of 700, records 64 -> 10 of
202, unions 344 -> 93 of 482; scr_cyc_alloc sites 659 -> 471. Mutable
hierarchy units 243 -> 4. Collector candidates on the self-check
(single-threaded) 6.02M -> 5.55M.