2022/07/22 今回の気になった bugs.ruby のチケット

今週は Time.new24時 を指定してタイムゾーンを渡した時に意図しない結果が返ってくるバグチケットがありました。

[Bug #18929] ruby master looks slower than 3.1 on a micro benchmark of short-lived objects

  • 次のように短命なオブジェクトを生成するときのベンチマークRuby 3.1 と比較して master が遅くなっているというバグ報告
$ time ruby -ve '10000000.times { Object.new }'
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux]

real    0m2.503s
user    0m2.484s
sys     0m0.016s

$ time ./local/bin/ruby -ve '10000000.times { Object.new }'
ruby 3.2.0dev (2022-07-20T00:40:59Z master e330dceb3f) [x86_64-linux]

real    0m3.074s
user    0m3.016s
sys     0m0.052s

[Bug #18927] Can't access class variable directly with class inheritance

  • 以下のように親クラスから子クラスのクラス変数にアクセスする事はできない
class Parent
  def self.class_var
    @@class_var
  end
end

class Child < Parent
  @@class_var = "class_var"
end

# error: uninitialized class variable @@class_var in Parent (NameError)
p Child.class_var
  • しかし、以下のように class_variable_get を使用すると子クラスのクラス変数を取得する事ができる
class Parent
  def self.class_var
    # class_variable_get だとアクセスする事ができる
    class_variable_get(:@@class_var)
  end
end

class Child < Parent
  @@class_var = "class_var"
end

p Child.class_var
# => "class_var"
  • このように @@class_varclass_variable_get(:@@class_var) で挙動が違うがこれは意図する挙動なのか?というチケットになる
  • クラス変数は自身とサブクラスでのみ参照できるのが期待する挙動になる
  • また以下のようにコメントされている
It's best to avoid using class variables completely in Ruby.
# 訳: Rubyでは、クラス変数を完全に使わない方がよいでしょう。

[Feature #18930] Officially deprecate class variables

  • 上の [Bug #18927] からの派生でクラス変数は紛らわしいので公式で非推奨にする提案
    • 具体的にはドキュメントに明記したりとか Warning[:deprecation] = true の場合にのみ警告を出したりとか
  • だいたいの場合はインスタンス変数で代替できるのでクラス変数がなくてもそんなに困らないとは思うんですが、既存のコードに対する影響はかなり大きそうですねえ

[Bug #18837] Not possible to evaluate expression with numbered parameters in it

  • 以下のように binding 経由で元のブロックの引数を参照する事ができる
def dumper(bnd)
  puts bnd.local_variable_get 'i'
  puts bnd.eval 'i * 10'
end

[1,2].each { |i| dumper(binding) }
  • しかし _1 の場合は Binding#eval で参照することができない
def dumper(bnd)
  puts bnd.local_variable_get('_1')
  puts bnd.eval '_1 * 10'
end

[1,2].each do
  # この行がないと local_variable_get でも _1 を参照する事ができない
  some = _1
  dumper(binding)
end

[Bug #18922] Time at 24:00:00 UTC is not normalized

  • Time.newタイムゾーンを指定して 24時 を指定した場合に意図しない挙動になっているバグ報告
# TimeZone を渡さない場合は問題ない
# 次の日の0時になっている
pp Time.new(2000, 1, 1, 24, 0, 0)
# => 2000-01-02 00:00:00 +0900

# TimeZone を渡した場合に意図しない時刻が返ってくる
pp Time.new(2000, 1, 1, 24, 0, 0, "UTC")
# => 2000-01-01 23:00:00 UTC

# 内部では 2000/01/01 24:00:00 というような情報を持っている
pp Time.new(2000, 1, 1, 24, 0, 0, "UTC").to_a
# => [0, 0, 24, 1, 1, 2000, 7, 0, true, "UTC"]
  • 開発版ではこの問題は既に修正済み
pp RUBY_VERSION   # => "3.2.0"
pp Time.new(2000, 1, 1, 24, 0, 0, "UTC")
# => 2000-01-02 00:00:00 UTC

pp Time.new(2000, 1, 1, 24, 0, 0, "UTC").to_a
# => [0, 0, 0, 2, 1, 2000, 1, 1, true, "UTC"]

[Bug #18038] Invalid interpolation in heredocs

  • 以下のようにヒアドキュメントで式展開をした時に意図しない結果になっているというバグ報告
pp RUBY_VERSION
# => "3.0.4"

var = 1

# 式展開されない
v1 = <<~CMD
  something
  #{"/#{var}"}
CMD

# 式展開される
v2 = <<~CMD
  something
  #{other = "/#{var}"}
CMD

# 式展開されない
v3 = <<~CMD
  something
  #{("/#{var}")}
CMD

p v1   # => "something\n/\n"
p v2   # => "something\n/1\n"
p v3   # => "something\n/\n"

p v1 == v2   # => false
p v2 == v3   # => false
  • この問題は Ruby 2.7 から発生していて Ruby 3.1 で修正済み