Use test_each for table-driven specs, and update spoom to 1.8.6 - #23359
Merged
Conversation
Also bumps `rbi` to 0.4.1, which spoom 1.8.6 requires, and regenerates the RBI files for both. `sorbet` is held at 0.6.13342: spoom only needs `>= 0.5.10187`, and bumping it would rewrite the vendored `sorbet-runtime` copy that is committed in-tree.
Sorbet only looks for spec methods at the top level of a class body or `describe` block, so example groups generated inside a plain `each` are invisible to it: their bodies typecheck against the group class rather than an example, and every call in the loop fails to resolve. One loop cascades into an error per call, which is usually enough to pin the file at `# typed: false`. Adds `Test::Helper::TestEach`, which Sorbet can see through, and applies the new `Sorbet/TestsDefinedWithEach` cop from rubocop-sorbet to the 22 loops that qualify. The rewrites are semantics-preserving: `each` already yielded one row per iteration, so multi-parameter blocks were auto-splatting what is now an explicit destructure. Six files come off `# typed: false` as a result, three of them all the way to `# typed: strict`. Both sigs are `checked(:never)`. A `Hash` reaches `test_each` rather than `test_each_hash` whenever the receiver is not a hash literal, and under `HOMEBREW_SORBET_RECURSIVE` a `Hash` only validates when the element type is a tuple, which plain array rows are not. No element type satisfies both, and a top-level union leaves the block parameter type unbound.
dduugg
force-pushed
the
dug/spoom-1.8.6-test-each
branch
from
July 30, 2026 04:55
4a8c5a0 to
d65b9c0
Compare
test_each for table-driven specs, and update spoom to 1.8.6
dduugg
marked this pull request as ready for review
July 30, 2026 04:57
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates vendored Sorbet tooling (spoom/rbi) and introduces an RSpec helper (test_each/test_each_hash) to replace table-driven specs written with plain .each, improving Sorbet’s ability to typecheck spec DSL inside those generated example groups.
Changes:
- Bump vendored gems:
spoom1.8.4 → 1.8.6 andrbi0.4.0 → 0.4.1, including updated load paths and regenerated gem RBIs. - Add
Test::Helper::TestEachand wire it into the test harness (RSpec config + RSpec RBI shim) so Sorbet can typecheck table-driven spec blocks. - Rewrite multiple specs to use
test_each/test_each_hash, enabling stricter Sorbet sigils in several test files.
Reviewed changes
Copilot reviewed 20 out of 23 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| Library/Homebrew/vendor/bundle/bundler/setup.rb | Updates vendored gem load paths for rbi and spoom new versions. |
| Library/Homebrew/Gemfile.lock | Records spoom/rbi version bumps and dependency constraint change. |
| Library/Homebrew/sorbet/rbi/gems/spoom@1.8.6.rbi | Regenerated RBI for spoom 1.8.6. |
| Library/Homebrew/sorbet/rbi/gems/rbi@0.4.1.rbi | Regenerated RBI for rbi 0.4.1. |
| Library/Homebrew/test/support/helper/test_each.rb | Adds the test_each/test_each_hash helper methods for table-driven specs. |
| Library/Homebrew/test/spec_helper.rb | Requires the new helper and extends it into RSpec example groups. |
| Library/Homebrew/sorbet/rbi/shims/rspec.rbi | Exposes test_each methods to Sorbet’s RSpec example-group type. |
| Library/Homebrew/test/vulns/cvss_spec.rb | Switches hash-table spec loop to test_each_hash and tightens sigil to strict. |
| Library/Homebrew/test/utils/linkage_spec.rb | Replaces OS-metadata .each loop with test_each. |
| Library/Homebrew/test/support/helper/spec/shared_examples/formulae_exist.rb | Replaces .each loop with test_each in shared examples. |
| Library/Homebrew/test/sandbox_shared_spec.rb | Replaces multiple .each loops with test_each and updates destructuring; sigil to true. |
| Library/Homebrew/test/rubocops/presence_spec.rb | Replaces operator .each loop with test_each; sigil to strict. |
| Library/Homebrew/test/mcp_server_spec.rb | Replaces .each over tools with test_each; sigil to true. |
| Library/Homebrew/test/livecheck_spec.rb | Replaces metadata hash .each loops with test_each_hash. |
| Library/Homebrew/test/formula_spec.rb | Replaces actions .each loop with test_each. |
| Library/Homebrew/test/formula_creator_spec.rb | Replaces .each loop with test_each + explicit destructure; sigil to strict. |
| Library/Homebrew/test/download_strategies/curl_spec.rb | Replaces invalid header value .each loop with test_each; sigil to true. |
| Library/Homebrew/test/cmd/link_spec.rb | Replaces formula-type hash .each loop with test_each_hash. |
| Library/Homebrew/test/cask/dsl/version_spec.rb | Replaces multiple .each loops with test_each and updates destructuring. |
| Library/Homebrew/test/cask/cask_spec.rb | Replaces shared-example hash .each loops with test_each and updates destructuring. |
| Library/Homebrew/test/cask/audit_spec.rb | Replaces stanza .each loop with test_each. |
| Library/Homebrew/test/cask/artifact/shared_examples/uninstall_zap.rb | Replaces .each loop with test_each. |
| Library/Homebrew/test/cask/artifact/abstract_uninstall_spec.rb | Replaces array .each loop with test_each + explicit destructure. |
Files not reviewed (1)
- Library/Homebrew/sorbet/rbi/shims/rspec.rbi: File type not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This was referenced Jul 30, 2026
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.
Two commits, reviewable separately.
1.
vendor-gems: update spoom to 1.8.6spoom1.8.4 → 1.8.6, plusrbi0.4.0 → 0.4.1 which spoom 1.8.6 requires, and the regenerated RBIs for both.sorbetis deliberately held at 0.6.13342. A plainbrew vendor-gems --update=spoomalso sweeps sorbet to 0.6.13371, but spoom only needssorbet-static-and-runtime >= 0.5.10187, and bumping it rewrites the vendoredsorbet-runtimecopy that is committed in-tree (~13k lines). That belongs in its own PR.2.
test: use test_each for table-driven specsSorbet only looks for spec methods at the top level of a class body or
describeblock, so example groups generated inside a plaineachare invisible to it: their bodies typecheck against the group class rather than an example, and every call in the loop fails to resolve. One loop cascades into an error per call, which is usually enough to pin the file at# typed: false.This adds
Test::Helper::TestEachand applies Shopify/rubocop-sorbet#391's newSorbet/TestsDefinedWithEachcop, which corrected 22 loops across 16 files. The rewrites are semantics-preserving:eachalready yielded one row per iteration, so multi-parameter blocks were auto-splatting what is now an explicit destructure.Six files come off
# typed: falseas a result, three of them all the way to# typed: strict:test/formula_creator_spec.rbfalsestricttest/rubocops/presence_spec.rbfalsestricttest/vulns/cvss_spec.rbfalsestricttest/download_strategies/curl_spec.rbfalsetruetest/mcp_server_spec.rbfalsetruetest/sandbox_shared_spec.rbfalsetrueWhy both sigs are
checked(:never)A
Hashreachestest_eachrather thantest_each_hashwhenever the receiver is not a hash literal — a constant, a local, a shared-example parameter. UnderHOMEBREW_SORBET_RECURSIVE,TypedEnumerable#valid?becomesrecursively_valid?(standalone/sorbet.rb:33), which accepts aHashonly when the element type is a tuple. Plain array rows are not tuples, so no single element type satisfies both, and widening it does not help: only a literal tuple passes theHashbranch, so aT.any(...)element type fails theis_a?(FixedArray)guard.A top-level union does not work either.
T.any(T::Enumerable[T.type_parameter(:U)], T::Hash[T.anything, T.anything])typechecks clean at the definition but leavesUunbound at hash call sites, which turns the corrected blocks into dead code:Since these methods exist only to give Sorbet a static view of the loop, validating the pass-through at runtime buys nothing.
Verification
brew typecheckandbrew style --changedare clean, and the 47 spec files covering all 16 changed files pass. Note that a repro outside the suite is misleading here:brew rubydisables runtime checks entirely, so theHashfailure only surfaces underbrew tests, whererspec-sorbetreinstates a raisingcall_validation_error_handler.brewcommands to reproduce the bug?brew lgtm(style, typechecking and tests) locally?AI/LLM disclosure: prepared with Claude Code (Opus 5) — it ran the gem update, ran the
new cop's autocorrection, and measured the sigil promotions with
spoom srb bump.