👏
[Bug #20392] super でブロック引数をフォワードするときのバグ報告
[Bug #20392] Delegate super calls with a block
- 次のように
...
とブロック引数を同時に渡すコードはシンタックスエラーになる
# Example 1
def foo ...
yield
end
def bar ...
# error: both block arg and actual block given (SyntaxError)
foo(...) { }
end
- しかし次のように
super
を経由してブロック引数を渡すとエラーにならずsuper
に渡したブロック引数が呼ばれる
# Example 2
class A
def foo
yield(3)
end
end
class B < A
def foo(...)
# A#foo にはブロック引数が渡され、それが評価される挙動になる
# super {} は super(...) {} と同等であるはず…?
super do |x|
yield(2 + x)
end
end
end
# ここで渡したブロック引数は呼び出されない
p B.new.foo { |x| x }
# => 5
- また次のように
super(...) {}
で呼び出した場合はそのブロック引数は渡されない
# Example 3
class A
def foo
yield(3)
end
end
class B < A
def foo(...)
# ここで渡したブロック引数は A#foo には渡されない
super(...) do |x|
raise "should I be called?"
end
end
end
# ここで渡したブロック引数が呼び出される
p B.new.foo { |x| x }
# => 3
- まとめると以下のような感じ
- Example 1 : シンタックスエラー
- Example 2 :
super
を呼び出したときのブロック引数が渡される - Example 3 *
super(...)
にフォワードしたブロック引数が渡される - ちなみに『引数がない
super
のことをzsuper
と呼んでいる』らしい
- Example 3 に関しては Ruby 3.2 では Example 1 と同じ挙動だったので Ruby 3.3 で挙動が変わってしまったらしい
- また Example 2 に関しては
super
が特殊なのでこのような挙動になっているらしい - 具体的には
super(arg)
でもブロック引数が暗黙的に渡されるようになっている
class A
def foo(a)
yield(a) if block_given?
end
end
class B < A
def foo(a, &block)
# super(a) でも暗黙的にブロック引数を A#foo に渡している
super(a)
end
end
p B.new.foo(3) { |x| 2 + x }
# => 5
- 明示的にブロック引数を渡さないようにするためには
&nil
を渡す必要がある
class A
def foo(a)
yield(a) if block_given?
end
end
class B < A
def foo(a, &block)
# &nil を渡すことでブロック引数を渡さないことを明示化する
super(a, &nil)
end
end
p B.new.foo(3) { |x| 2 + x }
# => nil
- このチケットでは Example 2 の場合に Ruby 3.2 と同様にシンタックスエラーになるように修正されている
class A
def foo
yield(3)
end
end
class B < A
def foo(...)
super(...) do |x|
raise "should I be called?"
end
end
end
p B.new.foo { |x| x }
# Ruby 3.2 => error: both block arg and actual block given (SyntaxError)
# Ruby 3.3 => 3
# Ruby 3.4 => error: both block arg and actual block given (SyntaxError)
Discussion