# 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 # 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, :tests, :always] default_checked_level def self.default_checked_level=(default_checked_level) T::Private::RuntimeLevels.default_checked_level = default_checked_level end # 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 # # @example # T::Configuration.inline_type_error_handler = lambda do |error| # 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) raise error end def self.inline_type_error_handler(error) if @inline_type_error_handler @inline_type_error_handler.call(error) else inline_type_error_handler_default(error) end nil end # 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 # 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 # 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 # 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 # 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 # 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 # Set a list of class strings that are to be considered scalar. # (pass nil to reset to default behavior) # # @param [String] value 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 = Set.new(values).freeze end end @default_scalar_types = Set.new(%w[ NilClass TrueClass FalseClass Integer Float String Symbol Time T::Enum ]).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 if T::Configuration::AT_LEAST_RUBY_2_7 @default_module_name_mangler = ->(type) {MODULE_NAME.bind_call(type)} else @default_module_name_mangler = ->(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] value 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 # 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] value 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] value 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 # Set to a function which can get the 'owner' of a class. This is # used in reporting deserialization errors # # @param [Lambda, Proc, nil] value 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 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 # @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