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

# /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.constantsModule.const_defined? を使用すると次のような結果が返ってくる
module M
  autoload :X, '/tmp/x'
end

# まだ M::X は定義されていないが定義されているかのように振る舞う
p M.constants(false)          # => [:X]
p M.const_defined?(:X, false) # => true
  • この時に /tmp/xM::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 では警告を出す対応になっている
# /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