Update WALKTHROUGH.md#1
Closed
syzygysolstice wants to merge 2 commits intocuzzo:masterfrom
Closed
Conversation
1a65094 to
bbde1c2
Compare
cuzzo
added a commit
that referenced
this pull request
Apr 4, 2026
test_tcp_charAt_stall.zig: 32 clients x 1000 msgs with charAt O(1) byte parsing across 8 schedulers. Passes 5/5 in both Debug and ReleaseFast. The documented "stall under concurrent TCP" was caused by epoll fd corruption (fixed in 62926ee), not charAt itself. Updated docs/update-bench.md: Bug #2 (String@raw stall) marked RESOLVED. Bug #1 (epoll migration) status updated to reflect the registerFd fix is working. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
Apr 6, 2026
Leak #1 (15 allocs, tests 25/26/27/28/68): Struct arrays/lists with heap-duped string fields had no cleanup. Fixed by adding :array_with_struct_strings kind to CleanupPlan.classify_binding and elem_has_string_fields? helper. Also extended list_with_elem_cleanup to detect struct elements with string fields. Leak #2 (2 allocs, test 103): promoteFields double-duped strings that CopyNode already heap-allocated. Fixed by marking CopyNode fields as handled in PromotionPlan so compute_struct_promote skips them. Added per-field promote via unhandled_promote_fields to avoid blanket promoteFields when some fields are already owned. Runtime fix: checkYield guards on scheduler_running to prevent crash when test harness runs without a fiber scheduler (test 174). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
Apr 11, 2026
test_tcp_charAt_stall.zig: 32 clients x 1000 msgs with charAt O(1) byte parsing across 8 schedulers. Passes 5/5 in both Debug and ReleaseFast. The documented "stall under concurrent TCP" was caused by epoll fd corruption (fixed in 71217ab), not charAt itself. Updated docs/update-bench.md: Bug #2 (String@raw stall) marked RESOLVED. Bug #1 (epoll migration) status updated to reflect the registerFd fix is working. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
Apr 11, 2026
Leak #1 (15 allocs, tests 25/26/27/28/68): Struct arrays/lists with heap-duped string fields had no cleanup. Fixed by adding :array_with_struct_strings kind to CleanupPlan.classify_binding and elem_has_string_fields? helper. Also extended list_with_elem_cleanup to detect struct elements with string fields. Leak #2 (2 allocs, test 103): promoteFields double-duped strings that CopyNode already heap-allocated. Fixed by marking CopyNode fields as handled in PromotionPlan so compute_struct_promote skips them. Added per-field promote via unhandled_promote_fields to avoid blanket promoteFields when some fields are already owned. Runtime fix: checkYield guards on scheduler_running to prevent crash when test harness runs without a fiber scheduler (test 174). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
675b960 to
7e9f0e9
Compare
cuzzo
added a commit
that referenced
this pull request
Apr 23, 2026
Two correctness fixes in the parking-lot / scheduler interaction. #1. Scheduler lock_timeout_ms only fired under specific conditions. Three cases where it silently did nothing: a. Idle scheduler with no sleepers and no pending I/O: io_uring_enter blocks indefinitely on CQEs. lock timeouts never fire. b. Fast-path starvation: on a scheduler that always has ready work, the loop never enters the slow-path `else` branch where the scan lived. Any parked fiber's timeout is permanently shadowed. c. Stale-entry leak: the lazy cleanup of lock_waiters entries whose waiting_for_lock was cleared also only ran in the slow path, so on a busy scheduler the list grew without bound. Fix: extract scanLockWaiters() so it runs on EVERY loop iteration (cheap no-op when empty). In the idle wait path, compute the earliest deadline across sleeping_queue and lock_waiters and arm an io_uring timeout for that deadline so an otherwise-idle scheduler still wakes to fire timeouts on schedule. #2. Cycle detection false-positive via stale waiting_for_lock_owner. When unlock() calls wakeNext() -> submitResume(), the waiter goes onto the ready queue but does not actually run until a later scheduler tick. Its waiting_for_lock_owner field is still the pre-wake pointer. If a second fiber calls lock() on a different lock in between, its detectCycle walk sees the stale chain and can return error.Deadlock for a cycle that no longer exists. Fix: in ParkingMutex.unlock and ParkingRwLock.wakeNext, clear the waiter's wait-state fields (waiting_for_lock, waiting_for_lock_owner, waiting_for_lock_list, lock_waiter_node, lock_counter_ptr) BEFORE submitResume. The post-yield cleanup in lock() becomes idempotent. Tests: - ParkingMutex: wakeNext clears wait-state so detectCycle has no TOCTOU false positive. Deterministic single-scheduler cooperative reproduction of the false-positive: A holds mu_a parked on rv; B parks on mu_a; C parks on mu_b; Main wakes A; A unlocks mu_a (wakes B into ready queue) and immediately tries mu_c. Without the fix, detectCycle walks C -> B -> B.waiting_for_lock_owner == A (stale) and returns Deadlock. Verified to FAIL without the fix. - The event-driven test 14 (write-lock timeout) is now purely event-driven (no wall-clock sleep) because the scheduler wakes on the lock deadline. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
Apr 23, 2026
…pers into state Two changes that significantly close the perf gap to pthread. #1. RwLock writer/reader non-fiber slow paths now use test-then-CAS instead of CAS-spinning. Old version did fetchAdd/cmpxchgWeak in a tight loop; under N-thread contention every iteration bounced the state cache line. New version reads (cache-shared) until the lock looks acquirable, then issues exactly one RMW. #2. ParkingMutex thread_sleepers folded into state as a third bit (HAS_THREAD_SLEEPER). unlock is now a single fetchAnd whose return value tells us whether to wake -- no separate cache-line load for the sleeper count. owner.store(null) on unlock removed (next acquirer overwrites it; re-entrancy detection still works). Bit is sticky: once any thread parks it stays set. Means every subsequent unlock pays a Futex.wake syscall, but the alternative (clearing the bit) creates a lost-wake race when multiple threads are parked. Lock fast path uses load-then-fetchOr (test-then-RMW) for the same reason as #1 -- read-shared cache hits under contention. Updated benchmark (32-thread machine, each cell >= 500ms): Lock | Pattern | Contention | Parking | pthread | Ratio Mutex | (any) | uncontended | ~570ms | ~525ms | 0.86-0.89x Mutex | (any) | heavy | ~5500ms | ~1380ms | 0.23-0.29x (was 0.15) Mutex | (any) | realistic | ~1000ms | ~280ms | 0.20-0.35x (was 0.15) RwLock | read-heavy | uncontended | 434ms | 754ms | 1.74x WIN RwLock | read-heavy | heavy | 2390ms | 4537ms | 1.90x WIN RwLock | read-heavy | realistic | 500ms | 881ms | 1.76x WIN RwLock | write-heavy | uncontended | 561ms | 739ms | 1.32x WIN (was 1.43) RwLock | write-heavy | heavy | 5827ms | 5636ms | 0.97x TIE (was 0.45 -- 2.1x improvement!) RwLock | write-heavy | realistic | 1134ms | 1162ms | 1.03x TIE (was 0.46 -- 2.2x improvement!) RwLock | mixed | uncontended | 556ms | 1021ms | 1.84x WIN RwLock | mixed | heavy | 5565ms | 38419ms | 6.90x WIN RwLock | mixed | realistic | 958ms | 8477ms | 8.85x WIN RwLock now beats or ties pthread across all 9 cells. Mutex still 4-5x slower under heavy contention; closing that gap further requires packing the owner pointer into state (loses cycle-detection chain) or inline-asm-level optimization to match glibc's hand-tuned mutex. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
Apr 23, 2026
Four coverage additions against the lock-safety Ruby-side surface, plus a user-requested mixed-capability + sort test. Gap #1 — error_registry unit spec (spec/error_registry_spec.rb, 22 examples). ERROR_KINDS is exactly the 6 documented kinds, frozen. ERROR_TYPES maps LockTimeout/LockCycle -> Transient, Deadlock -> System, each with the right Zig spelling. Direct coverage on error_kind?, error_type?, kind_of_type, zig_name_of_type, types_for_kind, and consistency invariants (every type's kind is a registered kind; types_for_kind is the inverse of kind_of_type; every zig_name is non-empty). Gap #2 — error_kind / error_type stamping spec (new describe block in spec/capabilities_spec.rb). Monkey-patches writeFile's stdlib entry for the duration of each example and verifies the annotator propagates :error_kind and :error_type to the AST call node. Also verifies the fields stay nil when the entry has no metadata. Before this spec, the plumbing (added with Phase 2 registry work) had no direct test — a silent regression would have been invisible. Gap #4 — runtime contention transpile-tests. - 267_retry_resolves.cht: holder sleeps 150ms (past one 100ms timeout window, within a second). Waiter uses RETRY(3) THEN PASS. The runtime LOCK TIMEOUT line fires on the first attempt; the retry acquires cleanly and the marker + counter end up in the expected state. - 268_multi_lock_sort_forced_contention.cht: two BG fibers hold their overlapping pair of locks for 50ms in OPPOSITE source orders. Without sort this is the classic AB/BA LockCycle setup; with sort both fibers resolve the same global pointer order, serialize, and complete. - Sub-item (b) — RAISE bubbling through a BG ~!Void / ~!Int64 promise — hit a linear-resource tracker interaction that needs a separate fix to express cleanly (linear promise "not consumed" when the CATCH-via-RAISE shape tries to observe the error). Deferring; the RAISE codegen itself is structurally covered by the Phase 2 annotator specs and by transpile-test 262. Gap #5 — rank inconsistency anchors at the second declaration, not the first. New spec asserts the error message includes "(Line 3)" (second decl) and NOT "(Line 2)" (first decl), and that both ranks appear so the programmer can see the conflict. User request — transpile-tests/270_mixed_capabilities_with_sort.cht: a single WITH acquires EXCLUSIVE a + RESTRICT r + BORROWED b + EXCLUSIVE c in one comma-separated list, then again with the EXCLUSIVE pair reversed. Verifies the multi-lock sort coexists cleanly with non-fallible (RESTRICT / BORROWED) captures, both orderings compile + run, aliases are usable in the body, and the sorted guard-var names don't collide with RESTRICT / BORROWED aliases. 2461 Ruby specs + 329 transpile-tests (zero memory leaks) all pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
Apr 28, 2026
Introduces a single fail-closed classifier that replaces (eventually) the four disjoint capture-handling paths in mir_lowering.lower_bg_block (pointer_captures, resource_captures, promoted_names, per-type forks) with one exhaustive dispatch. Five strategies, each dictating its own ctx-field type, ctx-init RHS, and required MIR markers: - ByValue : primitives, strings — byte copy, no markers - RcClone : @multiowned/@shared — Rc/Arc discipline handles it - FreshHeapCopy : user wrote COPY x — new AllocMark in fiber body - MoveInto : user wrote GIVE x — MoveMark on source - Refuse : heap-backed/borrow without transfer — lowering error Closes the unsafe default that let Bug #3 (slice-borrow-into-async-BG) slip through: anything not explicitly safe classifies as Refuse rather than silently falling through to byte-copy-the-header. MIRChecker stays on its seven invariants; no new invariant is needed. Existing INV #1 (leak) and INV #2 (orphan cleanup) fire naturally once the lowering emits the strategy's marker plan. This commit is Step 1 of the migration plan in docs/agents/vm-bugs.md: the classifier is pure analysis with 26 specs; it is NOT yet called from lower_bg_block. Step 2 wires it in behind an unchanged behavior path; Steps 3-5 migrate categories and delete the dead piecewise code. Also ships: - docs/agents/vm-bugs.md — bug catalog + gap analysis - spec/vm_bg_capture_bugs_spec.rb — live regression for Bug #1 and Bug #3 (UAF pattern through plain slice + through union-variant slice) - Deletion of stale spec/minivm_bytecode_compiler_spec.rb (referenced a file removed months ago; blocked running full spec/ without --exclude-pattern). 2548/2548 Ruby specs pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
Apr 28, 2026
…ep 3) Closes the dangling-pointer family of bugs (docs/agents/vm-bugs.md): any BG block that captures a heap-backed / borrow-like value without an explicit ownership transfer (GIVE / MOVE / COPY / CLONE inside the body) is now refused at lowering time with a CLEAR-level diagnostic. Key design point (per user directive): "same rules as everything else — do not implement a new system just for BG". The enforcement reads AST::MoveNode / CopyNode / CloneNode already in the BG body to populate CaptureStrategy::CaptureSiteInfo. No parallel analysis, no new marker nodes, no new MIRChecker invariant. Strategies recognized: ByValue : primitives + strings (POD). RcClone : @multiowned, @shared, @locked, @writeLocked, @Local, @Sharded, @striped — ref-counted across fibers. MoveInto : GIVE/MOVE in body, OR existing resource_captures (File, TCPClient, ...). FreshHeapCopy : COPY/CLONE in body. Refuse : everything else → compile error with actionable diagnostic explaining how to transfer ownership. Example (previously silent UAF, now a compile error): FN runit() RETURNS Int64 -> MUTABLE lst: Int64[]@list = List[]; lst.append(1_i64); slice: Int64[] = lst; p: ~Int64 = BG { work(slice); }; -- ← now rejected at compile RETURN NEXT p; END Diagnostic: BG block captures values that cannot safely cross the fiber boundary: - 'slice' is a slice borrow — COPY inside the BG body for a fresh heap copy. (See docs/agents/vm-bugs.md for the ownership rules.) Spec changes: - spec/vm_bg_capture_bugs_spec.rb: flipped the dangling-pointer asserts from "currently compiles + UAFs" to "refuses at compile time". - transpile-tests/273_bg_slice_capture.cht removed: its scenario is now covered by the spec (Bug #1 regression using COPY); the original bare-capture form is now correctly refused. Known limitations (escape hatches need downstream lowering work; filed as follow-up in docs/agents/vm-bugs.md): - COPY x inside BG body classifies correctly (FreshHeapCopy) but the AllocMark/Cleanup pair isn't emitted inside the fiber's scope yet, so the heap copy leaks. Tests that need this pattern should wait. - GIVE x inside BG body classifies correctly (MoveInto) but the Zig codegen still trips on "mutable not accessible from here" for @list sources. Same follow-up. Both escape hatches PARSE and CLASSIFY correctly; only the emission paths for them need Step 4/5 of the migration plan. The CLEAR-level diagnostic directs users at the right syntax regardless. 2549/2549 specs (1 pending), 333/333 transpile-tests, zero memory leaks. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 7, 2026
Self-host prep tracker task #10 (P1, Sorbet/RBS gradual typing). Adds: - Gemfile gems: sorbet, sorbet-runtime, tapioca (development group) - sorbet/config: --dir . with vendor/, zig*/, spec/, transpile-tests/, examples/, benchmarks/, tmp/, docs/ ignored - sorbet/rbi/clear-stubs.rbi: hand-written Token stub to silence the one default-typed-false name-resolution false positive in parser.rb:63 (Sorbet suggested Socket) - .gitignore allow-list entry for /sorbet/ - docs/agents/sorbet-string-symbol-workflow.md: how Sorbet drives the String/Symbol normalization sweep (tracker task #1) -- declare T.any(String, Symbol) on a method, tighten to one type, run `srb tc`, fix every flagged caller Status: $ bundle exec srb tc No errors! Great job. Why this lands earlier than its dependency order in the tracker suggested: Sorbet is the *driver* for tasks #1, #2, #5, #9, #13, not a follow-up to them. Installing it at default `# typed: false` does nothing on its own (no enforcement, no perf cost), but unlocks the type-narrowing workflow needed to land String/Symbol normalization without grep-and-pray. Files flip to `# typed: true` then `# typed: strict` per the per-file gate (task #20) as cleanup lands. Known incompat: tapioca 0.19 + Ruby 3.2 + sorbet-runtime 0.6.13196 crashes on `tapioca init` in `coerce_and_check_module_types`. Doesn't block the static checker; hand-written stubs cover the small surface area while we're at default typed:false. Workflow doc tracks the upstream fix. Verified: bundle exec prspec spec/ 3599 examples, 0 failures ./clear test transpile-tests/ 432 passed, 0 leaks bundle exec srb tc No errors
cuzzo
added a commit
that referenced
this pull request
May 7, 2026
…e_analysis
`fn_nodes[callee_name] || fn_nodes[callee_name.to_sym]` at
escape_analysis.rb:300 was defensive against a Symbol key that is
never written. Verified by tracing every fn_nodes write site:
- compiler_frontend.rb:45 fn_nodes[s.name] (s.name is String)
- compiler_frontend.rb:141 fn_nodes[synth_name] (synth_name = "__test_body_#{counter}")
- importer.rb:83 fn_nodes[s.name] (String)
- transpiler.rb:106 result.fn_nodes["main"] (String literal)
All writes use Strings. The `.to_sym` fallback never matched, so
dropping it has no behavioural effect on any existing path. Verified
on transpiler-cleanup branch with the standard suite:
bundle exec prspec spec/ 3599 examples, 0 failures
./clear test transpile-tests/ 432 passed, 0 leaks
Self-host prep tracker task #1 -- this is one of the easier wins
under the broader String/Symbol normalization sweep. The deeper
.to_sym sites in importer.rb / compiler_frontend.rb / mir_lowering.rb
schema-map writes (`schemas[stmt.name.to_sym] = ...`) are NOT dead --
they are real type bridges: stmt.name is a String (from the parser
storing Token.value, where VAR_ID/TYPE_ID values are `@s.matched`
strings), but the schema maps are Symbol-keyed because Type#resolved
returns a Symbol via `ft.to_sym`. Removing those `.to_sym` calls
requires picking a project-wide canonical type for identifiers --
either flow Strings to the schema maps (change Type#resolved to
return String, change ~30 reader sites) or flow Symbols up from the
parser (change lexer Token.value typing for VAR_ID/TYPE_ID, audit
~372 .name reads). That decision is captured at task #1 for the
follow-up commit.
cuzzo
added a commit
that referenced
this pull request
May 7, 2026
`# typed: true` on importer.rb couldn't see Struct attr_accessors like `AST::FunctionDef#needs_rt` and `#effects` because those are declared inside `Struct.new(...) do ... attr_accessor ... end` blocks that Sorbet's name resolver doesn't trace through. tools/gen_attr_rbi.rb walks src/**/*.rb with Prism, finds attr_accessor declarations inside Struct.new blocks and class bodies, and emits an RBI with T.untyped reader+writer sigs. Regenerable. Output (sorbet/rbi/clear-attr-accessors.rbi): 91 classes, 404 attr_* declarations. Also ignore tools/ in sorbet/config (it's a generator, not src) and allow tools/ through the root .gitignore allow-list. Verified: srb tc clean, prspec 3599/0, transpile-tests 432/0 leaks. Unblocks tasks #1 (String/Symbol normalization) and #10 (Sorbet gradual typing rollout) which both depend on the RBI shim. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 7, 2026
First step of folding hash-as-struct schemas into real types. Enum
schemas were `{ kind: :enum, variants: Set, visibility: Symbol }` and
dispatched everywhere via `schema.is_a?(Hash) && schema[:kind] == :enum`.
Now `Schemas::EnumSchema = Data.define(:variants, :visibility)`, dispatched
via `schema.is_a?(Schemas::EnumSchema)`. Migration handles the still-Hash
union/struct/resource schemas at sites that test for both (e.g.
`is_a?(EnumSchema) || (is_a?(Hash) && schema[:kind] == :union)`).
Subsumes part of the String/Symbol normalization (TODO #1, now folded
into #2): the schema was the carrier of mixed Symbol-metadata and
String-field-name hash keys.
Verified: srb tc clean, prspec 3599/0, transpile-tests 432/0 leaks.
Next steps: UnionSchema, StructSchema, ResourceSchema (the entangled
ones that share `:type_params`, `:extern_module`, `:as_type` carriers).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 7, 2026
Final step of the schema extraction. Adds
`Schemas::StructSchema = Data.define(:fields, :field_defaults,
:borrowed_fields, :type_params, :methods, :visibility, :extern_module,
:as_type)` and converts every struct-schema dispatch site:
- `schema.is_a?(Hash) && !schema[:kind]` -> `is_a?(StructSchema)`
- `schema.keys.reject { |k| k.is_a?(Symbol) }` -> `schema.fields.keys`
- `schema[fname]` (field type) -> `schema.fields[fname]`
- `schema.each` over mixed metadata+field keys -> `schema.fields.each`
- `schema[:field_defaults]`/`[:borrowed_fields]`/`[:methods]`/etc.
-> typed accessors
Also extends `ResourceSchema` to carry fields/type_params/extern_module/
as_type so `EXTERN STRUCT ... CLOSE` forms produce ResourceSchema
directly (eliminating the last `[:kind] == :resource` Hash dispatch).
Construction sites converted: visit_StructDef, visit_ExternStructDecl
(both struct and resource branches), built-in :Range, synthetic inline-
struct variant types, pipeline join types, and parallel `@struct_schemas
+ @union_schemas` registries in mir_lowering.rb and importer.rb.
Subsumes the String/Symbol normalization win from the original task #1:
the schema was the carrier of mixed Symbol-metadata + String-field-name
keys, and the `k.is_a?(Symbol)` filters disappear once `fields` is its
own typed Hash[String, Type].
Verified: srb tc clean, prspec 3599/0, transpile-tests 432/0 leaks
(one pre-existing failure on 346, unrelated).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 7, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 7, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 7, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 7, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 7, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 7, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 7, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 7, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 7, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 7, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 7, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 7, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 7, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 7, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 8, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 8, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 8, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 8, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 8, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 8, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 8, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 8, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo
added a commit
that referenced
this pull request
May 8, 2026
…ts, respond_to? cleanup
Squashed from 21 incremental commits. Original history preserved at tag
`transpiler-cleanup-original`. Each piece below was independently green
on srb tc / prspec (3598/0) / transpile-tests (432/0 leaks).
Hash-as-struct schemas → typed Data classes in src/ast/schemas.rb:
- `EnumSchema(variants, visibility)`
- `ResourceSchema(close_zig, static_methods, fields, type_params, ...)`
- `UnionSchema(variants, type_params, visibility)`
- `StructSchema(fields, field_defaults, borrowed_fields, type_params,
methods, visibility, extern_module, as_type)`
Eliminates 60+ `schema.is_a?(Hash) && schema[:kind] == :X` dispatches
across annotator, MIR pipeline, and tools. Fields cleanly separated
from metadata; the `schema.keys.reject { |k| k.is_a?(Symbol) }` pattern
goes away (subsumes the original String/Symbol normalization).
Parallel registries in `mir_lowering.rb` (`@struct_schemas`,
`@union_schemas`) and `importer.rb` carry typed values too.
- `Formatter::Emitter::FnSig(toks, start, arrow_idx, po, pc)` — 5
methods that took the same arg cluster.
- `PipelineHost::PipelineSite(list, options)` — 24 lower_X methods in
PipelineHost; clump dropped from 13 methods.
- `MIRPass::WalkCtx(bindings, promo)` — read-only carry through
transform_body / recurse_branches!.
- `OwnershipDataflow::DataflowStep(state, consumed)` — per-walk state
for collect_ownership_transfers + 4 helpers.
Introduced `AST::HasBodies` module. Body-owning AST nodes (12 types:
IfStatement, WhileLoop, ForRange, ForEach, MatchStatement, WithBlock,
DoBlock, BgBlock, BgStreamBlock, FunctionDef, TestBlock,
WhileBindLoop) declare `child_bodies`. `AST.walk_body` and
`AST._bg_visit_recursive` collapse from hand-coded case chains to
trait-driven loops. Adding a new body-owning node type is now a single
include + def, no walker edits.
655 → 504 sites (-151). Methodology: trace each receiver via Prism +
call-site grep before touching. Most "guards" were dead defensive code
where Locatable's universal accessors (token, full_type, type_info,
storage, matched_stdlib_def, was_moved, zig_pattern, mutates_receiver,
line, column, etc.) made the check pointless on AST receivers.
Clusters cleaned: `:strip` / `:empty?` (emit() returns String|nil),
`:line` / `:column` (Locatable + Token), `:matched_stdlib_def`,
`:was_moved`, `:zig_pattern`, `:mutates_receiver`, `:token`,
`:full_type` (40 sites), `:value` (sites with case/when narrowing).
False positives caught by tests: 1 in `visit_StubDecl` where
`node.value` is genuinely polymorphic (AST | Symbol). Replaced with
explicit `is_a?(AST::Locatable)` and a comment.
Also removed 1 spec test that locked in the dead-defensive code via
`Struct.new(:stack_tier, :stack_vars_bytes)` — lockstep deletion per
CLAUDE.md "test for deleted functionality."
Converted PipelineHost#transpile_pipeline's 24-arm if/elsif chain to
case/when (54 LOC → 30 LOC). Other AST-is_a? chains in src/ are 2-3
arms with mixed predicates (`is_a?(X) && was_moved`) where case/when
doesn't simplify cleanly — those stay as-is.
- `.github/workflows/transpile-pure.yml`: PR-body-triggered byte-diff
of generated .zig vs merge base. Gated on `#TRANSPILE_PURE` marker.
- `clear emit-zig <path> -o <outdir>` CLI subcommand: walks .cht files
and writes generated .zig to a mirrored tree without compiling.
- Used by the workflow to capture deterministic transpiler output.
- Gemfile: sorbet, sorbet-runtime, tapioca added to dev group.
- `sorbet/config` + `sorbet/rbi/clear-stubs.rbi` (minimal stubs to
unblock `srb tc`).
- `tools/gen_attr_rbi.rb` (Prism-based RBI generator for AST attr_*
declarations) → `sorbet/rbi/clear-attr-accessors.rbi` (91 classes,
404 attr_* shims).
- Test pilot: importer.rb at `# typed: true` clean.
- `tools/respond_to_inventory.rb` — Prism walks src/ast/ast.rb to map
AST classes → attrs (Struct members + include Locatable +
attr_accessor + custom getters), then walks src/**/*.rb for every
respond_to?(:X) site. Outputs CSVs and a summary md.
- `tools/respond_to_narrowing.rb` — per-site receiver-type classifier.
For each respond_to? site, finds prior is_a? guards, case/when
arms, .X assignments from Locatable attrs, and walker-block params.
Classifies as ast_locatable / typed_specific /
from_locatable_attr / walker_yielded / unknown. Drove Phase 1f's
full_type sweep (40 sites, 1 false positive).
- `docs/agents/respond_to_inventory.md` — methodology + phased plan.
- TODO.md updated: P0 self-host prep #1 String/Symbol normalization
folded into #2 (the schema work subsumes it).
- Two pure-deletion commits removed dead code unrelated to the main
themes (4 dead methods debride flagged, dead defensive guards in
escape_analysis).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Just for comments