The spec's §8 migration table, applied:
- 00-principles.md P3: "@gc is a per-class opt-in, reference-counted" ->
GC-ness is inferred; incremental per-shard mark-sweep in budgeted slices;
still no global pause by construction.
- OOP spec: decision-table GC row -> inferred (hybrid rule named); §3 rule 5
-> traced classes alias freely, which classes is inferred; §4 memory model
-> the RC + Bacon-Rajan paragraph replaced by tracing (snapshot roots,
Yuasa barrier, born-black, budgeted slices); header rc comment -> union'd
sweep link; mixing rule restated for tracing.
- 00-wob-format.md: header says version 4; opcodes 27-28 -> reserved (loader
rejects); the owned-temporary rule's @gc exclusion restated for tracing.
- 08-builtin-surface.md: the push RC_INC special case and the set(m,k,v)
retention gap DELETED — neither exists without RC; the corpus cycle is
collected by tracing.
- story 07b: status -> LANDED 2026-08-18 (with the historical note kept);
board: 7b row ✅ (supersedes iteration 2's RC memory model), pending row
removed.
- gc-cycle README: Phase 3 flipped to landed (the ring runs, is reclaimed,
ASan-clean; the ?Node RC_DEC-on-nil trap no longer exists); the barrier
prose corrected to the as-built design (snapshot-at-beginning + deletion
barrier + born-black, not per-slice root re-reads).
- plan 2026-08-18: all checkboxes ticked + a completion banner recording the
four deviations from the plan as written.
(Error catalog was already amended with the keyword-removal commit: WO-E104
added, WO-W201 retired.)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`@gc` is no longer part of the language: a developer never writes or mentions
it. GC-ness is decided entirely by inference (structural cycles + demand
promotion), which the earlier 7b commits made complete and precise.
- parser: `@gc` on a class is now WO-E104 ("GC-ness is inferred; run
`woc --dump-gc`. Remove it."). `is_gc` stays false; the class classifies by
inference. No `.wo` in the repo carries `@gc` anymore.
- types.ml: retired the WO-W201 machinery (suggest_gc_annotation,
has_recursive_structure(_type), has_unique_field, gc_suggestion_code) — it
suggested `@gc`, now obsolete since inference traces exactly those classes.
- runner.ml: deleted the 8 WO-W201 gc-suggestion test blocks; the @gc-exemption
test's `Cache` is made self-referential so inference classifies it gc without
an annotation.
- fixtures: dropped `@gc` from rc.wo (Cache demand-promotes via its escape),
elision.wo (Cache given a self-ref to stay structurally gc for the rc-elision
dump), pricing-demo.wo (PriceCache doesn't escape -> now owned), and the
abandoned-cycle/budget-steps corpus (Node is structurally gc). rc.wo keeps a
placeholder comment line so its line-indexed rc assertions hold. Goldens
re-blessed.
- docs: error catalog gains WO-E104 and marks WO-W201 retired; gc-cycle README
records the keyword removal.
Verified: woc-test 553/0 (was 566 minus the 13 retired WO-W201 checks),
test_diag 14/0, oop-e2e 79/0, employee 8/0, log-watcher 7/0. `git grep '@gc'`
finds only comments — success criterion 1 met.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Structural inference (2a) covers cyclic classes; this adds the DEMAND half for
the acyclic-but-aliased case, so @gc is now redundant everywhere.
- owner.ml: a `promote` sink on ctx. In collect mode, a class value that would
raise WO-E304 (escape) records its class instead of erroring — because a
class that MUST escape cannot be owned (second-class borrows can't be stored
or returned), so it must be traced. `class_of_ty` extracts the class from the
escaping local's type. analyze/analyze_fn take ?promote.
- main.ml typecheck_all: after structural injection, a fixpoint runs ownership
in collect mode over every file, unioning promotions into syms.traced (and
every module table), before owner/emit see it. Terminates (promotions only
grow, bounded by class count).
- gcinfer.render_final: --dump-gc now reads the authoritative is_gc_class
(structural + demand + the remaining @gc bridge), with the reason.
Effect: a class that escapes is inferred `gc` with no annotation — e.g. `Cache`
(rc.wo sans @gc) shows `gc (alias escape (demand))`; `Box` returned out of
`leak` is promoted and the program is valid. No false positives: employee's
Department/Employee stay owned; the 566 goldens + 14 test_diag unchanged.
Corpus: compile-fail/borrow-escape-return reclassified to run/ (prints 1) —
returning a borrowed class is now legal under demand promotion; the fixture
encoded pre-7b behavior.
Verified: woc-test 566/0 + test_diag 14/0; oop-e2e 79/0; employee 8/0;
log-watcher 7/0.
NOT in this slice: removing the `@gc` KEYWORD (parser rejection + rewriting the
RC/@gc golden + test_diag assertions + moving the inference injection into the
library so unit tests see it) — coupled to Phase 3, which deletes the RC
machinery those tests cover. The ring still needs Phase 3 to RUN (nullable
`?Node` gcref path).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
GC-ness now comes from the inference pass, not only the annotation. A class is
traced if the structural SCC put it in `syms.traced`, OR (temporary bridge
until demand-promotion lands) it still carries `@gc`.
- Types.symbols gains a `traced : StringSet.t`; is_gc_class reads it (union'd
with the surviving @gc annotation). All symbols literals + both merges carry
the field.
- typecheck_all injects the classification once (Gcinfer.classify -> traced)
into the merged table AND every module table, before typecheck/owner/emit.
- emit.ml routes the class gc-flag and the union/drop decision through
is_gc_class instead of the raw `.is_gc`, so structurally-inferred gc classes
get the runtime flag. Field-kind derivation already routed through is_gc_class.
- gcinfer.traced_names exposes the traced set for injection.
Effect: docs/examples/gc-cycle now COMPILES with no annotation (the WO-E301
use-after-move at the ring-closing store is gone) — traced classes alias
freely. Bytecode is byte-identical to writing `@gc class Node`.
Verified: woc-test 566/0 (goldens unchanged — every current @gc class stays gc
via the annotation branch, and no golden has a structural-gc-non-annotated
class); oop-e2e 79/0 (gc corpus green).
Not in this slice: demand-promotion (the acyclic-aliased PriceCache case still
needs the @gc bridge) and @gc-in-source-as-error (Phase 2b); the ring RUNNING
(the RC runtime doesn't implement nullable-gcref `?Node` fields — Phase 3).
WO-W201 still fires on gc-cycle (retired in Phase 4).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Design-first deliverable for iteration 7b (no runtime/compiler code yet).
docs/examples/gc-cycle explains, by example, how pointers flow through the heap
and the collector's mark-sweep logic:
- types.wo: Node (self-referential ?Node -> inferred `gc`/traced) vs Segment
(acyclic -> `owned`, deterministically dropped)
- main.wo: ring_demo builds a->b->c->a and abandons it; owned_demo shows the
drop path with no collector
- README.md: the model (ownership frees the 99%, tracing only the cyclic/
aliased residue, inference decides), the 16-byte header rewrite (retire
rc+borrow -> 8-byte sweep-list link, colors in flag bits), where a traced
pointer lives (root via pc gc-mask / GCREF field / container), and the
tri-color incremental algorithm with the Yuasa deletion barrier. Two mermaid
step diagrams (heap+roots, collector cycle) + the owned contrast.
Grounded in the approved spec (2026-08-11-inferred-gc-mark-sweep-design.md) and
the real runtime structures (obj.h/wob.h: wo_hdr, WO_K_GCREF, arena, wo_obj_size).
Run status: honest — the sample does NOT build today; woc reports WO-E301
(use-after-move at the ring-closing store), which is exactly the aliasing that
"traced classes alias freely" unblocks under 7b. README records this.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>