2021/12/09 今回の気になった bugs.ruby のチケット
今週は import_methods
に C拡張で定義されたモジュールを渡せないバグ報告などがありました。
[Bug #18396] An unexpected "hash value omission" syntax error when without parentheses call expr follows
- Ruby 3.1 では Hash の省略記法が新しく入ったが次のようなコードの時に『次の行の式』が値になってしまうというバグ報告
# error: syntax error, unexpected local variable or method, expecting `do' or '{' or '('
foo key:
foo arg
- もしくは以下のような感じ
def hoge(**kwd) pp kwd end key = 42 # 意図としては hoge(key: key) となってほしい # しかし実際には hoge(key: 1 + 2) と解釈される hoge key: 1 + 2 # => {:key=>3}
- Ruby 3.0 でも以下のように動作するのでこれは仕様とのこと
foo key: bar
- 今回の場合は
()
を記述する事で回避する事ができる
def hoge(**kwd) pp kwd end key = 42 hoge(key:) # => {:key=>42} 1 + 2
[Feature #18395] Introduce Array#subtract! for performance
- レシーバから特定の要素を取り除く
Array#subtract!
を追加する提案
ary = [0, 1, 1, 2, 1, 1, 3, 1, 1] ary.subtract!([1]) #=> [0, 2, 3] ary #=> [0, 2, 3] ary = [0, 1, 2, 3] ary.subtract!([3, 0], [1, 3]) #=> [2] ary #=> [2]
- これは
Array#-
と似たようなメソッドになる
ary = [0, 1, 1, 2, 1, 1, 3, 1, 1] ary - [1] #=> [0, 2, 3] # これが ary.subtract! と同じ挙動になる ary -= [1] #=> [0, 2, 3] ary #=> [0, 2, 3]
ary -= othetr
を高速化するためのメソッドとしてary.subtract!(other)
を追加したらしい#subtract!
の方が 1.22倍早いらしい
$ cat test.rb require 'benchmark' b_array = [2, 2, 2, 2, 2, 5, 5, 5, 5, 5, 99] n = 5_000_000 Benchmark.bm do |x| x.report("Array#subtract!") { n.times do; a = [1, 2, 3]; a.subtract!(b_array); end } x.report("Array#-= ") { n.times do; a = [1, 2, 3]; a -= b_array; end } end $ make runruby generating vm_call_iseq_optimized.inc vm_call_iseq_optimized.inc unchanged RUBY_ON_BUG='gdb -x ./.gdbinit -p' ./miniruby -I./lib -I. -I.ext/common ./tool/runruby.rb --extout=.ext -- --disable-gems ./test.rb user system total real able-gems ./test.rb user system total real Array#subtract! 1.168282 0.002174 1.170456 ( 1.172227) Array#-= 1.428345 0.004802 1.433147 ( 1.439785)
-=
と比較して新しい変数を生成しない分高速なんですかね?- ちなみに
Array#concat
は+=
よりも遅いらしい…Array#concat
は複数の引数を受け取るのが原因かも…?とのこと- https://github.com/ruby/ruby/pull/5110#issuecomment-967276896
$ cat test.rb require 'benchmark' b_array = [2, 2, 2, 2, 2, 5, 5, 5, 5, 5, 99] n = 5_000_000 Benchmark.bm do |x| x.report("Array#concat") { n.times do; a = [1, 2, 3]; a.concat(b_array); end } x.report("Array#+= ") { n.times do; a = [1, 2, 3]; a += b_array ; end } end $ make runruby compiling version.c generating vm_call_iseq_optimized.inc vm_call_iseq_optimized.inc unchanged linking miniruby /bin/sh ./tool/ifchange "--timestamp=.rbconfig.time" rbconfig.rb rbconfig.tmp rbconfig.rb unchanged creating verconf.h verconf.h updated compiling loadpath.c builtin_binary.inc updated compiling builtin.c linking static-library libruby.3.1-static.a linking ruby RUBY_ON_BUG='gdb -x ./.gdbinit -p' ./miniruby -I./lib -I. -I.ext/common ./tool/runruby.rb --extout=.ext -- --disable-gems ./test.rb user system total real Array#concat 0.785914 0.004957 0.790871 ( 0.794673) Array#+= 0.752269 0.007611 0.759880 ( 0.767160)
- 『このメソッドを使うと高速化する』っていうのを意識して書きたくないので
-=
の演算子自体が高速化しないですかねー
[PR debug #408] The console prevents Zeitwerk from autoloading certain constants
- debug.gem のコンソールで Zeitwerk の autoload が動作しないというバグ報告
- これは TracePoint がネストして呼び出されるような形になっているのが原因ぽい
- debug.gem 上で
TracePoint
を有効にしている時に Zeitwerk のTracePoint
が発動しない的な - byebug でも同様の問題があるっぽい
- 参照:Rails6でbyebugを利用してデバッグするときに気をつけたいこと - SmartHR Tech Blog
- debug.gem 上で
- これに対応する手段として『
TracePoint
をネストしても呼び出せるようにする』方向で議論が進んでるっぽい?
[Feature #15912] Allow some reentrancy during TracePoint events
- 上で書いた
TracePoint
のネストを許容するチケット - 元々は
byebug x Zeitwerk
での問題が起因だったが debug.gem でも同様の問題が発生しているのでこちらで議論されている - Ruby 3.1 までに結論が出てくれると嬉しいですが時期的にどうだろうなあ…
[Bug #18385] Refinement#import_methods(Enumerable) doesn't work
- 次のように
String
クラスに対してimport_methods Enumerable
するとエラーになるというバグ報告
module M refine String do # error: `import_methods': Can't import method: Enumerable#drop (ArgumentError) import_methods Enumerable end end
- これは
import_methods
が C拡張で定義されたモジュールに対応していない事が原因らしい - このチケットではこのバグを明確にするためにエラーメッセージを修正して対応している
module M refine String do # error `import_methods': Can't import method which is not defined with Ruby code: Enumerable#drop (ArgumentError) import_methods Enumerable end end
- 根本的な対応はどうするんですかね、これ
[Feature #18367] Stop the interpreter from escaping error messages
- エラーメッセージをエスケープしないようにする提案
class MyError < StandardError def message "foo\\bar" end end raise MyError #=> current: test.rb:7: in `<main>': foo\\bar (MyError) #=> excepted: test.rb:7: in `<main>': foo\bar (MyError)
# エラーメッセージを赤色で表示したいができない $ ruby -e 'raise "\e[31mRed\x1b[0m error"' -e:1:in `<main>': \e[31mRed\x1b[0m error (RuntimeError)
- 他には
"\\"
に対するエラーメッセージが紛らわしかったりerror_highlight
の位置もずれてしまう問題があるみたい
$ ruby -e '"\\".no_method' -e:1:in `<main>': undefined method `no_method' for "\\\\":String (NoMethodError) "\\\\".no_method ^^^^^^^^^^
- これはよさそうな変更ですね
- 移行パス的には Ruby 3.1 に入るというよりかはまず警告を出すようにする感じなんですかね?
- 少なくとも Ruby 3.1 には入らないっぽい
- https://bugs.ruby-lang.org/issues/18367#note-10
[Feature #18279] ENV.merge! support multiple arguments as Hash.merge!
Hash#merge!
と同様にENV.merge!
に複数のHash
を渡せるようにするチケット- 以下のようなケースで使いたいらしい
require 'yaml' env_files = ['config.yml', 'config.local'] envs = env_files.filter_map {|file| YAML.load_file(file)['env'] if File.file?(file) } ENV.merge!(*envs) # Raise wrong number of arguments (given 2, expected 1) # 現状はこういうコードを書いているらしい # ENV.merge!({}.merge!(*envs))
Hash
でできることはENV
でもしたいと思うのでこの対応はよさそうですね
[Misc #18354] Lazily create singletons on instance_{exec,eval}
instance_{exec, eval}
を呼び出した時に特異クラスが生成され、ブロック内でメソッドを定義するとその特異クラスにメソッドが追加される
obj = Object.new obj.instance_eval { # このメソッドは obj の特異クラスに定義される def hoge "hoge" end } p obj.hoge # => "hoge"
- このチケットはメソッド定義に必要な場合を除いて特異クラスの生成を遅延させる内容になっている
- 遅延することによって Rails のベンチマークが 1.09倍早くなり、YJIT だと 1.16倍早くなったらしい
- また今回の対応によって
TrueCLass/FalseClass/NilClass
が他のクラスと異なる挙動になっていたがこれも同じ挙動になるようになる
String::Foo = "foo" p "".instance_eval("Foo") # => "foo" Integer::Foo = "foo" p 123.instance_eval("Foo") # => "foo" TrueClass::Foo = "foo" p true.instance_eval("Foo") # Ruby 3.0 => NameError: uninitialized constant Foo # Ruby 3.1 => "foo"