2021/11/18 今回の気になった bugs.ruby のチケット

今週は Refinements 関連の便利メソッドのチケットが多いです。

[Bug #17429] Prohibit include/prepend in refinement modules

  • Refinements 時に include/prepend することを禁止するチケット
  • 背景として以下の様に Refinements 時に include/prepend すると意図しない挙動がある
class X; end

module M
  def foo
    hoge
  end

  def hoge
    "hoge"
  end

  refine X do
    include M
  end
end

using M

# error `foo': undefined local variable or method `hoge' for #<X:0x000056087d251648> (NameError)
# hoge は Refinements 内から呼ぶことができない
# なぜなら foo メソッド内では using してないから…
p X.new.foo
  • これらを解決する仕組みとして新しく Refinement#import_methods というメソッドが導入された
class X; end

module M
  def foo
    hoge
  end

  def hoge
    "hoge"
  end

  refine X do
    import_methods M
  end
end

using M

p X.new.foo
  • Refinement#import_methods を利用すると継承リストにモジュールが追加されるのではなくてモジュールのメソッドが直接 Refinements オブジェクトに定義される
class X; end

module M1
  def hoge; end

  refine X do
    # X の継承リストに追加される
    include M1
  end
end

module M2
  def foo; end

  refine X do
    # M2 のメソッドが Refinements オブジェクトに直接追加される
    import_methods M2
  end
end

using M1
using M2

p X.instance_method(:hoge).owner
# => M1

p X.instance_method(:foo).owner
# => #<refinement:X@M2>
  • また Refinement#include/prependRuby 3.2 で削除される予定なので -W を付けて実行すると警告がでる
module M1
  refine Object do
    # warning: Refinement#include is deprecated and will be removed in Ruby 3.2
    include M1

    # warning: Refinement#prepend is deprecated and will be removed in Ruby 3.2
    prepend M1
  end
end
  • これ自体はかなりよさそうなんですが互換性を保つようにするのがちょっと大変そう
  • Refinements がよくなってうれいし

[Feature #18270] Refinement#{extend_object,append_features,prepend_features} should be removed

[Feature #18334] ENV#to_h returns a new Hash object but Hash#to_h does not, which can cause inconsistencies

  • ENV.to_h は新しい Hash オブジェクトを返すが Hash#to_h はレシーバを返す
# ENV.to_h は異なる object_id になる
ENV.to_h.object_id
# => 14700
ENV.to_h.object_id
# => 14760

# Hash は同じ object_id になる
hash = {"FOO" => "bar"}
hash.to_h.object_id
# => 14820
hash.to_h.object_id
# => 14820

[Feature #14332] Module.used_refinements to list refinement modules

  • using されている Refinement オブジェクトを返す Module.used_refinements メソッドを追加する提案
module Json
  refine Integer do
    def to_json
      to_s
    end
  end

  refine String do
    def to_json
      inspect
    end
  end
end

module Fact
  refine Integer do
    def fact
      self <= 1 ? 1 : self * (self-1).fact
    end
  end
end

using Json
p Module.used_modules # => [Json]
# using されている Refinement オブジェクト一覧を返す
p Module.used_refinements # => [#<refinement:Integer@Json>, #<refinement:String@Json>]

using Fact
p Module.used_modules # => [Json, Fact]
p Module.used_refinements # => [#<refinement:Integer@Fact>, #<refinement:Integer@Json>, #<refinement:String@Json>]
  • あればいろいろとできそうだけど具体的にどういう時に使うのかはわからねえ

[Feature #12737] Module#defined_refinements

  • レシーバで定義されている refine されたオブジェクトとその Refinement オブジェクトの Hash を返す Module#defined_refinements メソッドを追加する提案
module M
  refine String do
    $M_String = self
  end

  refine Integer do
    $M_Integer = self
  end
end

p M.defined_refinements #=> {String => $M_String, Integer => $M_Integer}
  • これもあれば便利そう

[Bug #18343] empty hash passed to Array#pack causes Segmentation fault (2.6)

  • Ruby 2.4 ~ 2.6 で Array#pack に空の Hash を渡すと Segmentation fault が発生するというバグ報告
    • Ruby 2.7 以降では再現しない
# これで segv する
[0].pack('c', {})
  • Ruby 2.6 は現状セキリュティサポートのみなのでこのチケットは閉じられている

[Feature #18332] a ? b

  • a ? b : nila ? b とかけるようにする提案
    • && と似てるけどちょっと違う
    • a && b の場合は false が返ってくる
  • ユースケース
"#{current_path == "/" ? "font-bold"}"
"#{user.admin? ? "text-red-600"}"
  • root? ? "font-bold""font-bold" if root? どっちが読みやすい?みたいなコメントがされている