diff --git a/Library/Homebrew/cask.rb b/Library/Homebrew/cask.rb
index f4b32a21fd4a4fd4ed11b3e3440e3cb3e2bd6676..dacb4c9da3f46c2ff21ffe2461476404a55f24f4 100644
--- a/Library/Homebrew/cask.rb
+++ b/Library/Homebrew/cask.rb
@@ -23,4 +23,3 @@ require "cask/staged"
 require "cask/topological_hash"
 require "cask/url"
 require "cask/utils"
-require "cask/verify"
diff --git a/Library/Homebrew/cask/audit.rb b/Library/Homebrew/cask/audit.rb
index a9603a50e8e17fee1dd6917723975e914db29b81..1cc322e50730454122767afa8f93740e48bb3477 100644
--- a/Library/Homebrew/cask/audit.rb
+++ b/Library/Homebrew/cask/audit.rb
@@ -255,20 +255,20 @@ module Cask
       add_error "you should use sha256 :no_check when version is :latest"
     end
 
-    def check_sha256_actually_256(sha256: cask.sha256, stanza: "sha256")
-      odebug "Verifying #{stanza} string is a legal SHA-256 digest"
-      return unless sha256.is_a?(String)
-      return if sha256.length == 64 && sha256[/^[0-9a-f]+$/i]
+    def check_sha256_actually_256
+      odebug "Verifying sha256 string is a legal SHA-256 digest"
+      return unless cask.sha256.is_a?(Checksum)
+      return if cask.sha256.length == 64 && cask.sha256[/^[0-9a-f]+$/i]
 
-      add_error "#{stanza} string must be of 64 hexadecimal characters"
+      add_error "sha256 string must be of 64 hexadecimal characters"
     end
 
-    def check_sha256_invalid(sha256: cask.sha256, stanza: "sha256")
-      odebug "Verifying #{stanza} is not a known invalid value"
+    def check_sha256_invalid
+      odebug "Verifying sha256 is not a known invalid value"
       empty_sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
-      return unless sha256 == empty_sha256
+      return unless cask.sha256 == empty_sha256
 
-      add_error "cannot use the sha256 for an empty string in #{stanza}: #{empty_sha256}"
+      add_error "cannot use the sha256 for an empty string: #{empty_sha256}"
     end
 
     def check_latest_with_appcast
@@ -428,8 +428,7 @@ module Cask
       return unless download && cask.url
 
       odebug "Auditing download"
-      downloaded_path = download.perform
-      Verify.all(cask, downloaded_path)
+      download.fetch
     rescue => e
       add_error "download not possible: #{e}"
     end
diff --git a/Library/Homebrew/cask/cmd/fetch.rb b/Library/Homebrew/cask/cmd/fetch.rb
index 7add6ae1dc8a0f9949c859b3697484aa131e3f53..855efa7f0fbd6f67330af5256d5c3899161224b7 100644
--- a/Library/Homebrew/cask/cmd/fetch.rb
+++ b/Library/Homebrew/cask/cmd/fetch.rb
@@ -32,7 +32,6 @@ module Cask
         require "cask/installer"
 
         options = {
-          force:      args.force?,
           quarantine: args.quarantine?,
         }.compact
 
@@ -41,8 +40,9 @@ module Cask
         casks.each do |cask|
           puts Installer.caveats(cask)
           ohai "Downloading external files for Cask #{cask}"
-          downloaded_path = Download.new(cask, **options).perform
-          Verify.all(cask, downloaded_path)
+          download = Download.new(cask, **options)
+          download.clear_cache if args.force?
+          downloaded_path = download.fetch
           ohai "Success! Downloaded to -> #{downloaded_path}"
         end
       end
diff --git a/Library/Homebrew/cask/cmd/zap.rb b/Library/Homebrew/cask/cmd/zap.rb
index d5b5dc3c80a2c5b4bf52dbd168f406649f937912..028c103afda4ddb32a201233807506e963736b38 100644
--- a/Library/Homebrew/cask/cmd/zap.rb
+++ b/Library/Homebrew/cask/cmd/zap.rb
@@ -32,6 +32,15 @@ module Cask
 
       sig { void }
       def run
+        self.class.zap_casks(*casks, verbose: args.verbose?, force: args.force?)
+      end
+
+      sig { params(casks: Cask, force: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean)).void }
+      def self.zap_casks(
+        *casks,
+        force: nil,
+        verbose: nil
+      )
         require "cask/installer"
 
         casks.each do |cask|
@@ -43,10 +52,10 @@ module Cask
               cask = CaskLoader.load(installed_caskfile)
             end
           else
-            raise CaskNotInstalledError, cask unless args.force?
+            raise CaskNotInstalledError, cask unless force
           end
 
-          Installer.new(cask, verbose: args.verbose?, force: args.force?).zap
+          Installer.new(cask, verbose: verbose, force: force).zap
         end
       end
     end
diff --git a/Library/Homebrew/cask/download.rb b/Library/Homebrew/cask/download.rb
index 12e8a575ff3253890611781e22e521f160985eb9..5d577eeb2eaa3e33e0fc334cfa578b5f5097d7a9 100644
--- a/Library/Homebrew/cask/download.rb
+++ b/Library/Homebrew/cask/download.rb
@@ -4,25 +4,32 @@
 require "fileutils"
 require "cask/cache"
 require "cask/quarantine"
-require "cask/verify"
 
 module Cask
   # A download corresponding to a {Cask}.
   #
   # @api private
   class Download
+    include Context
+
     attr_reader :cask
 
-    def initialize(cask, force: false, quarantine: nil)
+    def initialize(cask, quarantine: nil)
       @cask = cask
-      @force = force
       @quarantine = quarantine
     end
 
-    def perform
-      clear_cache
-      fetch
-      quarantine
+    def fetch(verify_download_integrity: true)
+      downloaded_path = begin
+        downloader.fetch
+        downloader.cached_location
+      rescue => e
+        error = CaskError.new("Download failed on Cask '#{cask}' with message: #{e}")
+        error.set_backtrace e.backtrace
+        raise error
+      end
+      quarantine(downloaded_path)
+      self.verify_download_integrity(downloaded_path) if verify_download_integrity
       downloaded_path
     end
 
@@ -33,32 +40,43 @@ module Cask
       end
     end
 
-    private
-
-    attr_reader :force
-    attr_accessor :downloaded_path
-
     def clear_cache
-      downloader.clear_cache if force
+      downloader.clear_cache
+    end
+
+    def cached_download
+      downloader.cached_location
     end
 
-    def fetch
-      downloader.fetch
-      @downloaded_path = downloader.cached_location
-    rescue => e
-      error = CaskError.new("Download failed on Cask '#{cask}' with message: #{e}")
-      error.set_backtrace e.backtrace
-      raise error
+    def verify_download_integrity(fn)
+      if @cask.sha256 == :no_check
+        opoo "No checksum defined for cask '#{@cask}', skipping verification."
+        return
+      end
+
+      begin
+        ohai "Verifying checksum for cask '#{@cask}'." if verbose?
+        fn.verify_checksum(@cask.sha256)
+      rescue ChecksumMissingError
+        opoo <<~EOS
+          Cannot verify integrity of '#{fn.basename}'.
+          No checksum was provided for this cask.
+          For your reference, the checksum is:
+            sha256 "#{fn.sha256}"
+        EOS
+      end
     end
 
-    def quarantine
+    private
+
+    def quarantine(path)
       return if @quarantine.nil?
       return unless Quarantine.available?
 
       if @quarantine
-        Quarantine.cask!(cask: @cask, download_path: @downloaded_path)
+        Quarantine.cask!(cask: @cask, download_path: path)
       else
-        Quarantine.release!(download_path: @downloaded_path)
+        Quarantine.release!(download_path: path)
       end
     end
   end
diff --git a/Library/Homebrew/cask/dsl.rb b/Library/Homebrew/cask/dsl.rb
index 149fedb7dec0fc17f9313f69cd0e8b3e8ad06b61..b9f0ca2064d19512e8b3ba9f35476a368845b0ea 100644
--- a/Library/Homebrew/cask/dsl.rb
+++ b/Library/Homebrew/cask/dsl.rb
@@ -205,11 +205,14 @@ module Cask
 
     def sha256(arg = nil)
       set_unique_stanza(:sha256, arg.nil?) do
-        if !arg.is_a?(String) && arg != :no_check
+        case arg
+        when :no_check
+          arg
+        when String
+          Checksum.new(:sha256, arg)
+        else
           raise CaskInvalidError.new(cask, "invalid 'sha256' value: '#{arg.inspect}'")
         end
-
-        arg
       end
     end
 
diff --git a/Library/Homebrew/cask/exceptions.rb b/Library/Homebrew/cask/exceptions.rb
index b5576e9dee0fc08390cbd3089ebe7321c7dbd29f..9778c297255fe0231d64e8be8d78f28bd8519f56 100644
--- a/Library/Homebrew/cask/exceptions.rb
+++ b/Library/Homebrew/cask/exceptions.rb
@@ -207,76 +207,6 @@ module Cask
     end
   end
 
-  # Error with a cask's checksum.
-  #
-  # @api private
-  class CaskSha256Error < AbstractCaskErrorWithToken
-    attr_reader :expected, :actual
-
-    def initialize(token, expected = nil, actual = nil)
-      super(token)
-      @expected = expected
-      @actual = actual
-    end
-  end
-
-  # Error when a cask's checksum is missing.
-  #
-  # @api private
-  class CaskSha256MissingError < CaskSha256Error
-    extend T::Sig
-
-    sig { returns(String) }
-    def to_s
-      <<~EOS
-        Cask '#{token}' requires a checksum:
-          #{Formatter.identifier("sha256 \"#{actual}\"")}
-      EOS
-    end
-  end
-
-  # Error when a cask's checksum does not match.
-  #
-  # @api private
-  class CaskSha256MismatchError < CaskSha256Error
-    extend T::Sig
-
-    attr_reader :path
-
-    def initialize(token, expected, actual, path)
-      super(token, expected, actual)
-      @path = path
-    end
-
-    sig { returns(String) }
-    def to_s
-      <<~EOS
-        Checksum for Cask '#{token}' does not match.
-        Expected: #{Formatter.success(expected.to_s)}
-          Actual: #{Formatter.error(actual.to_s)}
-            File: #{path}
-        To retry an incomplete download, remove the file above.
-        If the issue persists, visit:
-          #{Formatter.url("https://github.com/Homebrew/homebrew-cask/blob/HEAD/doc/reporting_bugs/checksum_does_not_match_error.md")}
-      EOS
-    end
-  end
-
-  # Error when a cask has no checksum and the `--require-sha` flag is passed.
-  #
-  # @api private
-  class CaskNoShasumError < CaskSha256Error
-    extend T::Sig
-
-    sig { returns(String) }
-    def to_s
-      <<~EOS
-        Cask '#{token}' does not have a sha256 checksum defined and was not installed.
-        This means you have the #{Formatter.identifier("--require-sha")} option set, perhaps in your HOMEBREW_CASK_OPTS.
-      EOS
-    end
-  end
-
   # Error during quarantining of a file.
   #
   # @api private
diff --git a/Library/Homebrew/cask/installer.rb b/Library/Homebrew/cask/installer.rb
index 5c918660863401ae4332b7849ece222bb472e091..23eda92fe7a53c356c0fa9c9b7cf7ce535583e91 100644
--- a/Library/Homebrew/cask/installer.rb
+++ b/Library/Homebrew/cask/installer.rb
@@ -8,7 +8,6 @@ require "cask/topological_hash"
 require "cask/config"
 require "cask/download"
 require "cask/staged"
-require "cask/verify"
 require "cask/quarantine"
 
 require "cgi"
@@ -68,7 +67,6 @@ module Cask
       satisfy_dependencies
 
       download
-      verify
     end
 
     def stage
@@ -156,7 +154,7 @@ module Cask
       return @downloaded_path if @downloaded_path
 
       odebug "Downloading"
-      @downloaded_path = Download.new(@cask, force: false, quarantine: quarantine?).perform
+      @downloaded_path = Download.new(@cask, quarantine: quarantine?).fetch
       odebug "Downloaded to -> #{@downloaded_path}"
       @downloaded_path
     end
@@ -165,11 +163,10 @@ module Cask
       odebug "Checking cask has checksum"
       return unless @cask.sha256 == :no_check
 
-      raise CaskNoShasumError, @cask.token
-    end
-
-    def verify
-      Verify.all(@cask, @downloaded_path)
+      raise CaskError, <<~EOS
+        Cask '#{@cask}' does not have a sha256 checksum defined and was not installed.
+        This means you have the #{Formatter.identifier("--require-sha")} option set, perhaps in your HOMEBREW_CASK_OPTS.
+      EOS
     end
 
     def primary_container
diff --git a/Library/Homebrew/cask/verify.rb b/Library/Homebrew/cask/verify.rb
deleted file mode 100644
index 65f9998dee45ec49365c2602677b76da67e0341a..0000000000000000000000000000000000000000
--- a/Library/Homebrew/cask/verify.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# typed: false
-# frozen_string_literal: true
-
-module Cask
-  # Helper module for verifying a cask's checksum.
-  #
-  # @api private
-  module Verify
-    module_function
-
-    def all(cask, downloaded_path)
-      if cask.sha256 == :no_check
-        ohai "No SHA-256 checksum defined for Cask '#{cask}', skipping verification."
-        return
-      end
-
-      ohai "Verifying SHA-256 checksum for Cask '#{cask}'."
-
-      expected = cask.sha256
-      computed = downloaded_path.sha256
-
-      raise CaskSha256MissingError.new(cask.token, expected, computed) if expected.nil? || expected.empty?
-
-      return if expected == computed
-
-      ohai "Note: Running `brew update` may fix SHA-256 checksum errors."
-      raise CaskSha256MismatchError.new(cask.token, expected, computed, downloaded_path)
-    end
-  end
-end
diff --git a/Library/Homebrew/checksum.rb b/Library/Homebrew/checksum.rb
index 7a3219513d75f585afa811e66c649e2522fb3f69..a0489972b902353b9ed085f1f5d5ac304c8ed045 100644
--- a/Library/Homebrew/checksum.rb
+++ b/Library/Homebrew/checksum.rb
@@ -13,12 +13,19 @@ class Checksum
 
   def initialize(hash_type, hexdigest)
     @hash_type = hash_type
-    @hexdigest = hexdigest
+    @hexdigest = hexdigest.downcase
   end
 
-  delegate [:empty?, :to_s] => :@hexdigest
+  delegate [:empty?, :to_s, :length, :[]] => :@hexdigest
 
   def ==(other)
-    hash_type == other&.hash_type && hexdigest == other.hexdigest
+    case other
+    when String
+      to_s == other.downcase
+    when Checksum
+      hash_type == other.hash_type && hexdigest == other.hexdigest
+    else
+      false
+    end
   end
 end
diff --git a/Library/Homebrew/cli/args.rb b/Library/Homebrew/cli/args.rb
index b12f32712b9f2eb07f7e97beb42a28fcd0d72834..c9bdd72ccf740e38a326d32781bd4c6953ebaa2f 100644
--- a/Library/Homebrew/cli/args.rb
+++ b/Library/Homebrew/cli/args.rb
@@ -113,7 +113,7 @@ module Homebrew
 
       def build_from_source_formulae
         if build_from_source? || build_bottle?
-          named.to_formulae.map(&:full_name)
+          named.to_formulae_and_casks.select { |f| f.is_a?(Formula) }.map(&:full_name)
         else
           []
         end
diff --git a/Library/Homebrew/cli/named_args.rb b/Library/Homebrew/cli/named_args.rb
index f827bc070f452ac39e76fed51bc08dbff4957294..87663712c940d728c22adb2ae418f7772ed7baef 100644
--- a/Library/Homebrew/cli/named_args.rb
+++ b/Library/Homebrew/cli/named_args.rb
@@ -35,10 +35,10 @@ module Homebrew
         @to_formulae ||= to_formulae_and_casks(only: :formula).freeze
       end
 
-      def to_formulae_and_casks(only: nil, method: nil)
+      def to_formulae_and_casks(only: nil, ignore_unavailable: nil, method: nil)
         @to_formulae_and_casks ||= {}
         @to_formulae_and_casks[only] ||= begin
-          to_objects(only: only, method: method).reject { |o| o.is_a?(Tap) }.freeze
+          to_objects(only: only, ignore_unavailable: ignore_unavailable, method: method).freeze
         end
       end
 
@@ -49,10 +49,10 @@ module Homebrew
                                                 .map(&:freeze).freeze
       end
 
-      def to_formulae_and_casks_and_unavailable(method: nil)
+      def to_formulae_and_casks_and_unavailable(only: nil, method: nil)
         @to_formulae_casks_unknowns ||= {}
         @to_formulae_casks_unknowns[method] = downcased_unique_named.map do |name|
-          load_formula_or_cask(name, method: method)
+          load_formula_or_cask(name, only: only, method: method)
         rescue FormulaOrCaskUnavailableError => e
           e
         end.uniq.freeze
@@ -68,6 +68,9 @@ module Homebrew
               resolve_formula(name)
             when :keg
               resolve_keg(name)
+            when :kegs
+              rack = Formulary.to_rack(name)
+              rack.directory? ? rack.subdirs.map { |d| Keg.new(d) } : []
             else
               raise
             end
@@ -108,10 +111,12 @@ module Homebrew
       # Convert named arguments to {Formula} or {Cask} objects.
       # If both a formula and cask exist with the same name, returns the
       # formula and prints a warning unless `only` is specified.
-      def to_objects(only: nil, method: nil)
+      def to_objects(only: nil, ignore_unavailable: nil, method: nil)
         @to_objects ||= {}
-        @to_objects[only] ||= downcased_unique_named.map do |name|
+        @to_objects[only] ||= downcased_unique_named.flat_map do |name|
           load_formula_or_cask(name, only: only, method: method)
+        rescue NoSuchKegError, FormulaUnavailableError, Cask::CaskUnavailableError
+          ignore_unavailable ? [] : raise
         end.uniq.freeze
       end
       private :to_objects
@@ -142,7 +147,7 @@ module Homebrew
             paths << formula_path if formula_path.exist?
             paths << cask_path if cask_path.exist?
 
-            paths.empty? ? name : paths
+            paths.empty? ? Pathname(name) : paths
           end
         end.uniq.freeze
       end
@@ -159,11 +164,17 @@ module Homebrew
         end
       end
 
-      sig { params(only: T.nilable(Symbol)).returns([T::Array[Keg], T::Array[Cask::Cask]]) }
-      def to_kegs_to_casks(only: nil)
-        @to_kegs_to_casks ||= to_formulae_and_casks(only: only, method: :keg)
-                              .partition { |o| o.is_a?(Keg) }
-                              .map(&:freeze).freeze
+      sig do
+        params(only: T.nilable(Symbol), ignore_unavailable: T.nilable(T::Boolean), all_kegs: T.nilable(T::Boolean))
+          .returns([T::Array[Keg], T::Array[Cask::Cask]])
+      end
+      def to_kegs_to_casks(only: nil, ignore_unavailable: nil, all_kegs: nil)
+        method = all_kegs ? :kegs : :keg
+        @to_kegs_to_casks ||= {}
+        @to_kegs_to_casks[method] ||=
+          to_formulae_and_casks(only: only, ignore_unavailable: ignore_unavailable, method: method)
+          .partition { |o| o.is_a?(Keg) }
+          .map(&:freeze).freeze
       end
 
       sig { returns(T::Array[String]) }
diff --git a/Library/Homebrew/cmd/fetch.rb b/Library/Homebrew/cmd/fetch.rb
index ac03b5df072419e24cc432c6994a5b2d77a996c2..cb39cc36807baee0d3f8fde35a6b5fa992a83169 100644
--- a/Library/Homebrew/cmd/fetch.rb
+++ b/Library/Homebrew/cmd/fetch.rb
@@ -4,6 +4,7 @@
 require "formula"
 require "fetch"
 require "cli/parser"
+require "cask/download"
 
 module Homebrew
   extend T::Sig
@@ -18,8 +19,8 @@ module Homebrew
       usage_banner <<~EOS
         `fetch` [<options>] <formula>
 
-        Download a bottle (if available) or source packages for <formula>.
-        For tarballs, also print SHA-256 checksums.
+        Download a bottle (if available) or source packages for <formula>e
+        and binaries for <cask>s. For files, also print SHA-256 checksums.
       EOS
       switch "--HEAD",
              description: "Fetch HEAD version instead of stable version."
@@ -42,58 +43,98 @@ module Homebrew
       switch "--force-bottle",
              description: "Download a bottle if it exists for the current or newest version of macOS, "\
                           "even if it would not be used during installation."
+      switch "--[no-]quarantine",
+             description: "Disable/enable quarantining of downloads (default: enabled).",
+             env:         :cask_opts_quarantine
+
+      switch "--formula", "--formulae",
+             description: "Treat all named arguments as formulae."
+      switch "--cask", "--casks",
+             description: "Treat all named arguments as casks."
+      conflicts "--formula", "--cask"
 
       conflicts "--devel", "--HEAD"
       conflicts "--build-from-source", "--build-bottle", "--force-bottle"
-      min_named :formula
+      conflicts "--cask", "--HEAD"
+      conflicts "--cask", "--devel"
+      conflicts "--cask", "--deps"
+      conflicts "--cask", "-s"
+      conflicts "--cask", "--build-bottle"
+      conflicts "--cask", "--force-bottle"
+
+      min_named :formula_or_cask
     end
   end
 
   def fetch
     args = fetch_args.parse
 
-    if args.deps?
-      bucket = []
-      args.named.to_formulae.each do |f|
-        bucket << f
-        bucket.concat f.recursive_dependencies.map(&:to_formula)
+    only = :formula if args.formula? && !args.cask?
+    only = :cask if args.cask? && !args.formula?
+
+    bucket = if args.deps?
+      args.named.to_formulae_and_casks.flat_map do |formula_or_cask|
+        case formula_or_cask
+        when Formula
+          f = formula_or_cask
+
+          [f, *f.recursive_dependencies.map(&:to_formula)]
+        else
+          formula_or_cask
+        end
       end
-      bucket.uniq!
     else
-      bucket = args.named.to_formulae
-    end
+      args.named.to_formulae_and_casks(only: only)
+    end.uniq
 
     puts "Fetching: #{bucket * ", "}" if bucket.size > 1
-    bucket.each do |f|
-      f.print_tap_action verb: "Fetching"
-
-      fetched_bottle = false
-      if fetch_bottle?(f, args: args)
-        begin
-          fetch_formula(f.bottle, args: args)
-        rescue Interrupt
-          raise
-        rescue => e
-          raise if Homebrew::EnvConfig.developer?
-
-          fetched_bottle = false
-          onoe e.message
-          opoo "Bottle fetch failed: fetching the source."
-        else
-          fetched_bottle = true
+    bucket.each do |formula_or_cask|
+      case formula_or_cask
+      when Formula
+        f = formula_or_cask
+
+        f.print_tap_action verb: "Fetching"
+
+        fetched_bottle = false
+        if fetch_bottle?(f, args: args)
+          begin
+            fetch_formula(f.bottle, args: args)
+          rescue Interrupt
+            raise
+          rescue => e
+            raise if Homebrew::EnvConfig.developer?
+
+            fetched_bottle = false
+            onoe e.message
+            opoo "Bottle fetch failed: fetching the source."
+          else
+            fetched_bottle = true
+          end
         end
-      end
 
-      next if fetched_bottle
+        next if fetched_bottle
 
-      fetch_formula(f, args: args)
+        fetch_formula(f, args: args)
 
-      f.resources.each do |r|
-        fetch_resource(r, args: args)
-        r.patches.each { |p| fetch_patch(p, args: args) if p.external? }
-      end
+        f.resources.each do |r|
+          fetch_resource(r, args: args)
+          r.patches.each { |p| fetch_patch(p, args: args) if p.external? }
+        end
+
+        f.patchlist.each { |p| fetch_patch(p, args: args) if p.external? }
+      else
+        cask = formula_or_cask
+
+        options = {
+          force:      args.force?,
+          quarantine: args.quarantine?,
+        }.compact
+
+        options[:quarantine] = true if options[:quarantine].nil?
 
-      f.patchlist.each { |p| fetch_patch(p, args: args) if p.external? }
+        download = Cask::Download.new(cask, **options)
+        fetch_cask(download, args: args)
+      end
     end
   end
 
@@ -112,6 +153,13 @@ module Homebrew
     opoo "Formula reports different #{e.hash_type}: #{e.expected}"
   end
 
+  def fetch_cask(cask_download, args:)
+    fetch_fetchable cask_download, args: args
+  rescue ChecksumMismatchError => e
+    retry if retry_fetch?(cask_download, args: args)
+    opoo "Cask reports different #{e.hash_type}: #{e.expected}"
+  end
+
   def fetch_patch(p, args:)
     fetch_fetchable p, args: args
   rescue ChecksumMismatchError => e
diff --git a/Library/Homebrew/cmd/info.rb b/Library/Homebrew/cmd/info.rb
index 908642fcb8834656a669b1b5cdd4385924d515fc..80c8723438707a5c9d6aa8c2d54eedbd889450e5 100644
--- a/Library/Homebrew/cmd/info.rb
+++ b/Library/Homebrew/cmd/info.rb
@@ -25,11 +25,11 @@ module Homebrew
   def info_args
     Homebrew::CLI::Parser.new do
       usage_banner <<~EOS
-        `info` [<options>] [<formula>]
+        `info` [<options>] [<formula>|<cask>]
 
         Display brief statistics for your Homebrew installation.
 
-        If <formula> is provided, show summary of information about <formula>.
+        If a <formula> or <cask> is provided, show summary of information about it.
       EOS
       switch "--analytics",
              description: "List global Homebrew analytics data or, if specified, installation and "\
@@ -61,13 +61,23 @@ module Homebrew
       switch "-v", "--verbose",
              description: "Show more verbose analytics data for <formula>."
 
+      switch "--formula", "--formulae",
+             description: "Treat all named arguments as formulae."
+      switch "--cask", "--casks",
+             description: "Treat all named arguments as casks."
+      conflicts "--formula", "--cask"
+
       conflicts "--installed", "--all"
     end
   end
 
+  sig { void }
   def info
     args = info_args.parse
 
+    only = :formula if args.formula? && !args.cask?
+    only = :cask if args.cask? && !args.formula?
+
     if args.analytics?
       if args.days.present? && !VALID_DAYS.include?(args.days)
         raise UsageError, "--days must be one of #{VALID_DAYS.join(", ")}"
@@ -83,20 +93,21 @@ module Homebrew
         end
       end
 
-      print_analytics(args: args)
+      print_analytics(args: args, only: only)
     elsif args.json
-      print_json(args: args)
+      print_json(args: args, only: only)
     elsif args.github?
       raise FormulaOrCaskUnspecifiedError if args.no_named?
 
-      exec_browser(*args.named.to_formulae_and_casks.map { |f| github_info(f) })
+      exec_browser(*args.named.to_formulae_and_casks(only: only).map { |f| github_info(f) })
     elsif args.no_named?
       print_statistics
     else
-      print_info(args: args)
+      print_info(args: args, only: only)
     end
   end
 
+  sig { void }
   def print_statistics
     return unless HOMEBREW_CELLAR.exist?
 
@@ -104,13 +115,14 @@ module Homebrew
     puts "#{count} #{"keg".pluralize(count)}, #{HOMEBREW_CELLAR.dup.abv}"
   end
 
-  def print_analytics(args:)
+  sig { params(args: CLI::Args, only: T.nilable(Symbol)).void }
+  def print_analytics(args:, only: nil)
     if args.no_named?
       Utils::Analytics.output(args: args)
       return
     end
 
-    args.named.to_formulae_and_casks_and_unavailable.each_with_index do |obj, i|
+    args.named.to_formulae_and_casks_and_unavailable(only: only).each_with_index do |obj, i|
       puts unless i.zero?
 
       case obj
@@ -126,8 +138,9 @@ module Homebrew
     end
   end
 
-  def print_info(args:)
-    args.named.to_formulae_and_casks_and_unavailable.each_with_index do |obj, i|
+  sig { params(args: CLI::Args, only: T.nilable(Symbol)).void }
+  def print_info(args:, only: nil)
+    args.named.to_formulae_and_casks_and_unavailable(only: only).each_with_index do |obj, i|
       puts unless i.zero?
 
       case obj
@@ -159,7 +172,8 @@ module Homebrew
     version_hash[version]
   end
 
-  def print_json(args:)
+  sig { params(args: CLI::Args, only: T.nilable(Symbol)).void }
+  def print_json(args:, only: nil)
     raise FormulaOrCaskUnspecifiedError if !(args.all? || args.installed?) && args.no_named?
 
     json = case json_version(args.json)
@@ -179,7 +193,7 @@ module Homebrew
       elsif args.installed?
         [Formula.installed.sort, Cask::Caskroom.casks.sort_by(&:full_name)]
       else
-        args.named.to_formulae_to_casks
+        args.named.to_formulae_to_casks(only: only)
       end
 
       {
diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb
index 7b787d923efb733b90b2e40d8c8096a3ce8a2f3b..bb7968a9bf168a8a777eb9b945d8c8477951efbc 100644
--- a/Library/Homebrew/cmd/uninstall.rb
+++ b/Library/Homebrew/cmd/uninstall.rb
@@ -1,4 +1,4 @@
-# typed: false
+# typed: true
 # frozen_string_literal: true
 
 require "keg"
@@ -19,12 +19,17 @@ module Homebrew
   def uninstall_args
     Homebrew::CLI::Parser.new do
       usage_banner <<~EOS
-        `uninstall`, `rm`, `remove` [<options>] <formula>
+        `uninstall`, `rm`, `remove` [<options>] <formula>|<cask>
 
-        Uninstall <formula>.
+        Uninstall a <formula> or <cask>.
       EOS
       switch "-f", "--force",
-             description: "Delete all installed versions of <formula>."
+             description: "Delete all installed versions of <formula>. Uninstall even if <cask> is not " \
+                          "installed, overwrite existing files and ignore errors when removing files."
+      switch "--zap",
+             description: "Remove all files associated with a <cask>. " \
+                          "*May remove files which are shared between applications.*"
+      conflicts "--formula", "--zap"
       switch "--ignore-dependencies",
              description: "Don't fail uninstall, even if <formula> is a dependency of any installed "\
                           "formulae."
@@ -35,7 +40,7 @@ module Homebrew
              description: "Treat all named arguments as casks."
       conflicts "--formula", "--cask"
 
-      min_named :formula
+      min_named :formula_or_cask
     end
   end
 
@@ -45,51 +50,29 @@ module Homebrew
     only = :formula if args.formula? && !args.cask?
     only = :cask if args.cask? && !args.formula?
 
-    if args.force?
-      casks = []
-      kegs_by_rack = {}
+    all_kegs, casks = args.named.to_kegs_to_casks(only: only, ignore_unavailable: args.force?, all_kegs: args.force?)
+    kegs_by_rack = all_kegs.group_by(&:rack)
 
-      args.named.each do |name|
-        if only != :cask
-          rack = Formulary.to_rack(name)
-          kegs_by_rack[rack] = rack.subdirs.map { |d| Keg.new(d) } if rack.directory?
-        end
-
-        next if only == :formula
+    Uninstall.uninstall_kegs(
+      kegs_by_rack,
+      force:               args.force?,
+      ignore_dependencies: args.ignore_dependencies?,
+      named_args:          args.named,
+    )
 
-        begin
-          casks << Cask::CaskLoader.load(name)
-        rescue Cask::CaskUnavailableError
-          # Since the uninstall was forced, ignore any unavailable casks.
-        end
-      end
+    if args.zap?
+      Cask::Cmd::Zap.zap_casks(
+        *casks,
+        verbose: args.verbose?,
+        force:   args.force?,
+      )
     else
-      all_kegs, casks = args.named.to_kegs_to_casks(only: only)
-      kegs_by_rack = all_kegs.group_by(&:rack)
-    end
-
-    Uninstall.uninstall_kegs(kegs_by_rack,
-                             force:               args.force?,
-                             ignore_dependencies: args.ignore_dependencies?,
-                             named_args:          args.named)
-
-    return if casks.blank?
-
-    Cask::Cmd::Uninstall.uninstall_casks(
-      *casks,
-      binaries: EnvConfig.cask_opts_binaries?,
-      verbose:  args.verbose?,
-      force:    args.force?,
-    )
-  rescue MultipleVersionsInstalledError => e
-    ofail e
-  ensure
-    # If we delete Cellar/newname, then Cellar/oldname symlink
-    # can become broken and we have to remove it.
-    if HOMEBREW_CELLAR.directory?
-      HOMEBREW_CELLAR.children.each do |rack|
-        rack.unlink if rack.symlink? && !rack.resolved_path_exists?
-      end
+      Cask::Cmd::Uninstall.uninstall_casks(
+        *casks,
+        binaries: EnvConfig.cask_opts_binaries?,
+        verbose:  args.verbose?,
+        force:    args.force?,
+      )
     end
   end
 end
diff --git a/Library/Homebrew/dev-cmd/edit.rb b/Library/Homebrew/dev-cmd/edit.rb
index 5526e9809e917cfcaf1664fc58a78b6de04c7ed1..dc4a814e5ac58b5464e38f1b24397c32c8bc4eda 100644
--- a/Library/Homebrew/dev-cmd/edit.rb
+++ b/Library/Homebrew/dev-cmd/edit.rb
@@ -1,4 +1,4 @@
-# typed: false
+# typed: true
 # frozen_string_literal: true
 
 require "formula"
@@ -13,17 +13,27 @@ module Homebrew
   def edit_args
     Homebrew::CLI::Parser.new do
       usage_banner <<~EOS
-        `edit` [<formula>]
+        `edit` [<formula>|<cask>]
 
-        Open <formula> in the editor set by `EDITOR` or `HOMEBREW_EDITOR`, or open the
-        Homebrew repository for editing if no formula is provided.
+        Open a <formula> or <cask> in the editor set by `EDITOR` or `HOMEBREW_EDITOR`,
+        or open the Homebrew repository for editing if no formula is provided.
       EOS
+
+      switch "--formula", "--formulae",
+             description: "Treat all named arguments as formulae."
+      switch "--cask", "--casks",
+             description: "Treat all named arguments as casks."
+      conflicts "--formula", "--cask"
     end
   end
 
+  sig { void }
   def edit
     args = edit_args.parse
 
+    only = :formula if args.formula? && !args.cask?
+    only = :cask if args.cask? && !args.formula?
+
     unless (HOMEBREW_REPOSITORY/".git").directory?
       raise <<~EOS
         Changes will be lost!
@@ -32,7 +42,7 @@ module Homebrew
       EOS
     end
 
-    paths = args.named.to_formulae_paths.select do |path|
+    paths = args.named.to_paths(only: only).select do |path|
       next path if path.exist?
 
       raise UsageError, "#{path} doesn't exist on disk. " \
diff --git a/Library/Homebrew/exceptions.rb b/Library/Homebrew/exceptions.rb
index fadca48894bd1f2f2a335086637e96f7893874f9..428140ac4d22ac0345ac9575b6b06f9200de1a28 100644
--- a/Library/Homebrew/exceptions.rb
+++ b/Library/Homebrew/exceptions.rb
@@ -617,15 +617,15 @@ class ChecksumMissingError < ArgumentError; end
 class ChecksumMismatchError < RuntimeError
   attr_reader :expected, :hash_type
 
-  def initialize(fn, expected, actual)
+  def initialize(path, expected, actual)
     @expected = expected
     @hash_type = expected.hash_type.to_s.upcase
 
     super <<~EOS
       #{@hash_type} mismatch
-      Expected: #{expected}
-        Actual: #{actual}
-       Archive: #{fn}
+      Expected: #{Formatter.success(expected.to_s)}
+        Actual: #{Formatter.error(actual.to_s)}
+          File: #{path}
       To retry an incomplete download, remove the file above.
     EOS
   end
diff --git a/Library/Homebrew/extend/os/mac/requirements/java_requirement.rb b/Library/Homebrew/extend/os/mac/requirements/java_requirement.rb
index ebb4590511413055d701e8417cc3fb84e7d21396..5619850d0665c93fef2f3a89789c2c08786e8142 100644
--- a/Library/Homebrew/extend/os/mac/requirements/java_requirement.rb
+++ b/Library/Homebrew/extend/os/mac/requirements/java_requirement.rb
@@ -27,11 +27,13 @@ class JavaRequirement < Requirement
 
   def java_home_cmd
     # TODO: enable for all macOS versions and Linux on next minor release
-    #       but --version is broken on Big Sur today.
-    if @version && MacOS.version >= :big_sur
-      odisabled "depends_on :java",
-                '"depends_on "openjdk@11", "depends_on "openjdk@8" or "depends_on "openjdk"'
+    #       but --version with ranges is broken on Big Sur today.
+    if MacOS.version >= :big_sur && @version&.end_with?("+")
+      odisabled %Q(depends_on java: "#{@version}"),
+                'depends_on "openjdk@11", depends_on "openjdk@8" or depends_on "openjdk"'
     end
+    # odeprecated "depends_on :java",
+    #             'depends_on "openjdk@11", depends_on "openjdk@8" or depends_on "openjdk"'
 
     return unless File.executable?("/usr/libexec/java_home")
 
diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb
index ee2d2b02189fc8c8da8f9f3edaf3e35f0430e820..be714073e8516f0ce3a8b86dc1008d7193e796d2 100644
--- a/Library/Homebrew/formula_installer.rb
+++ b/Library/Homebrew/formula_installer.rb
@@ -654,7 +654,7 @@ class FormulaInstaller
     @show_header = true unless deps.empty?
   end
 
-  sig { params(dep: Formula).void }
+  sig { params(dep: Dependency).void }
   def fetch_dependency(dep)
     df = dep.to_formula
     fi = FormulaInstaller.new(
@@ -675,7 +675,7 @@ class FormulaInstaller
     fi.fetch
   end
 
-  sig { params(dep: Formula, inherited_options: Options).void }
+  sig { params(dep: Dependency, inherited_options: Options).void }
   def install_dependency(dep, inherited_options)
     df = dep.to_formula
     tab = Tab.for_formula(df)
diff --git a/Library/Homebrew/resource.rb b/Library/Homebrew/resource.rb
index 68c237471a756f259e7c8382568be30aadff7715..c2a2b749a851690f5e9e0ec0f5ac77886af13b6f 100644
--- a/Library/Homebrew/resource.rb
+++ b/Library/Homebrew/resource.rb
@@ -146,13 +146,16 @@ class Resource
 
   def verify_download_integrity(fn)
     if fn.file?
-      ohai "Verifying #{fn.basename} checksum" if verbose?
+      ohai "Verifying checksum for '#{fn.basename}'." if verbose?
       fn.verify_checksum(checksum)
     end
   rescue ChecksumMissingError
-    opoo "Cannot verify integrity of #{fn.basename}"
-    puts "A checksum was not provided for this resource."
-    puts "For your reference the SHA-256 is: #{fn.sha256}"
+    opoo <<~EOS
+      Cannot verify integrity of '#{fn.basename}'.
+      No checksum was provided for this resource.
+      For your reference, the checksum is:
+        sha256 "#{fn.sha256}"
+    EOS
   end
 
   Checksum::TYPES.each do |type|
diff --git a/Library/Homebrew/tap_auditor.rb b/Library/Homebrew/tap_auditor.rb
index c38e9abaf6436c1d7cc1e5b627aa89188d730282..d8294815f27459a7354932a275c1ca88ce876cb3 100644
--- a/Library/Homebrew/tap_auditor.rb
+++ b/Library/Homebrew/tap_auditor.rb
@@ -10,7 +10,7 @@ module Homebrew
 
     attr_reader :name, :path, :tap_audit_exceptions, :problems
 
-    sig { params(tap: Tap, strict: T::Boolean).void }
+    sig { params(tap: Tap, strict: T.nilable(T::Boolean)).void }
     def initialize(tap, strict:)
       @name                 = tap.name
       @path                 = tap.path
diff --git a/Library/Homebrew/test/cask/audit_spec.rb b/Library/Homebrew/test/cask/audit_spec.rb
index 497780f47059f39895dc1786c4d4cf034205687a..285c6ec3cc662fb554be1785b589f02484a9df85 100644
--- a/Library/Homebrew/test/cask/audit_spec.rb
+++ b/Library/Homebrew/test/cask/audit_spec.rb
@@ -808,7 +808,6 @@ describe Cask::Audit, :cask do
       let(:cask_token) { "with-binary" }
       let(:cask) { Cask::CaskLoader.load(cask_token) }
       let(:download_double) { instance_double(Cask::Download) }
-      let(:verify) { class_double(Cask::Verify).as_stubbed_const }
       let(:message) { "Download Failed" }
 
       before do
@@ -817,19 +816,12 @@ describe Cask::Audit, :cask do
       end
 
       it "when download and verification succeed it does not fail" do
-        expect(download_double).to receive(:perform)
-        expect(verify).to receive(:all)
+        expect(download_double).to receive(:fetch)
         expect(subject).to pass
       end
 
       it "when download fails it fails" do
-        expect(download_double).to receive(:perform).and_raise(StandardError.new(message))
-        expect(subject).to fail_with(/#{message}/)
-      end
-
-      it "when verification fails it fails" do
-        expect(download_double).to receive(:perform)
-        expect(verify).to receive(:all).and_raise(StandardError.new(message))
+        expect(download_double).to receive(:fetch).and_raise(StandardError.new(message))
         expect(subject).to fail_with(/#{message}/)
       end
     end
diff --git a/Library/Homebrew/test/cask/cmd/fetch_spec.rb b/Library/Homebrew/test/cask/cmd/fetch_spec.rb
index bc68dc9ec03bfbd56e9ca06dd9ace7340c7925ad..e3d12e9b8320b6ee2508cea5efcfa87aac50cd45 100644
--- a/Library/Homebrew/test/cask/cmd/fetch_spec.rb
+++ b/Library/Homebrew/test/cask/cmd/fetch_spec.rb
@@ -36,7 +36,7 @@ describe Cask::Cmd::Fetch, :cask do
   end
 
   it "prevents double fetch (without nuking existing installation)" do
-    cached_location = Cask::Download.new(local_transmission).perform
+    cached_location = Cask::Download.new(local_transmission).fetch
 
     old_ctime = File.stat(cached_location).ctime
 
@@ -47,7 +47,7 @@ describe Cask::Cmd::Fetch, :cask do
   end
 
   it "allows double fetch with --force" do
-    cached_location = Cask::Download.new(local_transmission).perform
+    cached_location = Cask::Download.new(local_transmission).fetch
 
     old_ctime = File.stat(cached_location).ctime
     sleep(1)
diff --git a/Library/Homebrew/test/cask/cmd/install_spec.rb b/Library/Homebrew/test/cask/cmd/install_spec.rb
index 7c051d9d1fd989f130a565208f8437ccee080d11..adb7b6da2be5d1490475b11752b9a48771f79380 100644
--- a/Library/Homebrew/test/cask/cmd/install_spec.rb
+++ b/Library/Homebrew/test/cask/cmd/install_spec.rb
@@ -11,7 +11,6 @@ describe Cask::Cmd::Install, :cask do
   it "displays the installation progress" do
     output = Regexp.new <<~EOS
       ==> Downloading file:.*caffeine.zip
-      ==> Verifying SHA-256 checksum for Cask 'local-caffeine'.
       ==> Installing Cask local-caffeine
       ==> Moving App 'Caffeine.app' to '.*Caffeine.app'.
       .*local-caffeine was successfully installed!
diff --git a/Library/Homebrew/test/cask/cmd/reinstall_spec.rb b/Library/Homebrew/test/cask/cmd/reinstall_spec.rb
index 8b93c92fc3918260ff9f366b991cdeb0e52aa352..4cf73637a1c5ff0ba3649d150fb23606ae5736b5 100644
--- a/Library/Homebrew/test/cask/cmd/reinstall_spec.rb
+++ b/Library/Homebrew/test/cask/cmd/reinstall_spec.rb
@@ -14,7 +14,6 @@ describe Cask::Cmd::Reinstall, :cask do
     output = Regexp.new <<~EOS
       ==> Downloading file:.*caffeine.zip
       Already downloaded: .*--caffeine.zip
-      ==> Verifying SHA-256 checksum for Cask 'local-caffeine'.
       ==> Uninstalling Cask local-caffeine
       ==> Backing App 'Caffeine.app' up to '.*Caffeine.app'.
       ==> Removing App '.*Caffeine.app'.
diff --git a/Library/Homebrew/test/cask/cmd/style_spec.rb b/Library/Homebrew/test/cask/cmd/style_spec.rb
index 7862cfd51934ac9b0cd4f0761fbaf7b7127b9e85..c494be65d9b4e95b8c1a072bbdb7231a6a5b4c5e 100644
--- a/Library/Homebrew/test/cask/cmd/style_spec.rb
+++ b/Library/Homebrew/test/cask/cmd/style_spec.rb
@@ -75,7 +75,7 @@ describe Cask::Cmd::Style, :cask do
       end
 
       it "tries to find paths for all tokens" do
-        expect(Cask::CaskLoader).to receive(:load).twice.and_return(double("cask", sourcefile_path: nil))
+        expect(Cask::CaskLoader).to receive(:load).twice.and_return(instance_double(Cask::Cask, sourcefile_path: nil))
         subject
       end
     end
diff --git a/Library/Homebrew/test/cask/cmd/upgrade_spec.rb b/Library/Homebrew/test/cask/cmd/upgrade_spec.rb
index e930e00b7ac9163b64ba05d00f053494620e5987..4c9f1a319720f8d8f73d600fcb2b4da9792e1365 100644
--- a/Library/Homebrew/test/cask/cmd/upgrade_spec.rb
+++ b/Library/Homebrew/test/cask/cmd/upgrade_spec.rb
@@ -344,7 +344,7 @@ describe Cask::Cmd::Upgrade, :cask do
 
       expect {
         described_class.run("bad-checksum")
-      }.to raise_error(Cask::CaskSha256MismatchError).and(not_to_output(output_reverted).to_stderr)
+      }.to raise_error(ChecksumMismatchError).and(not_to_output(output_reverted).to_stderr)
 
       expect(bad_checksum).to be_installed
       expect(bad_checksum_path).to be_a_directory
diff --git a/Library/Homebrew/test/cask/verify_spec.rb b/Library/Homebrew/test/cask/download_spec.rb
similarity index 56%
rename from Library/Homebrew/test/cask/verify_spec.rb
rename to Library/Homebrew/test/cask/download_spec.rb
index 8acc50baefb50eb7553d8ef403e405ccb5814d10..c439c2200a018b4b456ed2ca3715b105ab76e170 100644
--- a/Library/Homebrew/test/cask/verify_spec.rb
+++ b/Library/Homebrew/test/cask/download_spec.rb
@@ -2,26 +2,30 @@
 # frozen_string_literal: true
 
 module Cask
-  describe Verify, :cask do
-    describe "::all" do
-      subject(:verification) { described_class.all(cask, downloaded_path) }
+  describe Download, :cask do
+    describe "#verify_download_integrity" do
+      subject(:verification) { described_class.new(cask).verify_download_integrity(downloaded_path) }
 
       let(:cask) { instance_double(Cask, token: "cask", sha256: expected_sha256) }
       let(:cafebabe) { "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" }
       let(:deadbeef) { "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" }
       let(:computed_sha256) { cafebabe }
-      let(:downloaded_path) { instance_double(Pathname, sha256: computed_sha256) }
+      let(:downloaded_path) { Pathname.new("cask.zip") }
+
+      before do
+        allow(downloaded_path).to receive(:sha256).and_return(computed_sha256)
+      end
 
       context "when the expected checksum is :no_check" do
         let(:expected_sha256) { :no_check }
 
         it "skips the check" do
-          expect { verification }.to output(/skipping verification/).to_stdout
+          expect { verification }.to output(/skipping verification/).to_stderr
         end
       end
 
       context "when expected and computed checksums match" do
-        let(:expected_sha256) { cafebabe }
+        let(:expected_sha256) { Checksum.new(:sha256, cafebabe) }
 
         it "does not raise an error" do
           expect { verification }.not_to raise_error
@@ -31,24 +35,24 @@ module Cask
       context "when the expected checksum is nil" do
         let(:expected_sha256) { nil }
 
-        it "raises an error" do
-          expect { verification }.to raise_error(CaskSha256MissingError, /sha256 "#{computed_sha256}"/)
+        it "outputs an error" do
+          expect { verification }.to output(/sha256 "#{computed_sha256}"/).to_stderr
         end
       end
 
       context "when the expected checksum is empty" do
-        let(:expected_sha256) { "" }
+        let(:expected_sha256) { Checksum.new(:sha256, "") }
 
-        it "raises an error" do
-          expect { verification }.to raise_error(CaskSha256MissingError, /sha256 "#{computed_sha256}"/)
+        it "outputs an error" do
+          expect { verification }.to output(/sha256 "#{computed_sha256}"/).to_stderr
         end
       end
 
       context "when expected and computed checksums do not match" do
-        let(:expected_sha256) { deadbeef }
+        let(:expected_sha256) { Checksum.new(:sha256, deadbeef) }
 
         it "raises an error" do
-          expect { verification }.to raise_error CaskSha256MismatchError
+          expect { verification }.to raise_error ChecksumMismatchError
         end
       end
     end
diff --git a/Library/Homebrew/test/cask/installer_spec.rb b/Library/Homebrew/test/cask/installer_spec.rb
index b15bf205cdcd0c484db7c358aa4f18ff01823223..6a9a1bc67a478bae99416290e30cf534842436ae 100644
--- a/Library/Homebrew/test/cask/installer_spec.rb
+++ b/Library/Homebrew/test/cask/installer_spec.rb
@@ -65,14 +65,14 @@ describe Cask::Installer, :cask do
       bad_checksum = Cask::CaskLoader.load(cask_path("bad-checksum"))
       expect {
         described_class.new(bad_checksum).install
-      }.to raise_error(Cask::CaskSha256MismatchError)
+      }.to raise_error(ChecksumMismatchError)
     end
 
     it "blows up on a missing checksum" do
       missing_checksum = Cask::CaskLoader.load(cask_path("missing-checksum"))
       expect {
         described_class.new(missing_checksum).install
-      }.to raise_error(Cask::CaskSha256MissingError)
+      }.to output(/Cannot verify integrity/).to_stderr
     end
 
     it "installs fine if sha256 :no_check is used" do
@@ -87,7 +87,7 @@ describe Cask::Installer, :cask do
       no_checksum = Cask::CaskLoader.load(cask_path("no-checksum"))
       expect {
         described_class.new(no_checksum, require_sha: true).install
-      }.to raise_error(Cask::CaskNoShasumError)
+      }.to raise_error(/--require-sha/)
     end
 
     it "installs fine if sha256 :no_check is used with --require-sha and --force" do
@@ -116,7 +116,6 @@ describe Cask::Installer, :cask do
       }.to output(
         <<~EOS,
           ==> Downloading file://#{HOMEBREW_LIBRARY_PATH}/test/support/fixtures/cask/caffeine.zip
-          ==> Verifying SHA-256 checksum for Cask 'with-installer-manual'.
           ==> Installing Cask with-installer-manual
           To complete the installation of Cask with-installer-manual, you must also
           run the installer at:
diff --git a/Library/Homebrew/test/cask/quarantine_spec.rb b/Library/Homebrew/test/cask/quarantine_spec.rb
index 779c995eb3a59935d892f0718da016c8c3458dc2..426d7321cfac7fa54ab7f8d788c963f9a6cc54d2 100644
--- a/Library/Homebrew/test/cask/quarantine_spec.rb
+++ b/Library/Homebrew/test/cask/quarantine_spec.rb
@@ -31,7 +31,7 @@ describe Cask::Quarantine, :cask do
     it "quarantines Cask fetches" do
       Cask::Cmd::Fetch.run("local-transmission")
       local_transmission = Cask::CaskLoader.load(cask_path("local-transmission"))
-      cached_location = Cask::Download.new(local_transmission).perform
+      cached_location = Cask::Download.new(local_transmission).fetch
 
       expect(cached_location).to be_quarantined
     end
@@ -40,7 +40,7 @@ describe Cask::Quarantine, :cask do
       Cask::Cmd::Audit.run("local-transmission", "--download")
 
       local_transmission = Cask::CaskLoader.load(cask_path("local-transmission"))
-      cached_location = Cask::Download.new(local_transmission).perform
+      cached_location = Cask::Download.new(local_transmission).fetch
 
       expect(cached_location).to be_quarantined
     end
@@ -142,7 +142,7 @@ describe Cask::Quarantine, :cask do
     it "does not quarantine Cask fetches" do
       Cask::Cmd::Fetch.run("local-transmission", "--no-quarantine")
       local_transmission = Cask::CaskLoader.load(cask_path("local-transmission"))
-      cached_location = Cask::Download.new(local_transmission).perform
+      cached_location = Cask::Download.new(local_transmission).fetch
 
       expect(cached_location).not_to be_quarantined
     end
@@ -151,7 +151,7 @@ describe Cask::Quarantine, :cask do
       Cask::Cmd::Audit.run("local-transmission", "--download", "--no-quarantine")
 
       local_transmission = Cask::CaskLoader.load(cask_path("local-transmission"))
-      cached_location = Cask::Download.new(local_transmission).perform
+      cached_location = Cask::Download.new(local_transmission).fetch
 
       expect(cached_location).not_to be_quarantined
     end
diff --git a/Library/Homebrew/test/support/fixtures/cask/Casks/invalid/invalid-manpage-no-section.rb b/Library/Homebrew/test/support/fixtures/cask/Casks/invalid/invalid-manpage-no-section.rb
index 32d5ee74d0b985f5dbfbe1c81f54bef8e87c16de..ee9743e61d95d96c5b756f004107713b8a795fb9 100644
--- a/Library/Homebrew/test/support/fixtures/cask/Casks/invalid/invalid-manpage-no-section.rb
+++ b/Library/Homebrew/test/support/fixtures/cask/Casks/invalid/invalid-manpage-no-section.rb
@@ -1,6 +1,6 @@
 cask "invalid-manpage-no-section" do
   version "1.2.3"
-  sha256 "67cdb8a02803ef37fdbf7e0be205863172e41a561ca446cd84f0d7ab35a99d94"
+  sha256 "68b7e71a2ca7585b004f52652749589941e3029ff0884e8aa3b099594e0282c0"
 
   url "file://#{TEST_FIXTURE_DIR}/cask/AppWithManpage.zip"
   homepage "https://brew.sh/with-generic-artifact"
diff --git a/Library/Homebrew/test/support/fixtures/cask/Casks/with-autodetected-manpage-section.rb b/Library/Homebrew/test/support/fixtures/cask/Casks/with-autodetected-manpage-section.rb
index b330074da7367d0d496ad44e031f7883edcab037..b05174b3808774f5550c421c69365bbfdc46d35a 100644
--- a/Library/Homebrew/test/support/fixtures/cask/Casks/with-autodetected-manpage-section.rb
+++ b/Library/Homebrew/test/support/fixtures/cask/Casks/with-autodetected-manpage-section.rb
@@ -1,6 +1,6 @@
 cask "with-autodetected-manpage-section" do
   version "1.2.3"
-  sha256 "67cdb8a02803ef37fdbf7e0be205863172e41a561ca446cd84f0d7ab35a99d94"
+  sha256 "68b7e71a2ca7585b004f52652749589941e3029ff0884e8aa3b099594e0282c0"
 
   url "file://#{TEST_FIXTURE_DIR}/cask/AppWithManpage.zip"
   homepage "https://brew.sh/with-autodetected-manpage-section"
diff --git a/Library/Homebrew/test/support/fixtures/cask/Casks/with-non-executable-binary.rb b/Library/Homebrew/test/support/fixtures/cask/Casks/with-non-executable-binary.rb
index 0ed70fff2786f99378835585d4c349a9c83a9b52..2e4886462aad03e7675efd732146011e5d0ecbc4 100644
--- a/Library/Homebrew/test/support/fixtures/cask/Casks/with-non-executable-binary.rb
+++ b/Library/Homebrew/test/support/fixtures/cask/Casks/with-non-executable-binary.rb
@@ -1,6 +1,6 @@
 cask "with-non-executable-binary" do
   version "1.2.3"
-  sha256 "d5b2dfbef7ea28c25f7a77cd7fa14d013d82b626db1d82e00e25822464ba19e2"
+  sha256 "306c6ca7407560340797866e077e053627ad409277d1b9da58106fce4cf717cb"
 
   url "file://#{TEST_FIXTURE_DIR}/cask/naked_non_executable"
   homepage "https://brew.sh/with-binary"
diff --git a/Library/Homebrew/uninstall.rb b/Library/Homebrew/uninstall.rb
index 214f33a662975af8891df18f00d79147bc04efe0..cd3140e2bbdcd7b21b685c21ed434b6817982ebc 100644
--- a/Library/Homebrew/uninstall.rb
+++ b/Library/Homebrew/uninstall.rb
@@ -82,6 +82,16 @@ module Homebrew
           end
         end
       end
+    rescue MultipleVersionsInstalledError => e
+      ofail e
+    ensure
+      # If we delete Cellar/newname, then Cellar/oldname symlink
+      # can become broken and we have to remove it.
+      if HOMEBREW_CELLAR.directory?
+        HOMEBREW_CELLAR.children.each do |rack|
+          rack.unlink if rack.symlink? && !rack.resolved_path_exists?
+        end
+      end
     end
 
     def handle_unsatisfied_dependents(kegs_by_rack, ignore_dependencies: false, named_args: [])
diff --git a/docs/Manpage.md b/docs/Manpage.md
index c78ef52bd4020c7c57474f67b61f3adb159523b1..af61cac3c39722cf3bc72727818d32acb3b3ba70 100644
--- a/docs/Manpage.md
+++ b/docs/Manpage.md
@@ -199,8 +199,8 @@ an issue; just ignore this.
 
 ### `fetch` [*`options`*] *`formula`*
 
-Download a bottle (if available) or source packages for *`formula`*.
-For tarballs, also print SHA-256 checksums.
+Download a bottle (if available) or source packages for *`formula`*e
+and binaries for *`cask`*s. For files, also print SHA-256 checksums.
 
 * `--HEAD`:
   Fetch HEAD version instead of stable version.
@@ -220,6 +220,12 @@ For tarballs, also print SHA-256 checksums.
   Download source packages (for eventual bottling) rather than a bottle.
 * `--force-bottle`:
   Download a bottle if it exists for the current or newest version of macOS, even if it would not be used during installation.
+* `--[no-]quarantine`:
+  Disable/enable quarantining of downloads (default: enabled).
+* `--formula`:
+  Treat all named arguments as formulae.
+* `--cask`:
+  Treat all named arguments as casks.
 
 ### `formulae`
 
@@ -242,11 +248,11 @@ error message if no logs are found.
 Open *`formula`*'s homepage in a browser, or open Homebrew's own homepage
 if no formula is provided.
 
-### `info` [*`options`*] [*`formula`*]
+### `info` [*`options`*] [*`formula`*|*`cask`*]
 
 Display brief statistics for your Homebrew installation.
 
-If *`formula`* is provided, show summary of information about *`formula`*.
+If a *`formula`* or *`cask`* is provided, show summary of information about it.
 
 * `--analytics`:
   List global Homebrew analytics data or, if specified, installation and build error data for *`formula`* (provided neither `HOMEBREW_NO_ANALYTICS` nor `HOMEBREW_NO_GITHUB_API` are set).
@@ -264,6 +270,10 @@ If *`formula`* is provided, show summary of information about *`formula`*.
   Print JSON of all available formulae.
 * `-v`, `--verbose`:
   Show more verbose analytics data for *`formula`*.
+* `--formula`:
+  Treat all named arguments as formulae.
+* `--cask`:
+  Treat all named arguments as casks.
 
 ### `install` [*`options`*] *`formula`*|*`cask`*
 
@@ -567,12 +577,14 @@ If no *`tap`* names are provided, display brief statistics for all installed tap
 * `--json`:
   Print a JSON representation of *`tap`*. Currently the default and only accepted value for *`version`* is `v1`. See the docs for examples of using the JSON output: <https://docs.brew.sh/Querying-Brew>
 
-### `uninstall`, `rm`, `remove` [*`options`*] *`formula`*
+### `uninstall`, `rm`, `remove` [*`options`*] *`formula`*|*`cask`*
 
-Uninstall *`formula`*.
+Uninstall a *`formula`* or *`cask`*.
 
 * `-f`, `--force`:
-  Delete all installed versions of *`formula`*.
+  Delete all installed versions of *`formula`*. Uninstall even if *`cask`* is not installed, overwrite existing files and ignore errors when removing files.
+* `--zap`:
+  Remove all files associated with a *`cask`*. *May remove files which are shared between applications.*
 * `--ignore-dependencies`:
   Don't fail uninstall, even if *`formula`* is a dependency of any installed formulae.
 * `--formula`:
@@ -1001,10 +1013,15 @@ the Cellar and then link it into Homebrew's prefix with `brew link`.
 * `--version`:
   Explicitly set the *`version`* of the package being installed.
 
-### `edit` [*`formula`*]
+### `edit` [*`formula`*|*`cask`*]
+
+Open a *`formula`* or *`cask`* in the editor set by `EDITOR` or `HOMEBREW_EDITOR`,
+or open the Homebrew repository for editing if no formula is provided.
 
-Open *`formula`* in the editor set by `EDITOR` or `HOMEBREW_EDITOR`, or open the
-Homebrew repository for editing if no formula is provided.
+* `--formula`:
+  Treat all named arguments as formulae.
+* `--cask`:
+  Treat all named arguments as casks.
 
 ### `extract` [*`options`*] *`formula`* *`tap`*
 
diff --git a/manpages/brew.1 b/manpages/brew.1
index fd918d6f37c94361c6e0b752f490be3c5ffcd194..777ab1cd2f9a97c82496af36bcd86f1e498690ae 100644
--- a/manpages/brew.1
+++ b/manpages/brew.1
@@ -254,7 +254,7 @@ List all audit methods, which can be run individually if provided as arguments\.
 Enable debugging and profiling of audit methods\.
 .
 .SS "\fBfetch\fR [\fIoptions\fR] \fIformula\fR"
-Download a bottle (if available) or source packages for \fIformula\fR\. For tarballs, also print SHA\-256 checksums\.
+Download a bottle (if available) or source packages for \fIformula\fRe and binaries for \fIcask\fRs\. For files, also print SHA\-256 checksums\.
 .
 .TP
 \fB\-\-HEAD\fR
@@ -292,6 +292,18 @@ Download source packages (for eventual bottling) rather than a bottle\.
 \fB\-\-force\-bottle\fR
 Download a bottle if it exists for the current or newest version of macOS, even if it would not be used during installation\.
 .
+.TP
+\fB\-\-[no\-]quarantine\fR
+Disable/enable quarantining of downloads (default: enabled)\.
+.
+.TP
+\fB\-\-formula\fR
+Treat all named arguments as formulae\.
+.
+.TP
+\fB\-\-cask\fR
+Treat all named arguments as casks\.
+.
 .SS "\fBformulae\fR"
 List all locally installable formulae including short names\.
 .
@@ -313,11 +325,11 @@ The Gist will be marked private and will not appear in listings but will be acce
 .SS "\fBhome\fR [\fIformula\fR]"
 Open \fIformula\fR\'s homepage in a browser, or open Homebrew\'s own homepage if no formula is provided\.
 .
-.SS "\fBinfo\fR [\fIoptions\fR] [\fIformula\fR]"
+.SS "\fBinfo\fR [\fIoptions\fR] [\fIformula\fR|\fIcask\fR]"
 Display brief statistics for your Homebrew installation\.
 .
 .P
-If \fIformula\fR is provided, show summary of information about \fIformula\fR\.
+If a \fIformula\fR or \fIcask\fR is provided, show summary of information about it\.
 .
 .TP
 \fB\-\-analytics\fR
@@ -351,6 +363,14 @@ Print JSON of all available formulae\.
 \fB\-v\fR, \fB\-\-verbose\fR
 Show more verbose analytics data for \fIformula\fR\.
 .
+.TP
+\fB\-\-formula\fR
+Treat all named arguments as formulae\.
+.
+.TP
+\fB\-\-cask\fR
+Treat all named arguments as casks\.
+.
 .SS "\fBinstall\fR [\fIoptions\fR] \fIformula\fR|\fIcask\fR"
 Install a \fIformula\fR or \fIcask\fR\. Additional options specific to a \fIformula\fR may be appended to the command\.
 .
@@ -785,12 +805,16 @@ Show information on each installed tap\.
 \fB\-\-json\fR
 Print a JSON representation of \fItap\fR\. Currently the default and only accepted value for \fIversion\fR is \fBv1\fR\. See the docs for examples of using the JSON output: \fIhttps://docs\.brew\.sh/Querying\-Brew\fR
 .
-.SS "\fBuninstall\fR, \fBrm\fR, \fBremove\fR [\fIoptions\fR] \fIformula\fR"
-Uninstall \fIformula\fR\.
+.SS "\fBuninstall\fR, \fBrm\fR, \fBremove\fR [\fIoptions\fR] \fIformula\fR|\fIcask\fR"
+Uninstall a \fIformula\fR or \fIcask\fR\.
 .
 .TP
 \fB\-f\fR, \fB\-\-force\fR
-Delete all installed versions of \fIformula\fR\.
+Delete all installed versions of \fIformula\fR\. Uninstall even if \fIcask\fR is not installed, overwrite existing files and ignore errors when removing files\.
+.
+.TP
+\fB\-\-zap\fR
+Remove all files associated with a \fIcask\fR\. \fIMay remove files which are shared between applications\.\fR
 .
 .TP
 \fB\-\-ignore\-dependencies\fR
@@ -1396,8 +1420,16 @@ Explicitly set the \fIname\fR of the package being installed\.
 \fB\-\-version\fR
 Explicitly set the \fIversion\fR of the package being installed\.
 .
-.SS "\fBedit\fR [\fIformula\fR]"
-Open \fIformula\fR in the editor set by \fBEDITOR\fR or \fBHOMEBREW_EDITOR\fR, or open the Homebrew repository for editing if no formula is provided\.
+.SS "\fBedit\fR [\fIformula\fR|\fIcask\fR]"
+Open a \fIformula\fR or \fIcask\fR in the editor set by \fBEDITOR\fR or \fBHOMEBREW_EDITOR\fR, or open the Homebrew repository for editing if no formula is provided\.
+.
+.TP
+\fB\-\-formula\fR
+Treat all named arguments as formulae\.
+.
+.TP
+\fB\-\-cask\fR
+Treat all named arguments as casks\.
 .
 .SS "\fBextract\fR [\fIoptions\fR] \fIformula\fR \fItap\fR"
 Look through repository history to find the most recent version of \fIformula\fR and create a copy in \fItap\fR\fB/Formula/\fR\fIformula\fR\fB@\fR\fIversion\fR\fB\.rb\fR\. If the tap is not installed yet, attempt to install/clone the tap before continuing\. To extract a formula from a tap that is not \fBhomebrew/core\fR use its fully\-qualified form of \fIuser\fR\fB/\fR\fIrepo\fR\fB/\fR\fIformula\fR\.