Ruby 3.1.0 preview 1でTypeProf for IDEを試してみた

先日Ruby 3.1.0 Preview 1がリリースされました。

https://www.ruby-lang.org/en/news/2021/11/09/ruby-3-1-0-preview1-released/

先日のRubyKaigi Takeout 2021のKeynoteで発表があったようにRuby 3.1からTypeProfの Experimental IDE supportが実装されています。

https://rubykaigi.org/2021-takeout/presentations/mametter.html

面白そうだったのでちょっと触ってみて、「Rubyで静的型付けを使った現代的な開発体験」を体験してみました。

## 準備

```bash
$ brew upgrade && brew upgrade rbenv ruby-build
$ rbenv install --list-all | grep 3.1.0
3.1.0-dev
3.1.0-preview1
jruby-9.3.1.0
rbx-3.100

$ rbenv install 3.1.0-preview1
$ rbenv local 3.1.0-preview1
$ ruby -v
ruby 3.1.0preview1 (2021-11-09 master 5a3b2e6141) [x86_64-darwin18]
```

IDEの設定は以下のリンクに手順が乗っているので参考にしました。
already-configured repositoryがあるので、すでに設定されている環境で動作を試すこともできるのですが、既存のリポジトリに導入するイメージで`How to configure TypeProf for your code`を参考にやってみます。
サンプルは3.1.0-devでしたが、preview1が出たということで、今回はpreview1で触ってみます。

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

## 構築

既存のリポジトリと言いながら、already-configured repositoryからコードをお借りします。

- https://github.com/mame/rbswiki から`lib`ディレクトリと`rbs_collection.yaml`をコピーしてくる
- Gemfileに`gem "typeprof"`を追加する

準備ができたら、以下のコマンドを実行します。

```shell
$ bundle install
$ rbs collection install
$ bundle exec typeprof lib/rbswiki/wiki.rb
```

次にVSCodeに[Ruby TypeProfの拡張機能](https://marketplace.visualstudio.com/items?itemName=mame.ruby-typeprof)を追加します。
すると、以下の画像のように、メソッドの上に型のアノテーションが追加されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143317/33c1c97a-fae8-51c4-6359-d54ca39c72fa.png)

触ってみると、メソッドの型の推論してくれたり、引数の型のエラーを警告してくれたり、定義ジャンプ、入力補完をしてくれるといった体験ができます。

### 構築中にハマったポイント

実はすんなり上記のように行きませんでした。
TypeProfの拡張機能はLSPサーバーを起動しているようですが、この起動が失敗していました。
起動のエラーはVCSCodeのOUTPUTパネルで確認できます。([画像はこちらを引用](https://github.com/ruby/typeprof/blob/master/doc/typeprof-for-ide-log.png))
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143317/28299acb-cba9-f786-d716-8fb04fc01378.png)

エラーは以下のように、bundleのパスが意図していないものになっているようでした。(ruby 2.5.0を見ている)

```
[vscode] Try to start TypeProf for IDE
[vscode] stderr: /Users/kyntk/.rbenv/versions/2.5.3/lib/ruby/2.5.0/rubygems.rb:289:in `find_spec_for_exe': can't find gem bundler (>= 0.a) with executable bundle (Gem::GemNotFoundException)
[vscode] stderr: from /Users/kyntk/.rbenv/versions/2.5.3/lib/ruby/2.5.0/rubygems.rb:308:in `activate_bin_path'
[vscode] stderr: from /Users/kyntk/.rbenv/versions/2.5.3/bin/bundle:23:in `<main>'
[vscode] failed to invoke typeprof: error code 1
```

Ruby TypeProfの設定として、`typeprof.server.path`というものがあります。
[拡張機能のコード](https://github.com/ruby/typeprof/blob/ccbb2350765acfc8da2f551e2ddcd4ce2c3cd84c/vscode/src/extension.ts#L68) を読んでみると、以下のようになっていました。

```ts:src/extension.ts#L68
  const customServerPath = configuration.get<string | null>("server.path");
  const cwd = folder.uri.fsPath;

  let cmd: string;
  if (existsSync(`${cwd}/bin/typeprof`)) {
    cmd = "./bin/typeprof";
  }
  else if (customServerPath) {
    cmd = customServerPath;
  }
  else if (existsSync(`${cwd}/Gemfile`)) {
    cmd = "bundle exec typeprof";
  }
  else {
    cmd = "typeprof";
  }
  cmd = cmd + " " + arg;
```

`customServerPath`を渡すとそのコマンドを実行してくれるようです。
なのでまず`which bundle`をして出力をコピーして、`.vscode/settings.json`に設定をしてあげます。

```json:.vscode/settings.json
{
  "typeprof.server.path": "/Users/kyntk/.rbenv/shims/bundle exec typeprof"
}
```

設定後、cmd+shift+P から `TypeProf: Restart`コマンドを実行し、OUTPUTパネルで再度新しい方の`Ruby TypeProf`をセレクトすると、無事起動しました。

```
[vscode] Try to start TypeProf for IDE
[vscode] typeprof version: typeprof 0.20.3
[vscode] Starting Ruby TypeProf (typeprof 0.20.3)...
[vscode] Ruby TypeProf is running
[Info  - 8:04:41] TypeProf for IDE is started successfully
[Info  - 8:04:41] warning: rbs collection is experimental, and the behavior may change until RBS v2.0
```

## その他

RubyKaigi Takeout 2021 Keynote で @mametter さんの発表を見ると、デモもあったりしてめちゃくちゃわかりやすいのでそれを見るのが一番ですが、発表の内容を一部引用して、TypeProfについて補足させていただきます。

https://rubykaigi.org/2021-takeout/presentations/mametter.html

### TypeProf for IDEについて

最近人気の静的型付けを行う目的は現代的な開発体験をしたいからで、Rubyで自身を大きく変えることなく素のRubyでその体験を獲得するためのVSCode拡張です。
現代的な開発体験とは、VSCodeでのTypeScriptの開発体験を例に、即時のエラーレポーティング、定義ジャンプ、補完、引数のヒント表示などをあげています。

### Rubyでの型

RBSはRuby公式の型定義言語で、TypeProf、Steep、Sorbetは静的型解析をするツールの名前です。
以下のスライドの22ページの比較表にあるように、TypeProfはなるべく既存のRubyを保ったまま型解析を可能にするために型推論に力を入れているため、まだ誤ったエラーレポートをしたり、型解析のスピードが遅いという特徴があるようです。

https://www.slideshare.net/mametter/typeprof-for-ide-enrich-development-experience-without-annotations

TypeProfはプログラミングを型レベルで実行することで解析を行うので、すべてのメソッドを呼び出すことが必要らしいです。
そのため、以下のような記述をファイル下部に書いて実験をしてみているようです。

```rb
if $0 == __FILE__
  john = User.new("John")
  puts john.say_hello("Hello")
end
```

まだExperimental IDE supportなので機能改善やスピード改善などをしている途中で、小規模のプログラムで実験をしてみるようなフェーズとのことでした。

## 参考

https://rubykaigi.org/2021-takeout/presentations/mametter.html

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

https://techlife.cookpad.com/entry/2020/12/09/120314