RBSのuntyped, void, top, botの違いを理解する #asakusa_bashi_rbs

RBSdocs/syntax.mdに記載されている untyped, void, top, bot の違いを理解したけど、また忘れそうなのでブログにまとめておく。

untyped

どんな型でも代入できて、その変数を型検査の対象にしない場合に使う。

# ruby
class User
  def greet(user)
    user.something
  end
end

# rbs
class User
  def greet: (untyped) -> void
end

something メソッドの定義は存在しないのに、Steepの型検査でエラーは出ない。

No type error detected. 🫖

void

メソッドの戻り値を使わない場合に使う。

# ruby
class User
  def foo
    bar.upcase
  end

  def bar
    "hello"
  end
end

# rbs
class User
  def foo: () -> String
  def bar: () -> void
end

void である戻り値に対して upcase のメソッドを呼び出しているため、Steep の型検査でエラーが出る。

app/user.rb:3:8: [error] Type `void` does not have method `upcase`
│ Diagnostic ID: Ruby::NoMethod
│
└     bar.upcase
          ~~~~~~

top

どんな型でも代入できるが、その変数でメソッド呼び出しを禁止したい場合に使う。TypeScriptのunknown型と同じ。

# ruby
class User
  def greet(user)
    puts user.name
  end
end

user = User.new
user.greet("Pikachu")
user.greet(1)

# rbs
class User
  def greet: (top) -> void
end

型検査で引数に String と Integer を渡している部分ではエラーになっていないが、name のメソッドを呼ぶところはエラーが出る。

app/user.rb:3:14: [error] Type `top` does not have method `name`
│ Diagnostic ID: Ruby::NoMethod
│
└     puts user.name
                ~~~~

bot

例外が必ず発生するか、無限ループで終了しないメソッドで使う。TypeScriptのnever型と同じ。

# ruby
class User
  def foo
    raise NotImplementedError
  end

  def bar
    loop do
      puts "hello"
      sleep 1
    end
  end

  def buz
    puts "hi, Eevee"
  end
end

# rbs
class User
  def foo: () -> bot
  def bar: () -> bot
  def buz: () -> bot
end

Steepで型検査すると foo, bar は問題ないが、 buz は実装が誤っているためエラーが出る。

app/user.rb:13:6: [error] Cannot allow method body have type `nil` because declared as type `bot`
│   nil <: bot
│
│ Diagnostic ID: Ruby::MethodBodyTypeMismatch
│
└   def buz
        ~~~