mirror of
https://github.com/Homebrew/brew.git
synced 2026-08-12 22:29:27 +04:00
- Ruby 4.0's `continuation` warns `callcc is obsolete; use Fiber instead` whenever it is required, which happens on every `brew` command that loads a formula from source. - Run `Ignorable.hook_raise` blocks in a `Fiber`: `raise` now pauses at the raise site and asks an `on_ignorable` callback whether to resume (`:ignore`) or raise there as usual, replacing the rescue plus continuation jump and `Ignorable::ExceptionMixin#ignore`. - `Debrew` menus and `Formulary` `ignore_errors` decisions now happen before the stack unwinds, so `ensure` blocks only run when an exception is actually raised. - Only require `ignorable` when `Formulary` uses `ignore_errors` and drop the obsolete `brew verify-undefined` `Warnings` guard. Fixes https://github.com/Homebrew/brew/issues/23384
64 lines
2.0 KiB
Ruby
64 lines
2.0 KiB
Ruby
# typed: strict
|
|
# frozen_string_literal: true
|
|
|
|
# Provides the ability to optionally ignore errors raised and continue execution.
|
|
module Ignorable
|
|
# Marks exceptions which can be ignored and resumed from where they were raised.
|
|
module ExceptionMixin; end
|
|
|
|
# Runs the block in a Fiber whose `raise` pauses at the raise site and passes
|
|
# the exception to `on_ignorable`. If it returns `:ignore`, execution resumes
|
|
# after the raise site, otherwise the exception is raised there as usual.
|
|
sig {
|
|
type_parameters(:U)
|
|
.params(
|
|
on_ignorable: T.proc.params(exception: Exception).returns(Symbol),
|
|
block: T.proc.returns(T.type_parameter(:U)),
|
|
)
|
|
.returns(T.type_parameter(:U))
|
|
}
|
|
def self.hook_raise(on_ignorable:, &block)
|
|
fiber = Fiber.new(&block)
|
|
|
|
Object.class_eval do
|
|
# `define_method` keeps Sorbet happy inside this `class_eval` block.
|
|
define_method(:raise) do |*args, **kwargs|
|
|
super(*args, **kwargs)
|
|
# All possible exceptions must be pausable, not just `StandardError`.
|
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
if e.is_a?(ScriptError) || Fiber.current != fiber
|
|
super(e)
|
|
else
|
|
e.extend(ExceptionMixin)
|
|
super(e) if Fiber.yield(e) != :ignore
|
|
end
|
|
end
|
|
|
|
alias_method :fail, :raise
|
|
end
|
|
|
|
result = fiber.resume
|
|
while fiber.alive?
|
|
decision = begin
|
|
on_ignorable.call(result)
|
|
# Even `Interrupt` at the prompt must unwind the fiber, not abandon it.
|
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
e
|
|
end
|
|
|
|
result = case decision
|
|
when :ignore then fiber.resume(:ignore)
|
|
# Raise inside the fiber so its `ensure` blocks and rescues still run.
|
|
when Exception then fiber.raise(decision)
|
|
else fiber.resume(:raise)
|
|
end
|
|
end
|
|
result
|
|
ensure
|
|
Object.class_eval do
|
|
remove_method(:raise)
|
|
remove_method(:fail)
|
|
end
|
|
end
|
|
end
|