kachick / times_kachick

`#times_kachick channel in chat` as a public repository. Personal Note and TODOs
https://github.com/kachick/times_kachick/issues?q=is%3Aissue+is%3Aclosed
6 stars 0 forks source link

2022-05-21 - Ruby 3.1 から追加された、 `Refinement#import_methods` は、define_method も、自分で書いた Ruby コードの alias_method も弾く #160

Closed kachick closed 2 years ago

kachick commented 2 years ago

TL;DR

何についてか / About

ref:

経緯 / History

  1. refinement + Module#prepend を使っているコードの幾つかを Ruby 3.1 に上げようとしたらつまずいた。
  2. まずそのままだと警告が出る。prependは Ruby 3.2 から refine 内で使えなくなるので import_methods を使うようにとのこと。 warning: Refinement#prepend is deprecated and will be removed in Ruby 3.2
  3. そのまま置換して通るコードと通らないコードが合った。
  4. 通らない方は Ruby で書かれたコードじゃないとあかんよという例外が出る。import_methods': Can't import method which is not defined with Ruby code: TestRefinement::TestImport::B#bar (ArgumentError)
  5. builtin のメソッドを使えないのは https://github.com/ruby/ruby/blob/cae85c528c7c8ea1dd3ba634db5ccb9e465707f7/class.c#L772-L806 とかに書いてあるからわかっていたけれど、自前のメソッドも弾かれるのでちょっと詰まった。
  6. エラーに挙がっているメソッド名の辺りを見ていくと、alias_method と define_method で定義している物が軒並み死んでいる。それではということで module_eval で定義してみると通った。 https://github.com/kachick/lettercase/blob/b4cf149e3407f053788deb8392440e60b319c1f9/lib/lettercase/extensions.rb#L54-L73
  7. https://github.com/ruby/ruby/blob/65122d09d515c9183e643d5f7f31d24251b149ed/eval.c#L1628-L1630 を見ると VM_METHOD_TYPE_ISEQ というメソッド種別みたいな物で分岐している。 https://github.com/ruby/ruby/blob/65122d09d515c9183e643d5f7f31d24251b149ed/method.h#L109-L124 に定義が合って、Ruby で書かれたコードというのはこの形になるらしい。

    typedef enum {
        VM_METHOD_TYPE_ISEQ,      /*!< Ruby method */
        VM_METHOD_TYPE_CFUNC,     /*!< C method */
        VM_METHOD_TYPE_ATTRSET,   /*!< attr_writer or attr_accessor */
        VM_METHOD_TYPE_IVAR,      /*!< attr_reader or attr_accessor */
        VM_METHOD_TYPE_BMETHOD,
        VM_METHOD_TYPE_ZSUPER,
        VM_METHOD_TYPE_ALIAS,
        VM_METHOD_TYPE_UNDEF,
        VM_METHOD_TYPE_NOTIMPLEMENTED,
        VM_METHOD_TYPE_OPTIMIZED, /*!< Kernel#send, Proc#call, etc */
        VM_METHOD_TYPE_MISSING,   /*!< wrapper for method_missing(id) */
        VM_METHOD_TYPE_REFINED,   /*!< refinement */
    
        END_OF_ENUMERATION(VM_METHOD_TYPE)
    } rb_method_type_t;
  8. この辺全くわからないけどちょっと雰囲気で実装を眺めると、 alias_methodVM_METHOD_TYPE_ALIASdefine_methodVM_METHOD_TYPE_BMETHOD になるっぽい。てっきり自分がRubyで書いているコードをaliasしたりブロック渡しで定義しても内部で同じ様な扱いになってるんだろうと思ってたけど、フィールドとか結構違うみたいで難しいんだなぁという小並感。(何も考えず単純に分岐広げるとSEGVする)。10年以上使ってて初めて知った~
  9. refinement との組み合わせで使うシーンはあんま無いだろうけど、パッと見 attr_reader/attr_writer/attr_accessor 系とかもダメっぽい。 alias の方はまぁ対して難しくなく回避できたけど、 define_method が好きなので文字列 eval 族を引っ張り出すのにはちょっと抵抗があった。
  10. しかし、この変更に至った issue みたいなのが幾つもあって 😮 、自分も今までなんとなく動くから適当に refinement 使ってたけど、もし使い込んでたら怪しく動く所が一杯あったんだろうなー