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

今週は load メソッドにモジュールを渡せるようになりました。

[Bug #17675] StringIO#each_byte doesn't check for readabilty while iterating

  • IO がクローズしているのにイテレーションが処理されてしまうというバグ報告
require "stringio"

strio = StringIO.new("1234")
strio.each_byte do |byte|
  puts byte
  # ここでクローズしているがイテレーションは引き続き処理されている
  strio.close
end
# => 49
#    50
#    51
#    52
  • このバグは修正済みで最新版では IOError が発生する
    • おそらくマイナーバージョンでバックポートされる
require "stringio"

strio = StringIO.new("1234")
strio.each_byte do |byte|
  puts byte
  strio.close
end
# => 49
#    error: `each_byte': not opened for reading (IOError)

[Misc #18352] What is the Hash#grep expected?

  • Hash#grep はなにを期待しているのか?という質問
  • Array#grep の場合は以下のように利用できる
[:foo1, :foo2, :bar].grep /foo/
# => [:foo1, :foo2]
  • Hash#grep は以下のようになりなにを期待しているのかがよくわからない
{foo: '100', bar: '200'}.grep /foo/
# => []
class HashMatcher < Struct.new(:key, :value, keyword_init: true)
  def ===((k, v))
    (key.nil? || key === k) && (value.nil? || value === v)
  end
end

{foo1: '100', foo2: '200', bar: '200'}.grep(HashMatcher[key: /foo/])
# => [[:foo1, "100"], [:foo2, "200"]]

[Feature #18262] Enumerator::Lazy#partition

  • 以下のような場合はファイル全部が読み込まれてしまう
file = File.open('very-large-file.txt')
lines_with_errors, lines_without_errors = file.lazy.partition { _1.start_with?('E:') }
lines_with_errors.class
# => Array, all file is read by this moment
  • これを改善するために Enumerator::Lazy#partitionEnumerator::Lazy を返すようにする提案
  • 以下のように使えるのを想定している
# Enumerator.produce で 1, 2, 3,... という無限Range を生成する
Enumerator.produce(1) { |i| puts "processing #{i}"; i + 1 }.lazy
  .take(30)
  .partition(&:odd?)
  .then { |odd, even|
    # partition の戻り値から必要な数だけを取得する
    p odd.first(3), even.first(3)
  }
# Prints:
# processing 1
# processing 2
# processing 3
# processing 4
# processing 5
# [1, 3, 5]
# [2, 4, 6]
  • これは普通に便利そう

[Feature #12495] Make "private" return the arguments again, for chaining

  • private の戻り値をレシーバから引数を返すようにする提案
class X
  def hoge; end
  p private :hoge
  # 現在の挙動   => X
  # 変更する挙動 => :hoge
end
  • 以下のようなことをやりたい
def cached(name)
  # Rewrite method to include a cache
  return name
end

# これは OK
private cached def foo() end

# こうもかけるようにしたい
cached private def foo() end
  • これはマージされて Ruby 3.1 から挙動が変わる
class X
  def hoge; end
  p private :hoge
  # Ruby 3.0 => X
  # Ruby 3.1 => :hoge
end

[Feature #11256] anonymous block forwarding

  • 仮引数を定義してブロックを受け取ると遅いので仮引数を定義しないでブロックをフォワードしたいという提案
  • Ruby 3.1 から & だけ定義できるようになった
def foo(a)
  yield a
end

def bar(&)
  # ブロック引数を foo にフォワードする
  foo(1, &)
end

bar { p _1 }
  • また & を省略する事はできない
def foo(&) = bar(&) # OK
def foo = bar(&)    # NG
  • またブロックからは使えないぽい?
def hoge; end
# no anonymous block parameter
proc { |&| hoge(&) }

[Feature #6210] load should provide a way to specify the top-level module

  • load メソッドに Module を指定できるようにする提案
  • 以下のように特定のモジュールに対して load した Ruby のコードが展開される
# test.rb
def hoge
  "hoge"
end

class Foo
  def foo
    "foo"
  end
end
module M
end

# M に対して test.rb の中身が定義される
load "./test.rb", M

p M::Foo
# => M::Foo

p M::Foo.new.foo
# => "foo"

class X
  include M
  public :hoge
end

p X.new.hoge
# => "hoge"
  • これがマージされて Ruby 3.1 からこの機能が使えるようになった

[Feature #18331] Kernel.#Time

  • Kernel.#Integer などと同じような Kernel.#Time を追加する提案
    • Time.new の短縮形としてほしいみたい
Time("2021-11-13T21:21:18.027294 +0900") # => 2021-11-13 21:21:18.027294 +0900
Time("2021-11-13 21:21:18.027294 +0900") # => 2021-11-13 21:21:18.027294 +0900
Time("foo", exception: false) # => nil

2021/11/18 今回の気になった bugs.ruby のチケット

今週は Refinements 関連の便利メソッドのチケットが多いです。

[Bug #17429] Prohibit include/prepend in refinement modules

  • Refinements 時に include/prepend することを禁止するチケット
  • 背景として以下の様に Refinements 時に include/prepend すると意図しない挙動がある
class X; end

module M
  def foo
    hoge
  end

  def hoge
    "hoge"
  end

  refine X do
    include M
  end
end

using M

# error `foo': undefined local variable or method `hoge' for #<X:0x000056087d251648> (NameError)
# hoge は Refinements 内から呼ぶことができない
# なぜなら foo メソッド内では using してないから…
p X.new.foo
  • これらを解決する仕組みとして新しく Refinement#import_methods というメソッドが導入された
class X; end

module M
  def foo
    hoge
  end

  def hoge
    "hoge"
  end

  refine X do
    import_methods M
  end
end

using M

p X.new.foo
  • Refinement#import_methods を利用すると継承リストにモジュールが追加されるのではなくてモジュールのメソッドが直接 Refinements オブジェクトに定義される
class X; end

module M1
  def hoge; end

  refine X do
    # X の継承リストに追加される
    include M1
  end
end

module M2
  def foo; end

  refine X do
    # M2 のメソッドが Refinements オブジェクトに直接追加される
    import_methods M2
  end
end

using M1
using M2

p X.instance_method(:hoge).owner
# => M1

p X.instance_method(:foo).owner
# => #<refinement:X@M2>
  • また Refinement#include/prependRuby 3.2 で削除される予定なので -W を付けて実行すると警告がでる
module M1
  refine Object do
    # warning: Refinement#include is deprecated and will be removed in Ruby 3.2
    include M1

    # warning: Refinement#prepend is deprecated and will be removed in Ruby 3.2
    prepend M1
  end
end
  • これ自体はかなりよさそうなんですが互換性を保つようにするのがちょっと大変そう
  • Refinements がよくなってうれいし

[Feature #18270] Refinement#{extend_object,append_features,prepend_features} should be removed

[Feature #18334] ENV#to_h returns a new Hash object but Hash#to_h does not, which can cause inconsistencies

  • ENV.to_h は新しい Hash オブジェクトを返すが Hash#to_h はレシーバを返す
# ENV.to_h は異なる object_id になる
ENV.to_h.object_id
# => 14700
ENV.to_h.object_id
# => 14760

# Hash は同じ object_id になる
hash = {"FOO" => "bar"}
hash.to_h.object_id
# => 14820
hash.to_h.object_id
# => 14820

[Feature #14332] Module.used_refinements to list refinement modules

  • using されている Refinement オブジェクトを返す Module.used_refinements メソッドを追加する提案
module Json
  refine Integer do
    def to_json
      to_s
    end
  end

  refine String do
    def to_json
      inspect
    end
  end
end

module Fact
  refine Integer do
    def fact
      self <= 1 ? 1 : self * (self-1).fact
    end
  end
end

using Json
p Module.used_modules # => [Json]
# using されている Refinement オブジェクト一覧を返す
p Module.used_refinements # => [#<refinement:Integer@Json>, #<refinement:String@Json>]

using Fact
p Module.used_modules # => [Json, Fact]
p Module.used_refinements # => [#<refinement:Integer@Fact>, #<refinement:Integer@Json>, #<refinement:String@Json>]
  • あればいろいろとできそうだけど具体的にどういう時に使うのかはわからねえ

[Feature #12737] Module#defined_refinements

  • レシーバで定義されている refine されたオブジェクトとその Refinement オブジェクトの Hash を返す Module#defined_refinements メソッドを追加する提案
module M
  refine String do
    $M_String = self
  end

  refine Integer do
    $M_Integer = self
  end
end

p M.defined_refinements #=> {String => $M_String, Integer => $M_Integer}
  • これもあれば便利そう

[Bug #18343] empty hash passed to Array#pack causes Segmentation fault (2.6)

  • Ruby 2.4 ~ 2.6 で Array#pack に空の Hash を渡すと Segmentation fault が発生するというバグ報告
    • Ruby 2.7 以降では再現しない
# これで segv する
[0].pack('c', {})
  • Ruby 2.6 は現状セキリュティサポートのみなのでこのチケットは閉じられている

[Feature #18332] a ? b

  • a ? b : nila ? b とかけるようにする提案
    • && と似てるけどちょっと違う
    • a && b の場合は false が返ってくる
  • ユースケース
"#{current_path == "/" ? "font-bold"}"
"#{user.admin? ? "text-red-600"}"
  • root? ? "font-bold""font-bold" if root? どっちが読みやすい?みたいなコメントがされている

2021/11/11 今回の気になった bugs.ruby のチケット

今週は Time.at のパフォーマンス低下の報告などがありました。

[Bug #18293] Time.at in master branch was 25% slower then Ruby 3.0

[Bug #18292] 3.1.0-dev include cause Module to be marked as initialized

  • 開発版だと次のコードでエラーになってしまうというバグ報告
    • Module を継承して include した後に super を呼ぶとエラーになってしまうぽい?
    • [#17048] が原因かも?と書かれてはいる
class Mod1 < Module
  def initialize(...)
    super
  end
end
p Mod1.new
# => #<Mod1:0x000055b6dc5a5d00>

class Mod2 < Module
  def initialize(...)
    include Enumerable
    super
  end
end
p Mod2.new
# 3.0.2     => #<Mod2:0x000055b6dc5a59e0>
# 3.1.0-dev => error: `initialize': already initialized module (TypeError)
  • ちなみに以下のコードだけでもエラーになったので include 後に Module#initialize を呼ぶとダメなのかも
class Mod2 < Module
  def initialize(...)
    include Enumerable
    super
  end
end
p Mod2.new
# 3.0.2     => #<Mod2:0x000055b6dc5a59e0>
# 3.1.0-dev => error: `initialize': already initialized module (TypeError)

[Bug #18329] Calling super to non-existent method dumps core

  • 存在しない super を呼び出すとコアダンプするというバグ報告
    • 次のコードを Ruby 3.0 以降で実行すると segv する
module Probes
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def probe(*methods)
      prepend(probing_module(methods))
    end

    def probing_module(methods)
      Module.new do
        methods.each do |method|
          define_method(method) do |*args, **kwargs, &block|
            super(*args, **kwargs, &block)
          end
        end
      end
    end
  end
end

class Probed
  include Probes

  probe :danger!, :missing

  def danger!
    raise "BOOM"
  end
end

5.times do
  subject = Probed.new
  subject.danger! rescue RuntimeError
  subject.missing rescue NoMethodError
end
  • ちょっと分かりづらいので最小構成にすると以下のような感じ
class Probed
  def self.probing_module(methods)
    Module.new do
      methods.each do |method|
        define_method(method) do |*args, **kwargs, &block|
          super(*args, **kwargs, &block)
        end
      end
    end
  end

  prepend probing_module [:danger!, :missing]

  def danger!
  end
end

subject = Probed.new
subject.danger!

# ここで存在しない super を呼び出している
subject.missing rescue NoMethodError

# ここで segv
subject.danger!
  • なんもわからねえ

[Feature #16252] Hash#partition should return hashes

  • Hash#partition の戻り値は Array を返しているが Hash を返すようにする提案
{1=>2,3=>4}.partition{ |k,| k==1 }
# 期待する挙動 => [{1=>2}, {3=>4}]
# 実際の挙動   => [[[1, 2]], [[3, 4]]]
  • Hash#selectHash#rejectHash を返しているのでそれに合わせたいらしい
{ 1=>2,3=>4,5=>6 }.select{ |k, v| k == 1 }
# => {1=>2}
{ 1=>2,3=>4,5=>6 }.reject{ |k, v| k == 1 }
# => {3=>4, 5=>6}
  • #partitionEnumerable で実装されているけど #select#rejectHash で実装されているのでその差異ですかね?

2021/11/05 今回の気になった bugs.ruby のチケット

今週は Proc#bind_call(obj) を追加する提案などがありました。

[Bug #18282] Rails CI raises Segmentation fault with ruby 3.1.0dev supporting Class#descendants

  • 開発版 Ruby と最新版の Rails でクラッシュするというバグ報告
  • 最終的には以下のコードで再現できるようになったらしい
    • よく見つけるなあ…
class C
end

100000.times { Class.new(C) }

p C.descendants

[Bug #18283] Creating a subclass in Ractor dumps core

  • 上の [Bug #18282] 関連のバグ報告
  • 以下のコードでも Ruby がクラッシュする
class C
end
(1..10).map {
  Ractor.new { 100000.times { Class.new(C) } }
}.each {|r| r.take }

[Bug #18243] Ractor.make_shareable does not freeze the receiver of a Proc but allows accessing ivars of it

  • 次のように Ractor 内で別の Ractor のオブジェクトが書き換えられてしまうバグ報告
class C
  attr_accessor :foo
  def setter_proc
    Ractor.make_shareable(-> v { @foo = v })
  end
end

c = C.new
c.foo = 1
p c
# => #<C:0x0000559bf1df5880 @foo=1>

# インスタンス変数を書き換える proc を生成
# c 自体は freeze されてない
proc = c.setter_proc
p c.frozen?
# => false

# Ractor 内で setter_proc を呼び出すと c のオブジェクトが書き換えら得てしまう
# これがバグ
Ractor.new(proc) { |s| s.call(42) }.take
p c
# => #<C:0x0000559bf1df5880 @foo=42>

[Feature #18276] Proc#bind_call(obj) same as obj.instance_exec(..., &proc_obj)

  • obj.instance_exec(..., &proc_obj) と同じ意味の Proc#bind_call(obj) を追加する提案
    • proc_obj.bind_call(...)obj.instance_exec(..., &proc_obj) と同じ意味になる
  • [Bug #18243] への対応として Ractor の共有可能オブジェクトの Proc#call を禁止して obj.instance_exec(..., &proc_obj) で呼び出すようにすることも考慮しているとのこと
    • そのショートカットとして Proc#bind_call を使用する想定
  • Proc#bind_call を利用すると以下のように block オブジェクトも渡せるようようになるので個人的には普通にほしい
pr = proc { |*args, &block|
  # ...
  block.call(something)
}

# pr に対してブロック引数を渡すことができない
obj.instance_exec(arg, &pr)

# bind_call だと pr に対してブロック引数を渡すことができる
pr.bind_call(obj, arg) do |something|
  # ...
end
require "proc/unbind"

using Proc::Unbind

class X
  attr_accessor :value
end

x = X.new

set = proc { |x|
  @value = x
}
# Proc#rebind で特定のオブジェクトに bind する
set.rebind(x).call(42)

p x.value
# => 42

[Misc #18285] NoMethodError#message uses a lot of CPU/is really expensive to call

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'

  gem 'benchmark-ips'
end

puts RUBY_DESCRIPTION

class GemInformation
  def get_no_method_error
    method_does_not_exist
  rescue => e
    e
  end

  def get_runtime_error
    raise 'Another Error'
  rescue => e
    e
  end

  # NoMethodError#message でこのメソッドが呼び出される
  # Rails の Controller だと複雑な #inspect が実装されておりそれがボトルネックになっているらしい
  def inspect
    Gem::Specification._all.inspect
  end
end

NO_METHOD_ERROR_INSTANCE = GemInformation.new.get_no_method_error
RUNTIME_ERROR_INSTANCE = GemInformation.new.get_runtime_error

Benchmark.ips do |x|
  x.config(:time => 5, :warmup => 2)

  x.report("no method error message cost") { NO_METHOD_ERROR_INSTANCE.message }
  x.report("runtime error message cost") { RUNTIME_ERROR_INSTANCE.message }

  x.compare!
end

__END__

output:
ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-linux]
Warming up --------------------------------------
no method error message cost
                        20.000  i/100ms
runtime error message cost
                         1.620M i/100ms
Calculating -------------------------------------
no method error message cost
                        249.716  (± 6.0%) i/s -      1.260k in   5.068273s
runtime error message cost
                         16.046M (± 1.1%) i/s -     81.020M in   5.049800s

Comparison:
runtime error message cost: 16045983.5 i/s
no method error message cost:      249.7 i/s - 64256.99x  (± 0.00) slower
  • この例だとめっちゃ遅いですね
  • これは NoMethodError#message#inspect を呼び出しているのが原因らしい
class NoInspect
end

begin
  NoInspect.new.hoge
rescue => e
  pp e.message
  # => "undefined method `hoge' for #<NoInspect:0x00005644abd35978>"
end

class WithInspect
  def inspect
    "My WithInspect"
  end
end

begin
  WithInspect.new.hoge
rescue => e
  # メッセージに WithInspect#inspect も含められる
  # error: undefined method `hoge' for My WithInspect:WithInspect (NoMethodError)
  pp e.message
  # => "undefined method `hoge' for My WithInspect:WithInspect"
end
class WithInspect
  def inspect
    "A" * 66
  end
end

begin
  WithInspect.new.hoge
rescue => e
  pp e.message
  # Ruby 2.7 => "undefined method `hoge' for #<WithInspect:0x000055a784c8a560>"
  # Ruby 3.0 => "undefined method `hoge' for AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:WithInspect"
end

[Feature #18273] Class#subclasses

  • Class#descendants に関連して『自身の子クラスのみ』を返す Class#subclasses を追加する提案
class Class
  def subclasses
    descendants.select { |descendant| descendant.superclass == self }
  end
end


class A; end
class B < A; end
class C < B; end
class D < A; end

# 継承リストに A が含まれているクラスをすべて返す
pp A.descendants  # => [B, C]

# A を直接継承しているクラスのみ返す
pp A.subclasses   # => [D, B]
  • 確かにほしくなりそう

2021/10/28 今回の気になった bugs.ruby のチケット

今週は Enumerable#each_cons#each_slice の戻り値の変更がありました。

[PR #1509] Fix Enumerable#each_cons and each_slice to return a receiver

  • Enumerable#each_consEnumerable#each_slice の戻り値をレシーバにする PR
    • 現在は nil を返している
  • 2017年の PR で最近マージされた
    • チケットは特になさそう?
p [1, 2, 3].each_cons(2) {}
# Ruby 3.0 => nil
# Ruby 3.1 => [1, 2, 3]

[Bug #18268] Behavior change when each_cons and break ... if false are combined in Ruby 3.1

[Feature #16663] Add block or filtered forms of Kernel#caller to allow early bail-out

  • #caller に走査できる機能を追加しようという提案
    • この機能を追加することでスタックトレースを取得する際のオーバーヘッドを軽減させる目的
  • 例えば以下のようにブロックを渡すと以下のように利用できる
def find_matching_frame(regex)
  caller do |frame|
    # ファイル名が regex にマッチしたら早期 return する
    return frame if frame.file =~ regex
  end
end
  • また以下のように先頭4つを受け取るようにしたりとか
i = 0
ary = []
caller { |x|
  ary << x
  i += 1
  break if i == 4
}

[Feature #14394] Class.descendants

  • .descendants はレシーバが『継承されている』クラス/モジュールの一覧を返すメソッドの追加の提案
module A
end

module B
  include A
end

module C
  include B
end

A.descendants    #=> [A, C, B]
B.descendants    #=> [B, C]
C.descendants    #=> [C]
class A; end
class B < A; end
class C < B; end

pp A.descendants    #=> [B, C]
pp B.descendants    #=> [C]
pp C.descendants    #=> []

[Bug #18267] Argument forwarding requires parenthesis on method definitions

  • def hoge(foo, ...)def hoge foo, ... みたいに () を省略したいという提案
  • パーサ的にめっちゃむずそうな雰囲気があるんだけどどうなんだろう…
  • ちなみに ... 引数をすべて別のメソッドに forward する構文
def foo(...)
  # foo の引数をすべて bar に渡す
  bar(...)
end

[Feature #18275] Add an option to define_method to not capture the surrounding environment

  • define_method で定義したメソッドが Ractor から参照できない
def twice(a)
  a + a
end

define_method(:twice2) { |a|
  a + a
}


ractor = Ractor.new(10) do |loop_count|
  $stdout.sync = true
  # def で定義した twice は参照できる
  p "twice: #{twice loop_count}"

  # define_method で定義した twice2 は参照できない
  p "twice2: #{twice2 loop_count}"
end

ractor.take
  • これを解消するために define_method に変数などをキャプチャしないようにするオプションを追加する提案
    • 不要な変数までキャプチャすると遅くなってしまう
    • 明示的にキャプチャしないようにすることで Ractor から参照できるようにする
some_random_thing = “a” * 10000
some_captured_block = -> { ... }

# capture_environment: でキャプチャするかどうかを制御する
define_method(:my_method, capture_environment: false, &some_captured_block)
some_random_thing = “a” * 10000 # kept because `my_method` needs it
another_random_thing = “b” * 10000 # not kept
some_captured_block = -> { ... }

# 特定の変数のみをキャプチャする
define_method(:my_method, needs: [:some_random_thing], &some_captured_block)
# Proc を make_shareable 化しておけば OK
body = -> (a) {
  a + a
}
Ractor.make_shareable(body)
define_method(:twice, &body)

ractor = Ractor.new(10) do |loop_count|
  $stdout.sync = true
  # twice は参照できる
  p "twice: #{twice loop_count}"
end

ractor.take

[Feature #12495] Make "private" return the arguments again, for chaining

  • private の戻り値をレシーバから引数を返すようにする提案
class X
  def hoge
  p private :hoge
  # 現在の挙動   => X
  # 変更する挙動 => :hoge
end
  • 以下のようなことをやりたい
def cached(name)
  # Rewrite method to include a cache
  return name
end

# これは OK
private cached def foo() end

# こうもかけるようにしたい
cached private def foo() end

[Feature #11689] Add methods allow us to get visibility from Method and UnboundMethod object.

class Object
  def debugging(name)
    original = instance_method(name)

    # 提案
    method_visibility = original.visibility
    # 既存の実装だとこう書く必要がある
    # method_visibility = if private_method_defined?(name)
    #                       :private
    #                     elsif protected_method_defined?(name)
    #                       :protected
    #                     else
    #                       :public
    #                     end
    define_method(name) { |*args, &block|
      pp name
      original.bind(self).call(*args, &block)
    }

    # 元のメソッドに合わせる
    send(method_visibility, name)
  end
end

class X
  def hoge
    pp 1 + 2
  end
  private :hoge

  debugging :hoge
end

# error: private method `hoge' called for #<X:0x000055f0b1abf3e0> (NoMethodError)
X.new.hoge

Ruby でメソッドが呼び出された時にレシーバがあるかないかで処理を切り分ける

今日の Kaigi on Rails の kamipo さんの基調講演でそういう話がでてたのでやってみました。
要は User.where みたいに User. 付きか where だけで呼び出された場合で処理を切り分けたい的な話。

class User
  def self.where
    "レシーバなし"
  end

  def self.where_with_receiver
    "レシーバあり"
  end

  class <<self
    private :where

    # private メソッドでレシーバありで呼び出された場合に method_missing が呼ばれる
    def method_missing(name, *args)
      if name === :where
        where_with_receiver(*args)
      else
        super
      end
    end
  end
end

pp User.where
# => レシーバあり

pp User.instance_exec { where }
# => レシーバなし

# 動的呼び出しの場合
pp User.send(:where)
# => レシーバなし

pp User.public_send(:where)
# => レシーバあり

# NOTE: self. 付きは Ruby のバージョンによって変わる
pp User.instance_exec { self.where }
# Ruby 2.6 以前 => レシーバなし
# Ruby 2.7 以降 => レシーバなし

呼び出されたメソッドが private メソッドの場合『レシーバ付きだと method_missing が呼ばれる』のでそれで処理を切り分けるようにしています。
そもそもこの時に method_missing が呼ばれるのは期待する動作なのかどうか…。
Ruby 2.7 からは self. 付きでも private メソッドが呼び出せるようになっているのでその部分に差異があります。

2021/10/21 今回の気になった bugs.ruby のチケット

今週は YJIT が Ruby 本体に取り込まれました。

[Feature #6210] load should provide a way to specify the top-level module

  • load メソッドに Module を指定できるようにする提案
  • 以下のように特定のモジュールに対して load した Ruby のコードが展開される
# test.rb
def hoge
  "hoge"
end

class Foo
  def foo
    "foo"
  end
end
module M
end

# M に対して test.rb の中身が定義される
load "./test.rb", M

p M::Foo
# => M::Foo

p M::Foo.new.foo
# => "foo"

class X
  include M
  public :hoge
end

p X.new.hoge
# => "hoge"
  • チケット自体はだいぶ古いものだけど最近実装した PR ができてた
  • なにかいろいろと利用できそうな気がするんですが具体的にどういう場面で利用できそうかな…

[Bug #17719] Irregular evaluation order in hash literals

  • Hash リテラルで同名のキーが存在する場合に評価順が左からにならないバグ報告
  • 最近修正された
ary = []
{ a: ary << 1, b: ary << 2, a: ary << 3 }
pp ary
# Ruby 3.0 => [1, 3, 2]
# Ruby 3.1 => [1, 2, 3]

[Feature #18229] Proposal to merge YJIT

  • マージされました!!!
  • --yjit オプションを付けて実行できるよ!!!