Slide 1

Slide 1 text

TypeProf進捗 Yusuke Endoh (@mame) RubyKaigi 2024 followup

Slide 2

Slide 2 text

復習: TypeProf • 型注釈を必須としない Rubyの型解析・エディタ支援ツール • データフロー解析に基づく • RBSがあれば読み込む • RubyKaigiの発表:貢献を呼び掛けた • "Good first issues of TypeProf" • パッチの書き方、テストの仕方などを説明した 5.ti| 1 + "str" TypeError Do you mean: 5 .times

Slide 3

Slide 3 text

みんなありがとう • 100+ pull requests 0 2 4 6 8 10 12 14 16 18 20 PR数 ※数え間違えてたらごめんなさい

Slide 4

Slide 4 text

進捗 • ruby/ruby の lib/**/*.rb の全ファイルが ほぼ 解析できた • ただし • lib/ruby_vm/rjitだけは除く(パターンマッチ……) • 解析時間は要改善(850ファイルで8分、1ファイル平均0.6秒) • lib/reline/line_editor.rb 1つが数分くらいかかるので要調査 • ファイルごとに解析してる(相互作用によるバグは絶対まだまだある) • false positiveも気にしてない(これから潰し方を考えていく)

Slide 5

Slide 5 text

むずかしくて面白い問題 • 次のコードを解析するとTypeProfが無限ループ • lib/resolv.rbから簡略化して得た例 @a = @b[0] @b = "x" + @a

Slide 6

Slide 6 text

無限ループの仕組み (1) • 解析の初期状態 • @a: untyped • @b: untyped @a = @b[0] @b = "x" + @a

Slide 7

Slide 7 text

無限ループの仕組み (2) • 現在の状態 • @a: untyped, @b: untyped • 1行目の解析 • レシーバの@bはuntyped • @aはuntypedのまま • 新しい状態 • @a: untyped, @b: untyped @a = @b[0] @b = "x" + @a

Slide 8

Slide 8 text

無限ループの仕組み (2) • 現在の状態 • @a: untyped, @b: untyped • 2行目の解析 • String#+: (String) -> String • @bはuntyped(何にでもマッチ)なので@aはStringになる • 新しい状態 • @a: untyped, @b: String @a = @b[0] @b = "x" + @a

Slide 9

Slide 9 text

無限ループの仕組み (4) • 現在の状態 • @a: untyped, @b: String • 1行目の(再)解析 • レシーバの型が変わったので再解析が発生する • String#[]: (Integer) -> String? • @aはString?になる • 新しい状態 • @a: String?, @b: String @a = @b[0] @b = "x" + @a

Slide 10

Slide 10 text

無限ループの仕組み (5) • 現在の状態 • @a: String?, @b: String • 2行目の(再)解析 • 引数の型が変わったので再解析が発生する • String#+: (String) -> String • @aはString? なのでマッチしない!ので@bはuntypedになる • 新しい状態 • @a: String?, @b: untyped @a = @b[0] @b = "x" + @a

Slide 11

Slide 11 text

無限ループの仕組み (6) • 現在の状態 • @a: String?, @b: untyped • 1行目の(再)解析 • レシーバの型が変わったので再解析が発生する • レシーバの@bがuntypedなので、@aはuntypedにもどる • 新しい状態 • @a: untyped, @b: untyped → 初期状態! @a = @b[0] @b = "x" + @a

Slide 12

Slide 12 text

無限ループの仕組み: まとめ • 解析状態の更新がループしてしまっていた • @a: untyped, @b: untyped • @a: untyped, @b: String • @a: String?, @b: String • @a: String?, @b: untyped • @a: untyped, @b: untyped

Slide 13

Slide 13 text

暫定対応:引数マッチの処理を変更した • String#+: (String) -> String に String? を渡す場合 • 旧:String? はマッチしないのでuntypedを返す • 新:String? は String にマッチすることにする • 警告は出す(TODO) • これで一旦 lib/**/*.rb を全パスした(rjit除く)

Slide 14

Slide 14 text

完全ではなかった • この資料を作りながら考えてたら 刺さるシナリオが作れてしまった ## update: test.rbs class C def foo: (C) -> C def bar: -> Integer end ## update: test.rb def check c = C.new @a = @b.bar @b = c.foo(@a) end

Slide 15

Slide 15 text

問題の整理 • 一度マッチしたメソッド呼び出しは外れてほしくない • 考えられる直し方 • 保守的な案:untypedは何にもマッチしないことにする • "x" + untyped が String にならなくなる • TypeScript の any から大きく違う意味になる • 雑な案:引数の数さえあってれば常にマッチすることにする • "x" + 1 は String を返すと推論する(型エラーは出す) • 1 + 1 が (Complex | Rational | Integer | Float) になるのはちょっと不幸 • 鋭意検討中です

Slide 16

Slide 16 text

まとまらないまとめ • TypeProf鋭意開発中です • 進捗と現状 • Rubyの全文法対応までたぶんあと少し • いろんなコードに適用して問題洗い出し・対応中 • Ruby 3.4.0にはTypeProf v2をバンドルしたい • パターンマッチ構文のサポートと TypeProf v1の互換インターフェイスを作ったら最低限いけるはず