Adopt Sorbet with Tapioca-generated RBIs - #77
Merged
Merged
Conversation
Set up static type checking with Sorbet. No runtime dependency is added: sorbet-runtime is not in the gemspec, and sorbet/config enables RBS comments so signatures can be written as comments later. - Gemfile: add sorbet and tapioca, plus railties so Sorbet can resolve Rails and ActionController in lib/singed/railtie.rb. Resolving railties moves activesupport and activejob from 8.1.3.1 to 8.1.4. - sorbet/config, sorbet/tapioca/config.yml and bin/tapioca from `tapioca init`. - sorbet/tapioca/require.rb loads the gems and stdlib singed uses, plus sidekiq/api, sidekiq/testing and zeitwerk, which the sidekiq and railties annotations and spec/support/sidekiq.rb reference. - `# typed:` sigils on every file under lib/ and spec/. `spoom srb bump` raised the files that type-check to `typed: true`. The rest stay at `typed: false`: the spec files, because Sorbet does not know the RSpec matchers, and cli.rb, controller_ext.rb, railtie.rb and sidekiq.rb, which have type errors at `true`. - .rubocop.yml: stop disabling the Sorbet department. The 15 files below `typed: strict` go in .rubocop_todo.yml under Sorbet/StrictSigil. Sorbet/ConstantsFromStrings is disabled inline where a job class is resolved from the name in a Sidekiq payload, since that can only happen at runtime. - AGENTS.md: list the type-check and RBI regeneration commands.
Output of `bin/tapioca gems --all` and `bin/tapioca annotations`. No hand edits. `bin/tapioca gems --verify` reports all RBIs up to date.
rubocop-performance, rubocop-rake, rubocop-rspec and rubocop-sorbet were only in the bundle as dependencies of rubocop-gusto. They are now listed directly, like rubocop-gusto, so the Gemfile shows every plugin .rubocop.yml loads. The lockfile change is limited to DEPENDENCIES. .rubocop.yml now lists rubocop-sorbet in plugins. Until now it was loaded only through rubocop-gusto's config/default.yml.
Every file under lib/ is now `# typed: strict`. Signatures and type annotations are RBS comments, so sorbet-runtime is still not a dependency and nothing under lib/ references T at runtime. - sorbet/rbi/shims/ declares what the generated RBIs miss: the ActiveSupport::BacktraceCleaner and StackProf::Report ivars that singed reads, StackProf::Report#frames, and a requires_ancestor on Sidekiq::Job. - A few method bodies change shape so Sorbet can narrow nilable values. Singed.silence_line? and filter_line read backtrace_cleaner into a local, CLI#run reads @filename directly, Report#filter! reads @DaTa into a local, and Flamegraph#record declares its block. Behavior is unchanged. - Singed.backtrace_cleaner is typed untyped rather than ActiveSupport::BacktraceCleaner. Tapioca turns a gem's RBS comments into runtime sigs while it loads an app, and apps without ActiveSupport would fail to resolve the constant. - README: typed controllers need an explicit `include Singed::ControllerExt` so that Sorbet resolves `flamegraph :show` to the controller macro rather than Kernel#flamegraph. - .rubocop_todo.yml no longer excludes lib/ from Sorbet/StrictSigil. - AGENTS.md documents these conventions.
Adds a Sorbet workflow, modeled on the RuboCop one, that type-checks every push and pull request. Its Sorbet check becomes a required status check on main.
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.
Sets up static type checking with Sorbet and Tapioca, and raises every runtime file under
lib/to# typed: strict. Nothing changes at runtime.sorbet-runtimestays out of the gemspec. The# typed:sigils and the RBS comment signatures (#: (String) -> bool) are only comments, and nothing underlib/referencesT.sorbet/isn't part ofspec.files.This undoes the
Sorbet: Enabled: falseblock from #76, which was there because the gem didn't use Sorbet.Commits
Gemfile: addsorbetandtapioca, plusrailtiesso Sorbet can resolveRailsandActionControllerinlib/singed/railtie.rb. Adding railties moves activesupport and activejob from 8.1.3.1 to 8.1.4.sorbet/config(with--enable-experimental-rbs-comments),sorbet/tapioca/config.yml,bin/tapioca.sorbet/tapioca/require.rblists the gems and stdlib singed uses, plussidekiq/api,sidekiq/testingandzeitwerk. The sidekiq and railties annotations andspec/support/sidekiq.rbrefer to those.# typed:sigils on every file underlib/andspec/.spoom srb bumpraised the files that type-check totyped: true, and commit 4 raises all oflib/tostrict. The spec files stay attyped: false, because Sorbet doesn't know the RSpec matchers..rubocop.yml: the Sorbet department is on again.typed: strictare listed in.rubocop_todo.ymlunderSorbet/StrictSigil.Sorbet/ConstantsFromStringsis disabled inline where the job class is looked up by the name in a Sidekiq payload. That lookup can only happen at runtime.AGENTS.md: add the type-check and RBI regeneration commands.bin/tapioca gems --allandbin/tapioca annotations, markedlinguist-generated.rubocop-performance,rubocop-rake,rubocop-rspecandrubocop-sorbetwere only in the bundle as dependencies of rubocop-gusto. They are now listed directly. The lockfile change is limited toDEPENDENCIES..rubocop.ymlalso listsrubocop-sorbetinpluginsnow, instead of loading it only through rubocop-gusto's config.lib/has an RBS comment type. Casts use the comment forms (#: as !nil,#: self as Singed::Railtie,#: as untyped), so nothing needssorbet-runtime. The style follows sidekiq-gusto.flamegraph { 42 },Flamegraph#recordand the Sidekiq middleware'scallreturn the block's type.sorbet/rbi/shims/covers what the generated RBIs miss:ActiveSupport::BacktraceCleanerandStackProf::Reportivars that singed reads;StackProf::Report#frames;requires_ancestoronSidekiq::Job.Singed.silence_line?andfilter_linereadbacktrace_cleanerinto a local.CLI#runreads@filenamedirectly.Report#filter!reads@datainto a local.Flamegraph#recorddeclares its block.Singed.backtrace_cleaneris typeduntyped, notActiveSupport::BacktraceCleaner. Tapioca rewrites a gem's RBS comments into runtimesigs while it loads an app. In an app without ActiveSupport, that constant can't resolve, and the two methods would lose their sigs in the app's RBI.ControllerExt::ClassMethods#flamegraphaccepts a Symbol, a String, or an Array of them, like Rails'only:.include Singed::ControllerExt. The Railtie's include happens at runtime, so without it Sorbet checksflamegraph :showagainstKernel#flamegraph, which is typed now..rubocop_todo.ymlno longer excludeslib/fromSorbet/StrictSigil, so RuboCop keepslib/strict.AGENTS.mddocuments the conventions, including why these signatures still matter at runtime for apps that use Tapioca.Sorbetworkflow, modeled on the RuboCop one, runsbundle exec srb tcon every push and pull request. ItsSorbetcheck is now a required status check onmain.Verification
bundle exec srb tc: no errorsbundle exec rspec: 42 examples, 0 failuresbundle exec rubocop: no offensesbin/tapioca check-shims: no duplicatesbin/tapioca gems --verify: all RBIs up to datelib/without Bundler (sosorbet-runtimecan't load), with and without Rails, Sidekiq and RSpec loaded:Tstays undefined.lib/singed.rb, translated tosigs the way Tapioca does it, evaluate without ActiveSupport loaded.Not in this PR
bin/tapioca gems --verify. Until that exists, nothing stops the RBIs from going stale.typed: false.Singed.enabled = false,flamegraph { }raisesNoMethodErrorfromStackProf::Report#frames, becauseFlamegraph#savebuilds a report from a nil profile. The StackProf shim's@datatype records this; the behavior is unchanged.