4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

お題は不問!Qiita Engineer Festa 2023で記事投稿!

Sorbetのruntimeチェックを試してみた

Last updated at Posted at 2023-07-20

SorbetはRubyのシンタックスで書かれているため、型アノテーションの情報を実行時にも保持できます。
(Steepはコメントアウトで型アノテーションを記述するため、実行時には型の情報が捨てられてしまっているようです)

これを用いて、実行時に型アノテーションの検査ができるというので、手元で試してみました。

Sorbet Runtimeについて

Sorbetは部分的に型チェックを有効化、無効化することが可能です。そのため、型アノテーションを記述しても異なる型の値が渡される可能性があります。

Sorbet Runtimeは実行時に型情報を用いて、アノテーションをチェックすることができます。

これにより、型アノテーションの誤りに気づくことができます。

試してみる

まずは型チェックを無効化してプログラムを実行してみます。

untyped.rb
# typed: false

class Untyped
  attr_reader :age

  def initialize(age:)
    @age = age
  end

  def string_age
    age.to_s
  end
end
rspec
require 'rails_helper'

RSpec.describe Untyped do
  describe '#string_age' do
    subject { described_class.new(age: age).string_age }

    context 'when age is Integer' do
      let(:age) { 20 }

      it { is_expected.to eq('20') }
    end

    context 'when age is not Integer' do
      let(:age) { nil }

      it { is_expected.to eq('') }
    end
  end
end

image.png

エラーが起きていないことが確認できました。

型チェックを有効化

次に型アノテーションを記述して、型チェックを有効化します

typed.rb
# typed: strict

class Typed
  extend T::Sig

  sig { returns(Integer) }
  attr_reader :age

  sig { params(age: Integer).void }
  def initialize(age:)
    @age = T.let(age, Integer)
  end

  sig { returns(String) }
  def string_age
    age.to_s
  end
end

するとエラーが発生しました。

image.png

ageにIntegerを期待していたのにNilClassを受け取ったことでエラーになっています。そしてTypeErrorがraiseされています。

ランタイムチェックを無効化したいとき

デフォルトではすべての環境においてランタイムチェックが実行されます。

これを変更したり、無効化したりするには設定や環境変数が必要です。

T::Configuration.default_checked_level = :tests # default :always

環境変数の場合は SORBET_RUNTIME_DEFAULT_CHECKED_LEVEL を使います。

以下のようにアノテーションされているコードに対して、 SORBET_RUNTIME_DEFAULT_CHECKED_LEVEL=never で実行します。

# typed: true

class Typed
  extend T::Sig

  sig { returns(Integer) }
  attr_reader :age

  sig { params(age: Integer).void }
  def initialize(age:)
    @age = age
  end

  sig { returns(String) }
  def string_age
    age.to_s
  end
end

image.png

このようにエラーが発生しませんでした。

しかし、T.let, T.cast, T.must, T.bindといった型アサーションを使った場合、必ずチェックされるので注意が必要です。

# typed: strict

class Typed
  extend T::Sig

  sig { returns(Integer).checked(:never) }
  attr_reader :age

  sig { params(age: Integer).void.checked(:never) }
  def initialize(age:)
+   @age = T.let(age, Integer)
  end

  sig { returns(String).checked(:never) }
  def string_age
    age.to_s
  end
end

image.png

このように型アサーションは常にRubyコードとして実行されるため、わずかながらオーバーヘッドがかかるようです。そのため、ドキュメントにも型アサーションを使わず、メソッドシグニチャを使う方法も紹介されています。

終わりに

RSpec等を実行したときに型アノテーションのチェックができるというのはとてもいいと思いました。
YARDからRBIを生成するツールもあるので、それで生成した後にSorbet Runtimeでチェックするとかを今度試してみようと思いました。

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?