# 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