技術部の遠藤です。2日連続の投稿です。
今年のRubyKaigi 2022ではTRICKの発表をしますが、もうひとつ真面目な発表もします。Ruby 3.1の目玉機能であったerror_highlightについてです。
この発表内容について、あらすじを紹介したいと思います。
Ruby 3.1のerror_highlightとは
Ruby 3.1でNoMethodErrorが発生すると、次のようなエラーが表示されます。
$ ruby test.rb test.rb:1:in `<main>': undefined method `time' for 42:Integer (NoMethodError) 42.time { print "Hello" } ^^^^^ Did you mean? times
この42.time { print "Hello" }
とその下線を出しているのがerror_highlightという機能です。
当日は実装をかんたんに発表しますが、この内容はすでに記事を書いているので、興味があれば読んでください。
Ruby 3.2で何が変わるか
ざっくり言えば2つです。
- ArgumentErrorやTypeErrorでも下線が出るようになる
$ ruby test.rb test.rb:1:in `+': nil can't be coerced into Integer (TypeError) 1 + nil ^^^
- Railsのエラーページでも下線が出るようになる
これだけなんですが、これらを実現するためにRubyインタプリタのエラー処理まわりをいろいろと整理する必要がありました。
何がむずかしかったか
一言で言えば、「エラーメッセージの変更は非互換である」ということでした。
Ruby 3.1のerror_messageはException#message
をオーバーロードしてerror_highlightのメッセージを出すので、要するにエラーメッセージを書き換えていました。しかしエラーメッセージはターミナルなどに表示されるだけではなく、テストで参照されたり、ログファイルに書かれたりもするので、むやみに変えると非互換が問題になるのでした。
Ruby 3.1では比較的影響の少なそうなNameError/NoMethodErrorにとどめたのですが、それでもいくつか困りごとが報告されていました。いきなりArgumentErrorやTypeErrorに対象を広げると更に大きな問題になるので、非互換問題を先に解決する必要がありました。
Ruby 3.2の新機能、Exception#detailed_message
どうすればよいか。本来のException#message
を書き換えることなく、ターミナルなどに表示するメッセージだけ書き換えればよいです。
そこで、Exception#detailed_message
という新規メソッドをRuby 3.2に導入しました。
#message
は従来どおりのシンプルなメッセージを返します。
puts $!.message #=> undefined method `time' for 1:Integer
#detailed_message
はerror_highlightやdid_you_meanの情報が付加されたメッセージを返します。#detailed_message
は内部的に#message
を呼んでます。
puts $!.detailed_message #=> undefined method `time' for 1:Integer (NoMethodError) # # 1.time # ^^^^^ # Did you mean? times
テストやログでは従来どおり#message
を使い、ユーザにエラーを表示したいときは#detailed_message
を使う、という風に使い分けることになります。Rubyインタプリタのエラー表示も#detailed_message
を使うようにしました。
これで、#message
の内容を変えずにエラー表示を拡充出来るようになったので、非互換の問題なくArgumentErrorやTypeErrorでも下線を表示できるようになりました。
エコシステムへの対応
この#detailed_message
の変更は、Rubyのエラーを表示するフレームワークにちょっと対応をしてもらう必要があります。たとえばRackの開発モードのエラー画面では、did_you_meanやerror_highlightの情報も出してほしいでしょう。そのためには、#message
ではなく#detailed_message
を使うようにする必要があります。
Rackについてはプルリクエストを送ってマージしてもらいました。シンプルなので参考になると思います。
Railsについては、単純に#detailed_message
を使うのではなく、error_highlightのAPIを直接使って専用の表示をするようにしました。
SentryとDataDogは、Exception#detailed_message
を提案したチケットの中で、#detailed_message
を使うようにすることを約束してくれました。彼らの同意が得られたから#detailed_message
が導入できたという背景もあります。
しかし、エラー表示をしたいフレームワークは他にもたくさんあるので、地道に対応していく必要があるでしょう
まとめ
Ruby 3.2のerror_highlightでは、
- ArgumentErrorやTypeErrorでも下線が出るようになります。
- Railsのエラーページでも下線が出るようにもなります。
この2点を実現するために、Rubyのエラー処理まわりを整理しました。
という話を、RubyKaigiでします *1。3日目の一番最初のトークです。興味あればぜひ。
また、お使いのフレームワークに#detailed_message
を対応させる必要があるのかもしれません。Ruby 3.2にしたときに「did_you_meanやerror_highlightが表示されてないな」と思ったら、この内容を思い出してフレームワークにプルリクを作るなどしてもらえるとうれしいです。
*1:記事では省略しましたが、インタプリタが余計なことをするせいで下線位置がずれてしまう!というバグ修正の話なども。