Skip to content

Latest commit

 

History

History
106 lines (80 loc) · 3.56 KB

tconfiguration.md

File metadata and controls

106 lines (80 loc) · 3.56 KB
id title
tconfiguration
Runtime Configuration

Recall from Enabling runtime checks that sorbet-runtime raises exceptions for type errors that happen at runtime. This is to ensure that even code that is not covered by types statically can still benefit from type information, and that authors don't have to defend against their typed methods being called incorrectly.

The behavior of the sorbet-runtime package can be configured by registering callbacks before a program is run. While it's possible to use these callbacks to silence all runtime exceptions raised by sorbet-runtime, we strongly recommend not doing so. Runtime exceptions have proven to be invaluable in gaining confidence in the correctness of type signatures as we rolled out types to Stripe's Ruby codebase.

Note: Think carefully before disabling runtime checks!

These docs are somewhat low-level. For a higher-level description of how to change Sorbet's runtime, see Enabling Runtime Checks.

Errors from inline type assertions

There are four kinds of inline type assertions:

  • T.let(expr, Type)
  • T.cast(expr, Type)
  • T.must(expr)
  • T.assert_type!(expr, Type)

To customize the behavior when one of these assertions fails:

T::Configuration.inline_type_error_handler = lambda do |error|
  puts error.message
end

The default error handler is to raise (a TypeError).

Note that setting this callback will not handle type errors raised when a method with a signature is called incorrectly. For those, see the next section.

Errors from invalid method calls

To customize the behavior when a method with a sig is called and the argument types or return types don't match the actual value present at runtime, use this:

T::Configuration.call_validation_error_handler = lambda do |signature, opts|
  puts opts[:message]
end

The default error handler is to raise an error.

The example handler above does the same thing for every method. One thing which can be useful is to pass custom metadata to the call_validation handler, which can be done with .on_failure:

T::Configuration.call_validation_error_handler = lambda do |signature, opts|
  if signature.on_failure
    puts "Metadata: #{signature.on_failure}"
  end
  raise TypeError.new(opts[:pretty_message])
end

sig {params(x: Integer).void.on_failure(:hello, :world)}
def foo(x); end

When the call_validation_error_handler is called this time, it will be passed all the args that were given to on_failure for the specific sig that failed.

Errors from invalid sig procs

We write sigs using valid Ruby syntax. The body of the proc passed to a sig is executed (lazily, on first method call) to compute an in-memory data structure representing that sig's types. The execution of this proc can be invalid (for example, if returns or void is never called).

The default behavior when building a sig is invalid is to raise an ArgumentError. To customize this behavior, use this:

T::Configuration.sig_builder_error_handler = lambda do |error, location|
  puts error.message
end

Errors from invalid sigs

Method signatures that build correctly can still be invalid. For example, a sig marked override must actually override a method. Same for abstract methods. When overriding a parent sig, the variance must match on the input and output types. If a sig that built correctly is invalid in anyway, this error handler will be called:

T::Configuration.sig_validation_error_handler = lambda do |error, opts|
  puts error.message
end