From: manga.osyo@... Date: 2018-12-12T04:32:37+00:00 Subject: [ruby-dev:50702] [Ruby trunk Bug#15114] Ruby で定義したメソッドに `&:hoge` を渡しても refinements が有効にならない Issue #15114 has been updated by osyo (manga osyo). こちら、わたしの方でも調査しているんですが、ちょっと時間がかかりそうなので、引き続きわたしの方でも調査しますが詳しい方に見てもらえると助かります。 ---------------------------------------- Bug #15114: Ruby で定義したメソッドに `&:hoge` を渡しても refinements が有効にならない https://bugs.ruby-lang.org/issues/15114#change-75590 * Author: osyo (manga osyo) * Status: Open * Priority: Normal * Assignee: * Target version: * ruby -v: * Backport: 2.3: UNKNOWN, 2.4: UNKNOWN, 2.5: UNKNOWN ---------------------------------------- ## 概要 Ruby 2.4 で以下のように『refinements で追加したメソッドが `&:twice` で呼び出せる』ようになりました。 ```ruby # 新しく String#twice を refinements で追加 using Module.new { refine String do def twice self + self end end } # OK p ["homu", "mami", "mado"].map { |it| it.twice } # => ["homuhomu", "mamimami", "madomado"] # 2.4 以前では # Error: `map': undefined method `twice' for "homu":String (NoMethodError) # とエラーになっていたが、2.4 から Symbol#to_proc 内からでも refinements で定義されたメソッドが呼び出せるようになった p ["homu", "mami", "mado"].map(&:twice) # => ["homuhomu", "mamimami", "madomado"] ``` https://wandbox.org/permlink/EClJQZ9vXZfN1T7H ## 問題点 しかし、次のように Ruby で定義したメソッドに対して `&:twice` を渡しても反映されません。 ```ruby class X def meth &block block.call "homu" end end using Module.new { refine String do def twice self + self end end } # OK p X.new.meth { |it| it.twice } # => "homuhomu" # Error: `meth': undefined method `twice' for "homu":String (NoMethodError) p X.new.meth(&:twice) ``` https://wandbox.org/permlink/0OnXOQyVluZ6Sjgc これは、以下のように自作クラスで `include Enumerable` した際に `Array#map` と同様に『refinements で追加したメソッドを `&:twice` で呼び出したい場合』に問題になります。 ```ruby class MyArray include Enumerable def initialize ary @ary = ary end def each &block @ary.each &block end end using Module.new { refine String do def twice self + self end end } ary = MyArray.new ["homu", "mami", "mado"] # Error: `each': undefined method `twice' for "homu":String (NoMethodError) p ary.map(&:twice) ``` https://wandbox.org/permlink/8xbyN3xpXXG46wCa また、類似の問題として `Symbol#to_proc` から直接 `Proc` を生成し、呼び出した場合も refinements が反映されません。 ```ruby using Module.new { refine String do def twice self + self end end } # Error: undefined method `twice' for "homu":String (NoMethodError) :twice.to_proc.call "homu" ``` https://wandbox.org/permlink/KU3KWcjJ9kabpNtX ## 修正内容 ひとまず `meth &:twice` に対して修正を行い、それのパッチを添付してあります。 修正内容としては `metho &:twice` を呼び出して、[`Proc` オブジェクトを生成する時](https://github.com/osyo-manga/ruby/blob/69014e25ded0b4cf6f268983e5450557c6d64ec6/vm_args.c#L888)に `cref->refinements` を `refine_sym_proc_call` のコールバック引数に追加し、`Proc#call` 時に呼ばれる [`refine_sym_proc_call` 内で `cref->refinements` を参照してメソッド探査する](https://github.com/osyo-manga/ruby/blob/69014e25ded0b4cf6f268983e5450557c6d64ec6/vm_args.c#L862)ようにしました。 修正する方向性として問題ないようであれば `Symbol#to_proc` から呼び出した場合に対しても修正パッチを書こうと考えています。 ## 修正内容に対する問題点 ひとまず無理やり動作させるようにしてみたのですが、以下の点が気になっています。 * `cref->refinements` の寿命がわからない * `Proc#call` を呼び出した時点で死んでいる可能性があるかも? * `Proc` オブジェクトのキャッシュ化を無効 * これは以下のようなケースでキャッシュ化を有効にしていると正しく動作しないので無効にしました… ```ruby def meth &block block.call "homu" end using Module.new { refine String do def twice self + self end end } p meth &:twice # => "homuhomu" # String#twice を再定義する using Module.new { refine String do def twice self + self + self end end } # ここで新たに proc オブジェクトを生成してほしい p meth &:twice # => "homuhomuhomu" ``` 修正内容や方向性に対して意見があればコメントして頂けると助かります。 ---Files-------------------------------- fix_symbol_to_proc.patch (3.57 KB) -- https://bugs.ruby-lang.org/