Skip to content

Replace throw/catch control flow with block return values#22015

Merged
MikeMcQuaid merged 4 commits intomainfrom
refactor-throw-catch-to-constants
Apr 14, 2026
Merged

Replace throw/catch control flow with block return values#22015
MikeMcQuaid merged 4 commits intomainfrom
refactor-throw-catch-to-constants

Conversation

@dduugg
Copy link
Copy Markdown
Member

@dduugg dduugg commented Apr 14, 2026

Summary

Ruby's throw/catch is a non-local control flow mechanism (a form of goto). In this codebase, it was never used for true multi-frame unwinding; every throw occurred exactly one frame above its matching catch. Block return values handle that case directly and more simply, so this PR replaces all three uses with idiomatic Ruby patterns:

  • Dependency: Remove catch(:action) from action, use the block's return value directly. Callers signal actions via next with named constants (PRUNE, SKIP, KEEP_BUT_PRUNE_RECURSIVE_DEPS). Remove the prune, skip, and keep_but_prune_recursive_deps class methods.
  • Requirement: Remove catch(:prune) from prune?, check the block's return value against the PRUNE constant. Remove the prune class method.
  • Livecheck: Extract nested loop into find_version_update_revision helper and use return for early exit instead of throw :version_update_revision_found.
  • Dependable: Move PRUNE, SKIP, and KEEP_BUT_PRUNE_RECURSIVE_DEPS into the shared Dependable module (included by both Dependency and Requirement) so there is a single source of truth. Previously these were defined separately on each class with identical values, which was fragile for the polymorphic code in dependencies_helpers.rb.

Type signature improvements:

  • Block types corrected from .void (incorrect, since the return value was communicated via side-channel throw) to .returns(T.nilable(Symbol)).
  • Formula#recursive_requirements block param changed from T.untyped to a fully specified proc type. This was only possible because the block's return value is now explicit rather than communicated via throw.
  • CaskDependent#recursive_requirements block sig corrected from an incorrect .returns(CaskDependent::Requirement) to .returns(T.nilable(Symbol)).
  • Dependency.action return type changed from T.nilable(T.any(Integer, Symbol)) to T.nilable(Symbol), removing a stale Integer that was an artifact of the catch mechanism.
  • Requirement#prune? return type tightened from T.nilable(T::Boolean) to T::Boolean.

Test plan

  • brew lgtm --online passes (style, typecheck, and tests)
  • Verify dependency expansion still works correctly for formulae with optional, recommended, build, and test dependencies
  • Verify brew unbottled and brew test commands behave as before
  • Verify livecheck version timestamp detection is unchanged

  • Have you followed the guidelines in our Contributing document?
  • Have you checked to ensure there aren't other open Pull Requests for the same change?
  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests (excluding integration tests) for your changes? Here's an example.
  • Have you successfully run brew lgtm (style, typechecking and tests) with your changes locally?

  • AI was used to generate or assist with generating this PR. Claude Code was used for code review, identifying the fragile coupling between duplicate constants, and drafting the PR description. All code changes were manually directed and verified with brew lgtm --online.

dduugg added 2 commits April 13, 2026 20:46
Ruby's throw/catch is a non-local control flow mechanism (a form of
goto). Replace all three uses with idiomatic Ruby patterns:

- Dependency: remove catch(:action) from `action`, use the block's
  return value directly. Callers signal actions via `next` with
  named constants (PRUNE, SKIP, KEEP_BUT_PRUNE_RECURSIVE_DEPS).
- Requirement: remove catch(:prune) from `prune?`, check the block's
  return value against the PRUNE constant.
- Livecheck: extract nested loop into `find_version_update_revision`
  helper and use `return` for early exit.

Remove the now-unnecessary `prune`, `skip`, and
`keep_but_prune_recursive_deps` class methods and update all callers
and type signatures.
Document why the trailing nil in language/python.rb is necessary
(block sig requires T.nilable(Symbol), not Array) and why
Dependency::PRUNE works in the polymorphic Requirement context
in dependencies_helpers.rb (both constants are :prune).
Copilot AI review requested due to automatic review settings April 14, 2026 03:49
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR removes local throw/catch control flow from dependency/requirement expansion and replaces it with idiomatic block return values, centralizing action constants in Dependable and refactoring livecheck’s history scan to use early returns.

Changes:

  • Replace Dependency.prune/skip/keep_but_prune_recursive_deps and Requirement.prune throw-based helpers with block return values (Dependable::PRUNE, Dependable::SKIP, Dependable::KEEP_BUT_PRUNE_RECURSIVE_DEPS).
  • Unify action constants in Dependable and update Sorbet block signatures to reflect returned action values.
  • Refactor livecheck revision scanning into find_version_update_revision.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
Library/Homebrew/dependency.rb Removes catch(:action) control flow; switches to block return action symbols.
Library/Homebrew/requirement.rb Removes catch(:prune); prune? now checks returned action symbol and returns boolean.
Library/Homebrew/dependable.rb Introduces shared action constants PRUNE/SKIP/KEEP_BUT_PRUNE_RECURSIVE_DEPS.
Library/Homebrew/formula.rb Updates recursive dependency/requirement block type signatures; updates runtime dep expansion to return actions.
Library/Homebrew/cask_dependent.rb Updates recursive dep/req block type signatures to return action symbols.
Library/Homebrew/dependencies_helpers.rb Updates expansion filtering logic to return Dependable::* actions.
Library/Homebrew/formula_installer.rb Updates requirement/dependency expansion blocks to return Dependable::* actions; adds casts for typed blocks.
Library/Homebrew/build.rb Updates pruning logic in recursive dep/req expansion to return Dependable::* actions; adds cast.
Library/Homebrew/language/python.rb Updates pruning logic to return Dependable::PRUNE and ensures nil return for type safety.
Library/Homebrew/livecheck/livecheck.rb Extracts revision-search helper and replaces throw/catch early-exit with return.
Library/Homebrew/dev-cmd/test.rb Updates pruning logic in recursive_dependencies block to return Dependable::PRUNE.
Library/Homebrew/dev-cmd/unbottled.rb Updates pruning logic in Dependency.expand block to return Dependable::PRUNE.
Library/Homebrew/extend/os/linux/cmd/update-report.rb Updates pruning logic in Dependency.expand block to return Dependable::PRUNE.
Library/Homebrew/test/dependency_expansion_spec.rb Updates specs to use action constants returned from the block.
Library/Homebrew/test/formula_spec.rb Updates requirement pruning in spec to return Dependable::PRUNE.
Library/Homebrew/test_bot/test_formulae.rb Updates pruning in recursive_dependencies block to return Dependable::PRUNE.
Library/Homebrew/test_bot/formulae_dependents.rb Updates pruning/skip logic in dependency expansion blocks to return Dependable::* actions.
Library/Homebrew/test_bot/formulae.rb Updates dependency expansion filtering to return Dependable::* actions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Library/Homebrew/formula_installer.rb
Comment thread Library/Homebrew/livecheck/livecheck.rb
@dduugg dduugg force-pushed the refactor-throw-catch-to-constants branch from 9b67db0 to d3eb2ee Compare April 14, 2026 04:03
PRUNE, SKIP, and KEEP_BUT_PRUNE_RECURSIVE_DEPS were defined
separately on Dependency and Requirement with identical values.
This coupling was fragile — if either constant changed, the
polymorphic code in dependencies_helpers.rb would silently break.

Move the constants to the shared Dependable module (included by
both classes) so there is a single source of truth, and update
all callers to reference Dependable:: directly.
@dduugg dduugg force-pushed the refactor-throw-catch-to-constants branch 4 times, most recently from f2418b7 to d1ef9b6 Compare April 14, 2026 04:31
Add explicit nil return in formula_installer.rb where the else
branch returns an Array from <<, violating the block's declared
T.nilable(Symbol) return type.

Use T::Sig::WithoutRuntime for Formula#recursive_requirements
(matching recursive_dependencies) since CaskDependent may not be
initialized yet.
@dduugg dduugg force-pushed the refactor-throw-catch-to-constants branch from d1ef9b6 to 5833a05 Compare April 14, 2026 04:32
@dduugg
Copy link
Copy Markdown
Member Author

dduugg commented Apr 14, 2026

(I'm not married to the content of this PR, but I do personally consider it a readability / comprehensibility net win. No offense taken if I'm alone on this one though.)

Copy link
Copy Markdown
Member

@MikeMcQuaid MikeMcQuaid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeh, I agree, this is much nicer and more intuitive for non-Rubyists. Thanks!

@MikeMcQuaid MikeMcQuaid added this pull request to the merge queue Apr 14, 2026
Merged via the queue into main with commit 61ee02f Apr 14, 2026
37 checks passed
@MikeMcQuaid MikeMcQuaid deleted the refactor-throw-catch-to-constants branch April 14, 2026 07:58
indirect added a commit to spinel-coop/rv-ruby-dev that referenced this pull request Apr 18, 2026
indirect added a commit to spinel-coop/rv-ruby-dev that referenced this pull request Apr 18, 2026
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.

3 participants