2022/03/17 今回の気になった bugs.ruby のチケット
今週はブロックの引数に関するバグチケなどチケットがありました。
[Bug #18635] Enumerable#inject without block/symbol will return values or raise LocalJumpError
- ブロック引数のない
Enumerable#inject
を呼び出すと『値が返ってくるか例外が発生するか』で一貫性がないというバグ報告
pp [].inject # => nil pp [1].inject # => 1 pp [1, 2].inject # error: `each': no block given (LocalJumpError)
- 前者2つはイテレーションされないので『要素の最初の値』をそのまま返している感じですかね?
- チケットを立てた人は『常に
LocalJumpError
(もしくはArgumentError
)を発生させたほうがいい』とコメントしている
[Bug #18632] Struct.new wrongly treats a positional Hash as keyword arguments
- 以下のように
Hash
を位置引数として渡しているがキーワード引数としてエラーになっているというバグ報告
# これはキーワード引数なので意図するエラーになる # error: `new': unknown keyword: :name (ArgumentError) Struct.new(:a, name: "b")
# これは Hash を位置引数で渡している # しかし、キーワード引数を渡した時と同じエラーになる # error: `new': unknown keyword: :name (ArgumentError) Struct.new(:a, { name: "b" })
- また位置引数に
Hash
を渡すとエラーになるが、以下のように空のhash
を渡すとエラーにならない
# OK p Struct.new(:a, {}).members # => [:a]
- ややこしい…
[Bug #18633] proc { |a, **kw| a } autosplats and treats empty kwargs specially
- ブロックの引数は引数が2つ以上の場合は位置引数を分割引数として値を受け取る
# これは a = [1, 2] で受け取る p proc { |a| [a] }.call [1, 2] # => [[1, 2]] # これは a = 1, b = 2 と配列を分割して受け取る p proc { |a, b| [a, b] }.call [1, 2] # => [1, 2]
- これが『位置引数が1つでキーワード引数がある場合も同じ挙動になっている』というバグ報告
# キーワード引数がある場合も a = 1 として配列を分割して受け取る p proc { |a, **kwd| [a, kwd] }.call [1, 2] # => [1, {}]
- ちなみに実引数にキーワード引数がある場合は『分割引数として受け取らない』
p proc { |a, **kwd| [a, kwd] }.call [1, 2], c: 3 # => [[1, 2], {:c=>3}] # 空のキーワード引数でも同様 p proc { |a, **kwd| [a, kwd] }.call [1, 2], **{} # => [[1, 2], {}]
- Ruby むずかしいな?
- 修正PR は既にできてる
[Bug #18629] block args array splatting assigns to higher scope _ var
- 以下のように仮引数が
_
でない場合はスコープの外の変数は書き換わらないが_
の場合は外のスコープの変数が書き換わってしまうというバグ報告
# これは書き換わらない v = 1; [[2]].each{ |(v)| }; p v # => 1 # これは書き換わってしまう _ = 1; [[2]].each{ |(_)| }; p _ # => 2
- 『これは
_
付き変数(_
自体も含む)が特殊な変数になっているから』とコメントされている
# 同じ名前の仮引数があるとエラーになる def a(b, b) end # SyntaxError # 同じ名前でも _ が付いている場合はエラーにならない def a(_b, _b) end # no error
[Bug #18624] const_source_location
returns [false, 0] when autoload is defined for the constant
- 以下のように
autoload
を設定している定数に対してconst_source_location
を呼び出した時に[false, 0]
が返ってくるのは期待する挙動ではないというバグ報告['/path/to/test2.rb', 2]
が返ってくるのが期待する挙動
# test.rb path = File.join(__dir__, 'test2') Object.autoload 'Test2', path require path p Object.const_source_location 'Test2'
# test2.rb class Test2 end
$ ruby -v test.rb ruby 3.2.0dev (2022-03-11T08:38:13Z master 2e4516be26) [x86_64-linux] [false, 0]
- これは Zeitwerk を使った場合に同様の問題があるらしい
# test.rb require "zeitwerk" loader = Zeitwerk::Loader.for_gem loader.setup require File.join(__dir__, 'test2') p Zeitwerk::VERSION p Object.const_source_location 'Test2'
# test2.rb class Test2 end
- これに対する修正PR は既にでてる
autoload
ってconst_source_location
でも発火するのが期待する挙動になるのか
[Bug #18620] Not possible to partially curry lambda or proc's call
method
Method
オブジェクトを#curry
化すると次のように#[]
を別々に呼び出して評価できる
class Foo def foo(a, b) a + b end end Foo.new.method(:foo).curry[1][2] # => 3
- これは
proc
やlambda
でも同じ挙動になる
lambda { |a, b| a + b }.curry[1][2] # => 3
proc { |a, b| a + b }.curry[1][2] # => 3
- しかし
Proc#call
をMethod
オブジェクト化した場合は期待する挙動にならないというバグ報告
# error: `block in <top (required)>': wrong number of arguments (given 1, expected 2) (ArgumentError) lambda { |a, b| a + b }.method(:call).curry[1][2]
# error: `+': nil can't be coerced into Integer (TypeError) proc { |a, b| a + b }.method(:call).curry[1][2]
- これがバグかどうかは次の開発者会議で議論されるとの事
- ちなみに
#[]
に複数の引数を渡すと正しく動作する
lambda { |a, b| a + b }.method(:call).curry[1, 2] # => 3
proc { |a, b| a + b }.method(:call).curry[1, 2] # => 3
[Bug #18561] Make singleton def operation and define_singleton_method explicitly use public visibility
- 次のようにクラススコープで
private
を呼び出した後に『クラスメソッド』を定義してもprivate
にはならない
class X private X.define_singleton_method(:hoge) do "hoge" end end # クラスメソッドは private ではないので呼び出せる pp X.hoge # => "hoge"
- これは
private
になるのはあくまでも『クラスのインスタンスメソッド』になるから - このチケットでは『この挙動が仕様であることを明示的にドキュメントに書こう』という旨になる
- ただし、以下のように『特定のケースでクラスメソッドが
private
になる』というバグ報告もされている
class X class << X private # 特異クラスのスコープ内で define_singleton_method を定義すると private になる X.define_singleton_method(:hoge) do "hoge" end # こっちは private にならない def X.foo "foo" end end end # OK pp X.foo # => "foo" # NG # error: `<main>': private method `hoge' called for X:Class (NoMethodError) pp X.hoge
- 後者の方は修正PR が投げられている
[Bug #18622] const_get still looks in Object, while lexical constant lookup no longer does
- 次のように
::
で定数参照した場合とconst_get
で定数参照した場合で差異があるというバグ報告
module ConstantSpecsTwo Foo = :cs_two_foo end module ConstantSpecs end # これは定数参照できる p ConstantSpecs.const_get("ConstantSpecsTwo::Foo") # => :cs_two_foo # これはエラーになる # error: const_get.rb:9:in `<main>': uninitialized constant ConstantSpecs::ConstantSpecsTwo (NameError) p ConstantSpecs::ConstantSpecsTwo::Foo
- これは Ruby 2.5 で定数参照が変わった時に関連しているぽい
class C end # トップレベルで定義された定数は暗黙的に Object の配下で定義される class C2 end # なので Ruby 2.4 以前では C:: で参照する事ができていたが Ruby 2.5 からはできなくなった p C::C2 # Ruby 2.4 => C2 # Ruby 2.5 => error: `<main>': uninitialized constant C::C2 (NameError)
[Feature #18617] Allow multiples keys in Hash#[] acting like Hash#dig
Hash#dig
のようにHash#[]
に複数の引数を渡せるようにする提案
hash[:a][:b][:c][:d][:e][:f][:u]
- を
hash[:a, :b, :c, :d, :e, :lov, :u]
- と記述できるようにする提案
Hash#[]
で対応するのであればArray#[]
とかでも対応する必要がありそう