Skip to content

sorbet: enable experimental RSpec mode#21690

Draft
dduugg wants to merge 6 commits intomainfrom
deichelberger/sorbet-experimental-rspec
Draft

sorbet: enable experimental RSpec mode#21690
dduugg wants to merge 6 commits intomainfrom
deichelberger/sorbet-experimental-rspec

Conversation

@dduugg
Copy link
Member

@dduugg dduugg commented Mar 8, 2026

Summary

  • Enables Sorbet's --enable-experimental-rspec mode so example groups are type-checked with RSpec DSL awareness
  • Promotes all 753 spec files to # typed: true (from untyped) as a prerequisite
  • Generates tapioca gem RBIs for rspec and rspec-mocks (previously excluded)
  • Adds a tapioca DSL compiler that scans test files for custom matcher definitions and generates RSpec::Matchers stubs, reducing "Method does not exist" errors by ~560

Commits

  1. test: promote all spec files to # typed: true — blanket sigil promotion on all 753 spec files
  2. sorbet: enable experimental RSpec mode — adds --enable-experimental-rspec to sorbet config and explicit RSpec::Core::ExampleGroup includes in upstream.rbi
  3. sorbet: generate tapioca gem RBIs for rspec and rspec-mocks — removes the exclusions and regenerates those gem RBIs
  4. sorbet: add tapioca DSL compiler for custom RSpec matchers — scans test files for define/alias_matcher/define_negated_matcher/matcher calls and generates typed stubs

Error count

brew typecheck errors: 5,990 → 5,432 after this PR (−558). The remaining errors are unrelated to custom matchers (built-in dynamic predicate matchers, include/module-helper resolution, etc.).

🤖 Generated with Claude Code

dduugg added 5 commits March 8, 2026 11:48
Adds `# typed: true` to all 753 spec files that previously had no Sorbet
sigil, in preparation for enabling Sorbet's experimental RSpec mode.
Adds `--enable-experimental-rspec` to the Sorbet config so that Sorbet
type-checks RSpec example groups with awareness of the DSL structure
(describe/context/it/let/before/etc.).

Also adds explicit includes on `RSpec::Core::ExampleGroup` in upstream.rbi
so Sorbet resolves `RSpec::Matchers`, `RSpec::SharedContext`, and
`RSpec::Mocks::ExampleMethods` methods within example group bodies.
These gems were previously excluded from tapioca gem RBI generation.
Removing the exclusions and generating their RBIs gives Sorbet the type
information needed to resolve rspec/rspec-mocks constants and methods
under the new experimental RSpec mode.
Adds a tapioca DSL compiler that scans test files for custom RSpec matcher
definitions and generates RBI stubs for them on `RSpec::Matchers`.

Handles all four definition styles:
  - RSpec::Matchers.define :name do |args|
  - (RSpec::Matchers.)?define_negated_matcher :name, :base
  - (RSpec::Matchers.)?alias_matcher :new, :old
  - matcher :name do |args| (inside describe/context/shared_context)

Because example groups include `RSpec::Matchers`, adding stubs there makes
custom matchers visible to Sorbet's experimental RSpec type-checker.
The generated RBI reduces "Method does not exist" errors by ~560.
`RSpec::Matchers::MatcherDelegator` forwards unknown calls to its wrapped
`base_matcher` via `method_missing`. Sorbet cannot follow that delegation,
so aliased/negated wrappers of `output` (e.g. `not_to_output`) appear to
lack `to_stdout`, `to_stderr`, and their `_from_any_process` variants.

Declaring those four methods on `MatcherDelegator` in upstream.rbi — with
`T.self_type` as the return type to preserve chainability — eliminates the
"Method to_stderr does not exist on AliasedNegatedMatcher" errors (-129).
@MikeMcQuaid
Copy link
Member

"Looks" good when 🟢. I'd suggest you keep these all typed: false for now and fix them one-by-one? Will be easier to merge earlier and actually provide review.

…ioca

Instead of manually shim-ing individual methods (e.g. to_stdout/to_stderr)
onto RSpec::Matchers::MatcherDelegator, a tapioca DSL compiler now enumerates
every public method defined on a concrete RSpec::Matchers::BuiltIn subclass
(excluding those already on BaseMatcher or MatcherDelegator) and declares
them on MatcherDelegator.

This is more precise than a blanket T.untyped escape-hatch: only methods
that genuinely exist on some built-in matcher are permitted on delegators
(to_stdout/to_stderr from Output, by/from/to from Change, with/argument
from RespondTo, etc.). Calls to methods that exist on no built-in matcher
still raise a 7003 error.

Removes the manual upstream.rbi shim added in the previous commit.
@dduugg
Copy link
Member Author

dduugg commented Mar 9, 2026

@MikeMcQuaid This is just a scratch PR for me to track the progress in resolving major categories of rspec/sorbet compatibility (and possibly to link back to when upstreaming work to e.g. sorbet or tapioca). I think it would actually be counterproductive to start flipping the typed: true switch this earlier. It'd be super fragile, and I don't want to confuse folks/agents who fall into type errors when making reasonable, incremental changes. (If you'd prefer I not use a draft PR for this stage, just lmk, and i'll figure out another means of surfacing progress.)

@MikeMcQuaid
Copy link
Member

@dduugg cool works for me!

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