ネタgemです。
Overview
こういうやつです。
WhatDyaReturn::Extractor.new.extract(<<-CODE) def foo 42 end CODE # => ['42'] WhatDyaReturn::Extractor.new.extract(<<-CODE) def foo if bar 1 else 2 end end CODE # => ['1', '2'] # `bar` が true か false か確定していないため、2つともありえる
できること/できないこと
確認しているシンタックスは test/what_dya_return/extractor_test.rb にずらーっと書いているので、そちらをご覧ください。
✅ early return や break はそれっぽく動いています。
WhatDyaReturn::Extractor.new.extract(<<-CODE) def foo return 42 'baz' # unreachable end CODE # => ['42']
✅ while
とか for
あたりもそれっぽく動くと思います。
WhatDyaReturn::Extractor.new.extract(<<-CODE) def foo while bar 1 break 2 if baz 3 end end CODE # => ['2', 'nil'] # break せずに while を抜ける場合は nil が返る
❌ リテラル値が入っていることが確定している変数でも展開はしません。
WhatDyaReturn::Extractor.new.extract(<<-CODE) def foo a = 1 a end CODE # => ['a']
❌ その他、まだ見ぬ(試していない)シンタックス。たくさんありそう。
作るに至ったモチベーション
- 業務や趣味のプロダクトでは RuboCop を使っている
- 「今更だけど RuboCop ってどうやってコードをチェックしているんだろう」
- rubocop から rubocop-ast に辿りつく
- AbstractSyntaxTreeは存在は知っていたものの、あまり深く入り込んだことがなかった
- なんかこれを使ってネタコード書いてみたいわ ← 今ここ
題材としては「そういえばRubyは(基本的には)最後に評価された値が返り値になるよなー」からの「じゃあ node.children.last 返すやつ書けばいいんじゃね!簡単!」ってことで、今回のテーマに決まりました。(実際にはそんなに簡単ではなかったけど)
というわけで作ってみました
当初は RubyVM::AbstractSyntaxTree
や parser gem を直接使う方向で実装していたのですが
- パース後のツリーを辿っていく時は、ある程度抽象化された形でアクセスしたいな
- ひたすら
children[N]
でやっていくのもつらいしのぉ…… - 自分で
Parser::AST::Node
を拡張するという手も考えましたが、ここで次の話題に
- ひたすら
- early return や if/else などの「これ以降のコードには絶対到達しない」場合は省略したいな
- そういえば RuboCop には Lint/UnreachableCode があるやん
- 実装真似したいけど、結構
RuboCop::AST::Node
ありきの書き方が多いな - まるごとパクってもいいけど……
- ほなら
RuboCop::AST
に乗っかった方が早いやんけ! ← 今ここ
というわけで、実装は RuboCop::AST にがっつり依存することにしました。
結果としては上記の Lint/UnreachableCode
以外の実装も参考になったため、良い方針転換になりました。
まとめ
そこそこのパターンでそれっぽく動いたので満足しています。 まだ見ぬ未対応シンタックスについては暇潰しの1つとしてゆったり対応していこうと思います。
[おまけ1] gemの名前について
- 「そのメソッド、何返すの?」的な役割なので、まさにそれを表現できる名前にしたかった
- 「それってこれ?」みたいな奴で似たようなもの did_you_mean を思い浮かべた
というわけで what do you return(?)
→ what dya return
としました。
[おまけ2] 先行事例について
多分見つけられていないだけでありそう。 RBSが 似たようなことをやっている のも気になる(本 gem みたいにがっつり値を返すってことまではしていないはずだけど)
[おまけ3] ChatGPT
賢すぎる……