Skip to content

Transpiler cleanup final#20

Merged
cuzzo merged 1 commit intomasterfrom
transpiler-cleanup-final
May 8, 2026
Merged

Transpiler cleanup final#20
cuzzo merged 1 commit intomasterfrom
transpiler-cleanup-final

Conversation

@cuzzo
Copy link
Copy Markdown
Owner

@cuzzo cuzzo commented May 8, 2026

Summary

Self-host preparation cleanup: typed schemas, struct extractions, walker trait migration, and respond_to? duck-typing reduction. Replaces the pre-rebase transpiler-cleanup branch after a rebase onto master that pulled in 20 commits of new master work.

What's included

P0 (foundational):

  • Schemas::* typed schemas (StructSchema, UnionSchema, ResourceSchema, EnumSchema) replacing hash-as-struct dispatch in src/ast/schemas.rb
  • PipelineSite struct extraction from PipelineHost/PipelineGenerator/PipelineRewriter
  • Formatter::Emitter::EmitterState extraction
  • MIRPass::WalkState + OwnershipDataflow::DataflowStep extractions
  • Schemas.as_*_schema coercion helpers so consumers handle either typed or raw-Hash schemas without per-site is_a? chains

P1 (cleanup):

  • FunctionSignature Hash-shim layer fully removed ([], []=, key?, is_a?(Hash) override, dig, merge)
  • AST is_a? dispatch chains converted to Ruby 3 pattern matching
  • 22 dead &. operators removed via Prism flow analysis
  • 5 helper modules flipped to # typed: true with 259 T.bind(self, SemanticAnnotator) annotations
  • Sorbet RBI generators emit per-class typed sigs for AST node Struct fields

Tooling added under tools/:

  • dead_nil_check_finder.rb / dead_nil_check_fixer.rb / dead_trailing_if.rb — Prism-based dead &. detection + auto-fix
  • respond_to_inventory.rb / respond_to_narrowing.rb — classify defensive respond_to? sites
  • gen_struct_fields_rbi.rb / struct_field_nilability.rb — generate typed RBI overrides for Struct accessors
  • srb_nil_origins.rb — aggregate Sorbet nil-origin traces to find root causes

Test plan

  • bundle exec prspec spec/ — 4130 / 4130 pass
  • ./clear test transpile-tests/ — 520 / 520, 0 memory leaks, 0 errors
  • cd transpile-tests/module-integration && zig build test
  • cd transpile-tests/ffi-integration && zig build test
  • 4 originally-failing transpiler specs (786, 804, 821, 839) pass after rebase

Notes for reviewer

  • Rebase resolution took master's version for 5 helper files where conflicts were too dense; a follow-up commit (d8cf0048) re-applies the typed:true sigils, T.bind annotations, and one dead-check fix that were lost. Some respond_to? cleanups need per-site review (task Polymorphic sync v2 #9 follow-on).
  • Sorbet errors (~58 local) are not gated by CI. Most are recursive-lambda traverse = patterns Sorbet can't see through.
  • The FS Hash-shim removal interacts with Type#[] and schema dispatch; coercion helpers maintain uniform shape so consumers don't need to know whether a schema came from the annotator (raw Hash) or MIR lowering (Schemas::*).

#TRANSPILE_PURE

@cuzzo cuzzo force-pushed the transpiler-cleanup-final branch 2 times, most recently from d8cf004 to d33fd5f Compare May 8, 2026 12:37
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

🐰 Bencher Report

Branchtranspiler-cleanup-final
Testbedubuntu-latest

⚠️ WARNING: No Threshold found!

Without a Threshold, no Alerts will ever be generated.

Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the --ci-only-thresholds flag.

Click to view all benchmark results
Benchmarkleak-build-msMeasure (units) x 1e3leak-countMeasure (units)leak-run-msMeasure (units)
benchmarks/concurrent/04_fanout_fanin/bench📈 view plot
⚠️ NO THRESHOLD
5.09 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
3,559.95 units
benchmarks/concurrent/09_kvstore/bench📈 view plot
⚠️ NO THRESHOLD
5.06 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
60,004.29 units
benchmarks/concurrent/14_nested_lock/bench📈 view plot
⚠️ NO THRESHOLD
4.95 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
415.65 units
benchmarks/inter-clear/02_concurrent_fsm_vs_stackful/bench_fsm📈 view plot
⚠️ NO THRESHOLD
4.91 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
164.87 units
benchmarks/inter-clear/02_concurrent_fsm_vs_stackful/bench_stackful📈 view plot
⚠️ NO THRESHOLD
4.94 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
316.73 units
benchmarks/sequential/11_pipeline_overhead/bench📈 view plot
⚠️ NO THRESHOLD
4.90 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
13,544.31 units
🐰 View full continuous benchmarking report in Bencher

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 8, 2026

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 83.88942% with 169 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.88%. Comparing base (d92080e) to head (e68e8d1).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/backends/pipeline_host.rb 36.76% 86 Missing ⚠️
src/ast/ast.rb 77.19% 13 Missing ⚠️
src/mir/control_flow.rb 90.00% 8 Missing ⚠️
src/mir/escape_analysis.rb 50.00% 8 Missing ⚠️
src/mir/mir_lowering.rb 89.87% 8 Missing ⚠️
src/annotator-helpers/pipe_analysis.rb 91.78% 6 Missing ⚠️
src/mir/mir_pass.rb 81.81% 6 Missing ⚠️
src/annotator-helpers/capabilities.rb 91.80% 5 Missing ⚠️
src/annotator-helpers/fixable_helpers.rb 90.90% 5 Missing ⚠️
src/mir/promotion_plan.rb 86.11% 5 Missing ⚠️
... and 12 more
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #20      +/-   ##
==========================================
+ Coverage   89.85%   89.88%   +0.02%     
==========================================
  Files         182      183       +1     
  Lines       47332    47636     +304     
  Branches    11766    11598     -168     
==========================================
+ Hits        42529    42816     +287     
- Misses       4803     4820      +17     
Flag Coverage Δ
ruby 86.07% <83.88%> (+0.08%) ⬆️
zig 95.59% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cuzzo cuzzo force-pushed the transpiler-cleanup-final branch from d33fd5f to 02c786c Compare May 8, 2026 12:41
…strap

Squashed 3 commits:
- 6551d7a Self-host preparation: typed schemas, struct extractions, walker traits, respond_to? cleanup
- 02c786c Restore rebase-clobbered cleanup: typed:true, T.bind, dead-check fixes
- e8e909a refactor: drop dead respond_to? guards on Locatable/Type attrs

What's included:
- Typed schemas (Schemas::StructSchema / UnionSchema / ResourceSchema /
  EnumSchema) replace Hash-as-struct dispatch. Coercion helpers
  (Schemas.as_struct_schema / as_union_schema / as_resource_schema)
  bridge consumers that still see raw Hash from the annotator.
- PipelineSite struct extraction (PipelineHost / PipelineGenerator /
  PipelineRewriter).
- Formatter::Emitter::EmitterState struct extraction.
- MIRPass::WalkState + OwnershipDataflow::DataflowStep struct
  extractions.
- AST is_a? dispatch chains converted to Ruby 3 pattern matching.
- FunctionSignature Hash-shim layer eliminated.
- Sorbet bootstrap: # typed: true sigils on helper modules, T.bind
  in mixin methods, RBI overrides for AST Struct fields (Token typed
  non-nilable on AST::Locatable -- fires srb.help/7034 dead-check
  signals).
- 60 dead respond_to? guards removed where the receiver is always an
  AST node carrying the attr via AST::Locatable (or always a Type for
  type-method calls). Three sites needed `&.` instead of unconditional
  removal (reg / decl_node / decl can be nil).
- Memory-leak fix in elem_needs_cleanup? (chained-coercion clobbered
  elem_schema for struct types).
- Transpiler-output fix in control_flow.rb skip_rhs_move (Schemas
  coercion for union map values restores missing
  `defer CheatLib.cleanup` emission).

Verification:
- bundle exec prspec spec/ -> 4294 examples, 0 failures
- ./clear test transpile-tests/ -> 0 leaks, 0 errors
- module-integration / ffi-integration green

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cuzzo cuzzo force-pushed the transpiler-cleanup-final branch from e8e909a to e68e8d1 Compare May 8, 2026 13:21
@cuzzo cuzzo merged commit f9c81f0 into master May 8, 2026
32 checks passed
cuzzo added a commit that referenced this pull request May 8, 2026
Squashed 14 commits. Builds on the typed-schemas / Sorbet bootstrap from
PR #20 (commit f9c81f0 on master) to bring all 90 src/ files to
# typed: true with a 0-error Sorbet baseline gated in CI.

What's in this PR:

CI gate:
- New `sorbet` job in .github/workflows/ci.yml runs `bundle exec srb tc`
  on every PR. Fails on any new typed:true error. Job runs in parallel
  with ruby-unit, completes in <1s after gem cache warm.

Baseline cleanup (83 → 0 errors before any flips):
- Added missing `T.bind(self, SemanticAnnotator) rescue nil` to 25
  mixin instance methods in fixable_helpers / effects / capabilities
  whose includer's instance methods were unresolvable.
- Replaced `var = nil; var = lambda {...}` recursive-lambda pattern
  with `var = T.let(nil, T.untyped); var = lambda do ... end` at 4
  sites (effects.rb scan_for_calls / scan_for_raises and the
  mark_fn_value_references! traversal). Sorbet's flow analysis was
  seeing the inner self-reference as a call-on-nil.
- T.let widening on 4 boolean flag arrays / outer-loop changed flags /
  literal-typed empty-array accumulators where Sorbet inferred too
  narrow a type for later mutation.
- Standard `T.any(FalseClass, Type)` → `is_a?(Type)` narrowing fix
  applied across 8 sites where the
  `ti = something rescue nil; ti = Type.new(ti) if ti && !ti.is_a?(Type)`
  idiom leaks FalseClass through the conjunction's false branch.
- Dropped 5 dead `stmt.body || []` / `stmt.cases || []` / `stmt.branches
  || []` defensive expressions where the RBI guarantees the field is
  T::Array.
- Migrated one raw-string `error!` site to a registry code
  (RETURN_LIFETIME_NOT_ASSOCIATED) so the call-site audit spec stayed
  clean.
- Per-class :body override in tools/gen_struct_fields_rbi.rb for
  AST::LambdaLit (single expression, not Array) and AST::HashLit
  (Hash, not Array) so RBI sigs match runtime.

Tranche 1-9 — flip 90 src/ files to # typed: true:

Easy flips (T1-T3): 35 files in 75-500 LOC range, mostly clean as-is.
Required `T.bind(self, SemanticAnnotator)` on alloc.rb's 3 mixin
methods. Stripped 3 dead `&.` operators in string_concat_rewriter.rb
flagged by srb.help/7034 — RBI says `node.body` / `node.args` /
`node.cases` are non-nilable Arrays.

Medium flips (T4): 12 files in 500-2900 LOC range. Stripped 40 more
dead `&.` operators across mir_pass.rb / escape_analysis.rb /
promotion_plan.rb / pipeline_rewriter.rb (Array attrs proven non-
nilable by RBI). Several Type-narrowing fixes. Two real bugs: the
2-arg `CompilerError.new(message, location)` call shape that would
have ArgumentError'd if reached (SourceError expects 3 args:
token/message/source).

Big-file flips (T5-T7): 6 files including annotator.rb (6510),
mir_lowering.rb (7235). Required several real-bug fixes uncovered by
typing:
- annotator.rb: AST::StaticCall's `.name` accessor doesn't exist (the
  Struct fields are `:token, :type_name, :method_name, :args`); fixed
  to `.method_name`. Two more 2-arg CompilerError calls fixed.
  function_analysis.rb's `error!` call missing the node arg.
- type.rb: UInt64 max literal `18_446_744_073_709_551_615` rewritten
  as `(2**64) - 1` (Sorbet 3002 doesn't support 64-bit unsigned).
  Two more single-arg CompilerError calls fixed.
- parser.rb: `Token.new(...)` peek fallback resolved to top-level
  Token stub instead of `Lexer::Token`; renamed to disambiguate.

Module/Kernel deferred-file recovery (T5/T8): 11 files where
module-singleton methods called Kernel built-ins (`raise`, `lambda`,
`Array()`, `Integer()`) that Sorbet doesn't see on bare modules.
Applied uniform `T.bind(self, T.untyped) rescue nil` at the start
of every method body — Sorbet then accepts the dispatch. Cost:
suppresses per-call type-checking inside those methods. Benefit:
the file participates in typed:true and any non-method-dispatch
error (T.let mismatches, dead code, return-type contradictions)
still fires. Also gets us a clean Sorbet baseline across the whole
project.

DSL pattern (T9 - parser.rb): The Parser DSL stores blocks for later
instance_exec; Sorbet sees `self == T.class_of(Parser)` at definition
time and the parse_X instance methods don't resolve on the singleton.
Wrapped each block (single-line `{ ... }` and multi-line `do ... end`
via Prism walker) with `T.bind(self, Parser) rescue nil`. 79 blocks
across stmt/primary/suffix calls + 2 pattern-engine lambdas.

Schemas refactor (T9 - schemas.rb): Replaced
`Data.define(:fields, ...) do def initialize(kw:, ...); super end end`
with plain classes (attr_reader + kwarg initialize + freeze). Sorbet
4010 fired on the kwarg-only override of Data.define's auto-positional
initialize. No call site relied on Data's `==` / `hash` / `with`
methods so the migration is structural-equivalence.

Cumulative dead-check signals fired and cleaned: ~52 dead `&.`
operators removed across the project (Sorbet srb.help/7034). All
verified by spec + transpile-tests as runtime-equivalent — the RBI
sigs match real runtime invariants.

Real bugs fixed (would crash if reached at runtime):
- 5x `CompilerError.new(message, location)` 2-arg calls (SourceError
  expects 3 args)
- 1x `error!("string")` missing node arg
- 1x `n.name.to_s` on AST::StaticCall (no `.name`, should be
  `.method_name`)
- 1x bare `Token.new(...)` resolving to top-level Token stub instead
  of `Lexer::Token`

Verification:
- bundle exec srb tc -> No errors!
- bundle exec prspec spec/ -> 4300 examples, 0 failures
- ./clear test transpile-tests/ -> 0 leaks, 0 errors

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo added a commit that referenced this pull request May 8, 2026
Squashed 14 commits. Builds on the typed-schemas / Sorbet bootstrap from
PR #20 (commit f9c81f0 on master) to bring all 90 src/ files to
# typed: true with a 0-error Sorbet baseline gated in CI.

What's in this PR:

CI gate:
- New `sorbet` job in .github/workflows/ci.yml runs `bundle exec srb tc`
  on every PR. Fails on any new typed:true error. Job runs in parallel
  with ruby-unit, completes in <1s after gem cache warm.

Baseline cleanup (83 → 0 errors before any flips):
- Added missing `T.bind(self, SemanticAnnotator) rescue nil` to 25
  mixin instance methods in fixable_helpers / effects / capabilities
  whose includer's instance methods were unresolvable.
- Replaced `var = nil; var = lambda {...}` recursive-lambda pattern
  with `var = T.let(nil, T.untyped); var = lambda do ... end` at 4
  sites (effects.rb scan_for_calls / scan_for_raises and the
  mark_fn_value_references! traversal). Sorbet's flow analysis was
  seeing the inner self-reference as a call-on-nil.
- T.let widening on 4 boolean flag arrays / outer-loop changed flags /
  literal-typed empty-array accumulators where Sorbet inferred too
  narrow a type for later mutation.
- Standard `T.any(FalseClass, Type)` → `is_a?(Type)` narrowing fix
  applied across 8 sites where the
  `ti = something rescue nil; ti = Type.new(ti) if ti && !ti.is_a?(Type)`
  idiom leaks FalseClass through the conjunction's false branch.
- Dropped 5 dead `stmt.body || []` / `stmt.cases || []` / `stmt.branches
  || []` defensive expressions where the RBI guarantees the field is
  T::Array.
- Migrated one raw-string `error!` site to a registry code
  (RETURN_LIFETIME_NOT_ASSOCIATED) so the call-site audit spec stayed
  clean.
- Per-class :body override in tools/gen_struct_fields_rbi.rb for
  AST::LambdaLit (single expression, not Array) and AST::HashLit
  (Hash, not Array) so RBI sigs match runtime.

Tranche 1-9 — flip 90 src/ files to # typed: true:

Easy flips (T1-T3): 35 files in 75-500 LOC range, mostly clean as-is.
Required `T.bind(self, SemanticAnnotator)` on alloc.rb's 3 mixin
methods. Stripped 3 dead `&.` operators in string_concat_rewriter.rb
flagged by srb.help/7034 — RBI says `node.body` / `node.args` /
`node.cases` are non-nilable Arrays.

Medium flips (T4): 12 files in 500-2900 LOC range. Stripped 40 more
dead `&.` operators across mir_pass.rb / escape_analysis.rb /
promotion_plan.rb / pipeline_rewriter.rb (Array attrs proven non-
nilable by RBI). Several Type-narrowing fixes. Two real bugs: the
2-arg `CompilerError.new(message, location)` call shape that would
have ArgumentError'd if reached (SourceError expects 3 args:
token/message/source).

Big-file flips (T5-T7): 6 files including annotator.rb (6510),
mir_lowering.rb (7235). Required several real-bug fixes uncovered by
typing:
- annotator.rb: AST::StaticCall's `.name` accessor doesn't exist (the
  Struct fields are `:token, :type_name, :method_name, :args`); fixed
  to `.method_name`. Two more 2-arg CompilerError calls fixed.
  function_analysis.rb's `error!` call missing the node arg.
- type.rb: UInt64 max literal `18_446_744_073_709_551_615` rewritten
  as `(2**64) - 1` (Sorbet 3002 doesn't support 64-bit unsigned).
  Two more single-arg CompilerError calls fixed.
- parser.rb: `Token.new(...)` peek fallback resolved to top-level
  Token stub instead of `Lexer::Token`; renamed to disambiguate.

Module/Kernel deferred-file recovery (T5/T8): 11 files where
module-singleton methods called Kernel built-ins (`raise`, `lambda`,
`Array()`, `Integer()`) that Sorbet doesn't see on bare modules.
Applied uniform `T.bind(self, T.untyped) rescue nil` at the start
of every method body — Sorbet then accepts the dispatch. Cost:
suppresses per-call type-checking inside those methods. Benefit:
the file participates in typed:true and any non-method-dispatch
error (T.let mismatches, dead code, return-type contradictions)
still fires. Also gets us a clean Sorbet baseline across the whole
project.

DSL pattern (T9 - parser.rb): The Parser DSL stores blocks for later
instance_exec; Sorbet sees `self == T.class_of(Parser)` at definition
time and the parse_X instance methods don't resolve on the singleton.
Wrapped each block (single-line `{ ... }` and multi-line `do ... end`
via Prism walker) with `T.bind(self, Parser) rescue nil`. 79 blocks
across stmt/primary/suffix calls + 2 pattern-engine lambdas.

Schemas refactor (T9 - schemas.rb): Replaced
`Data.define(:fields, ...) do def initialize(kw:, ...); super end end`
with plain classes (attr_reader + kwarg initialize + freeze). Sorbet
4010 fired on the kwarg-only override of Data.define's auto-positional
initialize. No call site relied on Data's `==` / `hash` / `with`
methods so the migration is structural-equivalence.

Cumulative dead-check signals fired and cleaned: ~52 dead `&.`
operators removed across the project (Sorbet srb.help/7034). All
verified by spec + transpile-tests as runtime-equivalent — the RBI
sigs match real runtime invariants.

Real bugs fixed (would crash if reached at runtime):
- 5x `CompilerError.new(message, location)` 2-arg calls (SourceError
  expects 3 args)
- 1x `error!("string")` missing node arg
- 1x `n.name.to_s` on AST::StaticCall (no `.name`, should be
  `.method_name`)
- 1x bare `Token.new(...)` resolving to top-level Token stub instead
  of `Lexer::Token`

Verification:
- bundle exec srb tc -> No errors!
- bundle exec prspec spec/ -> 4300 examples, 0 failures
- ./clear test transpile-tests/ -> 0 leaks, 0 errors

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cuzzo added a commit that referenced this pull request May 8, 2026
Squashed 14 commits. Builds on the typed-schemas / Sorbet bootstrap from
PR #20 (commit f9c81f0 on master) to bring all 90 src/ files to
# typed: true with a 0-error Sorbet baseline gated in CI.

What's in this PR:

CI gate:
- New `sorbet` job in .github/workflows/ci.yml runs `bundle exec srb tc`
  on every PR. Fails on any new typed:true error. Job runs in parallel
  with ruby-unit, completes in <1s after gem cache warm.

Baseline cleanup (83 → 0 errors before any flips):
- Added missing `T.bind(self, SemanticAnnotator) rescue nil` to 25
  mixin instance methods in fixable_helpers / effects / capabilities
  whose includer's instance methods were unresolvable.
- Replaced `var = nil; var = lambda {...}` recursive-lambda pattern
  with `var = T.let(nil, T.untyped); var = lambda do ... end` at 4
  sites (effects.rb scan_for_calls / scan_for_raises and the
  mark_fn_value_references! traversal). Sorbet's flow analysis was
  seeing the inner self-reference as a call-on-nil.
- T.let widening on 4 boolean flag arrays / outer-loop changed flags /
  literal-typed empty-array accumulators where Sorbet inferred too
  narrow a type for later mutation.
- Standard `T.any(FalseClass, Type)` → `is_a?(Type)` narrowing fix
  applied across 8 sites where the
  `ti = something rescue nil; ti = Type.new(ti) if ti && !ti.is_a?(Type)`
  idiom leaks FalseClass through the conjunction's false branch.
- Dropped 5 dead `stmt.body || []` / `stmt.cases || []` / `stmt.branches
  || []` defensive expressions where the RBI guarantees the field is
  T::Array.
- Migrated one raw-string `error!` site to a registry code
  (RETURN_LIFETIME_NOT_ASSOCIATED) so the call-site audit spec stayed
  clean.
- Per-class :body override in tools/gen_struct_fields_rbi.rb for
  AST::LambdaLit (single expression, not Array) and AST::HashLit
  (Hash, not Array) so RBI sigs match runtime.

Tranche 1-9 — flip 90 src/ files to # typed: true:

Easy flips (T1-T3): 35 files in 75-500 LOC range, mostly clean as-is.
Required `T.bind(self, SemanticAnnotator)` on alloc.rb's 3 mixin
methods. Stripped 3 dead `&.` operators in string_concat_rewriter.rb
flagged by srb.help/7034 — RBI says `node.body` / `node.args` /
`node.cases` are non-nilable Arrays.

Medium flips (T4): 12 files in 500-2900 LOC range. Stripped 40 more
dead `&.` operators across mir_pass.rb / escape_analysis.rb /
promotion_plan.rb / pipeline_rewriter.rb (Array attrs proven non-
nilable by RBI). Several Type-narrowing fixes. Two real bugs: the
2-arg `CompilerError.new(message, location)` call shape that would
have ArgumentError'd if reached (SourceError expects 3 args:
token/message/source).

Big-file flips (T5-T7): 6 files including annotator.rb (6510),
mir_lowering.rb (7235). Required several real-bug fixes uncovered by
typing:
- annotator.rb: AST::StaticCall's `.name` accessor doesn't exist (the
  Struct fields are `:token, :type_name, :method_name, :args`); fixed
  to `.method_name`. Two more 2-arg CompilerError calls fixed.
  function_analysis.rb's `error!` call missing the node arg.
- type.rb: UInt64 max literal `18_446_744_073_709_551_615` rewritten
  as `(2**64) - 1` (Sorbet 3002 doesn't support 64-bit unsigned).
  Two more single-arg CompilerError calls fixed.
- parser.rb: `Token.new(...)` peek fallback resolved to top-level
  Token stub instead of `Lexer::Token`; renamed to disambiguate.

Module/Kernel deferred-file recovery (T5/T8): 11 files where
module-singleton methods called Kernel built-ins (`raise`, `lambda`,
`Array()`, `Integer()`) that Sorbet doesn't see on bare modules.
Applied uniform `T.bind(self, T.untyped) rescue nil` at the start
of every method body — Sorbet then accepts the dispatch. Cost:
suppresses per-call type-checking inside those methods. Benefit:
the file participates in typed:true and any non-method-dispatch
error (T.let mismatches, dead code, return-type contradictions)
still fires. Also gets us a clean Sorbet baseline across the whole
project.

DSL pattern (T9 - parser.rb): The Parser DSL stores blocks for later
instance_exec; Sorbet sees `self == T.class_of(Parser)` at definition
time and the parse_X instance methods don't resolve on the singleton.
Wrapped each block (single-line `{ ... }` and multi-line `do ... end`
via Prism walker) with `T.bind(self, Parser) rescue nil`. 79 blocks
across stmt/primary/suffix calls + 2 pattern-engine lambdas.

Schemas refactor (T9 - schemas.rb): Replaced
`Data.define(:fields, ...) do def initialize(kw:, ...); super end end`
with plain classes (attr_reader + kwarg initialize + freeze). Sorbet
4010 fired on the kwarg-only override of Data.define's auto-positional
initialize. No call site relied on Data's `==` / `hash` / `with`
methods so the migration is structural-equivalence.

Cumulative dead-check signals fired and cleaned: ~52 dead `&.`
operators removed across the project (Sorbet srb.help/7034). All
verified by spec + transpile-tests as runtime-equivalent — the RBI
sigs match real runtime invariants.

Real bugs fixed (would crash if reached at runtime):
- 5x `CompilerError.new(message, location)` 2-arg calls (SourceError
  expects 3 args)
- 1x `error!("string")` missing node arg
- 1x `n.name.to_s` on AST::StaticCall (no `.name`, should be
  `.method_name`)
- 1x bare `Token.new(...)` resolving to top-level Token stub instead
  of `Lexer::Token`

Verification:
- bundle exec srb tc -> No errors!
- bundle exec prspec spec/ -> 4300 examples, 0 failures
- ./clear test transpile-tests/ -> 0 leaks, 0 errors

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants