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

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

[PR irb #212] Complete require and require_relative

  • irbrequire のファイル名補完をする機能の追加
irb(main):001:0" require "irb/<Tab>  # <- Tab を押すとファイル名を補完してくれる
  • これは普通に便利そう

[PR irb #204] Add whereami command

  • irbprywhereami コマンドを追加する PR
    • whereami すると binding.irb した付近のコードを確認することができる
$ ruby /tmp/a.rb

From: /tmp/a.rb @ line 3 :

    1: a = 1
    2: @b = 2
 => 3: binding.irb

irb(main)[01:0]> a
 => 1
irb(main)[02:0]> @b
 => 2
irb(main)[03:0]> whereami

From: /tmp/a.rb @ line 3 :

    1: a = 1
    2: @b = 2
 => 3: binding.irb

 => nil
  • pry の whereami コマンド知らなかった
  • これはめっちゃ便利そう…

[PR irb #203] Implement pry-like ls command

  • irbpryls コマンドを追加する PR
    • ls すると引数オブジェクトのメソッド一覧やインスタンス一覧を出力してくれる
$ irb
irb(main):001:0> require "erb"
 => true
irb(main):002:0> ls ERB.new('test')
ERB#methods: 
  def_class  def_method     def_module  encoding          filename  filename=    lineno  lineno=
  location=  make_compiler  result      result_with_hash  run       set_eoutvar  src   
instance variables: @_init  @encoding  @filename  @frozen_string  @lineno  @src
 => nil
  • これも知らなかった
  • めっちゃ便利そう…

[Feature #17411] Allow expressions in pattern matching

  • 以下のように ^ を使ってパターンマッチに式をかけるようにする提案
user = { name: "homu", age: 14 }
case user
# 式を書く場合は ^() を使う
in { age: ^(7 + 7) }
end

[Bug #17738] Ruby can still freeze ENV

  • ENV は普通は freeze できないががんばれば freeze できるという報告
# これは error
ENV.freeze

# こうすると freeze できる
Kernel.instance_method(:freeze).bind(ENV).call()
p ENV.frozen? #=> true
  • Ruby むずかしい…

[Bug #17735] Hash#transform_keys! drops non evaluated keys

  • 以下のように Hash#transform_valies! のブロック内で raise するとレシーバは変わらない
# transform_values! はレシーバの要素を保持する
hash = {a: 1, b: 2, c: 3}
a = hash.transform_values! { raise } rescue
p hash #=> {:a=>1, :b=>2, :c=>3}
  • しかし Hash#transform_keys! の場合はレシーバが空になる
# transform_keys! はレシーバの要素が空になる
hash = {a: 1, b: 2, c: 3}
hash.transform_keys!(){ raise } rescue
p hash #=> {}

[Bug #17736] Destructive methods inconsistently handle receiver frozen state in given block

  • Array#select! のブロック内でレシーバを freeze するとレシーバが空の配列になる
array = [1, 2, 3, 42]
array.select! do
  array.freeze
  false
end
p array #=> []
  • Array#uniq! の場合は例外になりレシーバはそのままになる
array = [1, 2, 3, 42, 2, 3]
begin
  array.uniq! do |item|
    array.freeze
    item
  end
rescue => err
  p err #=> #<FrozenError: can't modify frozen Array: [1, 2, 3, 42, 2, 3]>
end

p array #=> [1, 2, 3, 42, 2, 3]
  • Array#select! の方はバグということで修正された
array = [1, 2, 3, 42]
# Ruby 3.1.0-dev だとエラー
# error: `select!': can't modify frozen Array: [1, 2, 3, 42] (FrozenError)
array.select! do
  array.freeze
  false
end

[Bug #17739] Array#sort! changes the order even if the receiver raises FrozenError in given block

  • Array#sort! のブロック内でレシーバを freeze すると例外が発生するがソート済みになっているというバグ報告
array = [1, 2, 3, 4, 5]
begin
  array.sort! do |a, b|
    array.freeze if a == 3
    1
  end
rescue => err
  # 例外が発生する
  p err #=> #<FrozenError: can't modify frozen Array: [5, 4, 3, 2, 1]>
end

# 例外が発生してもソート済みになっている
p array #=> [5, 4, 3, 2, 1]
  • ちなみに break した場合はそこまでのソートになっている
array = [1, 2, 3, 4, 5]
array.sort! do |a, b|
  break if a == 3
  1
end

# 途中までソートされた状態
p array #=> [3, 4, 2, 1, 5]
  • これはまだ未修正

[Feature #17743] Show argument types in backtrace

  • バックトレースに引数情報も追加する提案
  • 例えば以下のようなコードを実行すると
def say_hi(person)
  puts message(person)
end

def message(person)
  "hi: #{person.name}"
end

say_hi(nil)
  • 以下のようなバックトレースが出力される
/tmp/vTaZxJg/70:6:in `message': undefined method `name' for nil:NilClass (NoMethodError)
    from hi.rb:2:in `say_hi'
    from hi.rb:9:in `<main>'
  • これを以下のようにする提案
hi.rb:6:in `message': undefined method `name' for nil:NilClass (NoMethodError)
    from hi.rb:2:in `say_hi' called with NilClass
    from hi.rb:9:in `<main>' called with NilClass
  • どのような表記にするのか(クラスだけ?値は必要がない?複数の引数の場合は?)やパフォーマンス的な懸念点がないか議論されている

[Feature #17744] Accumulate Enumerable#tally results

  • 以下のように Enumerable#tally に引数を渡してそれに結果を集計していくような提案
h = {}
[:a,:b,:c].tally(h)
[:a,:b,:d].tally(h)

# 2個の tally の結果が h に蓄積される
p h #=> {:a=>2, :b=>2, :c=>1, :d=>1}
  • 便利な気がするけど引数が変更されてしまうのはなんかちょっと気持ち悪い…