🎃

Sorbetのruntimeでの型チェック抑制方法まとめ

2024/11/08に公開

最近Sorbetを使い始めたのでメモっておきます。

Rubyの型チェックツールであるSorbetはruntimeでの型チェックができることが一つの特徴です。
ただし、型導入時に設定をミスると本番環境でエラーになってしまいます。
そこでエラーの抑制方法について理解を深めるためにコードとともに挙動を見てみます。

※ 公式のドキュメントのRuntime Configurationも合わせてご覧ください。

コードによるRuntime Configuration

Runtimeでの型チェックはコードの設定によって挙動を変えることができます。
項目ごとに簡単に説明します。

default_checked_level

  • sigに.checkedを指定してない場合にruntimeでの型チェックを実行するかどうか :always(default), :tests, :never
  • ただし、これで抑制できるのはsig と実際の値が合わない(call_validation_error)だけ
  • 環境変数 SORBET_RUNTIME_DEFAULT_CHECKED_LEVEL でも設定可能
T::Configuration.default_checked_level = :tests

enable_checking_for_sigs_marked_checked_tests

  • .checked(:tests)を指定している場合も通常のsigと同じに扱うかどうか
  • 環境変数 SORBET_RUNTIME_ENABLE_CHECKING_IN_TESTS でも設定可能
T::Configuration.enable_checking_for_sigs_marked_checked_tests

inline_type_error_handler

  • T.let, cast, must, assert_type! で間違った変換をしている場合(inline type assertions)のエラーハンドリング
T::Configuration.inline_type_error_handler = lambda do |error, opts|
  puts error.message
end

call_validation_error_handler

  • sig と実際の値が合わない場合(invalid method calls)のエラーハンドリング
T::Configuration.call_validation_error_handler = lambda do |signature, opts|
  puts opts[:pretty_message]
end

sig_builder_error_handler

  • sigのproc{}部分でreturnsやvoidを指定していないなどの文法ミスがあった場合(invalid sig procs)のエラーハンドリング
T::Configuration.sig_builder_error_handler = lambda do |error, location|
  puts error.message
end

sig_validation_error_handler

  • sig自体の文法は正しいが、overrideやabstractの指定と実態が異なる場合(invalid sigs)のエラーハンドリング
T::Configuration.sig_validation_error_handler = lambda do |error, opts|
  puts error.message
end

コード例

# typed: true

require 'sorbet-runtime'

# config
T::Configuration.default_checked_level = :always
T::Configuration.enable_checking_for_sigs_marked_checked_tests
T::Configuration.inline_type_error_handler = lambda do |error, opts|
  puts "\n--- inline_type_error_handler ---\n#{error.message}\n"
end
T::Configuration.call_validation_error_handler = lambda do |signature, opts|
  puts "\n--- call_validation_error_handler ---\n#{opts[:pretty_message]}\n"
end
T::Configuration.sig_builder_error_handler = lambda do |error, location|
  puts "\n--- sig_builder_error_handler ---\n#{error.message}\n"
end
T::Configuration.sig_validation_error_handler = lambda do |error, opts|
  puts "\n--- sig_validation_error_handler ---\n#{error.message}\n"
end

class Sample
  extend T::Sig

  # T.letでStringの値をIntegerに変換しようとしている
  # -> inline_type_error_handler
  sig { params(num: Integer).returns(Integer) }
  def a(num)
    T.let('string', Integer)
    1
  end

  # sig内でStringを返すと指定しているが、実際はIntegerが返ってくる
  # -> call_validation_error
  sig { params(num: Integer).returns(String) }
  def b(num)
    1
  end

  # sig内のprocで.returnsや.voidを指定してない
  # -> sig_builder_error_handler
  sig { params(num: Integer) }
  def c(num)
    1
  end

  # .overrideと書いているが、実際はoverrideしていない
  # -> sig_validation_error_handler
  sig { params(num: Integer).override.returns(Integer) }
  def d(num)
    1
  end
end

Sample.new.a(1)
Sample.new.b(1)
Sample.new.c(1)
Sample.new.d(1)

これを実行すると、以下のようにエラーが出力されます。

$ ruby lib/sample.rb

--- inline_type_error_handler ---
T.let: Expected type Integer, got type String with value "string"
Caller: lib/sample.rb:44

--- call_validation_error_handler ---
Return value: Expected type String, got type Integer with value 1
Caller: lib/sample.rb:68
Definition: lib/sample.rb:50 (Sample#b)

--- sig_builder_error_handler ---
You must provide a return type; use the `.returns` or `.void` builder methods.

--- sig_validation_error_handler ---
You marked `d` as .override, but that method doesn't already exist in this class/module to be overridden.
  Either check for typos and for missing includes or super classes to make the parent method shows up
  ... or remove .override here: Sample at lib/sample.rb:62

ハンドラーの設定自体をコメントアウトすれば、runtimeエラーが出力されます。

参考になれば幸いです。

Discussion