diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 19b3fb31dc00b..d5e23a7d7a3c7 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -77,6 +77,9 @@ class Formula extend Cachable extend Attrable extend APIHashable + extend T::Helpers + + abstract! SUPPORTED_NETWORK_ACCESS_PHASES = [:build, :test, :postinstall].freeze private_constant :SUPPORTED_NETWORK_ACCESS_PHASES @@ -1596,7 +1599,11 @@ def patch # Yields |self,staging| with current working directory set to the uncompressed tarball # where staging is a {Mktemp} staging context. - def brew(fetch: true, keep_tmp: false, debug_symbols: false, interactive: false) + sig(:final) { + params(fetch: T::Boolean, keep_tmp: T::Boolean, debug_symbols: T::Boolean, interactive: T::Boolean, + _blk: T.proc.params(arg0: Formula, arg1: Mktemp).void).void + } + def brew(fetch: true, keep_tmp: false, debug_symbols: false, interactive: false, &_blk) @prefix_returns_versioned_prefix = true active_spec.fetch if fetch stage(interactive:, debug_symbols:) do |staging| @@ -3346,6 +3353,7 @@ def method_added(method) case method when :brew + # This method has a `:final` sig, but we want to enforce it even when the sorbet runtime is disabled. raise "You cannot override Formula#brew in class #{name}" when :test define_method(:test_defined?) { true } diff --git a/Library/Homebrew/requirement.rb b/Library/Homebrew/requirement.rb index 9edd6a9a2306c..9a25941df19ba 100644 --- a/Library/Homebrew/requirement.rb +++ b/Library/Homebrew/requirement.rb @@ -13,14 +13,15 @@ class Requirement include Dependable extend Cachable + extend T::Helpers + + # This base class enforces no constraints on its own. + # Individual subclasses use the `satisfy` DSL to define those constraints. + abstract! attr_reader :name, :cask, :download def initialize(tags = []) - # Only allow instances of subclasses. This base class enforces no constraints on its own. - # Individual subclasses use the `satisfy` DSL to define those constraints. - raise "Do not call `Requirement.new' directly without a subclass." unless self.class < Requirement - @cask = self.class.cask @download = self.class.download tags.each do |tag| diff --git a/Library/Homebrew/standalone/sorbet.rb b/Library/Homebrew/standalone/sorbet.rb index 9cd7fa24a8df6..406582618d2e1 100644 --- a/Library/Homebrew/standalone/sorbet.rb +++ b/Library/Homebrew/standalone/sorbet.rb @@ -8,7 +8,9 @@ # In the future we should consider not doing this monkey patch, # if assured that there is no performance hit from removing this. # There are mechanisms to achieve a middle ground (`default_checked_level`). -unless ENV["HOMEBREW_SORBET_RUNTIME"] +if ENV["HOMEBREW_SORBET_RUNTIME"] + T::Configuration.enable_final_checks_on_hooks +else # Redefine `T.let`, etc. to make the `checked` parameter default to `false` rather than `true`. # @private module TNoChecks diff --git a/Library/Homebrew/test/formula_spec.rb b/Library/Homebrew/test/formula_spec.rb index 7346b11226d40..f2f9a993fbae5 100644 --- a/Library/Homebrew/test/formula_spec.rb +++ b/Library/Homebrew/test/formula_spec.rb @@ -60,6 +60,11 @@ expect { klass.new }.to raise_error(ArgumentError) end + specify "formula instantiation without a subclass" do + expect { described_class.new(name, path, spec) } + .to raise_error(RuntimeError, "Do not call `Formula.new' directly without a subclass.") + end + context "when in a Tap" do let(:tap) { Tap.fetch("foo", "bar") } let(:path) { (tap.path/"Formula/#{name}.rb") } diff --git a/Library/Homebrew/test/requirement_spec.rb b/Library/Homebrew/test/requirement_spec.rb index 7789e2f5091f1..0024127c20bf2 100644 --- a/Library/Homebrew/test/requirement_spec.rb +++ b/Library/Homebrew/test/requirement_spec.rb @@ -10,6 +10,13 @@ let(:klass) { Class.new(described_class) } + describe "base class" do + it "raises an error when instantiated" do + expect { described_class.new } + .to raise_error(RuntimeError, "Requirement is declared as abstract; it cannot be instantiated") + end + end + describe "#tags" do subject(:req) { klass.new(tags) } @@ -52,6 +59,17 @@ describe "#fatal is omitted" do it { is_expected.not_to be_fatal } end + + describe "in subclasses" do + it "raises an error when instantiated" do + expect do + Class.new(described_class) do + def fatal? = false + end + end + .to raise_error(RuntimeError, /The method `fatal\?` on #{described_class.name} was declared as final/) + end + end end describe "#satisfied?" do