Bug #22332
openRuby::Box: prelude is evaluated only in the master box, so pp and binding.irb load into it
Description
Under RUBY_BOX=1, Kernel#pp prints but leaves nothing behind in the box that called it.
$ RUBY_BOX=1 ruby --disable-gems -W:no-experimental -e 'pp({a: 1}); p $LOADED_FEATURES.grep(/pp\.rb/).size, defined?(PP)'
{a: 1}
0
nil
$ ruby --disable-gems -e 'pp({a: 1}); p $LOADED_FEATURES.grep(/pp\.rb/).size, defined?(PP)'
{a: 1}
1
"constant"
Requiring it afterwards therefore loads the same file a second time, and it redefines its own methods:
$ RUBY_BOX=1 ruby --disable-gems -W:no-experimental -e 'pp({a: 1}); require "pp"'
{a: 1}
.../lib/ruby/4.1.0+4/pp.rb:412: warning: method redefined; discarding old pretty_print
.../lib/ruby/4.1.0+4/pp.rb:412: warning: previous definition of pretty_print was here
(and 20 more)
A box created before the call cannot see it either, while one created afterwards inherits it from the master box:
$ RUBY_BOX=1 ruby --disable-gems -W:no-experimental -e 'before = Ruby::Box.new; pp({a: 1}); after = Ruby::Box.new; p before.eval("defined?(PP)"), after.eval("defined?(PP)")'
{a: 1}
nil
"constant"
Binding#irb fails outright, because its rescue LoadError, Gem::LoadError clause is evaluated where RubyGems is not loaded:
$ RUBY_BOX=1 ruby -W:no-experimental -e 'binding.irb' < /dev/null
-e:1:in 'Binding#irb': uninitialized constant Binding::Gem (NameError)
Without a box the same command starts irb.
prelude.rb is evaluated once in rb_call_builtin_inits() (Init_builtin_prelude(), inits.c:114), before the root and main boxes exist, so the box recorded on Kernel#pp and Binding#irb is the master box, and the require they run loads there. gem_prelude already avoids this: rb_load_gem_prelude() (builtin.c:114) runs for root and main in Init_builtin_features() and again for every new box (box.c:435).
So the question is where prelude should be evaluated. Three shapes I can see:
- load prelude per box the way gem_prelude already is (https://github.com/ruby/ruby/pull/18713);
- keep the single evaluation and mark the prelude methods
Primitive.attr! :caller_user_box, so only theirrequireresolves in the caller's box; - make
requirecalled from a master-box method resolve in the caller's box in general, which needs care not to become local rebinding (#21362).
Is there another shape you would prefer?
Reproduced on master 3f6143715a and on released 4.0.7.
Related to #22275
Updated by hsbt (Hiroshi SHIBATA) 3 days ago
- Related to Misc #22275: Ruby::Box support plan for RubyGems and Bundler added