🙆

【Ruby 3.4 Advent Calender】ブロック引数を利用しないメソッドにブロック引数を渡すと警告がでる【6日目】

2024/12/06に公開

Ruby 3.4 Advent Calender 6日目の記事です。

これはなに

今年 2024年12月25日にリリースされる予定の Ruby 3.4 の新機能や変更点などを1つずつ紹介していく Advent Calender になります。
基本的には NEWS に載っている機能を紹介すると思うんですがここにない機能についても書くかもしれません。
また、記事を書いてる時点ではまだ Ruby 3.4 はリリースされる前なので Ruby 3.4 がリリースされた時点で機能が変わっている 可能性があるかもしれないので注意してください。
記事のまとめは ここを参照 してください。

ブロック引数を利用しないメソッドにブロック引数を渡すと警告がでる

Ruby 3.4 から次のように『ブロック引数を受け取らない(利用していない)メソッドに対してブロック引数を渡す』と警告が出るようになりました。

# このメソッドではブロック引数は利用していない
def hoge
end

# メソッド内でブロック引数を利用していない場合に警告がでる
# warning: the block passed to 'Object#hoge' defined at test.rb:2 may be ignored
hoge {}

この警告は**-W オプションがあるときのみ**出力されます。

# test.rb
def hoge
  puts "hello"
end

hoge {}
# -w がない場合は警告はでない
$ ruby test.rb
hello
# -W がある場合に警告が出る
$ ruby -W test.rb
test.rb:6: warning: the block passed to 'Object#hoge' defined at test.rb:2 may be ignored
hello

また類似で -W:strict_unused_block オプションもあるんですがこれは後述で説明します。

背景

これは『ブロック引数を渡してもそれが利用されていないこと』を明示的に検知したいのが目的となっています。
例えば次のように『意図しないブロック引数の渡し方』をしてしまった場合にユーザが気づくのが難しいので、それに気づけるようにしたい感じですね。

def my_open(name)
  open(name)
end

# my_open 自体はブロック引数を受け取らないが、
# Kernel#open と同じような感じでブロック引数と渡してしまうと意図する挙動にならない
my_open(name){ |f| important_work_with f }

もう少し細かい話

以下のケースに該当する場合は『ブロック引数が利用されている』という風に定義されます。

  • (1) メソッドの引数にブロック引数がある場合( hoge(&block)
  • (2) メソッド内で yield を利用している場合
  • (3) メソッド内で super を利用したときにブロック引数をフォワードしている場合

なので次のようなメソッドに対してブロック引数を渡した場合は警告はでません。

# (1) メソッドの引数にブロック引数がある場合( `hoge(&block)` )
def with_block(&block)
end

# no warning
with_block {}


# (2) メソッド内で `yield` を利用している場合
def use_yield
  yield
end

# no warning
use_yield {}

『(3) メソッド内で super を利用したときにブロック引数をフォワードしている場合』がちょっとややこしくて super では暗黙的に『自身のブロック引数が親メソッドに渡される』ようになっています。
この『自身のブロック引数が親メソッドに渡される = ブロック引数が利用されている』という解釈になります。
逆に super {} のように呼び出した場合は『暗黙的にブロック引数が渡されない = ブロック引数が利用されていない』という解釈になります。

# (3) メソッド内で `super` を利用したときにブロック引数をフォワードしている場合
class Super
  def case1; end
  def case2; end
end

class Sub < Super
  # super だけの場合
  # このときは super に暗黙的に自身のブロック引数を渡している
  def case1
    super
  end

  # super にブロック引数を渡している場合
  # このときは super に自身のブロック引数は渡されない = ブロック引数は利用されていない
  def case2
    super {}
  end
end

sub = Sub.new

# no warning
sub.case1 { pp "case1" }   # => nil

# warning: the block passed to 'Sub#case2' defined at test.rb:16 may be ignored
sub.case2 { pp "case2" }   # => nil

また『定義されているクラスは違うが、いずれかの同名のメソッドでブロック引数を利用している場合』は警告はでないようになっています。

class X
  def hoge
    yield
  end
end

class Y
  def hoge
  end
end

# Y#hoge だけを呼び出した場合でも警告がでない
# no warning
Y.new.hoge {}

これは -W:strict_unused_block オプションや Warning[:strict_unused_block]警告を出すように制御することができます。

class X
  def hoge
    yield
  end
end

class Y
  def hoge
  end
end

Warning[:strict_unused_block] = true

# warning: the block passed to 'Y#hoge' defined at test.rb:8 may be ignored
Y.new.hoge {}

その他、詳しい挙動に関しては下記を参照してください。

GitHubで編集を提案

Discussion