TypeProf VSCode ExtensionでRubyの静的型解析を体験する

# はじめに

こちらは社内技術勉強会用の資料として作成したものです。

Ruby 3.0 がリリースされる前に、Ruby にも型宣言の考え方がどーの、みたいな雰囲気のことをぼんやり聞いていたのですが、ぜんぜんわかっていませんでした。あちこちで引用されているかと思いますが、

https://www.ruby-lang.org/ja/news/2020/12/25/ruby-3-0-0-released/

```
Rubyは抽象解釈を武器に、型宣言なしで静的型チェックする未来を目指します。
```

型宣言するのではなく、むしろ型宣言はありませんでした...

わかっていなかったので、Rubyの静的型解析について勉強しておきます。

---

# 型情報の利用

RBSという、クラスやモジュールの型を定義するための言語が用意されました。これはRubyとは異なる言語です。

以下は [WEB+DB PRESS Vol.121](https://gihyo.jp/magazine/wdpress/archive/2021/vol121) の紙面からの引用です。

https://gihyo.jp/magazine/wdpress/archive/2021/vol121

↓↓↓↓↓
Rubyの静的型解析において最も特徴的なのが、「Rubyの構文が変更されていない」という点です。PHPやPythonと異なり、Rubyではプログラムの構文を型のために拡張することをしませんでした。
↑↑↑↑↑

つまり型情報は定義できるが、Rubyのプログラムの実行にはまったく関係しない、ということのようです。

では、どのようなところで型情報が利用されるのでしょうか。

---

# IDEでの型情報の利用

## 型チェック(メソッドの引数)

例えば本棚(Rack)に本を分類(Category)して並べておく場所があり、その中に本(Book)が並んでいるとします。そのうちの、「本(Book)」を表すクラスは以下のとおりであるとします。こちらは Visual Studio Code の画面です。

![20211017_Rbs_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27959/dd82da66-990d-46a3-01ff-0061ee5e1c73.png)

メソッドや attr_reader の上に何か表示されていますね。

これは「[Ruby TypeProf](https://marketplace.visualstudio.com/items?itemName=mame.ruby-typeprof)」という、Visual Studio Code の Extension を利用している状態です。

---

型情報は、現在の Visual Studio Code の Extension 用には、typeprof.rbs というファイルに記述します。以下のように記述しています。

```
type rackData = Hash[Symbol, String] | Array[rackData]

interface _Converter
  def convert: -> rackData
end

module Bookstore
  module Element
    class Book
      attr_reader title: String?
      attr_reader author: String?
      def initialize: (?title: String, ?author: String) -> _Converter
      def convert: -> rackData
    end
  end
end
```

なるほど、RBSファイルに記載している内容がエディター上に表示されているようです。

---

呼び出し側ではどうでしょうか。`initialize` メソッドの引数はキーワード引数で与えるようにしていますが、あえてキーワードなしで書いてみます。

![20211017_Rbs_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27959/bd156791-bc8e-b183-0ff6-08f344205493.png)

通常の引数を受け付けられる定義ではない、という意味でしょうか、エラーが表示されました。型チェックに利用されている、ということが確認できました。

---

## 型チェック(nil判定)

もう一つ、分類(Category)というクラスの定義が以下であったとします。

![20211017_Rbs_05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27959/13e99f6f-55e6-d07f-c915-e5d4a8648426.png)

---

少しコードを書き換えてみましょう。

![20211017_Rbs_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27959/a830f43c-2540-4f76-06d5-43137dea14a6.png)

`@book_list&.convert` を `@book_list.convert` に変えてみました。

`@book_list` の型は「Bookstore::Element::List?」で nil が許容されているので、nil に対して `convert` メソッド呼び出しが行われるかもしれないよ、ということでしょうか。

厳しい判定です...

---

## 定義元の参照

呼び出し元で Ctrl キーを押しながらカーソルをクラス名の上に置くと、クラス定義の一部が表示されます。

![20211017_Rbs_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27959/8327f7aa-0996-30fa-bf2a-975340f906c5.png)

また、Ctrlキーを押したままクラス名をクリックするか、もしくは右クリックのメニュー「Go to Definition」を実行すると、クラス定義のソースコードに移動しました。

メソッドの定義元にも移動できました。

---

## 補完

呼び出し元で、変数名に続けてドットを入力してみます。

![20211017_Rbs_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27959/72eb4d9b-8ff1-1a3c-e017-8c3c517b825d.png)

ドットに続けて入力できる文字やキーワードの候補の一覧が表示されました。ためしに「c」と入力してみると、候補の中に「convert」というメソッドが含まれています。

Visual Studio Codeの自動補完だとよく間違ってしまうので、正しい名前が選択できるのはいいですね！

---

# Ruby TypeProf VSCode Extension のインストール

IDEでの型情報の利用を体験してみましょう。

こちらを参考に、Visual Studio Code の Extension をインストールします。

https://github.com/ruby/typeprof/blob/master/doc/ide.md

2021年10月現在、Ruby 3.1.0-dev が必要ということなので、インストールしておきます。

また、TypeProf を利用したいプロジェクトに対して bundle install しておく必要があるので、Gemfile に以下のように追加してインストールします。

```ruby:Gemfile
gem 'rbs', '>=1.6.2'
gem 'typeprof', '>=0.20.0'
```

typeprof のバージョンは 0.20.0 以降が必要、これをインストールするには　rbs 1.6.2 以降が必要、ということなので、それぞれそのようにバージョンを指定します。

準備ができたら、Extension をインストールします。Windows の WSL を使用している場合は、WSL 側にインストールします。

![20211017_Rbs_08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27959/ec9013bf-8c1e-129b-7ddd-3a2ddfd40cb1.png)

[Troubleshooting](https://github.com/ruby/typeprof/blob/master/doc/ide.md#troubleshooting) にあるとおり、「TypeProf for IDE is started successfully」と出力されていればインストール完了です！

---

# 注意点

現時点では [Protips, limitation, unimplemented features, ...](https://github.com/ruby/typeprof/blob/master/doc/ide.md#protips-limitation-unimplemented-features-) にある通り、以下の注意が必要です。

- RBSファイルは、「typeprof.rbs」というファイルが参照されます。このファイルが、Visual Studio Codeで開いているフォルダのルートに置かれている必要があります。

---

# 型宣言をまかせてみる

それでは、TypeProf が有効の状態でコードを書き始めてみましょう。

クラス名だけ書きました。

![20211017_Rbs_09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27959/ccdf6e41-6238-23d8-a544-c0dc7ee33b75.png)

まだ普段通りです。

---

initialize メソッドを書きました。

![20211017_Rbs_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27959/905f7e54-4d9d-7f14-7b18-b72e17e8e724.png)

推定された型が自動で表示されました。

---

続けてメソッドを一つ追加しました。

![20211017_Rbs_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27959/b1666b99-c03e-cf58-6ef1-589dd9c3e60f.png)

メソッドの戻り値が推定されました。

---

もう一つメソッドを用意し、一つ目のメソッドを呼び出してみました。

![20211017_Rbs_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27959/1d3c1842-82fb-c49e-2af0-19b1ec58baf2.png)

さて、ここまで書いたところで、

---

あえて、メソッド名を間違えてみます。

![20211017_Rbs_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27959/1cb0445f-a78a-1262-928e-d92b7561d58a.png)

エラーが表示されました。新しく書き始めたクラス分は型宣言をしていないのに、メソッドの型が表示されました。そして、ブロックの中でも型の判定が行われました。おもしろいですね！

---

# よくわからないところ

先ほどのコードに型宣言を書いてみます。

```
class Sample
  def initialize: -> void
  def make_converter_ary: -> Array[_Converter]
  def start: -> void
end
```

`_Converter` は convert というメソッドを持つクラスである、という定義としていて、それを initialize の戻り値の型としてみました。

---

![20211017_Rbs_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27959/90751521-28bc-8ca8-3e89-062e70cb34a1.png)

おや、型の表示が消えてしまいました。どこへ行ってしまったのか...

型情報を typeprof.rbsに書くと、構文は間違っていなさそうなのに、記述なしの時には表示されていたエディター上の型情報が表示されなくなることがありました。

---

この状態でメソッド名を間違えてみると、

![20211017_Rbs_15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27959/42456b6e-ff01-7be5-d15e-b949843afe59.png)

エラーは表示されました。型情報の表示はなくなりましたが、型チェックは動作しているようです。

---

# 半日分の知見

- typeprof.rbs ファイルを書き換えたときは、Visual Studio Codeで、一度 Ruby のソースコードのタブを閉じて、もう一度開き直すと、反映されます。他に方法があるのかもしれませんが、わかりませんでした。
- typeprof.rbs で構文を間違えると、次にソースコードのファイルを開いたときにエラーが出力されます。エラーは、[Troubleshooting](https://github.com/ruby/typeprof/blob/master/doc/ide.md#troubleshooting) にある、「TypeProf for IDE is started successfully」と出力されているところで見ることができます。この出力内容をよーく読むと、どこを間違えているのかわかるかもしれません。
- TypeProfが動いているのかわからなくなったときは、とりあえずVisual Studio Codeのウィンドウを閉じて、もう一度開き直してみます。もしかすると復旧するかも。

---

# おわりに

Ruby の静的型解析を体験してみました。

既存のソースコード用にRBSファイルを書くのは億劫ですが、新しく開発を始める場合であれば使ってみたくなりました。

小さいソースコードで試しただけなので実用できるかはまだ判断できませんが、型情報を利用できる場面が増えていくことを期待するとともに、活用もしていきたいと思います。

---

# 参考

- [TypeProf: 抽象解釈に基づくRubyコードの型解析ツール](https://github.com/ruby/typeprof/blob/master/doc/doc.ja.md)
- [How to use TypeProf for IDE](https://github.com/ruby/typeprof/blob/master/doc/ide.md)
- [Ruby 3 の静的解析ツール TypeProf の使い方](https://techlife.cookpad.com/entry/2020/12/09/120314)
- [RubyKaigi Takeout 2021 TypeProf for IDE: Enrich Dev-Experience without Annotations](https://rubykaigi.org/2021-takeout/presentations/mametter.html)
- [ruby/rbs](https://github.com/ruby/rbs)
- [Ruby TypeProf](https://marketplace.visualstudio.com/items?itemName=mame.ruby-typeprof)