👏

[Bug #20392] super でブロック引数をフォワードするときのバグ報告

2024/04/05に公開

[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)
GitHubで編集を提案

Discussion