Project

General

Profile

Actions

Bug #22332

open

Ruby::Box: prelude is evaluated only in the master box, so pp and binding.irb load into it

Bug #22332: Ruby::Box: prelude is evaluated only in the master box, so pp and binding.irb load into it

Added by hsbt (Hiroshi SHIBATA) 3 days ago.

Status:
Open
Target version:
-
ruby -v:
ruby 4.1.0dev (2026-09-17T00:01:17Z master 3f6143715a) [arm64-darwin27]
[ruby-core:126765]

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 their require resolves in the caller's box;
  • make require called 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


Related issues 1 (1 open0 closed)

Related to Ruby - Misc #22275: Ruby::Box support plan for RubyGems and BundlerOpenActions

Updated by hsbt (Hiroshi SHIBATA) 3 days ago Actions #1

  • Related to Misc #22275: Ruby::Box support plan for RubyGems and Bundler added
Actions

Also available in: PDF Atom