Zenn
👋

【bugs.ruby Advent Calender】ブロック引数の値を書き換えたあとに super を呼び出したときの挙動【21日目】

2024/12/21に公開

bugs.ruby Advent Calender 21日目の記事です。

これはなに

今年1年間通してみてきた bugs.ruby のチケットの中から気になったものを1つずつ取り上げていく Advent Calender です。
取り上げるチケットは基本的にこのブログで取り上げたものになります。
記事のまとめは ここを参照 してください。

[Bug #20505] Reassigning the block argument in method body keeps old block when calling super with implicit arguments

チケットの内容は次のように super を呼び出す前にブロック引数の値を変えたときに意図しない挙動になるバグ報告になります。

class A
  def positional_arg(a)
    puts a
  end

  def block_arg(&block)
    yield
  end
end

class B < A
  def positional_arg(a = nil)
    a = 'b'
    # これは親メソッドに書き換えた a の値が渡される
    super
  end

  def block_arg(&block)
    block = proc { puts 'b' }
    # このときに書き換えた block の値が親メソッドに渡されない
    super
  end
end

B.new.positional_arg('a')      # => b
B.new.block_arg { puts 'a' }   # => a

super という呼び出しだと『暗黙的に自身の引数を親メソッドに渡す』ような挙動になることを期待します。
なので block 変数を書き換えた場合にその値が親メソッドに渡されることを期待するんですがそれが反映されない、という旨になります。

これなんですが super 自体がかなり特殊な動きになっており『 supersuper(&block) は等価ではない』という話みたいですね。
なので block の変数を変えても super が渡すブロック引数には影響がないみたいです。
これは仕様というか『現状そういう挙動になっています』以上の話ではないみたいですねー

上記の話も結構ギョッとしたんですがコメントではもっとギョッとする話も書かれており、例えば以下のように super() で呼び出した場合でも『ブロック引数は』親メソッドに渡される挙動になっています。

class A
  def hello(name = "homu")
    yield(name)
  end
end

class B < A
  def hello(name)
    # 親メソッドに位置引数などは渡されないがブロック引数は渡される
    super()
  end
end

# ここで渡したブロック引数は A#foo まで渡される
pp B.new.hello("mami") { "Hello, #{_1}" }
# => "Hello, homu"

super() だと親メソッドに引数が渡されないと思っていたんですがブロック引数は渡されるような挙動になっているんですねー全然知らなかった。

関連

GitHubで編集を提案

Discussion

ログインするとコメントできます