2022/07/22 今回の気になった bugs.ruby のチケット
今週は Time.new
に 24時
を指定してタイムゾーンを渡した時に意図しない結果が返ってくるバグチケットがありました。
[Bug #18929] ruby master looks slower than 3.1 on a micro benchmark of short-lived objects
$ 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
- https://github.com/ruby/ruby/commit/85479b34f76d5b426c2a8224d8ed6d8c2ad81ca2 のコミットがトリガーになっているらしいが、これを戻してもパフォーマンスは戻らなかったらしい
- 謎い…
- 修正PR: https://github.com/ruby/ruby/pull/6156
[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_var
とclass_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
- 最適化のために実際の引数を削除する可能性があるのであとから
_1
を参照するのは難しい、という旨のコメントなどがされている
[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"]
- これってそもそも
24
を指定できるのがバグの温床なのではなかろうか…- るりまだと
0 ~ 23
を指定できると書いてはあたt - https://docs.ruby-lang.org/ja/latest/method/Time/s/new.html
- RDoc でも同様
- るりまだと
[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
2022/07/14 今回の気になった bugs.ruby のチケット
今週は :"@=".inspect
の結果が Ruby で評価できない文字列を返すというバグ報告がありました。
[Bug #18905] :"@=".inspect is non-evaluatable
:"@=".inspect
の結果が Ruby で評価できない文字列を返すというバグ報告
# :@= を返すが p :"@=".inspect # => ":@=" # :@= という Symbol をリテラルで定義できない # :@= のような Symbol を定義する場合は "" でくくる必要がある # error: `@' without identifiers is not allowed as an instance variable name :@=
- これは以下の条件の時に再現する
@
@@
$
[
から始まり- prefix の後に識別子が続かず
=
で終了する
# こういう Symbol は "" がつく p :"42hoge" # => :"42hoge" p :"+aa" # => :"+aa" p :"@+" # => :"@+" # 以下は "" がつかない p :"@=" # => :@= p :"@hoge=" # => :@hoge= p :"[][]=" # => :[][]= p :"$$$$=" # => :$$$$=
- 前提として
#inspect
は評価できる Ruby のコードを返すわけではない
# 必ずしもこういう式が成り立つわけではない eval(obj.inspect)
- これは
ruby-2.2.0-preview2
のタイミングで挙動が変わったらしい
$ docker run --rm -e "ALL_RUBY_SINCE=ruby-2.0" rubylang/all-ruby ./all-ruby -e "puts '4_to_5='.to_sym.inspect" ruby-2.0.0-p0 :"4_to_5=" ... ruby-2.2.0-preview1 :"4_to_5=" ruby-2.2.0-preview2 :4_to_5= ... ruby-3.2.0-preview1 :4_to_5=
[Feature #18913] Add object name to the NoMethodError error message: undefined method _method_' for
class' in `object_name'
- エラーメッセージにオブジェクト名を追加したいという提案
- 例えば以下のようなエラーメッセージに対して
bar = nil bar.i_wish_i_saw_the_name_bar # (irb):00:in `<main>': undefined method `i_wish_i_saw_the_name_bar' for nil:NilClass (NoMethodError)
- 以下のように
bar
というレシーバの名前を出したいという提案
bar = nil bar.i_wish_i_saw_the_name_bar #(irb):00:in `<main>': undefined method `i_wish_i_saw_the_name_bar' for nil:NilClass in `bar' (NoMethodError) # ^^^ It should mention the object name
- 例えば以下のようなケースで役に立つ
- どの
foo
呼び出しでエラーになっているのかがわからない
- どの
a = OpenStruct.new b = nil c = nil who_failed = a.foo & b.foo & c.foo # (irb):00:in `<main>': undefined method `foo' for nil:NilClass (NoMethodError)
- Ruby 3.1 だと
error_highlight
があるのでこれでカバーできるかも?
$ ruby test.rb test.rb:5:in `<main>': undefined method `foo' for nil:NilClass (NoMethodError) who_failed = a.foo & b.foo & c.foo ^^^^ $ ruby -v ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux]
2022/07/07 今回の気になった bugs.ruby のチケット
今週は Ractor.make_shareable(obj)
時に obj.make_shareable
を呼び出せるようにする提案がありました。
[Bug #18896] Shellwords.escape(nil) returns "empty" string
require 'shellwords' pattern = 'Jan 15' puts "grep #{Shellwords.escape(pattern)} file" # => grep Jan\ 15 file
- このモジュールに対して
Shellwords.escape(nil)
を渡すと空の文字列が返ってくるがこれが意図していないのではないかというバグ報告- 例外を出すのが意図しているとのこと
require "shellwords" puts Shellwords.escape(nil) # => "''"
- このライブラリしらなかった
[Feature #18894] Object#make_shareable
Marshal.dump(obj)
がobj.marshal_dump
を呼び出すようにRactor.make_shareable(obj)
を呼び出した時にobj.make_shareable
が呼び出されるようにする提案obj
ごとに固有のmake_shareable
を実装する必要がある時は便利そう?- 例えば『
Resolv::Hosts
の場合は遅延して初期化を行っているので Ractor では利用できない』とコメントされていますね- なので
make_shareable
ないで事前にロードする仕組みが必要
- なので
- 例えば『
- ちなみに
Ractor.make_shareable
は任意のオブジェクトをRactor
間でやりとりできるようにするための仕組み- 基本的に
Ractor
でオブジェクトを扱う場合は不変である必要がある - 参照: 【Ruby 3.0 Advent Calendar 2020】Ractor の共有可能オブジェクトについて【24日目】 - Secret Garden(Instrumental)
- 基本的に
obj = [1, 2, "hoge"] # 共有可能オブジェクトではない p Ractor.shareable? obj # => false # Ractor.make_shareable(obj) を呼び出すと obj を Ractor で利用できるようにする # これは結果的に全てのオブジェクトが freeze されることになる Ractor.make_shareable(obj) p Ractor.shareable? obj # => true p obj.frozen? # => true p obj[2].frozen? # => true
2022/06/30 今回の気になった bugs.ruby のチケット
今週は Module#autoload
で意図しない定数が定義された時に対応するチケットがありました。
[Feature #18815] instance_{eval,exec} vs Proc#>>
- 次のように
Proc#>>
した結果はinstance_eval / instance_exec
に適さない
measure = proc { p "self=#{self}"; size } multiply = proc { '*' * _1 } # measure のブロック内のレシーバは 'test' になる 'test'.instance_eval(&measure) # "self=test" # => 4 # しかし Proc#>> した場合はその限りではない 'test'.instance_eval(&measure >> multiply) # "self=main" # NameError (undefined local variable or method `size' for main:Object)
- これを動くようにサポートしませんか?という提案
- コメントでは次のようなユースケースが提示されている
FLATTEN = -> { [*_1] } IDENTITY = -> { _1 } DATE = -> { Date.parse(_1) } # パラメータのリストをどのように処理するのかの定義を持つ定数を定義したい TRANSFORMATIONS = { param1: FLATTEN, param2: FLATTEN, param3: IDENTITY, # この時に他の処理を呼び出しつつ、コンテキストに依存するような処理を定義したい # param4: FLATTEN >> -> { allowed?(:something) ? _1 : DEFAULT } # 現状だと以下のように定義する必要がある param4: -> { allowed?(:something) ? FLATTEN.(_1) : DEFAULT } }
[Bug #18813] Let Module#autoload be strict about the autoloaded constant
Module#autoload
は定数が最初に参照された時に自動でrequire
する機能になる
# /tmp/x.rb module M class X end end
# sample.rb module M # M::X を参照した時に自動的に require '/tmp/x' される autoload :X, '/tmp/x' end # このタイミングで require '/tmp/x' される M::X
- また、まだ読み込まれていない場合に
Module.constants
やModule.const_defined?
を使用すると次のような結果が返ってくる
module M autoload :X, '/tmp/x' end # まだ M::X は定義されていないが定義されているかのように振る舞う p M.constants(false) # => [:X] p M.const_defined?(:X, false) # => true
- この時に
/tmp/x
でM::X
が定義されていない場合に意図しない動作になる
# /tmp/x.rb # M の配下ではなくてトップレベルに X が定義される class X end
# sample.rb module M autoload :X, '/tmp/x' end p M.constants(false) # => [:X] p M.const_defined?(:X, false) # => true module M # このタイミングで require '/tmp/x' される # しかし、実際には M::X は定義されない X end # それにより結果が変わる p M.constants(false) # => [] p M.const_defined?(:X, false) # => false
- このように
autoload
するときの構造と実際にrequire
された結果が違う場合はエラーにしたいというのがこのチケットの趣旨になる - 今回の対応では Ruby 3.2 では警告を出す対応になっている
-W
を付けた時のみ警告がでる- 将来的にエラーにするかどうかは互換性の問題があるのでまた決まってないかも?
- https://bugs.ruby-lang.org/issues/18813#note-5
# /tmp/x.rb # M の配下ではなくてトップレベルに X が定義される class X end
# sample.rb # -W を付けた時のみ警告が出る pp $VERBOSE # => true module M autoload :X, '/tmp/x' end module M X # => /tmp/voLY8oW/52:10: warning: Expected /tmp/x to define M::X but it didn't end
2022/06/23 今回の気になった bugs.ruby のチケット
今週はトップレベルで include
したあとにクラス定義すると予期しないクラスを再オープンするというバグ報告がありました。
[Feature #18832] Do not have class/module keywords consider ancestors of Object
- 次のように
include M
した後にC < String
を定義すると意図しないエラーになるバグ報告M::C
とトップレベルのC
は別のクラスとして扱われることを期待する
module M class C end end include M # C は定義されていない p Object.const_defined?(:C, false) # => false # M::C を再オープンしようとしていてエラーになっている # error: superclass mismatch for class C (TypeError) class C < String # (1) end
- これは
include M
するとC
を参照する時にM::C
を参照するようになっているからぽい
module M class C end end include M # これは明示的に親スコープがない C を参照するので false p Object.const_defined?(:C, false) # これは C を探索しようとして M::C を見つけるのでそれを参照する p C # => M::C # この C も M::C を参照する class C < String # (1) end
- ちなみに以下のようにトップレベル以外だとエラーにはならない
module M class C end end module N include M # これは M::C を参照する pp C # = > M::C # これは N::C を参照する class C < String pp self # => N::C end # これは N::C を参照する pp C # = > N::C end
- 具体的には以下のようなケースで困っているらしい
require "active_record" require "rexml" include REXML class Comment < ActiveRecord::Base # superclass mismatch for class Comment (TypeError) end
- この問題をどう扱うのかは開発者会議で議論されて決定される予定
2022/06/17 今回の気になった bugs.ruby のチケット
[Bug #18826] Symbol#to_proc inconsistent, sometimes calls private methods
#tap
に&
渡しでメソッドを呼び出す場合にprivate
やprotected
メソッドを呼び出す事ができるバグ報告
class Test protected def referenced_columns puts "hello" end end # protected メソッドを呼び出す事ができる Test.new.tap(&:referenced_columns) # => hello # Symbol#to_proc 経由でも呼び出せる :referenced_columns.to_proc.call Test.new # => hello
:foo.to_proc
はlambda{|t| t.foo}
と等価であるべきという理由から修正される流れになっている
[Bug #18827] __ENCODING__
is not set to the source encoding when saving script lines
p __ENCODING__ # => #<Encoding:UTF-8>
# encoding: euc-jp p __ENCODING__ # => #<Encoding:EUC-JP>
SCRIPT_LINES__
が設定されていた時にこれが無視されるというバグ報告
# -Ke を渡すと #<Encoding:EUC-JP> になる $ ruby -Ke -e 'p __ENCODING__' #<Encoding:EUC-JP> $ cat script_lines.rb SCRIPT_LINES__ = {} # -Ke を渡すと #<Encoding:EUC-JP> になるがそうでない $ ruby -r./script_lines.rb -Ke -e 'p __ENCODING__' #<Encoding:UTF-8>
2022/06/11 今回の気になった bugs.ruby のチケット
今週は標準ライブラリをパターンマッチに対応させる提案がありました。
[Feature #18821] Expose Pattern Matching interfaces in core classes
- Ruby のいくつかの標準ライブラリをパターンマッチに対応させるチケット
- 提案されているのは以下の通り
Set
Array
のようなパターンマッチを行う
# Hypothetical implementation class Set alias_method :deconstruct, :to_a end Set[1, 2, 3] in [1, 2, *] # => true
Matrix
Array
のようなパターンマッチを行う
class Matrix alias_method :deconstruct, :to_a end # => :deconstruct Matrix[[25, 93], [-1, 66]] in [[20..30, _], [..0, _]] # => true
CSV
- ヘッダーカラムをキーにして
Hash
のようなパターンマッチを行う
require "csv" require "net/http" require "json" # Hypothetical implementation class CSV::Row def deconstruct_keys(keys) # Symbol/String is contentious, yes, I will address in a moment self.to_h.transform_keys(&:to_sym) end end # Creating some sample data for example: json_data = URI("https://jsonplaceholder.typicode.com/todos") .then { Net::HTTP.get(_1) } .then { JSON.parse(_1, symbolize_names: true) } headers = json_data.first.keys rows = json_data.map(&:values) # Yes yes, hacky csv_data = CSV.generate do |csv| csv << headers rows.each { csv << _1 } end.then { CSV.parse(_1, headers: true) } # But can provide very interesting results: csv_data.select { _1 in userId: "1", completed: "true" }.size # => 11
Regexp
の MatchData
- 名前付きキャプチャに対して
Hash
のようなパターンマッチを行う
class MatchData alias_method :deconstruct, :to_a def deconstruct_keys(keys) named_captures.transform_keys(&:to_sym).slice(*keys) end end IP_REGEX = / (?<first_octet>\d{1,3})\. (?<second_octet>\d{1,3})\. (?<third_octet>\d{1,3})\. (?<fourth_octet>\d{1,3}) /x '192.168.1.1'.match(IP_REGEX) in { first_octet: '198', fourth_octet: '1' } # => true
[Feature #18183] make SecureRandom.choose public
SecureRandom.choose
というprivate
メソッドをpublic
メソッドにするチケット
require "securerandom" # 第一引数の配列の中からランダムで10文字を返す pp SecureRandom.send(:choose, ['a', 'b', 'c'], 10) # => "cabbbaaaab" pp SecureRandom.send(:choose, [*'A'..'Z', *'0'..'9'], 10) # => "7JZMZUK4GD"
- 背景として過去に
SecureRandom.alphanumeric
が追加された時にその実装としてSecureRandom.choose
も追加されたが適切な名前でなかったのでprivate
になっていたらしい - このチケットでは
.random_string
という名前で公開するのはどうか、という旨のチケットになる .random_string
も動作を説明してる名前ではないという事で Reject されている- どういう名前だとわかりやすいんですかね?
.alphanumeric
にキーワード引数を追加する案もコメントに書かれている
def alphanumeric(n=nil, alphabet: ALPHANUMERIC) n = 16 if n.nil? choose(alphabet, n) end
[Feature #11689] Add methods allow us to get visibility from Method and UnboundMethod object.
- Ruby 3.1 で
{Method,UnboundMethod}#{public?,private?,protected?}
が新しく追加された
class X private def private_method end public def public_method end end # 該当する可視性であれば true を返す pp X.instance_method(:private_method).private? # => true pp X.instance_method(:private_method).public? # => false pp X.instance_method(:public_method).private? # => false pp X.instance_method(:public_method).public? # => true
- 最近この実装に対して matz がコメントしており Ruby 3.2 では Revert される流れになっている
- https://bugs.ruby-lang.org/issues/11689#note-24
- このチケットでは可視性はメソッドの属性という前提だったが、実際には各クラスで可視性ごとのメソッドのリストが必要とのこと
- 参照しているメソッドは同じでもクラスによって可視性が変わる、ってことなんですかねー
[Bug #18790] cannot load such file -- digest (LoadError)
- 特定の環境で CRuby をビルドすると
require': cannot load such file -- digest (LoadError)
になってビルドに失敗するので対処するチケット- 手元の環境(
Ubuntu 20.04
)で再現したsudo apt install libyaml-dev
すれば対処できる- https://secret-garden.hatenablog.com/entry/2022/04/04/211158
- Ruby 3.2 から
libyaml
が同梱されなくなったのが原因ぽい
- 手元の環境(
- 地味に困っているので何かしら本体で対応されるとうれしいなあ