【RubyKaigi発表予告】error_highlight: user-friendly error diagnostics

技術部の遠藤です。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という機能です。

当日は実装をかんたんに発表しますが、この内容はすでに記事を書いているので、興味があれば読んでください。

techlife.cookpad.com

Ruby 3.2で何が変わるか

ざっくり言えば2つです。

  • ArgumentErrorやTypeErrorでも下線が出るようになる
$ ruby test.rb
test.rb:1:in `+': nil can't be coerced into Integer (TypeError)

1 + nil
    ^^^
  • Railsのエラーページでも下線が出るようになる

Railsのエラー画面のerror_highlight

これだけなんですが、これらを実現するために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についてはプルリクエストを送ってマージしてもらいました。シンプルなので参考になると思います。

github.com

Railsについては、単純に#detailed_messageを使うのではなく、error_highlightのAPIを直接使って専用の表示をするようにしました。

github.com

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:記事では省略しましたが、インタプリタが余計なことをするせいで下線位置がずれてしまう!というバグ修正の話なども。