👋
【bugs.ruby Advent Calender】ブロック引数の値を書き換えたあとに super を呼び出したときの挙動【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 自体がかなり特殊な動きになっており『 super と super(&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() だと親メソッドに引数が渡されないと思っていたんですがブロック引数は渡されるような挙動になっているんですねー全然知らなかった。
Discussion