先日、RuboCop の ruby-head CI が落ちていたので、bugs.ruby-lang.org にフィードバックしていたイシューが以下。
osyo さんがコメントで教えてくれた PR が以下で、each_cons
と each_slice
の戻り値が nil から self に変わっているというものだった。
以下は破壊的変更が起きる (単純化した) サンプルです。ブロックで条件によって break
を使った戻り値を使うか、each_cons
としての戻り値を使うか判定するようなロジックが遭遇したケースです。
Ruby 3.0 以下
% ruby -v ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-darwin19] % irb irb(main):001:0> [1, 2, 3].each_cons(2) { |i| break i if true} => [1, 2] irb(main):002:0> [1, 2, 3].each_cons(2) { |i| break i if false} => nil
Ruby 3.1
% ruby -v ruby 3.1.0dev (2021-10-26T00:30:42Z master 7d4c59203f) [x86_64-darwin19] % irb irb(main):002:0> [1, 2, 3].each_cons(2) { |i| break i if true} => [1, 2] irb(main):003:0> [1, 2, 3].each_cons(2) { |i| break i if false} => [1, 2, 3]
たしかに振る舞いとして戻り値 nil より自然になる気もするが、Ruby 3.1 向けに進んでいる NEWS などで見かけなかったので、影響をどれくらい加味された変更かよく分かっていなかった。
Rails/OSS パッチ会で osyo さんと NEWS にあるといいですねと話していて、osyo さんがパッチを出してくれたのが以下。
ということで、こういったケースで Ruby 3.1 での非互換変更として影響を受ける可能性があるので、いちおう each_cons
と each_slice
で同様のことを行っていないか見ておくと良いです。
(なお、最初に遭遇した RuboCop の方はそもそものロジックが 🤔 な部分もあったので、Ruby 3.1 互換のロジックに修正済みです。)