SteepのAnnotationに関する備忘録 #asakusa_bashi_rbs

Steepのmanual/annotations.mdを読みながら、実際にコードを書いて覚えたことをブログにまとめる。

変数

変数の型を String? から String にするときに便利そう。

# @type var value: String
value = %w[a b c].sample
puts('Hi, ' + value)

アノテーションがない場合、 sample: () -> String? なので型検査エラーになる。

app/user.rb:2:14: [error] Cannot pass a value of type `(::String | nil)` as an argument of type `::string`
│   (::String | nil) <: ::string
│     (::String | nil) <: (::String | ::_ToStr)
│       nil <: (::String | ::_ToStr)
│         nil <: ::String
│
│ Diagnostic ID: Ruby::ArgumentTypeMismatch
│
└ puts('Hi, ' + value)
                ~~~~~

Detected 1 problem from 1 file

インスタンス変数

変数と同じく、インスタンス変数の型を変えるときに便利そう。

# ruby
class User
  def initialize(name)
    @name = name
  end

  def hi
    # @type ivar @name: String
    puts('Hi, ' + @name)
  end

  def bye
    puts('bye, ' + @name)
  end
end

# rbs
class User
  @name: String?

  def initialize: (String?) -> void

  def hi: () -> void

  def bye: () -> void
end

これは bye の方だけ型検査エラーになる。

app/user.rb:12:19: [error] Cannot pass a value of type `(::String | nil)` as an argument of type `::string`
│   (::String | nil) <: ::string
│     (::String | nil) <: (::String | ::_ToStr)
│       nil <: (::String | ::_ToStr)
│         nil <: ::String
│
│ Diagnostic ID: Ruby::ArgumentTypeMismatch
│
└     puts('bye, ' + @name)
                     ~~~~~

Detected 1 problem from 1 file

クラスとメソッド

Rubyのコード内にアノテーションを書くと、RBSなしでもSteepで型チェックできる。

# @type const User : User
class User
  # @type method say: (String, Integer) -> void
  def say(name, age)
    puts(name + age) # 型検査エラー
  end
end


User.new.say(1, 2) # ここは型エラーにならない

このコードをSteepで検査すると、 say メソッドの中だけ型検査の対象になっていることがわかる。

app/user.rb:5:16: [error] Cannot pass a value of type `::Integer` as an argument of type `::string`
│   ::Integer <: ::string
│     ::Integer <: (::String | ::_ToStr)
│       ::Integer <: ::String
│         ::Numeric <: ::String
│           ::Object <: ::String
│             ::BasicObject <: ::String
│
│ Diagnostic ID: Ruby::ArgumentTypeMismatch
│
└     puts(name + age) # 型検査エラー
                  ~~~

Detected 1 problem from 1 file

pure

メソッドに副作用がないことを明示するときに指定する。*1

# ruby
class User
  def initialize(name)
    @name = name
  end

  def name
    @name
  end
end

user = User.new(nil)
if user.name
  puts("Hi, " + user.name)
end

# rbs
class User
  def initialize: (String?) -> void

  %a{pure}
  def name: () -> String?
end

%a{pure} を指定しない場合、以下のような型検査エラーが起きる。

app/user.rb:13:16: [error] Cannot pass a value of type `(::String | nil)` as an argument of type `::string`
│   (::String | nil) <: ::string
│     (::String | nil) <: (::String | ::_ToStr)
│       nil <: (::String | ::_ToStr)
│         nil <: ::String
│
│ Diagnostic ID: Ruby::ArgumentTypeMismatch
│
└   puts("Hi, " + user.name)
                  ~~~~~~~~~

Detected 1 problem from 1 file

*1:ドキュメントには記載がない