2021/01/14 今週の気になった bugs.ruby のチケット

内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
あくまでも『わたしが気になったチケット』で全ての bugs.ruby のチケットを載せているわけではありません。

[Feature #17485] Keyword argument for timezone in Time.new

  • Time.newタイムゾーンを指定する場合、以下のように時間をすべて指定する必要がある
# OK
Time.new(2021, 1, 1, 0, 0, 0, "+09:00") #=> ok: 2021-01-01 00:00:00 +0900

# これは意図するタイムゾーンを設定できない
Time.new(2021, 1, 1, "+09:00")          #=> bad: 2021-01-01 09:00:00 +0900
Time.new(2021, 1, "+09:00")             #=> bad: 2021-01-09 00:00:00 +0900
Time.new(2021, "+09:00")                #=> ArgumentError (mon out of range)
  • キーワード引数 inタイムゾーンを設定できるようにするチケット
Time.new(2021, 1, 1, in: "+09:00") #=> ok: 2021-01-01 00:00:00 +0900
Time.new(2021, in: "+09:00")       #=> ok: 2021-01-01 00:00:00 +0900

[Feature #16806] Struct#initialize accepts keyword arguments too by default

  • Structkeyword_init: をデフォルトで有効化させるチケット
User = Struct.new(:name, :age)

# これは以前の挙動のまま
homu = User.new("homu", 14)

# キーワード引数を渡すと keyword_init: true と同じように初期化される
homu = User.new(name: "homu", age: 14)
  • ただし、以下のようなケースで互換性が壊れるかもしれない
User = Struct.new(:name, :age)

# 現状だと name に Hash オブジェクトが入ってしまうので既存の挙動と変わってしまう
p User.new(name: "homu", age: 14)
# => #<struct User name={:name=>"homu", :age=>14}, age=nil>

[Bug #17519] set_visibility fails when a prepended module and a refinement both exist

  • 以下のように refine 後のメソッドを特異クラスを経由して private 化しようとするとエラーになるというバグ報告
module Nothing; end

class X
  # prepend しなかったらエラーにはならない
  prepend Nothing

  def hoge
  end
end

# これは OK
X.new.singleton_class.class_eval { private :hoge }

module NeverUsed
  refine X do
    def hoge(*keys)
    end
  end
end

# `private': undefined method `hoge' for class `#<Class:#<X:0x0000558fa95b7d70>>' (NameError)
# Refinements で拡張したあとに呼ぶとエラーになる
X.new.singleton_class.class_eval { private :hoge }
  • なにもわからない…
  • これは Ruby 2.5, 2.6, 2.7 でも再現していた

[Bug #17533] Named capture is not assigned to the same variable as reserved words.

  • 次のように正規表現で任意の変数に結果をキャプチャすることができる
# マッチした部分を result 変数に保存する
/(?<result>\d+).*/ =~ "1234hoge"
pp result
# => "1234"
  • この時に予約語をキーワード引数として定義している時に正しく代入されていなかった
def test(nil: :ng)
  # nil という変数名にマッチしたテキストが代入されるのを期待する
  # しかし、代入されない
  /(?<nil>\d+).*/ =~ "1234hoge"
  binding.local_variable_get(:nil)  # => :ng
end

test
def test(nil: :ng)
  /(?<nil>\d+).*/ =~ "1234hoge"
  binding.local_variable_get(:nil)
  # 3.0 => :ng
  # 3.1 => "1234"
end

test

[Bug #17534] Pattern-matching is broken with find pattern

  • 次のような find パターンを含んだパターンマッチでぶっ壊れるという報告
case [1, 2, 3]
in y
  puts "branch1"
in [*, x, *]
  puts "branch2"
else
  puts "branch3"
end
# output:
__END__
-- raw disasm--------
   trace: 1
   0000 putnil                                                           (   2)
   0001 duparray             <hidden>                                    (   1)
   0003 dup                                                              (   2)
   0004 setlocal_WC_0        4                                           (   2)
   0006 jump                 <L002>                                      (   2)
   0008 dup                                                              (   4)
   0009 topn                 2                                           (   4)
   0011 opt_le               <calldata:<=, 1>                            (   4)
   0013 branchunless         <L013>                                      (   4)
   0015 topn                 3                                           (   4)
   0017 topn                 1                                           (   4)
   0019 opt_aref             <calldata:[], 1>                            (   4)
   0021 setlocal_WC_0        3                                           (   4)
   0023 jump                 <L012>                                      (   4)
 <L013> [sp: 2]
   0025 pop                                                              (   4)
   0026 pop                                                              (   4)
*  0027 pop                                                              (   4)
   0028 jump                 <L006>                                      (   4)
 <L012> [sp: 2]
   0030 pop                                                              (   4)
   0031 pop                                                              (   4)
   0032 pop                                                              (   4)
   0033 pop                                                              (   4)
   0034 jump                 <L004>                                      (   4)
 <L006> [sp: -1]
   0036 pop                                                              (   4)
 <L001> [sp: -1]
   0037 pop                                                              (   7)
   0038 pop                                                              (   7)
   trace: 1
   0039 putself                                                          (   7)
   0040 putstring            "branch3"                                   (   7)
   0042 opt_send_without_block <calldata:puts, 1>                        (   7)
   0044 leave                                                            (   7)
 <L002> [sp: 2]
   0045 pop                                                              (   2)
   0046 pop                                                              (   2)
   trace: 1
   0047 putself                                                          (   3)
   0048 putstring            "branch1"                                   (   3)
   0050 opt_send_without_block <calldata:puts, 1>                        (   3)
   0052 leave                                                            (   7)
 <L004> [sp: -1]
   0053 pop                                                              (   4)
   0054 pop                                                              (   4)
   trace: 1
   0055 putself                                                          (   5)
   0056 putstring            "branch2"                                   (   5)
   0058 opt_send_without_block <calldata:puts, 1>                        (   5)
   0060 leave                                                            (   7)
---------------------
/tmp/vxdawqc/180:4: warning: Find pattern is experimental, and the behavior may change in future versions of Ruby!
/tmp/vxdawqc/180:4: argument stack underflow (-1)
/tmp/vxdawqc/180: compile error (SyntaxError)
  • こんなエラーはじめてみた…
  • 結構簡単に再現しそうなので怖い

[Feature #13683] Add strict Enumerable#single

  • レシーバの要素を1つだけ返す Enumerable#single を追加する提案
  • 似たようなメソッドに Enumerable#first があるがちょっと意味が違う
    • Enumerable#first : 先頭の要素を返す。見つからなかった場合は nil を返す
    • Enumerable#single : 先頭の要素を返す。ただし、要素が2個以上、または存在しない場合は例外を発生させる
  • あんまり利便性がわからないけど便利なのかな…?
  • ちなみに ActiveSupport にも同様の PR が投げられてます
  • 現状は #single という名前ではなくて違う名前の方がいいんじゃないか、みたいな名前付けの議論で止まってるぽい
  • と、思ってたら ActiveRecord で同じような機能の ActiveRecord::FinderMethods#sole というメソッドが追加されました
  • 更に ActiveSupportEnumerable#sole を追加する PR もある
  • ActiveSupportEnumerable#sole が追加されたあとに Ruby の標準に別名で同等の機能が入った場合、混乱しそうだなあ
  • ちなみにパターンマッチで取得するのはどうか、というコメントもされている
case []; in [a]; p a; end #=> NoMatchingPatternError ([])
case [1]; in [a]; p a; end #=> 1
case [1,2]; in [a]; p a; end #=> NoMatchingPatternError ([1, 2])