Merge branch 'Homebrew:master' into mohammad

This commit is contained in:
Mohammad Zain Abbas 2022-07-24 13:39:59 +02:00 committed by GitHub
commit 96a7ff1019
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
230 changed files with 11949 additions and 323 deletions

View File

@ -37,6 +37,7 @@ jobs:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 21
days-before-close: 7
close-issue-reason: "not_planned"
stale-issue-message: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs.

3
.gitignore vendored
View File

@ -150,8 +150,7 @@
**/vendor/bundle/ruby/*/gems/simplecov-*/
**/vendor/bundle/ruby/*/gems/simplecov-html-*/
**/vendor/bundle/ruby/*/gems/sorbet-*/
**/vendor/bundle/ruby/*/gems/sorbet-runtime-*/
!**/vendor/bundle/ruby/*/gems/sorbet-runtime-stub-*/
!**/vendor/bundle/ruby/*/gems/sorbet-runtime-*/
**/vendor/bundle/ruby/*/gems/spoom-*/
**/vendor/bundle/ruby/*/gems/stackprof-*/
**/vendor/bundle/ruby/*/gems/strscan-*/

View File

@ -2,6 +2,8 @@
source "https://rubygems.org"
ruby ">= 2.6.0"
# disallowed gems (should not be used)
# * nokogiri - use rexml instead for XML parsing
@ -17,6 +19,7 @@ gem "rspec-github", require: false
gem "rspec-its", require: false
gem "rspec_junit_formatter", require: false
gem "rspec-retry", require: false
gem "rspec-sorbet", require: false
gem "rspec-wait", require: false
gem "rubocop", require: false
gem "rubocop-ast", require: false
@ -26,13 +29,12 @@ gem "warning", require: false
group :sorbet, optional: true do
gem "parlour", require: false
gem "rspec-sorbet", require: false
gem "sorbet-static-and-runtime", require: false
gem "tapioca", require: false
end
# vendored gems
gem "activesupport", "< 7" # 7 requires Ruby 2.7
gem "activesupport"
gem "addressable"
gem "concurrent-ruby"
gem "did_you_mean" # remove when HOMEBREW_REQUIRED_RUBY_VERSION >= 2.7
@ -44,4 +46,4 @@ gem "rubocop-rails"
gem "rubocop-rspec"
gem "rubocop-sorbet"
gem "ruby-macho"
gem "sorbet-runtime-stub"
gem "sorbet-runtime"

View File

@ -124,19 +124,19 @@ GEM
rspec (>= 3, < 4)
rspec_junit_formatter (0.5.1)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.31.2)
rubocop (1.32.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.1.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.18.0, < 2.0)
rubocop-ast (>= 1.19.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.19.1)
parser (>= 3.1.1.0)
rubocop-performance (1.14.2)
rubocop-performance (1.14.3)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
rubocop-rails (2.15.2)
@ -159,14 +159,13 @@ GEM
simplecov (~> 0.19)
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.4)
sorbet (0.5.10160)
sorbet-static (= 0.5.10160)
sorbet-runtime (0.5.10160)
sorbet-runtime-stub (0.2.0)
sorbet-static (0.5.10160-universal-darwin-14)
sorbet-static-and-runtime (0.5.10160)
sorbet (= 0.5.10160)
sorbet-runtime (= 0.5.10160)
sorbet (0.5.10175)
sorbet-static (= 0.5.10175)
sorbet-runtime (0.5.10175)
sorbet-static (0.5.10175-universal-darwin-14)
sorbet-static-and-runtime (0.5.10175)
sorbet (= 0.5.10175)
sorbet-runtime (= 0.5.10175)
spoom (1.1.11)
sorbet (>= 0.5.9204)
sorbet-runtime (>= 0.5.9204)
@ -181,7 +180,7 @@ GEM
thor (>= 1.2.0)
yard-sorbet
thor (1.2.1)
tzinfo (2.0.4)
tzinfo (2.0.5)
concurrent-ruby (~> 1.0)
unf (0.1.4)
unf_ext
@ -205,7 +204,7 @@ PLATFORMS
ruby
DEPENDENCIES
activesupport (< 7)
activesupport
addressable
bootsnap
byebug
@ -235,10 +234,13 @@ DEPENDENCIES
ruby-macho
simplecov
simplecov-cobertura
sorbet-runtime-stub
sorbet-runtime
sorbet-static-and-runtime
tapioca
warning
RUBY VERSION
ruby 2.6.8p205
BUNDLED WITH
1.17.3

View File

@ -30,10 +30,8 @@ module Homebrew
def all_formulae
@all_formulae ||= begin
curl_args = %w[--compressed --silent https://formulae.brew.sh/api/formula.json]
if cached_formula_json_file.exist?
last_modified = cached_formula_json_file.mtime.utc
last_modified = last_modified.strftime("%a, %d %b %Y %H:%M:%S GMT")
curl_args = ["--header", "If-Modified-Since: #{last_modified}", *curl_args]
if cached_formula_json_file.exist? && !cached_formula_json_file.empty?
curl_args.prepend("--time-cond", cached_formula_json_file)
end
curl_download(*curl_args, to: HOMEBREW_CACHE_API/"#{formula_api_path}.json", max_time: 5)

View File

@ -103,7 +103,15 @@ begin
possible_tap = OFFICIAL_CMD_TAPS.find { |_, cmds| cmds.include?(cmd) }
possible_tap = Tap.fetch(possible_tap.first) if possible_tap
if !possible_tap || possible_tap.installed? || Tap.untapped_official_taps.include?(possible_tap.name)
if !possible_tap ||
possible_tap.installed? ||
(blocked_tap = Tap.untapped_official_taps.include?(possible_tap.name))
if blocked_tap
onoe <<~EOS
`brew #{cmd}` is unavailable because #{possible_tap.name} was manually untapped.
Run `brew tap #{possible_tap.name}` to reenable `brew #{cmd}`.
EOS
end
# Check for cask explicitly because it's very common in old guides
odie "`brew cask` is no longer a `brew` command. Use `brew <command> --cask` instead." if cmd == "cask"
odie "Unknown command: #{cmd}"

View File

@ -219,7 +219,7 @@ module Cask
end
alias == eql?
def to_hash
def to_h
{
"token" => token,
"full_token" => full_name,
@ -243,8 +243,8 @@ module Cask
}
end
def to_h
hash = to_hash
def to_hash_with_variations
hash = to_h
variations = {}
hash_keys_to_skip = %w[outdated installed versions]
@ -252,21 +252,20 @@ module Cask
if @dsl.on_system_blocks_exist?
[:arm, :intel].each do |arch|
MacOSVersions::SYMBOLS.each_key do |os_name|
# Big Sur is the first version of macOS that supports arm
next if arch == :arm && MacOS::Version.from_symbol(os_name) < MacOS::Version.from_symbol(:big_sur)
bottle_tag = ::Utils::Bottles::Tag.new(system: os_name, arch: arch)
next unless bottle_tag.valid_combination?
Homebrew::SimulateSystem.os = os_name
Homebrew::SimulateSystem.arch = arch
refresh
bottle_tag = ::Utils::Bottles::Tag.new(system: os_name, arch: arch).to_sym
to_hash.each do |key, value|
to_h.each do |key, value|
next if hash_keys_to_skip.include? key
next if value.to_s == hash[key].to_s
variations[bottle_tag] ||= {}
variations[bottle_tag][key] = value
variations[bottle_tag.to_sym] ||= {}
variations[bottle_tag.to_sym][key] = value
end
end
end

View File

@ -61,6 +61,9 @@ module Homebrew
switch "--all",
depends_on: "--json",
description: "Print JSON of all available formulae."
switch "--variations",
depends_on: "--json",
description: "Include the variations hash in each formula's JSON output."
switch "-v", "--verbose",
description: "Show more verbose analytics data for <formula>."
switch "--formula", "--formulae",
@ -202,6 +205,8 @@ module Homebrew
if args.bottle?
formulae.map(&:to_recursive_bottle_hash)
elsif args.variations?
formulae.map(&:to_hash_with_variations)
else
formulae.map(&:to_hash)
end
@ -216,6 +221,11 @@ module Homebrew
if args.bottle?
{ "formulae" => formulae.map(&:to_recursive_bottle_hash) }
elsif args.variations?
{
"formulae" => formulae.map(&:to_hash_with_variations),
"casks" => casks.map(&:to_hash_with_variations),
}
else
{
"formulae" => formulae.map(&:to_hash),

View File

@ -88,7 +88,7 @@ module Homebrew
def tests
args = tests_args.parse
Homebrew.install_bundler_gems!(groups: ["sorbet"])
Homebrew.install_bundler_gems!
require "byebug" if args.byebug?

View File

@ -37,6 +37,17 @@ module Superenv
paths
end
def homebrew_extra_isystem_paths
paths = []
# Add paths for GCC headers when building against glibc@2.13 because we have to use -nostdinc.
if deps.any? { |d| d.name == "glibc@2.13" }
gcc_include_dir = Utils.safe_popen_read(cc, "--print-file-name=include").chomp
gcc_include_fixed_dir = Utils.safe_popen_read(cc, "--print-file-name=include-fixed").chomp
paths << gcc_include_dir << gcc_include_fixed_dir
end
paths
end
def determine_rpath_paths(formula)
PATH.new(
*formula&.lib,

View File

@ -2005,6 +2005,45 @@ class Formula
hsh
end
# @private
def to_hash_with_variations
hash = to_hash
variations = {}
os_versions = [*MacOSVersions::SYMBOLS.keys, :linux]
if path.exist? && self.class.on_system_blocks_exist?
formula_contents = path.read
[:arm, :intel].each do |arch|
os_versions.each do |os_name|
bottle_tag = Utils::Bottles::Tag.new(system: os_name, arch: arch)
next unless bottle_tag.valid_combination?
Homebrew::SimulateSystem.os = os_name
Homebrew::SimulateSystem.arch = arch
variations_namespace = Formulary.class_s("Variations#{bottle_tag.to_sym.capitalize}")
variations_formula_class = Formulary.load_formula(name, path, formula_contents, variations_namespace,
flags: self.class.build_flags, ignore_errors: true)
variations_formula = variations_formula_class.new(name, path, :stable,
alias_path: alias_path, force_bottle: force_bottle)
variations_formula.to_hash.each do |key, value|
next if value.to_s == hash[key].to_s
variations[bottle_tag.to_sym] ||= {}
variations[bottle_tag.to_sym][key] = value
end
end
end
end
Homebrew::SimulateSystem.clear
hash["variations"] = variations
hash
end
# @api private
# Generate a hash to be used to install a formula from a JSON file
def to_recursive_bottle_hash(top_level: true)
@ -2469,6 +2508,7 @@ class Formula
# The methods below define the formula DSL.
class << self
extend Predicable
include BuildEnvironment::DSL
include OnSystem::MacOSAndLinux
@ -2483,6 +2523,11 @@ class Formula
end
end
# Whether this formula contains OS/arch-specific blocks
# (e.g. `on_macos`, `on_arm`, `on_monterey :or_older`, `on_system :linux, macos: :big_sur_or_newer`).
# @private
attr_predicate :on_system_blocks_exist?
# The reason for why this software is not linked (by default) to
# {::HOMEBREW_PREFIX}.
# @private
@ -3170,6 +3215,8 @@ class Formula
# Permit links to certain libraries that don't exist. Available on Linux only.
def ignore_missing_libraries(*)
return if Homebrew::SimulateSystem.linux?
raise FormulaSpecificationError, "#{__method__} is available on Linux only"
end

View File

@ -49,6 +49,7 @@ class Formula
def env; end
def conflicts; end
def self.on_system_blocks_exist?; end
# This method is included by `OnSystem`
def self.on_macos(&block); end
end

View File

@ -264,6 +264,7 @@ module Formulary
end
def self.convert_to_deprecate_disable_reason_string_or_symbol(string)
require "deprecate_disable"
return string unless DeprecateDisable::DEPRECATE_DISABLE_REASONS.keys.map(&:to_s).include?(string)
string.to_sym

View File

@ -198,7 +198,7 @@ class LinkageChecker
# In macOS Big Sur and later, system libraries do not exist on-disk and instead exist in a cache.
# If dlopen finds the dylib, then the linkage is not broken.
@system_dylibs << dylib
else
elsif !system_framework?(dylib)
@broken_dylibs << dylib
end
else
@ -306,11 +306,13 @@ class LinkageChecker
def harmless_broken_link?(dylib)
# libgcc_s_* is referenced by programs that use the Java Service Wrapper,
# and is harmless on x86(_64) machines
return true if [
[
"/usr/lib/libgcc_s_ppc64.1.dylib",
"/opt/local/lib/libgcc/libgcc_s.1.dylib",
].include?(dylib)
end
def system_framework?(dylib)
dylib.start_with?("/System/Library/Frameworks/")
end

View File

@ -376,6 +376,14 @@ module Homebrew
base[:StartCalendarInterval] = @cron.reject { |_, value| value == "*" }
end
# Adding all session types has as the primary effect that if you initialise it through e.g. a Background session
# and you later "physically" sign in to the owning account (Aqua session), things shouldn't flip out.
# Also, we're not checking @process_type here because that is used to indicate process priority and not
# necessarily if it should run in a specific session type. Like database services could run with ProcessType
# Interactive so they have no resource limitations enforced upon them, but they aren't really interactive in the
# general sense.
base[:LimitLoadToSessionType] = %w[Aqua Background LoginWindow StandardIO System]
base.to_plist
end

View File

@ -304,7 +304,11 @@ class Cmd
end
def cppflags
path_flags("-isystem", isystem_paths) + path_flags("-I", include_paths)
args = []
args += path_flags("-isystem", isystem_paths) + path_flags("-I", include_paths)
# Add -nostdinc when building against glibc@2.13 to avoid mixing system and brewed glibc headers.
args << "-nostdinc" if @deps.include?("glibc@2.13")
args
end
def ldflags_mac(args)
@ -322,10 +326,17 @@ class Cmd
def ldflags_linux(args)
unless mode == :ld
wl = "-Wl,"
args << "-B#{@opt}/glibc/lib"
if @deps.include?("glibc@2.13")
args << "-B#{@opt}/glibc@2.13/lib"
else
args << "-B#{@opt}/glibc/lib"
end
end
args += rpath_flags("#{wl}-rpath=", rpath_paths)
args += ["#{wl}--dynamic-linker=#{dynamic_linker_path}"] if dynamic_linker_path
# Use -rpath-link to make sure linker uses glibc@2.13 rather than the system glibc for indirect
# dependencies because -L will only handle direct dependencies.
args << "#{wl}-rpath-link=#{@opt}/glibc@2.13/lib" if @deps.include?("glibc@2.13")
args
end

View File

@ -205,6 +205,8 @@ class SoftwareSpec
def patch(strip = :p1, src = nil, &block)
p = Patch.create(strip, src, &block)
return if p.is_a?(ExternalPatch) && p.url.blank?
dependency_collector.add(p.resource) if p.is_a? ExternalPatch
patches << p
end

View File

@ -6,6 +6,7 @@
module RuboCop; end
module RuboCop::Cop; end
RuboCop::Cop::IgnoredPattern = RuboCop::Cop::AllowedPattern
module RuboCop::Cop::Performance; end
class RuboCop::Cop::Performance::AncestorsInclude < ::RuboCop::Cop::Base

View File

@ -3157,13 +3157,15 @@ class RuboCop::Cop::Layout::LineContinuationLeadingSpace < ::RuboCop::Cop::Base
private
def continuation?(line); end
def investigate(first_line, second_line, range_start); end
def offense_range(range_start, matches); end
def enforced_style_leading?; end
def investigate_leading_style(first_line, end_of_first_line); end
def investigate_trailing_style(second_line, end_of_first_line); end
def leading_offense_range(end_of_first_line, matches); end
def message(_range); end
def raw_lines(node); end
def trailing_offense_range(end_of_first_line, matches); end
end
RuboCop::Cop::Layout::LineContinuationLeadingSpace::MSG = T.let(T.unsafe(nil), String)
class RuboCop::Cop::Layout::LineContinuationSpacing < ::RuboCop::Cop::Base
include ::RuboCop::Cop::RangeHelp
extend ::RuboCop::Cop::AutoCorrector
@ -3216,6 +3218,7 @@ class RuboCop::Cop::Layout::LineLength < ::RuboCop::Cop::Base
def max=(value); end
def on_array(node); end
def on_block(node); end
def on_def(node); end
def on_hash(node); end
def on_investigation_end; end
def on_new_investigation; end
@ -3417,6 +3420,15 @@ RuboCop::Cop::Layout::MultilineMethodDefinitionBraceLayout::ALWAYS_SAME_LINE_MES
RuboCop::Cop::Layout::MultilineMethodDefinitionBraceLayout::NEW_LINE_MESSAGE = T.let(T.unsafe(nil), String)
RuboCop::Cop::Layout::MultilineMethodDefinitionBraceLayout::SAME_LINE_MESSAGE = T.let(T.unsafe(nil), String)
class RuboCop::Cop::Layout::MultilineMethodParameterLineBreaks < ::RuboCop::Cop::Base
include ::RuboCop::Cop::MultilineElementLineBreaks
extend ::RuboCop::Cop::AutoCorrector
def on_def(node); end
end
RuboCop::Cop::Layout::MultilineMethodParameterLineBreaks::MSG = T.let(T.unsafe(nil), String)
class RuboCop::Cop::Layout::MultilineOperationIndentation < ::RuboCop::Cop::Base
include ::RuboCop::Cop::ConfigurableEnforcedStyle
include ::RuboCop::Cop::Alignment
@ -5128,14 +5140,21 @@ class RuboCop::Cop::Lint::NonAtomicFileOperation < ::RuboCop::Cop::Base
def allowable_use_with_if?(if_node); end
def autocorrect(corrector, node, range); end
def autocorrect_replace_method(corrector, node); end
def force_method?(node); end
def force_method_name?(node); end
def force_option?(node); end
def message(node); end
def if_node_child?(node); end
def message_remove_file_exist_check(node); end
def register_offense(node, exist_node); end
def replacement_method(node); end
end
RuboCop::Cop::Lint::NonAtomicFileOperation::MAKE_FORCE_METHODS = T.let(T.unsafe(nil), Array)
RuboCop::Cop::Lint::NonAtomicFileOperation::MAKE_METHODS = T.let(T.unsafe(nil), Array)
RuboCop::Cop::Lint::NonAtomicFileOperation::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::NonAtomicFileOperation::MSG_CHANGE_FORCE_METHOD = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::NonAtomicFileOperation::MSG_REMOVE_FILE_EXIST_CHECK = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::NonAtomicFileOperation::REMOVE_FORCE_METHODS = T.let(T.unsafe(nil), Array)
RuboCop::Cop::Lint::NonAtomicFileOperation::REMOVE_METHODS = T.let(T.unsafe(nil), Array)
RuboCop::Cop::Lint::NonAtomicFileOperation::RESTRICT_ON_SEND = T.let(T.unsafe(nil), Array)
@ -5554,6 +5573,13 @@ end
RuboCop::Cop::Lint::RequireParentheses::MSG = T.let(T.unsafe(nil), String)
class RuboCop::Cop::Lint::RequireRangeParentheses < ::RuboCop::Cop::Base
def on_erange(node); end
def on_irange(node); end
end
RuboCop::Cop::Lint::RequireRangeParentheses::MSG = T.let(T.unsafe(nil), String)
class RuboCop::Cop::Lint::RequireRelativeSelfPath < ::RuboCop::Cop::Base
include ::RuboCop::Cop::RangeHelp
extend ::RuboCop::Cop::AutoCorrector
@ -5605,6 +5631,7 @@ RuboCop::Cop::Lint::ReturnInVoidContext::MSG = T.let(T.unsafe(nil), String)
class RuboCop::Cop::Lint::SafeNavigationChain < ::RuboCop::Cop::Base
include ::RuboCop::Cop::AllowedMethods
include ::RuboCop::Cop::NilMethods
extend ::RuboCop::Cop::AutoCorrector
extend ::RuboCop::Cop::TargetRubyVersion
def bad_method?(param0 = T.unsafe(nil)); end
@ -5612,6 +5639,8 @@ class RuboCop::Cop::Lint::SafeNavigationChain < ::RuboCop::Cop::Base
private
def add_safe_navigation_operator(offense_range:, send_node:); end
def autocorrect(corrector, offense_range:, send_node:); end
def method_chain(node); end
end
@ -7127,11 +7156,16 @@ module RuboCop::Cop::PercentArray
private
def allowed_bracket_array?(node); end
def build_bracketed_array_with_appropriate_whitespace(elements:, node:); end
def build_message_for_bracketed_array(preferred_array_code); end
def check_bracketed_array(node, literal_prefix); end
def check_percent_array(node); end
def comments_in_array?(node); end
def invalid_percent_array_contents?(_node); end
def invalid_percent_array_context?(node); end
def whitespace_between(node); end
def whitespace_leading(node); end
def whitespace_trailing(node); end
end
module RuboCop::Cop::PercentLiteral
@ -8551,6 +8585,16 @@ end
RuboCop::Cop::Style::EmptyElse::MSG = T.let(T.unsafe(nil), String)
class RuboCop::Cop::Style::EmptyHeredoc < ::RuboCop::Cop::Base
include ::RuboCop::Cop::Heredoc
include ::RuboCop::Cop::RangeHelp
extend ::RuboCop::Cop::AutoCorrector
def on_heredoc(node); end
end
RuboCop::Cop::Style::EmptyHeredoc::MSG = T.let(T.unsafe(nil), String)
class RuboCop::Cop::Style::EmptyLambdaParameter < ::RuboCop::Cop::Base
include ::RuboCop::Cop::EmptyParameter
include ::RuboCop::Cop::RangeHelp
@ -8791,47 +8835,25 @@ RuboCop::Cop::Style::ExponentialNotation::MESSAGES = T.let(T.unsafe(nil), Hash)
class RuboCop::Cop::Style::FetchEnvVar < ::RuboCop::Cop::Base
extend ::RuboCop::Cop::AutoCorrector
def block_control?(param0 = T.unsafe(nil)); end
def env_with_bracket?(param0 = T.unsafe(nil)); end
def offensive_nodes(param0); end
def on_send(node); end
def operand_of_or?(param0 = T.unsafe(nil)); end
private
def allowable_use?(node); end
def allowed_var?(node); end
def assigned?(node); end
def configured_indentation; end
def conterpart_rhs_of(node); end
def default_nil(node, name_node); end
def default_rhs(node, name_node); end
def default_rhs_in_outer_or(node, name_node); end
def default_rhs_in_same_or(node, name_node); end
def default_to_rhs?(node); end
def first_line_of(source); end
def left_end_of_or_chains?(node); end
def message_chained_with_dot?(node); end
def message_template_for(rhs); end
def new_code_default_nil(name_node); end
def new_code_default_rhs(node, name_node); end
def new_code_default_rhs_multiline(node, name_node); end
def new_code_default_rhs_single_line(node, name_node); end
def new_code(name_node); end
def offensive?(node); end
def or_chain_root(node); end
def or_lhs?(node); end
def partial_matched?(node, condition); end
def rhs_can_be_default_value?(node); end
def rhs_is_block_control?(node); end
def right_end_of_or_chains?(node); end
def used_as_flag?(node); end
def used_if_condition_in_body(node); end
def used_in_condition?(node, condition); end
end
RuboCop::Cop::Style::FetchEnvVar::MSG_DEFAULT_NIL = T.let(T.unsafe(nil), String)
RuboCop::Cop::Style::FetchEnvVar::MSG_DEFAULT_RHS_MULTILINE_BLOCK = T.let(T.unsafe(nil), String)
RuboCop::Cop::Style::FetchEnvVar::MSG_DEFAULT_RHS_SECOND_ARG_OF_FETCH = T.let(T.unsafe(nil), String)
RuboCop::Cop::Style::FetchEnvVar::MSG_DEFAULT_RHS_SINGLE_LINE_BLOCK = T.let(T.unsafe(nil), String)
RuboCop::Cop::Style::FetchEnvVar::MSG = T.let(T.unsafe(nil), String)
class RuboCop::Cop::Style::FileRead < ::RuboCop::Cop::Base
include ::RuboCop::Cop::RangeHelp
@ -11453,8 +11475,10 @@ class RuboCop::Cop::Style::Semicolon < ::RuboCop::Cop::Base
def check_for_line_terminator_or_opener; end
def each_semicolon; end
def expressions_per_line(exprs); end
def find_range_node(token_before_semicolon); end
def find_semicolon_positions(line); end
def register_semicolon(line, column, after_expression); end
def range_nodes; end
def register_semicolon(line, column, after_expression, token_before_semicolon = T.unsafe(nil)); end
def tokens_for_lines; end
class << self
@ -11971,6 +11995,7 @@ class RuboCop::Cop::Style::TopLevelMethodDefinition < ::RuboCop::Cop::Base
end
RuboCop::Cop::Style::TopLevelMethodDefinition::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Style::TopLevelMethodDefinition::RESTRICT_ON_SEND = T.let(T.unsafe(nil), Array)
class RuboCop::Cop::Style::TrailingBodyOnClass < ::RuboCop::Cop::Base
include ::RuboCop::Cop::Alignment

View File

@ -4,7 +4,11 @@
# This is an autogenerated file for types exported from the `tzinfo` gem.
# Please instead update this file by running `bin/tapioca gem tzinfo`.
module TZInfo; end
module TZInfo
class << self
def eager_load!; end
end
end
class TZInfo::AbsoluteDayOfYearTransitionRule < ::TZInfo::DayOfYearTransitionRule
def initialize(day, transition_at = T.unsafe(nil)); end
@ -94,6 +98,7 @@ class TZInfo::DataSource
def country_codes; end
def data_timezone_identifiers; end
def eager_load!; end
def get_country_info(code); end
def get_timezone_info(identifier); end
def inspect; end
@ -275,6 +280,7 @@ end
TZInfo::DataSources::ZoneinfoDataSource::DEFAULT_ALTERNATE_ISO3166_TAB_SEARCH_PATH = T.let(T.unsafe(nil), Array)
TZInfo::DataSources::ZoneinfoDataSource::DEFAULT_SEARCH_PATH = T.let(T.unsafe(nil), Array)
TZInfo::DataSources::ZoneinfoDataSource::EXCLUDED_FILENAMES = T.let(T.unsafe(nil), Array)
class TZInfo::DataSources::ZoneinfoDirectoryNotFound < ::StandardError; end
class TZInfo::DataSources::ZoneinfoReader

View File

@ -1,11 +1,47 @@
# typed: true
# frozen_string_literal: true
# Explicitly prevent `sorbet-runtime` from being loaded.
def gem(name, *)
raise Gem::LoadError if name == "sorbet-runtime"
require "sorbet-runtime"
super
# Disable runtime checking unless enabled.
# 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"]
# Redefine T.let etc to make the `checked` parameter default to false rather than true.
# @private
module TNoChecks
def cast(value, type, checked: false)
super(value, type, checked: checked)
end
def let(value, type, checked: false)
super(value, type, checked: checked)
end
def bind(value, type, checked: false)
super(value, type, checked: checked)
end
def assert_type!(value, type, checked: false)
super(value, type, checked: checked)
end
end
# @private
module T
class << self
prepend TNoChecks
end
# Redefine T.sig to be noop.
# @private
module Sig
def sig(arg0 = nil, &blk); end
end
end
# For any cases the above doesn't handle: make sure we don't let TypeError slip through.
T::Configuration.call_validation_error_handler = ->(signature, opts) do end
T::Configuration.inline_type_error_handler = ->(error, opts) do end
end
require "sorbet-runtime-stub"

View File

@ -7,4 +7,4 @@ require_relative "standalone/load_path"
require_relative "startup/ruby_path"
require "startup/config"
require_relative "startup/bootsnap"
require_relative "startup/sorbet"
require_relative "standalone/sorbet"

View File

@ -1,10 +0,0 @@
# typed: strict
# frozen_string_literal: true
if ENV["HOMEBREW_SORBET_RUNTIME"]
# This is only supported under the brew environment.
Homebrew.install_bundler_gems!(groups: ["sorbet"])
require "sorbet-runtime"
else
require "standalone/sorbet"
end

View File

@ -211,4 +211,64 @@ describe Cask::Cask, :cask do
end
end
end
describe "#to_hash_with_variations" do
let!(:original_macos_version) { MacOS.full_version.to_s }
let(:expected_variations) {
<<~JSON
{
"arm64_big_sur": {
"url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.2.0/arm.zip",
"version": "1.2.0",
"sha256": "8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b"
},
"monterey": {
"url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.2.3/intel.zip"
},
"big_sur": {
"url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.2.0/intel.zip",
"version": "1.2.0",
"sha256": "8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b"
},
"catalina": {
"url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.0.0/intel.zip",
"version": "1.0.0",
"sha256": "1866dfa833b123bb8fe7fa7185ebf24d28d300d0643d75798bc23730af734216"
},
"mojave": {
"url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.0.0/intel.zip",
"version": "1.0.0",
"sha256": "1866dfa833b123bb8fe7fa7185ebf24d28d300d0643d75798bc23730af734216"
}
}
JSON
}
before do
# Use a more limited symbols list to shorten the variations hash
symbols = {
monterey: "12",
big_sur: "11",
catalina: "10.15",
mojave: "10.14",
}
stub_const("MacOSVersions::SYMBOLS", symbols)
# For consistency, always run on Monterey and ARM
MacOS.full_version = "12"
allow(Hardware::CPU).to receive(:type).and_return(:arm)
end
after do
MacOS.full_version = original_macos_version
end
it "returns the correct variations hash" do
c = Cask::CaskLoader.load("multiple-versions")
h = c.to_hash_with_variations
expect(h).to be_a(Hash)
expect(JSON.pretty_generate(h["variations"])).to eq expected_variations.strip
end
end
end

View File

@ -120,9 +120,7 @@ describe Cask::Cmd::List, :cask do
},
"conflicts_with": null,
"container": null,
"auto_updates": null,
"variations": {
}
"auto_updates": null
},
{
"token": "local-transmission",
@ -151,9 +149,7 @@ describe Cask::Cmd::List, :cask do
},
"conflicts_with": null,
"container": null,
"auto_updates": null,
"variations": {
}
"auto_updates": null
},
{
"token": "multiple-versions",
@ -185,32 +181,7 @@ describe Cask::Cmd::List, :cask do
},
"conflicts_with": null,
"container": null,
"auto_updates": null,
"variations": {
"arm_big_sur": {
"url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.2.0/arm.zip",
"version": "1.2.0",
"sha256": "8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b"
},
"intel_monterey": {
"url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.2.3/intel.zip"
},
"intel_big_sur": {
"url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.2.0/intel.zip",
"version": "1.2.0",
"sha256": "8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b"
},
"intel_catalina": {
"url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.0.0/intel.zip",
"version": "1.0.0",
"sha256": "1866dfa833b123bb8fe7fa7185ebf24d28d300d0643d75798bc23730af734216"
},
"intel_mojave": {
"url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.0.0/intel.zip",
"version": "1.0.0",
"sha256": "1866dfa833b123bb8fe7fa7185ebf24d28d300d0643d75798bc23730af734216"
}
}
"auto_updates": null
},
{
"token": "third-party-cask",
@ -239,9 +210,7 @@ describe Cask::Cmd::List, :cask do
},
"conflicts_with": null,
"container": null,
"auto_updates": null,
"variations": {
}
"auto_updates": null
}
]
EOS

View File

@ -905,6 +905,99 @@ describe Formula do
expect(h["versions"]["bottle"]).to be_truthy
end
describe "#to_hash_with_variations", :needs_macos do
let(:formula_path) { CoreTap.new.formula_dir/"foo-variations.rb" }
let(:formula_content) do
<<~RUBY
class FooVariations < Formula
url "file://#{TEST_FIXTURE_DIR}/tarballs/testball-0.1.tbz"
sha256 TESTBALL_SHA256
on_intel do
depends_on "intel-formula"
end
on_big_sur do
depends_on "big-sur-formula"
end
on_catalina :or_older do
depends_on "catalina-or-older-formula"
end
on_linux do
depends_on "linux-formula"
end
end
RUBY
end
let(:expected_variations) {
<<~JSON
{
"arm64_big_sur": {
"dependencies": [
"big-sur-formula"
]
},
"monterey": {
"dependencies": [
"intel-formula"
]
},
"big_sur": {
"dependencies": [
"intel-formula",
"big-sur-formula"
]
},
"catalina": {
"dependencies": [
"intel-formula",
"catalina-or-older-formula"
]
},
"mojave": {
"dependencies": [
"intel-formula",
"catalina-or-older-formula"
]
},
"x86_64_linux": {
"dependencies": [
"intel-formula",
"linux-formula"
]
}
}
JSON
}
before do
# Use a more limited symbols list to shorten the variations hash
symbols = {
monterey: "12",
big_sur: "11",
catalina: "10.15",
mojave: "10.14",
}
stub_const("MacOSVersions::SYMBOLS", symbols)
# For consistency, always run on Monterey and ARM
allow(MacOS).to receive(:version).and_return(MacOS::Version.new("12"))
allow(Hardware::CPU).to receive(:type).and_return(:arm)
formula_path.dirname.mkpath
formula_path.write formula_content
end
it "returns the correct variations hash" do
h = Formulary.factory("foo-variations").to_hash_with_variations
expect(h).to be_a(Hash)
expect(JSON.pretty_generate(h["variations"])).to eq expected_variations.strip
end
end
specify "#to_recursive_bottle_hash" do
f1 = formula "foo" do
url "foo-1.0"

View File

@ -180,6 +180,14 @@ describe Homebrew::Service do
\t<true/>
\t<key>LegacyTimers</key>
\t<true/>
\t<key>LimitLoadToSessionType</key>
\t<array>
\t\t<string>Aqua</string>
\t\t<string>Background</string>
\t\t<string>LoginWindow</string>
\t\t<string>StandardIO</string>
\t\t<string>System</string>
\t</array>
\t<key>ProcessType</key>
\t<string>Interactive</string>
\t<key>ProgramArguments</key>
@ -221,6 +229,14 @@ describe Homebrew::Service do
<dict>
\t<key>Label</key>
\t<string>homebrew.mxcl.formula_name</string>
\t<key>LimitLoadToSessionType</key>
\t<array>
\t\t<string>Aqua</string>
\t\t<string>Background</string>
\t\t<string>LoginWindow</string>
\t\t<string>StandardIO</string>
\t\t<string>System</string>
\t</array>
\t<key>ProgramArguments</key>
\t<array>
\t\t<string>#{HOMEBREW_PREFIX}/opt/formula_name/bin/beanstalkd</string>
@ -262,6 +278,14 @@ describe Homebrew::Service do
<dict>
\t<key>Label</key>
\t<string>homebrew.mxcl.formula_name</string>
\t<key>LimitLoadToSessionType</key>
\t<array>
\t\t<string>Aqua</string>
\t\t<string>Background</string>
\t\t<string>LoginWindow</string>
\t\t<string>StandardIO</string>
\t\t<string>System</string>
\t</array>
\t<key>ProgramArguments</key>
\t<array>
\t\t<string>#{HOMEBREW_PREFIX}/opt/formula_name/bin/beanstalkd</string>
@ -289,6 +313,14 @@ describe Homebrew::Service do
<dict>
\t<key>Label</key>
\t<string>homebrew.mxcl.formula_name</string>
\t<key>LimitLoadToSessionType</key>
\t<array>
\t\t<string>Aqua</string>
\t\t<string>Background</string>
\t\t<string>LoginWindow</string>
\t\t<string>StandardIO</string>
\t\t<string>System</string>
\t</array>
\t<key>ProgramArguments</key>
\t<array>
\t\t<string>#{HOMEBREW_PREFIX}/opt/formula_name/bin/beanstalkd</string>
@ -318,6 +350,14 @@ describe Homebrew::Service do
<dict>
\t<key>Label</key>
\t<string>homebrew.mxcl.formula_name</string>
\t<key>LimitLoadToSessionType</key>
\t<array>
\t\t<string>Aqua</string>
\t\t<string>Background</string>
\t\t<string>LoginWindow</string>
\t\t<string>StandardIO</string>
\t\t<string>System</string>
\t</array>
\t<key>ProgramArguments</key>
\t<array>
\t\t<string>#{HOMEBREW_PREFIX}/opt/formula_name/bin/beanstalkd</string>
@ -356,6 +396,14 @@ describe Homebrew::Service do
\t</dict>
\t<key>Label</key>
\t<string>homebrew.mxcl.formula_name</string>
\t<key>LimitLoadToSessionType</key>
\t<array>
\t\t<string>Aqua</string>
\t\t<string>Background</string>
\t\t<string>LoginWindow</string>
\t\t<string>StandardIO</string>
\t\t<string>System</string>
\t</array>
\t<key>ProgramArguments</key>
\t<array>
\t\t<string>#{HOMEBREW_PREFIX}/opt/formula_name/bin/beanstalkd</string>
@ -387,6 +435,14 @@ describe Homebrew::Service do
\t</dict>
\t<key>Label</key>
\t<string>homebrew.mxcl.formula_name</string>
\t<key>LimitLoadToSessionType</key>
\t<array>
\t\t<string>Aqua</string>
\t\t<string>Background</string>
\t\t<string>LoginWindow</string>
\t\t<string>StandardIO</string>
\t\t<string>System</string>
\t</array>
\t<key>ProgramArguments</key>
\t<array>
\t\t<string>#{HOMEBREW_PREFIX}/opt/formula_name/bin/beanstalkd</string>
@ -418,6 +474,14 @@ describe Homebrew::Service do
\t</dict>
\t<key>Label</key>
\t<string>homebrew.mxcl.formula_name</string>
\t<key>LimitLoadToSessionType</key>
\t<array>
\t\t<string>Aqua</string>
\t\t<string>Background</string>
\t\t<string>LoginWindow</string>
\t\t<string>StandardIO</string>
\t\t<string>System</string>
\t</array>
\t<key>ProgramArguments</key>
\t<array>
\t\t<string>#{HOMEBREW_PREFIX}/opt/formula_name/bin/beanstalkd</string>

View File

@ -171,5 +171,12 @@ describe SoftwareSpec do
expect(spec.patches.count).to eq(1)
expect(spec.patches.first.strip).to eq(:p1)
end
it "doesn't add a patch with no url" do
spec.patch do
sha256 "7852a7a365f518b12a1afd763a6a80ece88ac7aeea3c9023aa6c1fe46ac5a1ae"
end
expect(spec.patches.empty?).to be true
end
end
end

View File

@ -36,4 +36,37 @@ describe Utils::Bottles::Tag do
expect(tag.linux?).to be true
expect(tag.to_sym).to eq(symbol)
end
describe "#standardized_arch" do
it "returns :x86_64 for :intel" do
expect(described_class.new(system: :all, arch: :intel).standardized_arch).to eq(:x86_64)
end
it "returns :arm64 for :arm" do
expect(described_class.new(system: :all, arch: :arm).standardized_arch).to eq(:arm64)
end
end
describe "#valid_combination?" do
it "returns true for intel archs" do
tag = described_class.new(system: :big_sur, arch: :intel)
expect(tag.valid_combination?).to be true
tag = described_class.new(system: :linux, arch: :x86_64)
expect(tag.valid_combination?).to be true
end
it "returns false for arm archs and macos versions older than big_sur" do
tag = described_class.new(system: :catalina, arch: :arm64)
expect(tag.valid_combination?).to be false
tag = described_class.new(system: :mojave, arch: :arm)
expect(tag.valid_combination?).to be false
end
it "returns false for arm archs and linux" do
tag = described_class.new(system: :linux, arch: :arm64)
expect(tag.valid_combination?).to be false
tag = described_class.new(system: :linux, arch: :arm)
expect(tag.valid_combination?).to be false
end
end
end

View File

@ -169,14 +169,22 @@ module Utils
[system, arch].hash
end
sig { returns(Symbol) }
def standardized_arch
return :x86_64 if [:x86_64, :intel].include? arch
return :arm64 if [:arm64, :arm].include? arch
arch
end
sig { returns(Symbol) }
def to_sym
if system == :all && arch == :all
:all
elsif macos? && arch == :x86_64
elsif macos? && [:x86_64, :intel].include?(arch)
system
else
"#{arch}_#{system}".to_sym
"#{standardized_arch}_#{system}".to_sym
end
end
@ -203,6 +211,15 @@ module Utils
false
end
sig { returns(T::Boolean) }
def valid_combination?
return true unless [:arm64, :arm].include? arch
return false if linux?
# Big Sur is the first version of macOS that runs on ARM
to_macos_version >= :big_sur
end
sig { returns(String) }
def default_prefix
if linux?

View File

@ -6,7 +6,7 @@ path = File.expand_path('..', __FILE__)
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/concurrent-ruby-1.1.10/lib/concurrent-ruby"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/i18n-1.12.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/minitest-5.16.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/tzinfo-2.0.4/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/tzinfo-2.0.5/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/zeitwerk-2.6.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/activesupport-6.1.6.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/public_suffix-4.0.7/lib"
@ -60,7 +60,7 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parallel-1.22.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parallel_tests-3.11.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parser-3.1.2.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rainbow-3.1.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-runtime-0.5.10160/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-runtime-0.5.10175/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parlour-8.0.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/patchelf-1.3.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/plist-3.6.0/lib"
@ -86,8 +86,8 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rspec_junit_formatter
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-ast-1.19.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-progressbar-1.11.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unicode-display_width-2.2.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-1.31.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-performance-1.14.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-1.32.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-performance-1.14.3/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rails-2.15.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rspec-2.12.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-sorbet-0.6.11/lib"
@ -96,10 +96,9 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov-html-0.12.3
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov_json_formatter-0.1.4/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov-0.21.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov-cobertura-2.1.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-static-0.5.10160-universal-darwin-15/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-0.5.10160/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-runtime-stub-0.2.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-static-and-runtime-0.5.10160/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-static-0.5.10175-universal-darwin-15/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-0.5.10175/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-static-and-runtime-0.5.10175/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/thor-1.2.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/spoom-1.1.11/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/yard-0.9.28/lib"

View File

@ -160,7 +160,7 @@ Performance/FlatMap:
Description: >-
Use `Enumerable#flat_map`
instead of `Enumerable#map...Array#flatten(1)`
or `Enumberable#collect..Array#flatten(1)`.
or `Enumerable#collect..Array#flatten(1)`.
Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablemaparrayflatten-vs-enumerableflat_map-code'
Enabled: true
VersionAdded: '0.30'

View File

@ -47,13 +47,13 @@ module RuboCop
RETURNS_NEW_ARRAY = (ALWAYS_RETURNS_NEW_ARRAY + RETURNS_NEW_ARRAY_WHEN_NO_BLOCK).freeze
MSG = 'Use unchained `%<method>s` and `%<second_method>s!` '\
'(followed by `return array` if required) instead of chaining '\
MSG = 'Use unchained `%<method>s` and `%<second_method>s!` ' \
'(followed by `return array` if required) instead of chaining ' \
'`%<method>s...%<second_method>s`.'
def_node_matcher :chain_array_allocation?, <<~PATTERN
(send {
(send _ $%RETURN_NEW_ARRAY_WHEN_ARGS {int lvar ivar cvar gvar})
(send _ $%RETURN_NEW_ARRAY_WHEN_ARGS {int lvar ivar cvar gvar send})
(block (send _ $%ALWAYS_RETURNS_NEW_ARRAY) ...)
(send _ $%RETURNS_NEW_ARRAY ...)
} $%HAS_MUTATION_ALTERNATIVE ...)

View File

@ -32,7 +32,7 @@ module RuboCop
# end
#
class CollectionLiteralInLoop < Base
MSG = 'Avoid immutable %<literal_class>s literals in loops. '\
MSG = 'Avoid immutable %<literal_class>s literals in loops. ' \
'It is better to extract it into a local variable or a constant.'
POST_CONDITION_LOOP_TYPES = %i[while_post until_post].freeze

View File

@ -58,8 +58,7 @@ module RuboCop
# `key?`/`value?` method.
corrector.replace(
node.loc.expression,
"#{autocorrect_hash_expression(node)}."\
"#{autocorrect_method(node)}(#{autocorrect_argument(node)})"
"#{autocorrect_hash_expression(node)}.#{autocorrect_method(node)}(#{autocorrect_argument(node)})"
)
end
end
@ -68,8 +67,7 @@ module RuboCop
private
def message(node)
"Use `##{autocorrect_method(node)}` instead of "\
"`##{current_method(node)}.include?`."
"Use `##{autocorrect_method(node)}` instead of `##{current_method(node)}.include?`."
end
def autocorrect_method(node)

View File

@ -38,7 +38,7 @@ module RuboCop
remove_class_variable remove_method undef_method class_variable_get class_variable_set
deprecate_constant module_function private private_constant protected public public_constant
remove_const ruby2_keywords
define_singleton_method instance_variable_defined instance_variable_get instance_variable_set
define_singleton_method instance_variable_defined? instance_variable_get instance_variable_set
method public_method public_send remove_instance_variable respond_to? send singleton_method
__send__
].freeze

View File

@ -7,7 +7,7 @@ module RuboCop
# in some Enumerable object can be replaced by `Enumerable#sum` method.
#
# @safety
# Auto-corrections are unproblematic wherever an initial value is provided explicitly:
# Autocorrections are unproblematic wherever an initial value is provided explicitly:
#
# [source,ruby]
# ----
@ -43,7 +43,7 @@ module RuboCop
#
# @example OnlySumOrWithInitialValue: false (default)
# # bad
# [1, 2, 3].inject(:+) # Auto-corrections for cases without initial value are unsafe
# [1, 2, 3].inject(:+) # Autocorrections for cases without initial value are unsafe
# [1, 2, 3].inject(&:+) # and will only be performed when using the `-A` option.
# [1, 2, 3].reduce { |acc, elem| acc + elem } # They can be prohibited completely using `SafeAutoCorrect: true`.
# [1, 2, 3].reduce(10, :+)

View File

@ -4,7 +4,7 @@ module RuboCop
module Performance
# This module holds the RuboCop Performance version information.
module Version
STRING = '1.14.2'
STRING = '1.14.3'
def self.document_version
STRING.match('\d+\.\d+').to_s

View File

@ -0,0 +1,116 @@
# frozen_string_literal: true
# typed: true
# This file is hand-crafted to encode the dependencies. They load the whole type
# system since there is such a high chance of it being used, using an autoloader
# wouldn't buy us any startup time saving.
# Namespaces without any implementation
module T; end
module T::Helpers; end
module T::Private; end
module T::Private::Abstract; end
module T::Private::Types; end
# Each section is a group that I believe need a fixed ordering. There is also
# an ordering between groups.
# These are pre-reqs for almost everything in here.
require_relative 'types/configuration'
require_relative 'types/_types'
require_relative 'types/private/decl_state'
require_relative 'types/private/class_utils'
require_relative 'types/private/runtime_levels'
require_relative 'types/private/methods/_methods'
require_relative 'types/sig'
require_relative 'types/helpers'
require_relative 'types/private/final'
require_relative 'types/private/sealed'
# The types themselves. First base classes
require_relative 'types/types/base'
require_relative 'types/types/typed_enumerable'
# Everything else
require_relative 'types/types/class_of'
require_relative 'types/types/enum'
require_relative 'types/types/fixed_array'
require_relative 'types/types/fixed_hash'
require_relative 'types/types/intersection'
require_relative 'types/types/noreturn'
require_relative 'types/types/proc'
require_relative 'types/types/attached_class'
require_relative 'types/types/self_type'
require_relative 'types/types/simple'
require_relative 'types/types/t_enum'
require_relative 'types/types/type_parameter'
require_relative 'types/types/typed_array'
require_relative 'types/types/typed_enumerator'
require_relative 'types/types/typed_enumerator_lazy'
require_relative 'types/types/typed_hash'
require_relative 'types/types/typed_range'
require_relative 'types/types/typed_set'
require_relative 'types/types/union'
require_relative 'types/types/untyped'
require_relative 'types/private/types/not_typed'
require_relative 'types/private/types/void'
require_relative 'types/private/types/string_holder'
require_relative 'types/private/types/type_alias'
require_relative 'types/types/type_variable'
require_relative 'types/types/type_member'
require_relative 'types/types/type_template'
# Call validation
require_relative 'types/private/methods/modes'
require_relative 'types/private/methods/call_validation'
# Signature validation
require_relative 'types/private/methods/signature_validation'
require_relative 'types/abstract_utils'
require_relative 'types/private/abstract/validate'
# Catch all. Sort of built by `cd extn; find types -type f | grep -v test | sort`
require_relative 'types/generic'
require_relative 'types/interface_wrapper'
require_relative 'types/private/abstract/declare'
require_relative 'types/private/abstract/hooks'
require_relative 'types/private/casts'
require_relative 'types/private/methods/decl_builder'
require_relative 'types/private/methods/signature'
require_relative 'types/private/retry'
require_relative 'types/utils'
require_relative 'types/boolean'
# Props dependencies
require_relative 'types/private/abstract/data'
require_relative 'types/private/mixins/mixins'
require_relative 'types/props/_props'
require_relative 'types/props/custom_type'
require_relative 'types/props/decorator'
require_relative 'types/props/errors'
require_relative 'types/props/plugin'
require_relative 'types/props/utils'
require_relative 'types/enum'
# Props that run sigs statically so have to be after all the others :(
require_relative 'types/props/private/setter_factory'
require_relative 'types/props/private/apply_default'
require_relative 'types/props/has_lazily_specialized_methods'
require_relative 'types/props/optional'
require_relative 'types/props/weak_constructor'
require_relative 'types/props/constructor'
require_relative 'types/props/pretty_printable'
require_relative 'types/props/private/serde_transform'
require_relative 'types/props/private/deserializer_generator'
require_relative 'types/props/private/serializer_generator'
require_relative 'types/props/serializable'
require_relative 'types/props/type_validation'
require_relative 'types/props/private/parser'
require_relative 'types/props/generated_code_validation'
require_relative 'types/struct'
require_relative 'types/non_forcing_constants'
require_relative 'types/compatibility_patches'
# Sorbet Compiler support module
require_relative 'types/private/compiler'

View File

@ -0,0 +1,316 @@
# frozen_string_literal: true
# typed: true
# This is where we define the shortcuts, so we can't use them here
# _____
# |_ _| _ _ __ ___ ___
# | || | | | '_ \ / _ \/ __|
# | || |_| | |_) | __/\__ \
# |_| \__, | .__/ \___||___/
# |___/|_|
#
# Docs at https://sorbet.org/docs/sigs
#
# Types that you can pass to `sig`:
#
# - a Ruby class
#
# - [<Type>, <Type>, ...] -- to specify a "tuple"; a fixed-size array with known types for each member
#
# - {key: <Type>, key2: <Type>, ...} -- to speicfy a "shape"; a fixed-size hash
# with known keys and type values
#
# - Any of the `T.foo` methods below
module T
# T.any(<Type>, <Type>, ...) -- matches any of the types listed
def self.any(type_a, type_b, *types)
type_a = T::Utils.coerce(type_a)
type_b = T::Utils.coerce(type_b)
types = types.map {|t| T::Utils.coerce(t)} if !types.empty?
T::Types::Union::Private::Pool.union_of_types(type_a, type_b, types)
end
# Shorthand for T.any(type, NilClass)
def self.nilable(type)
T::Types::Union::Private::Pool.union_of_types(T::Utils.coerce(type), T::Utils::Nilable::NIL_TYPE)
end
# Matches any object. In the static checker, T.untyped allows any
# method calls or operations.
def self.untyped
T::Types::Untyped::Private::INSTANCE
end
# Indicates a function never returns (e.g. "Kernel#raise")
def self.noreturn
T::Types::NoReturn::Private::INSTANCE
end
# T.all(<Type>, <Type>, ...) -- matches an object that has all of the types listed
def self.all(type_a, type_b, *types)
T::Types::Intersection.new([type_a, type_b] + types)
end
# Matches any of the listed values
# @deprecated Use T::Enum instead.
def self.deprecated_enum(values)
T::Types::Enum.new(values)
end
# Creates a proc type
def self.proc
T::Private::Methods.start_proc
end
# Matches `self`:
def self.self_type
T::Types::SelfType::Private::INSTANCE
end
# Matches the instance type in a singleton-class context
def self.attached_class
T::Types::AttachedClassType::Private::INSTANCE
end
# Matches any class that subclasses or includes the provided class
# or module
def self.class_of(klass)
T::Types::ClassOf.new(klass)
end
## END OF THE METHODS TO PASS TO `sig`.
# Constructs a type alias. Used to create a short name for a larger type. In Ruby this returns a
# wrapper that contains a proc that is evaluated to get the underlying type. This syntax however
# is needed for support by the static checker.
#
# @example
# NilableString = T.type_alias {T.nilable(String)}
#
# sig {params(arg: NilableString, default: String).returns(String)}
# def or_else(arg, default)
# arg || default
# end
#
# The name of the type alias is not preserved; Error messages will
# be printed with reference to the underlying type.
#
# TODO Remove `type` parameter. This was left in to make life easier while migrating.
def self.type_alias(type=nil, &blk)
if blk
T::Private::Types::TypeAlias.new(blk)
else
T::Utils.coerce(type)
end
end
# References a type parameter which was previously defined with
# `type_parameters`.
#
# This is used for generic methods.
#
# @example
# sig
# .type_parameters(:U)
# .params(
# blk: T.proc.params(arg0: Elem).returns(T.type_parameter(:U)),
# )
# .returns(T::Array[T.type_parameter(:U)])
# def map(&blk); end
def self.type_parameter(name)
T::Types::TypeParameter.new(name)
end
# Tells the typechecker that `value` is of type `type`. Use this to get additional checking after
# an expression that the typechecker is unable to analyze. If `checked` is true, raises an
# exception at runtime if the value doesn't match the type.
#
# Compared to `T.let`, `T.cast` is _trusted_ by static system.
def self.cast(value, type, checked: true)
return value unless checked
Private::Casts.cast(value, type, cast_method: "T.cast")
end
# Tells the typechecker to declare a variable of type `type`. Use
# like:
#
# seconds = T.let(0.0, Float)
#
# Compared to `T.cast`, `T.let` is _checked_ by static system.
#
# If `checked` is true, raises an exception at runtime if the value
# doesn't match the type.
def self.let(value, type, checked: true)
return value unless checked
Private::Casts.cast(value, type, cast_method: "T.let")
end
# Tells the type checker to treat `self` in the current block as `type`.
# Useful for blocks that are captured and executed later with instance_exec.
# Use like:
#
# seconds = lambda do
# T.bind(self, NewBinding)
# ...
# end
#
# `T.bind` behaves like `T.cast` in that it is assumed to be true statically.
#
# If `checked` is true, raises an exception at runtime if the value
# doesn't match the type (this is the default).
def self.bind(value, type, checked: true)
return value unless checked
Private::Casts.cast(value, type, cast_method: "T.bind")
end
# Tells the typechecker to ensure that `value` is of type `type` (if not, the typechecker will
# fail). Use this for debugging typechecking errors, or to ensure that type information is
# statically known and being checked appropriately. If `checked` is true, raises an exception at
# runtime if the value doesn't match the type.
def self.assert_type!(value, type, checked: true)
return value unless checked
Private::Casts.cast(value, type, cast_method: "T.assert_type!")
end
# For the static type checker, strips all type information from a value
# and returns the same value, but statically-typed as `T.untyped`.
# Can be used to tell the static checker to "trust you" by discarding type information
# you know to be incorrect. Use with care!
# (This has no effect at runtime.)
#
# We can't actually write this sig because we ourselves are inside
# the `T::` module and doing this would create a bootstrapping
# cycle. However, we also don't actually need to do so; An untyped
# identity method works just as well here.
#
# `sig {params(value: T.untyped).returns(T.untyped)}`
def self.unsafe(value)
value
end
# A convenience method to `raise` when the argument is `nil` and return it
# otherwise.
#
# Intended to be used as:
#
# needs_foo(T.must(maybe_gives_foo))
#
# Equivalent to:
#
# foo = maybe_gives_foo
# raise "nil" if foo.nil?
# needs_foo(foo)
#
# Intended to be used to promise sorbet that a given nilable value happens
# to contain a non-nil value at this point.
#
# `sig {params(arg: T.nilable(A)).returns(A)}`
def self.must(arg)
return arg if arg
return arg if arg == false
begin
raise TypeError.new("Passed `nil` into T.must")
rescue TypeError => e # raise into rescue to ensure e.backtrace is populated
T::Configuration.inline_type_error_handler(e, {kind: 'T.must', value: arg, type: nil})
end
end
# A way to ask Sorbet to show what type it thinks an expression has.
# This can be useful for debugging and checking assumptions.
# In the runtime, merely returns the value passed in.
def self.reveal_type(value)
value
end
# A way to ask Sorbet to prove that a certain branch of control flow never
# happens. Commonly used to assert that a case or if statement exhausts all
# possible cases.
def self.absurd(value)
msg = "Control flow reached T.absurd."
case value
when Kernel
msg += " Got value: #{value}"
end
begin
raise TypeError.new(msg)
rescue TypeError => e # raise into rescue to ensure e.backtrace is populated
T::Configuration.inline_type_error_handler(e, {kind: 'T.absurd', value: value, type: nil})
end
end
### Generic classes ###
module Array
def self.[](type)
if type.is_a?(T::Types::Untyped)
T::Types::TypedArray::Untyped.new
else
T::Types::TypedArray.new(type)
end
end
end
module Hash
def self.[](keys, values)
if keys.is_a?(T::Types::Untyped) && values.is_a?(T::Types::Untyped)
T::Types::TypedHash::Untyped.new
else
T::Types::TypedHash.new(keys: keys, values: values)
end
end
end
module Enumerable
def self.[](type)
if type.is_a?(T::Types::Untyped)
T::Types::TypedEnumerable::Untyped.new
else
T::Types::TypedEnumerable.new(type)
end
end
end
module Enumerator
def self.[](type)
if type.is_a?(T::Types::Untyped)
T::Types::TypedEnumerator::Untyped.new
else
T::Types::TypedEnumerator.new(type)
end
end
module Lazy
def self.[](type)
if type.is_a?(T::Types::Untyped)
T::Types::TypedEnumeratorLazy::Untyped.new
else
T::Types::TypedEnumeratorLazy.new(type)
end
end
end
end
module Range
def self.[](type)
T::Types::TypedRange.new(type)
end
end
module Set
def self.[](type)
if type.is_a?(T::Types::Untyped)
T::Types::TypedSet::Untyped.new
else
T::Types::TypedSet.new(type)
end
end
end
end

View File

@ -0,0 +1,50 @@
# frozen_string_literal: true
# typed: true
module T::AbstractUtils
Methods = T::Private::Methods
# Returns whether a module is declared as abstract. After the module is finished being declared,
# this is equivalent to whether it has any abstract methods that haven't been implemented
# (because we validate that and raise an error otherwise).
#
# Note that checking `mod.is_a?(Abstract::Hooks)` is not a safe substitute for this method; when
# a class extends `Abstract::Hooks`, all of its subclasses, including the eventual concrete
# ones, will still have `Abstract::Hooks` as an ancestor.
def self.abstract_module?(mod)
!T::Private::Abstract::Data.get(mod, :abstract_type).nil?
end
def self.abstract_method?(method)
signature = Methods.signature_for_method(method)
signature&.mode == Methods::Modes.abstract
end
# Given a module, returns the set of methods declared as abstract (in itself or ancestors)
# that have not been implemented.
def self.abstract_methods_for(mod)
declared_methods = declared_abstract_methods_for(mod)
declared_methods.select do |declared_method|
actual_method = mod.instance_method(declared_method.name)
# Note that in the case where an abstract method is overridden by another abstract method,
# this method will return them both. This is intentional to ensure we validate the final
# implementation against all declarations of an abstract method (they might not all have the
# same signature).
abstract_method?(actual_method)
end
end
# Given a module, returns the set of methods declared as abstract (in itself or ancestors)
# regardless of whether they have been implemented.
def self.declared_abstract_methods_for(mod)
methods = []
mod.ancestors.each do |ancestor|
ancestor_methods = ancestor.private_instance_methods(false) + ancestor.instance_methods(false)
ancestor_methods.each do |method_name|
method = ancestor.instance_method(method_name)
methods << method if abstract_method?(method)
end
end
methods
end
end

View File

@ -0,0 +1,8 @@
# typed: strict
# frozen_string_literal: true
module T
# T::Boolean is a type alias helper for the common `T.any(TrueClass, FalseClass)`.
# Defined separately from _types.rb because it has a dependency on T::Types::Union.
Boolean = T.type_alias {T.any(TrueClass, FalseClass)}
end

View File

@ -0,0 +1,93 @@
# frozen_string_literal: true
# typed: ignore
# Work around an interaction bug with sorbet-runtime and rspec-mocks,
# which occurs when using message expectations (*_any_instance_of,
# expect, allow) and and_call_original.
#
# When a sig is defined, sorbet-runtime will replace the sigged method
# with a wrapper that, upon first invocation, re-wraps the method with a faster
# implementation.
#
# When expect_any_instance_of is used, rspec stores a reference to the first wrapper,
# to be restored later.
#
# The first wrapper is invoked as part of the test and sorbet-runtime replaces
# the method definition with the second wrapper.
#
# But when mocks are cleaned up, rspec restores back to the first wrapper.
# Upon subsequent invocations, the first wrapper is called, and sorbet-runtime
# throws a runtime error, since this is an unexpected state.
#
# We work around this by forcing re-wrapping before rspec stores a reference
# to the method.
if defined? ::RSpec::Mocks
module T
module CompatibilityPatches
module RSpecCompatibility
module RecorderExtensions
def observe!(method_name)
method = @klass.instance_method(method_name.to_sym)
T::Private::Methods.maybe_run_sig_block_for_method(method)
super(method_name)
end
end
::RSpec::Mocks::AnyInstance::Recorder.prepend(RecorderExtensions) if defined?(::RSpec::Mocks::AnyInstance::Recorder)
module MethodDoubleExtensions
def initialize(object, method_name, proxy)
if ::Kernel.instance_method(:respond_to?).bind(object).call(method_name, true)
method = ::RSpec::Support.method_handle_for(object, method_name)
T::Private::Methods.maybe_run_sig_block_for_method(method)
end
super(object, method_name, proxy)
end
end
::RSpec::Mocks::MethodDouble.prepend(MethodDoubleExtensions) if defined?(::RSpec::Mocks::MethodDouble)
end
end
end
end
# Work around for sorbet-runtime wrapped methods.
#
# When a sig is defined, sorbet-runtime will replace the sigged method
# with a wrapper. Those wrapper methods look like `foo(*args, &blk)`
# so that wrappers can handle and pass on all the arguments supplied.
#
# However, that creates a problem with runtime reflection on the methods,
# since when a sigged method is introspected, it will always return its
# `arity` as `-1`, its `parameters` as `[[:rest, :args], [:block, :blk]]`,
# and its `source_location` as `[<some_file_in_sorbet>, <some_line_number>]`.
#
# This might be a problem for some applications that rely on getting the
# correct information from these methods.
#
# This compatibility module, when prepended to the `Method` class, would fix
# the return values of `arity`, `parameters` and `source_location`.
#
# @example
# require 'sorbet-runtime'
# ::Method.prepend(T::CompatibilityPatches::MethodExtensions)
module T
module CompatibilityPatches
module MethodExtensions
def arity
arity = super
return arity if arity != -1 || self.is_a?(Proc)
sig = T::Private::Methods.signature_for_method(self)
sig ? sig.method.arity : arity
end
def source_location
sig = T::Private::Methods.signature_for_method(self)
sig ? sig.method.source_location : super
end
def parameters
sig = T::Private::Methods.signature_for_method(self)
sig ? sig.method.parameters : super
end
end
end
end

View File

@ -0,0 +1,591 @@
# typed: true
# frozen_string_literal: true
module T::Configuration
# Cache this comparisonn to avoid two allocations all over the place.
AT_LEAST_RUBY_2_7 = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7')
# Announces to Sorbet that we are currently in a test environment, so it
# should treat any sigs which are marked `.checked(:tests)` as if they were
# just a normal sig.
#
# If this method is not called, sigs marked `.checked(:tests)` will not be
# checked. In fact, such methods won't even be wrapped--the runtime will put
# back the original method.
#
# Note: Due to the way sigs are evaluated and methods are wrapped, this
# method MUST be called before any code calls `sig`. This method raises if
# it has been called too late.
def self.enable_checking_for_sigs_marked_checked_tests
T::Private::RuntimeLevels.enable_checking_in_tests
end
# Announce to Sorbet that we would like the final checks to be enabled when
# including and extending modules. Iff this is not called, then the following
# example will not raise an error.
#
# ```ruby
# module M
# extend T::Sig
# sig(:final) {void}
# def foo; end
# end
# class C
# include M
# def foo; end
# end
# ```
def self.enable_final_checks_on_hooks
T::Private::Methods.set_final_checks_on_hooks(true)
end
# Undo the effects of a previous call to
# `enable_final_checks_on_hooks`.
def self.reset_final_checks_on_hooks
T::Private::Methods.set_final_checks_on_hooks(false)
end
@include_value_in_type_errors = true
# Whether to include values in TypeError messages.
#
# Including values is useful for debugging, but can potentially leak
# sensitive information to logs.
#
# @return [T::Boolean]
def self.include_value_in_type_errors?
@include_value_in_type_errors
end
# Configure if type errors excludes the value of the problematic type.
#
# The default is to include values in type errors:
# TypeError: Expected type Integer, got String with value "foo"
#
# When values are excluded from type errors:
# TypeError: Expected type Integer, got String
def self.exclude_value_in_type_errors
@include_value_in_type_errors = false
end
# Opposite of exclude_value_in_type_errors.
# (Including values in type errors is the default)
def self.include_value_in_type_errors
@include_value_in_type_errors = true
end
# Whether VM-defined prop serialization/deserialization routines can be enabled.
#
# @return [T::Boolean]
def self.can_enable_vm_prop_serde?
T::Props::Private::DeserializerGenerator.respond_to?(:generate2)
end
@use_vm_prop_serde = false
# Whether to use VM-defined prop serialization/deserialization routines.
#
# The default is to use runtime codegen inside sorbet-runtime itself.
#
# @return [T::Boolean]
def self.use_vm_prop_serde?
@use_vm_prop_serde || false
end
# Enable using VM-defined prop serialization/deserialization routines.
#
# This method is likely to break things outside of Stripe's systems.
def self.enable_vm_prop_serde
if !can_enable_vm_prop_serde?
hard_assert_handler('Ruby VM is not setup to use VM-defined prop serde')
end
@use_vm_prop_serde = true
end
# Disable using VM-defined prop serialization/deserialization routines.
def self.disable_vm_prop_serde
@use_vm_prop_serde = false
end
# Configure the default checked level for a sig with no explicit `.checked`
# builder. When unset, the default checked level is `:always`.
#
# Note: setting this option is potentially dangerous! Sorbet can't check all
# code statically. The runtime checks complement the checks that Sorbet does
# statically, so that methods don't have to guard themselves from being
# called incorrectly by untyped code.
#
# @param [:never, :compiled, :tests, :always] default_checked_level
def self.default_checked_level=(default_checked_level)
T::Private::RuntimeLevels.default_checked_level = default_checked_level
end
@inline_type_error_handler = nil
# Set a handler to handle `TypeError`s raised by any in-line type assertions,
# including `T.must`, `T.let`, `T.cast`, and `T.assert_type!`.
#
# By default, any `TypeError`s detected by this gem will be raised. Setting
# inline_type_error_handler to an object that implements :call (e.g. proc or
# lambda) allows users to customize the behavior when a `TypeError` is
# raised on any inline type assertion.
#
# @param [Lambda, Proc, Object, nil] value Proc that handles the error (pass
# nil to reset to default behavior)
#
# Parameters passed to value.call:
#
# @param [TypeError] error TypeError that was raised
# @param [Hash] opts A hash containing contextual information on the error:
# @option opts [String] :kind One of:
# ['T.cast', 'T.let', 'T.bind', 'T.assert_type!', 'T.must', 'T.absurd']
# @option opts [Object, nil] :type Expected param/return value type
# @option opts [Object] :value Actual param/return value
#
# @example
# T::Configuration.inline_type_error_handler = lambda do |error, opts|
# puts error.message
# end
def self.inline_type_error_handler=(value)
validate_lambda_given!(value)
@inline_type_error_handler = value
end
private_class_method def self.inline_type_error_handler_default(error, opts)
raise error
end
def self.inline_type_error_handler(error, opts={})
if @inline_type_error_handler
# Backwards compatibility before `inline_type_error_handler` took a second arg
if @inline_type_error_handler.arity == 1
@inline_type_error_handler.call(error)
else
@inline_type_error_handler.call(error, opts)
end
else
inline_type_error_handler_default(error, opts)
end
nil
end
@sig_builder_error_handler = nil
# Set a handler to handle errors that occur when the builder methods in the
# body of a sig are executed. The sig builder methods are inside a proc so
# that they can be lazily evaluated the first time the method being sig'd is
# called.
#
# By default, improper use of the builder methods within the body of a sig
# cause an ArgumentError to be raised. Setting sig_builder_error_handler to an
# object that implements :call (e.g. proc or lambda) allows users to
# customize the behavior when a sig can't be built for some reason.
#
# @param [Lambda, Proc, Object, nil] value Proc that handles the error (pass
# nil to reset to default behavior)
#
# Parameters passed to value.call:
#
# @param [StandardError] error The error that was raised
# @param [Thread::Backtrace::Location] location Location of the error
#
# @example
# T::Configuration.sig_builder_error_handler = lambda do |error, location|
# puts error.message
# end
def self.sig_builder_error_handler=(value)
validate_lambda_given!(value)
@sig_builder_error_handler = value
end
private_class_method def self.sig_builder_error_handler_default(error, location)
raise ArgumentError.new("#{location.path}:#{location.lineno}: Error interpreting `sig`:\n #{error.message}\n\n")
end
def self.sig_builder_error_handler(error, location)
if @sig_builder_error_handler
@sig_builder_error_handler.call(error, location)
else
sig_builder_error_handler_default(error, location)
end
nil
end
@sig_validation_error_handler = nil
# Set a handler to handle sig validation errors.
#
# Sig validation errors include things like abstract checks, override checks,
# and type compatibility of arguments. They happen after a sig has been
# successfully built, but the built sig is incompatible with other sigs in
# some way.
#
# By default, sig validation errors cause an exception to be raised.
# Setting sig_validation_error_handler to an object that implements :call
# (e.g. proc or lambda) allows users to customize the behavior when a method
# signature's build fails.
#
# @param [Lambda, Proc, Object, nil] value Proc that handles the error (pass
# nil to reset to default behavior)
#
# Parameters passed to value.call:
#
# @param [StandardError] error The error that was raised
# @param [Hash] opts A hash containing contextual information on the error:
# @option opts [Method, UnboundMethod] :method Method on which the signature build failed
# @option opts [T::Private::Methods::Declaration] :declaration Method
# signature declaration struct
# @option opts [T::Private::Methods::Signature, nil] :signature Signature
# that failed (nil if sig build failed before Signature initialization)
# @option opts [T::Private::Methods::Signature, nil] :super_signature Super
# method's signature (nil if method is not an override or super method
# does not have a method signature)
#
# @example
# T::Configuration.sig_validation_error_handler = lambda do |error, opts|
# puts error.message
# end
def self.sig_validation_error_handler=(value)
validate_lambda_given!(value)
@sig_validation_error_handler = value
end
private_class_method def self.sig_validation_error_handler_default(error, opts)
raise error
end
def self.sig_validation_error_handler(error, opts={})
if @sig_validation_error_handler
@sig_validation_error_handler.call(error, opts)
else
sig_validation_error_handler_default(error, opts)
end
nil
end
@call_validation_error_handler = nil
# Set a handler for type errors that result from calling a method.
#
# By default, errors from calling a method cause an exception to be raised.
# Setting call_validation_error_handler to an object that implements :call
# (e.g. proc or lambda) allows users to customize the behavior when a method
# is called with invalid parameters, or returns an invalid value.
#
# @param [Lambda, Proc, Object, nil] value Proc that handles the error
# report (pass nil to reset to default behavior)
#
# Parameters passed to value.call:
#
# @param [T::Private::Methods::Signature] signature Signature that failed
# @param [Hash] opts A hash containing contextual information on the error:
# @option opts [String] :message Error message
# @option opts [String] :kind One of:
# ['Parameter', 'Block parameter', 'Return value']
# @option opts [Symbol] :name Param or block param name (nil for return
# value)
# @option opts [Object] :type Expected param/return value type
# @option opts [Object] :value Actual param/return value
# @option opts [Thread::Backtrace::Location] :location Location of the
# caller
#
# @example
# T::Configuration.call_validation_error_handler = lambda do |signature, opts|
# puts opts[:message]
# end
def self.call_validation_error_handler=(value)
validate_lambda_given!(value)
@call_validation_error_handler = value
end
private_class_method def self.call_validation_error_handler_default(signature, opts)
raise TypeError.new(opts[:pretty_message])
end
def self.call_validation_error_handler(signature, opts={})
if @call_validation_error_handler
@call_validation_error_handler.call(signature, opts)
else
call_validation_error_handler_default(signature, opts)
end
nil
end
@log_info_handler = nil
# Set a handler for logging
#
# @param [Lambda, Proc, Object, nil] value Proc that handles the error
# report (pass nil to reset to default behavior)
#
# Parameters passed to value.call:
#
# @param [String] str Message to be logged
# @param [Hash] extra A hash containing additional parameters to be passed along to the logger.
#
# @example
# T::Configuration.log_info_handler = lambda do |str, extra|
# puts "#{str}, context: #{extra}"
# end
def self.log_info_handler=(value)
validate_lambda_given!(value)
@log_info_handler = value
end
private_class_method def self.log_info_handler_default(str, extra)
puts "#{str}, extra: #{extra}"
end
def self.log_info_handler(str, extra)
if @log_info_handler
@log_info_handler.call(str, extra)
else
log_info_handler_default(str, extra)
end
end
@soft_assert_handler = nil
# Set a handler for soft assertions
#
# These generally shouldn't stop execution of the program, but rather inform
# some party of the assertion to action on later.
#
# @param [Lambda, Proc, Object, nil] value Proc that handles the error
# report (pass nil to reset to default behavior)
#
# Parameters passed to value.call:
#
# @param [String] str Assertion message
# @param [Hash] extra A hash containing additional parameters to be passed along to the handler.
#
# @example
# T::Configuration.soft_assert_handler = lambda do |str, extra|
# puts "#{str}, context: #{extra}"
# end
def self.soft_assert_handler=(value)
validate_lambda_given!(value)
@soft_assert_handler = value
end
private_class_method def self.soft_assert_handler_default(str, extra)
puts "#{str}, extra: #{extra}"
end
def self.soft_assert_handler(str, extra)
if @soft_assert_handler
@soft_assert_handler.call(str, extra)
else
soft_assert_handler_default(str, extra)
end
end
@hard_assert_handler = nil
# Set a handler for hard assertions
#
# These generally should stop execution of the program, and optionally inform
# some party of the assertion.
#
# @param [Lambda, Proc, Object, nil] value Proc that handles the error
# report (pass nil to reset to default behavior)
#
# Parameters passed to value.call:
#
# @param [String] str Assertion message
# @param [Hash] extra A hash containing additional parameters to be passed along to the handler.
#
# @example
# T::Configuration.hard_assert_handler = lambda do |str, extra|
# raise "#{str}, context: #{extra}"
# end
def self.hard_assert_handler=(value)
validate_lambda_given!(value)
@hard_assert_handler = value
end
private_class_method def self.hard_assert_handler_default(str, _)
raise str
end
def self.hard_assert_handler(str, extra={})
if @hard_assert_handler
@hard_assert_handler.call(str, extra)
else
hard_assert_handler_default(str, extra)
end
end
@scalar_types = nil
# Set a list of class strings that are to be considered scalar.
# (pass nil to reset to default behavior)
#
# @param [String] values Class name.
#
# @example
# T::Configuration.scalar_types = ["NilClass", "TrueClass", "FalseClass", ...]
def self.scalar_types=(values)
if values.nil?
@scalar_types = values
else
bad_values = values.reject {|v| v.class == String}
unless bad_values.empty?
raise ArgumentError.new("Provided values must all be class name strings.")
end
@scalar_types = values.each_with_object({}) {|x, acc| acc[x] = true}.freeze
end
end
@default_scalar_types = {
"NilClass" => true,
"TrueClass" => true,
"FalseClass" => true,
"Integer" => true,
"Float" => true,
"String" => true,
"Symbol" => true,
"Time" => true,
"T::Enum" => true,
}.freeze
def self.scalar_types
@scalar_types || @default_scalar_types
end
# Guard against overrides of `name` or `to_s`
MODULE_NAME = Module.instance_method(:name)
private_constant :MODULE_NAME
@default_module_name_mangler = if T::Configuration::AT_LEAST_RUBY_2_7
->(type) {MODULE_NAME.bind_call(type)}
else
->(type) {MODULE_NAME.bind(type).call}
end
@module_name_mangler = nil
def self.module_name_mangler
@module_name_mangler || @default_module_name_mangler
end
# Set to override the default behavior for converting types
# to names in generated code. Used by the runtime implementation
# associated with `--stripe-packages` mode.
#
# @param [Lambda, Proc, nil] handler Proc that converts a type (Class/Module)
# to a String (pass nil to reset to default behavior)
def self.module_name_mangler=(handler)
@module_name_mangler = handler
end
@sensitivity_and_pii_handler = nil
# Set to a PII handler function. This will be called with the `sensitivity:`
# annotations on things that use `T::Props` and can modify them ahead-of-time.
#
# @param [Lambda, Proc, nil] handler Proc that takes a hash mapping symbols to the
# prop values. Pass nil to avoid changing `sensitivity:` annotations.
def self.normalize_sensitivity_and_pii_handler=(handler)
@sensitivity_and_pii_handler = handler
end
def self.normalize_sensitivity_and_pii_handler
@sensitivity_and_pii_handler
end
@redaction_handler = nil
# Set to a redaction handling function. This will be called when the
# `_redacted` version of a prop reader is used. By default this is set to
# `nil` and will raise an exception when the redacted version of a prop is
# accessed.
#
# @param [Lambda, Proc, nil] handler Proc that converts a value into its
# redacted version according to the spec passed as the second argument.
def self.redaction_handler=(handler)
@redaction_handler = handler
end
def self.redaction_handler
@redaction_handler
end
@class_owner_finder = nil
# Set to a function which can get the 'owner' of a class. This is
# used in reporting deserialization errors
#
# @param [Lambda, Proc, nil] handler Proc that takes a class and
# produces its owner, or `nil` if it does not have one.
def self.class_owner_finder=(handler)
@class_owner_finder = handler
end
def self.class_owner_finder
@class_owner_finder
end
# Temporarily disable ruby warnings while executing the given block. This is
# useful when doing something that would normally cause a warning to be
# emitted in Ruby verbose mode ($VERBOSE = true).
#
# @yield
#
def self.without_ruby_warnings
if $VERBOSE
begin
original_verbose = $VERBOSE
$VERBOSE = false
yield
ensure
$VERBOSE = original_verbose
end
else
yield
end
end
@legacy_t_enum_migration_mode = false
def self.enable_legacy_t_enum_migration_mode
@legacy_t_enum_migration_mode = true
end
def self.disable_legacy_t_enum_migration_mode
@legacy_t_enum_migration_mode = false
end
def self.legacy_t_enum_migration_mode?
@legacy_t_enum_migration_mode || false
end
@prop_freeze_handler = ->(instance, prop_name) {}
def self.prop_freeze_handler=(handler)
@prop_freeze_handler = handler
end
def self.prop_freeze_handler
@prop_freeze_handler
end
@sealed_violation_whitelist = nil
# @param [Array] sealed_violation_whitelist An array of Regexp to validate
# whether inheriting /including a sealed module outside the defining module
# should be allowed. Useful to whitelist benign violations, like shim files
# generated for an autoloader.
def self.sealed_violation_whitelist=(sealed_violation_whitelist)
if !@sealed_violation_whitelist.nil?
raise ArgumentError.new("Cannot overwrite sealed_violation_whitelist after setting it")
end
case sealed_violation_whitelist
when Array
sealed_violation_whitelist.each do |x|
case x
when Regexp then nil
else raise TypeError.new("sealed_violation_whitelist accepts an Array of Regexp")
end
end
else
raise TypeError.new("sealed_violation_whitelist= accepts an Array of Regexp")
end
@sealed_violation_whitelist = sealed_violation_whitelist
end
def self.sealed_violation_whitelist
@sealed_violation_whitelist
end
private_class_method def self.validate_lambda_given!(value)
if !value.nil? && !value.respond_to?(:call)
raise ArgumentError.new("Provided value must respond to :call")
end
end
end

View File

@ -0,0 +1,377 @@
# frozen_string_literal: true
# typed: strict
# Enumerations allow for type-safe declarations of a fixed set of values.
#
# Every value is a singleton instance of the class (i.e. `Suit::SPADE.is_a?(Suit) == true`).
#
# Each value has a corresponding serialized value. By default this is the constant's name converted
# to lowercase (e.g. `Suit::Club.serialize == 'club'`); however a custom value may be passed to the
# constructor. Enum will `freeze` the serialized value.
#
# @example Declaring an Enum:
# class Suit < T::Enum
# enums do
# CLUB = new
# SPADE = new
# DIAMOND = new
# HEART = new
# end
# end
#
# @example Custom serialization value:
# class Status < T::Enum
# enums do
# READY = new('rdy')
# ...
# end
# end
#
# @example Accessing values:
# Suit::SPADE
#
# @example Converting from serialized value to enum instance:
# Suit.deserialize('club') == Suit::CLUB
#
# @example Using enums in type signatures:
# sig {params(suit: Suit).returns(Boolean)}
# def is_red?(suit); ...; end
#
# WARNING: Enum instances are singletons that are shared among all their users. Their internals
# should be kept immutable to avoid unpredictable action at a distance.
class T::Enum
extend T::Sig
extend T::Props::CustomType
# TODO(jez) Might want to restrict this, or make subclasses provide this type
SerializedVal = T.type_alias {T.untyped}
private_constant :SerializedVal
### Enum class methods ###
sig {returns(T::Array[T.attached_class])}
def self.values
if @values.nil?
raise "Attempting to access values of #{self.class} before it has been initialized." \
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
end
@values
end
# This exists for compatibility with the interface of `Hash` & mostly to support
# the HashEachMethods Rubocop.
sig {params(blk: T.nilable(T.proc.params(arg0: T.attached_class).void)).returns(T.any(T::Enumerator[T.attached_class], T::Array[T.attached_class]))}
def self.each_value(&blk)
if blk
values.each(&blk)
else
values.each
end
end
# Convert from serialized value to enum instance
#
# Note: It would have been nice to make this method final before people started overriding it.
# Note: Failed CriticalMethodsNoRuntimeTypingTest
sig {params(serialized_val: SerializedVal).returns(T.nilable(T.attached_class)).checked(:never)}
def self.try_deserialize(serialized_val)
if @mapping.nil?
raise "Attempting to access serialization map of #{self.class} before it has been initialized." \
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
end
@mapping[serialized_val]
end
# Convert from serialized value to enum instance.
#
# Note: It would have been nice to make this method final before people started overriding it.
# Note: Failed CriticalMethodsNoRuntimeTypingTest
#
# @return [self]
# @raise [KeyError] if serialized value does not match any instance.
sig {overridable.params(serialized_val: SerializedVal).returns(T.attached_class).checked(:never)}
def self.from_serialized(serialized_val)
res = try_deserialize(serialized_val)
if res.nil?
raise KeyError.new("Enum #{self} key not found: #{serialized_val.inspect}")
end
res
end
# Note: It would have been nice to make this method final before people started overriding it.
# @return [Boolean] Does the given serialized value correspond with any of this enum's values.
sig {overridable.params(serialized_val: SerializedVal).returns(T::Boolean).checked(:never)}
def self.has_serialized?(serialized_val)
if @mapping.nil?
raise "Attempting to access serialization map of #{self.class} before it has been initialized." \
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
end
@mapping.include?(serialized_val)
end
# Note: Failed CriticalMethodsNoRuntimeTypingTest
sig {override.params(instance: T.nilable(T::Enum)).returns(SerializedVal).checked(:never)}
def self.serialize(instance)
# This is needed otherwise if a Chalk::ODM::Document with a property of the shape
# T::Hash[T.nilable(MyEnum), Integer] and a value that looks like {nil => 0} is
# serialized, we throw the error on L102.
return nil if instance.nil?
if self == T::Enum
raise "Cannot call T::Enum.serialize directly. You must call on a specific child class."
end
if instance.class != self
raise "Cannot call #serialize on a value that is not an instance of #{self}."
end
instance.serialize
end
# Note: Failed CriticalMethodsNoRuntimeTypingTest
sig {override.params(mongo_value: SerializedVal).returns(T.attached_class).checked(:never)}
def self.deserialize(mongo_value)
if self == T::Enum
raise "Cannot call T::Enum.deserialize directly. You must call on a specific child class."
end
self.from_serialized(mongo_value)
end
### Enum instance methods ###
sig {returns(T.self_type)}
def dup
self
end
sig {returns(T.self_type).checked(:tests)}
def clone
self
end
# Note: Failed CriticalMethodsNoRuntimeTypingTest
sig {returns(SerializedVal).checked(:never)}
def serialize
assert_bound!
@serialized_val
end
sig {params(args: T.untyped).returns(T.untyped)}
def to_json(*args)
serialize.to_json(*args)
end
sig {params(args: T.untyped).returns(T.untyped)}
def as_json(*args)
serialized_val = serialize
return serialized_val unless serialized_val.respond_to?(:as_json)
serialized_val.as_json(*args)
end
sig {returns(String)}
def to_s
inspect
end
sig {returns(String)}
def inspect
"#<#{self.class.name}::#{@const_name || '__UNINITIALIZED__'}>"
end
sig {params(other: BasicObject).returns(T.nilable(Integer))}
def <=>(other)
case other
when self.class
self.serialize <=> other.serialize
else
nil
end
end
# NB: Do not call this method. This exists to allow for a safe migration path in places where enum
# values are compared directly against string values.
#
# Ruby's string has a weird quirk where `'my_string' == obj` calls obj.==('my_string') if obj
# responds to the `to_str` method. It does not actually call `to_str` however.
#
# See https://ruby-doc.org/core-2.4.0/String.html#method-i-3D-3D
sig {returns(String)}
def to_str
msg = 'Implicit conversion of Enum instances to strings is not allowed. Call #serialize instead.'
if T::Configuration.legacy_t_enum_migration_mode?
T::Configuration.soft_assert_handler(
msg,
storytime: {class: self.class.name},
)
serialize.to_s
else
raise NoMethodError.new(msg)
end
end
sig {params(other: BasicObject).returns(T::Boolean).checked(:never)}
def ==(other)
case other
when String
if T::Configuration.legacy_t_enum_migration_mode?
comparison_assertion_failed(:==, other)
self.serialize == other
else
false
end
else
super(other)
end
end
sig {params(other: BasicObject).returns(T::Boolean).checked(:never)}
def ===(other)
case other
when String
if T::Configuration.legacy_t_enum_migration_mode?
comparison_assertion_failed(:===, other)
self.serialize == other
else
false
end
else
super(other)
end
end
sig {params(method: Symbol, other: T.untyped).void}
private def comparison_assertion_failed(method, other)
T::Configuration.soft_assert_handler(
'Enum to string comparison not allowed. Compare to the Enum instance directly instead. See go/enum-migration',
storytime: {
class: self.class.name,
self: self.inspect,
other: other,
other_class: other.class.name,
method: method,
}
)
end
### Private implementation ###
sig {params(serialized_val: SerializedVal).void}
def initialize(serialized_val=nil)
raise 'T::Enum is abstract' if self.class == T::Enum
if !self.class.started_initializing?
raise "Must instantiate all enum values of #{self.class} inside 'enums do'."
end
if self.class.fully_initialized?
raise "Cannot instantiate a new enum value of #{self.class} after it has been initialized."
end
serialized_val = serialized_val.frozen? ? serialized_val : serialized_val.dup.freeze
@serialized_val = T.let(serialized_val, T.nilable(SerializedVal))
@const_name = T.let(nil, T.nilable(Symbol))
self.class._register_instance(self)
end
sig {returns(NilClass).checked(:never)}
private def assert_bound!
if @const_name.nil?
raise "Attempting to access Enum value on #{self.class} before it has been initialized." \
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
end
end
sig {params(const_name: Symbol).void}
def _bind_name(const_name)
@const_name = const_name
@serialized_val = const_to_serialized_val(const_name) if @serialized_val.nil?
freeze
end
sig {params(const_name: Symbol).returns(String)}
private def const_to_serialized_val(const_name)
# Historical note: We convert to lowercase names because the majority of existing calls to
# `make_accessible` were arrays of lowercase strings. Doing this conversion allowed for the
# least amount of repetition in migrated declarations.
-const_name.to_s.downcase.freeze
end
sig {returns(T::Boolean)}
def self.started_initializing?
unless defined?(@started_initializing)
@started_initializing = T.let(false, T.nilable(T::Boolean))
end
T.must(@started_initializing)
end
sig {returns(T::Boolean)}
def self.fully_initialized?
unless defined?(@fully_initialized)
@fully_initialized = T.let(false, T.nilable(T::Boolean))
end
T.must(@fully_initialized)
end
# Maintains the order in which values are defined
sig {params(instance: T.untyped).void}
def self._register_instance(instance)
@values ||= []
@values << T.cast(instance, T.attached_class)
end
# Entrypoint for allowing people to register new enum values.
# All enum values must be defined within this block.
sig {params(blk: T.proc.void).void}
def self.enums(&blk)
raise "enums cannot be defined for T::Enum" if self == T::Enum
raise "Enum #{self} was already initialized" if fully_initialized?
raise "Enum #{self} is still initializing" if started_initializing?
@started_initializing = true
@values = T.let(nil, T.nilable(T::Array[T.attached_class]))
yield
@mapping = T.let(nil, T.nilable(T::Hash[SerializedVal, T.attached_class]))
@mapping = {}
# Freeze the Enum class and bind the constant names into each of the instances.
self.constants(false).each do |const_name|
instance = self.const_get(const_name, false)
if !instance.is_a?(self)
raise "Invalid constant #{self}::#{const_name} on enum. " \
"All constants defined for an enum must be instances itself (e.g. `Foo = new`)."
end
instance._bind_name(const_name)
serialized = instance.serialize
if @mapping.include?(serialized)
raise "Enum values must have unique serializations. Value '#{serialized}' is repeated on #{self}."
end
@mapping[serialized] = instance
end
@values.freeze
@mapping.freeze
orphaned_instances = T.must(@values) - @mapping.values
if !orphaned_instances.empty?
raise "Enum values must be assigned to constants: #{orphaned_instances.map {|v| v.instance_variable_get('@serialized_val')}}"
end
@fully_initialized = true
end
sig {params(child_class: Module).void}
def self.inherited(child_class)
super
raise "Inheriting from children of T::Enum is prohibited" if self != T::Enum
end
# Marshal support
sig {params(_level: Integer).returns(String)}
def _dump(_level)
Marshal.dump(serialize)
end
sig {params(args: String).returns(T.attached_class)}
def self._load(args)
deserialize(Marshal.load(args)) # rubocop:disable Security/MarshalLoad
end
end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
# typed: true
# Use as a mixin with extend (`extend T::Generic`).
module T::Generic
include T::Helpers
include Kernel
### Class/Module Helpers ###
def [](*types)
self
end
def type_member(variance=:invariant, &blk)
T::Types::TypeMember.new(variance)
end
def type_template(variance=:invariant, &blk)
T::Types::TypeTemplate.new(variance)
end
end

View File

@ -0,0 +1,58 @@
# frozen_string_literal: true
# typed: true
# Use as a mixin with extend (`extend T::Helpers`).
# Docs at https://sorbet.org/docs/
module T::Helpers
extend T::Sig
Private = T::Private
### Class/Module Helpers ###
def abstract!
Private::Abstract::Declare.declare_abstract(self, type: :abstract)
end
def interface!
Private::Abstract::Declare.declare_abstract(self, type: :interface)
end
def final!
Private::Final.declare(self)
end
def sealed!
Private::Sealed.declare(self, Kernel.caller(1..1)&.first&.split(':')&.first)
end
# Causes a mixin to also mix in class methods from the named module.
#
# Nearly equivalent to
#
# def self.included(other)
# other.extend(mod)
# end
#
# Except that it is statically analyzed by sorbet.
def mixes_in_class_methods(mod, *mods)
Private::Mixins.declare_mixes_in_class_methods(self, [mod].concat(mods))
end
# Specify an inclusion or inheritance requirement for `self`.
#
# Example:
#
# module MyHelper
# extend T::Helpers
#
# requires_ancestor { Kernel }
# end
#
# class MyClass < BasicObject # error: `MyClass` must include `Kernel` (required by `MyHelper`)
# include MyHelper
# end
#
# TODO: implement the checks in sorbet-runtime.
def requires_ancestor(&block); end
end

View File

@ -0,0 +1,158 @@
# frozen_string_literal: true
# typed: false
# Wraps an object, exposing only the methods defined on a given class/module. The idea is that, in
# the absence of a static type checker that would prevent you from calling non-Bar methods on a
# variable of type Bar, we can use these wrappers as a way of enforcing it at runtime.
#
# Once we ship static type checking, we should get rid of this entirely.
class T::InterfaceWrapper
extend T::Sig
module Helpers
def wrap_instance(obj)
T::InterfaceWrapper.wrap_instance(obj, self)
end
def wrap_instances(arr)
T::InterfaceWrapper.wrap_instances(arr, self)
end
end
private_class_method :new # use `wrap_instance`
def self.wrap_instance(obj, interface_mod)
wrapper = wrapped_dynamic_cast(obj, interface_mod)
if wrapper.nil?
raise "#{obj.class} cannot be cast to #{interface_mod}"
end
wrapper
end
sig do
params(
arr: Array,
interface_mod: T.untyped
)
.returns(Array)
end
def self.wrap_instances(arr, interface_mod)
arr.map {|instance| self.wrap_instance(instance, interface_mod)}
end
def initialize(target_obj, interface_mod)
if target_obj.is_a?(T::InterfaceWrapper)
# wrapped_dynamic_cast should guarantee this never happens.
raise "Unexpected: wrapping a wrapper. Please report this bug at https://github.com/sorbet/sorbet/issues"
end
if !target_obj.is_a?(interface_mod)
# wrapped_dynamic_cast should guarantee this never happens.
raise "Unexpected: `is_a?` failed. Please report this bug at https://github.com/sorbet/sorbet/issues"
end
if target_obj.class == interface_mod
# wrapped_dynamic_cast should guarantee this never happens.
raise "Unexpected: exact class match. Please report this bug at https://github.com/sorbet/sorbet/issues"
end
@target_obj = target_obj
@interface_mod = interface_mod
self_methods = self.class.self_methods
# If perf becomes an issue, we can define these on an anonymous subclass, and keep a cache
# so we only need to do it once per unique `interface_mod`
T::Utils.methods_excluding_object(interface_mod).each do |method_name|
if self_methods.include?(method_name)
raise "interface_mod has a method that conflicts with #{self.class}: #{method_name}"
end
define_singleton_method(method_name) do |*args, &blk|
target_obj.send(method_name, *args, &blk)
end
if target_obj.singleton_class.public_method_defined?(method_name)
# no-op, it's already public
elsif target_obj.singleton_class.protected_method_defined?(method_name)
singleton_class.send(:protected, method_name)
elsif target_obj.singleton_class.private_method_defined?(method_name)
singleton_class.send(:private, method_name)
else
raise "This should never happen. Report this bug at https://github.com/sorbet/sorbet/issues"
end
end
end
def kind_of?(other)
is_a?(other)
end
def is_a?(other)
if !other.is_a?(Module)
raise TypeError.new("class or module required")
end
# This makes is_a? return true for T::InterfaceWrapper (and its ancestors),
# as well as for @interface_mod and its ancestors.
self.class <= other || @interface_mod <= other
end
# Prefixed because we're polluting the namespace of the interface we're wrapping, and we don't
# want anyone else (besides dynamic_cast) calling it.
def __target_obj_DO_NOT_USE # rubocop:disable Naming/MethodName
@target_obj
end
# Prefixed because we're polluting the namespace of the interface we're wrapping, and we don't
# want anyone else (besides wrapped_dynamic_cast) calling it.
def __interface_mod_DO_NOT_USE # rubocop:disable Naming/MethodName
@interface_mod
end
# "Cast" an object to another type. If `obj` is an InterfaceWrapper, returns the the wrapped
# object if that matches `type`. Otherwise, returns `obj` if it matches `type`. Otherwise,
# returns nil.
#
# @param obj [Object] object to cast
# @param mod [Module] type to cast `obj` to
#
# @example
# if (impl = T::InterfaceWrapper.dynamic_cast(iface, MyImplementation))
# impl.do_things
# end
def self.dynamic_cast(obj, mod)
if obj.is_a?(T::InterfaceWrapper)
target_obj = obj.__target_obj_DO_NOT_USE
target_obj.is_a?(mod) ? target_obj : nil
elsif obj.is_a?(mod)
obj
else
nil
end
end
# Like dynamic_cast, but puts the result in its own wrapper if necessary.
#
# @param obj [Object] object to cast
# @param mod [Module] type to cast `obj` to
def self.wrapped_dynamic_cast(obj, mod)
# Avoid unwrapping and creating an equivalent wrapper.
if obj.is_a?(T::InterfaceWrapper) && obj.__interface_mod_DO_NOT_USE == mod
return obj
end
cast_obj = dynamic_cast(obj, mod)
if cast_obj.nil?
nil
elsif cast_obj.class == mod
# Nothing to wrap, they want the full class
cast_obj
else
new(cast_obj, mod)
end
end
def self.self_methods
@self_methods ||= self.instance_methods(false).to_set
end
end

View File

@ -0,0 +1,65 @@
# frozen_string_literal: true
# typed: strict
module T::NonForcingConstants
# NOTE: This method is documented on the RBI in Sorbet's payload, so that it
# shows up in the hover/completion documentation via LSP.
T::Sig::WithoutRuntime.sig {params(val: BasicObject, klass: String, package: T.nilable(String)).returns(T::Boolean)}
def self.non_forcing_is_a?(val, klass, package: nil)
method_name = "T::NonForcingConstants.non_forcing_is_a?"
if klass.empty?
raise ArgumentError.new("The string given to `#{method_name}` must not be empty")
end
# We don't treat packages differently at runtime, but the static
# type-checker still needs to have the package and constant
# separated out. This just re-assembles the string as needed
if !package.nil?
klass = "::#{package}::#{klass}"
end
current_klass = T.let(nil, T.nilable(Module))
current_prefix = T.let(nil, T.nilable(String))
parts = klass.split('::')
parts.each do |part|
if current_klass.nil?
# First iteration
if part != "" && package.nil?
# if we've supplied a package, we're probably running in
# package mode, which means absolute references are
# meaningless
raise ArgumentError.new("The string given to `#{method_name}` must be an absolute constant reference that starts with `::`")
end
current_klass = Object
current_prefix = ''
# if this had a :: prefix, then there's no more loading to
# do---skip to the next one
next if part == ""
end
if current_klass.autoload?(part)
# There's an autoload registered for that constant, which means it's not
# yet loaded. `value` can't be an instance of something not yet loaded.
return false
end
# Sorbet guarantees that the string is an absolutely resolved name.
search_inheritance_chain = false
if !current_klass.const_defined?(part, search_inheritance_chain)
return false
end
current_klass = current_klass.const_get(part)
current_prefix = "#{current_prefix}::#{part}"
if !Module.===(current_klass)
raise ArgumentError.new("#{current_prefix} is not a class or module")
end
end
current_klass.===(val)
end
end

Some files were not shown because too many files have changed in this diff Show More