😊

[Bug #21525] ブロック引数があるメソッドの評価順序が一貫していないバグ報告

に公開

[Bug #21525] Inconsistent execution order for methods with constant blocks

  • 次のようにメソッド引数で * を使用している場合に『変更前の配列のコピー』がメソッドの引数に渡される
  • なので次のようにブロック引数に副作用がある場合でも『変更前の配列』がメソッドに渡される
ARRAY = [1, 2, 3]

# このメソッドで ARRAY の末尾が削除される
def bar
  ARRAY.pop
  -> {}
end

def example(*args, &)
  args
end

# bar で ARRAY の値が書き換わるがメソッドの引数は書き換わる前の値が渡される
pp example(*ARRAY, &bar)
# => [1, 2, 3]

# ARRAY 自体の値は変わっている
pp ARRAY
# => [1, 2]
  • しかし、次のように #to_proc で値を書き換えた場合に『書き換わった後の値』を渡す可能性がある
ARRAY = [1, 2, 3]

module Foo
  module Bar
    # &Foo::Bar のタイミングで #to_proc が呼び出される
    def self.to_proc
      ARRAY.pop
      -> {}
    end
  end
end

def example(*args, &)
  args
end

pp example(*ARRAY, &Foo::Bar)
# => [1, 2]
  • また次のように const_missing で書き換えた場合にも同様の結果になる
ARRAY = [1, 2, 3]

module Foo
  def self.const_missing(name)
    ARRAY.pop
    -> {}
  end
end

def example(*args, &)
  args
end

pp example(*ARRAY, &Foo::Bar)
# => [1, 2]
  • これらはいずれも [1, 2] ではなくて [1, 2, 3] を期待する、というバグ報告
  • これは評価順序の問題になるんですがコメントを見るとアロケーションを回避するための挙動とのトレードオフになるみたいですね
  • これは他にもエッジケースがあるみたいで以下のようなコードの場合に
method(*args, **kwargs) 
method(*args, &block) 
method(**kwargs, &block) 
  • kwargs.to_hashblock.to_procargskwargs を変更すると同様に評価順序の問題が発生するみたい
  • これらを対応する場合に VM 命令が必要になりエッジケースのためだけに対応するのはコストがかかるのでそれなりの理由が必要になるとのこと
  • これ自体は起票者も納得しているのでチケット自体はクローズされている
GitHubで編集を提案

Discussion