Bug #22015
closedSuccess without RUBY_BOX=1, Failure with RUBY_BOX=1
Description
It ran fine as expected, but it failed when I added RUBY_BOX=1.
$ RUBY_BOX=1 ruby -e 'require "bundler/inline"; gemfile {}'
ruby: warning: Ruby::Box is experimental, and the behavior may change in the future!
See https://docs.ruby-lang.org/en/master/Ruby/Box.html for known issues, etc.
/Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/rubygems/specification.rb:2064:in 'Gem::Specification#method_missing': undefined method 'expanded_dependencies' for an instance of Gem::Specification (NoMethodError)
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver/spec_group.rb:42:in 'Array#each'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver/spec_group.rb:42:in 'Enumerable#flat_map'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver/spec_group.rb:42:in 'Bundler::Resolver::SpecGroup#dependencies'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver/spec_group.rb:67:in 'Bundler::Resolver::SpecGroup#equivalent?'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver/spec_group.rb:51:in 'Bundler::Resolver::SpecGroup#merge'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver.rb:279:in 'block (2 levels) in Bundler::Resolver#all_versions_for'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver.rb:278:in 'Array#each'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver.rb:278:in 'block in Bundler::Resolver#all_versions_for'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver.rb:258:in 'Hash#each'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver.rb:258:in 'Enumerable#reduce'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver.rb:258:in 'Bundler::Resolver#all_versions_for'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver.rb:54:in 'block in Bundler::Resolver#setup_solver'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver.rb:378:in 'Bundler::Resolver#filtered_versions_for'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver.rb:58:in 'block in Bundler::Resolver#setup_solver'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver.rb:169:in 'Bundler::Resolver#versions_for'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver.rb:449:in 'block in Bundler::Resolver#prepare_dependencies'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver.rb:443:in 'Hash#each'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver.rb:443:in 'Enumerable#filter_map'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver.rb:443:in 'Bundler::Resolver#prepare_dependencies'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver.rb:63:in 'Bundler::Resolver#setup_solver'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/resolver.rb:28:in 'Bundler::Resolver#start'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/definition.rb:750:in 'Bundler::Definition#start_resolution'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/definition.rb:351:in 'Bundler::Definition#resolve'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/definition.rb:253:in 'Bundler::Definition#missing_specs'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/definition.rb:257:in 'Bundler::Definition#missing_specs?'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/inline.rb:64:in 'block in Object#gemfile'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/settings.rb:146:in 'Bundler::Settings#temporary'
from /Users/niku/.local/share/mise/installs/ruby/4.1-dev/lib/ruby/4.1.0+1/bundler/inline.rb:60:in 'Object#gemfile'
from -e:1:in '<main>'
Updated by jneen (Jeanine Adkisson) 5 months ago
ยท Edited
I believe this to be related to Symbol#to_proc, as changing bundler/resolver/spec_group.rb:41 from:
to
removes the error.
The method is on the module Bundler::MatchMetadata, which is included into ::Gem::Specification with a re-opening in bundler/rubygems_ext.rb. What's strange is I verified that these all load in the user box, and that the object_id of ::Gem::Specification (including @specs[0].class) are all identical.
Updated by niku (niku _) 4 months ago
I found a minimal reproduction case.
The issue seems to relate to how Enumerable#flat_map handles Symbol#to_proc within a Ruby::Box environment. Notably, map(&:method) works as expected, while flat_map(&:method) fails.
$ ruby -v
ruby 4.1.0dev (2026-05-01T19:25:51Z master f2845eab29) +PRISM [arm64-darwin25]
$ RUBY_BOX=1 ruby -e "
box = Ruby::Box.new
box.eval(<<~'RUBY')
class Array
def box_only_method
:ok
end
end
# Works as expected
p [[1]].flat_map { |ary| ary.box_only_method } # => [:ok]
p [[1]].map(&:box_only_method) # => [:ok]
# Fails with NoMethodError
p [[1]].flat_map(&:box_only_method)
RUBY
"
[:ok]
[:ok]
eval:12:in 'Array#each': undefined method 'box_only_method' for an instance of Array (NoMethodError)
from eval:12:in 'Enumerable#flat_map'
Updated by niku (niku _) 4 months ago
I believe https://github.com/ruby/ruby/pull/16865 addresses this issue.
Updated by mame (Yusuke Endoh) 4 months ago
- Status changed from Open to Assigned
- Assignee set to tagomoris (Satoshi Tagomori)
Updated by hsbt (Hiroshi SHIBATA) 10 days ago
- Related to Misc #22275: Ruby::Box support plan for RubyGems and Bundler added
Updated by niku (niku _) about 14 hours ago
- Status changed from Assigned to Closed
Applied in changeset git|3b9ada18c9acad899b178b49dbc4a87f8b9561ab.
Ensure Symbol#to_proc respects caller's Ruby::Box context
[Bug #22015]
During a normal method call, the VM creates a control frame, and the Ruby::Box associated with that frame determines the execution context.
However, Symbol#to_proc does not automatically create a frame.
Therefore, we need to manually push a frame associated with the appropriate Box context.
When determining the Box to associate with this new frame, we cannot simply look at the closest Ruby-level control frame.
This is because some built-in methods (like Enumerable#map) are implemented in Ruby (e.g., <internal:enum>).
These built-in Ruby frames belong to the default Root or Main Box, which would obscure the actual user-defined Box context we need to capture.
To fix this properly, we now traverse the frames and check their inherent Box states.
Since built-in Ruby frames always run in the Root or Main Box, we safely skip them by continuing our traversal until we find a user-defined Box (an Optional Box, indicated by BOX_OPTIONAL_P).
Updated by hsbt (Hiroshi SHIBATA) about 13 hours ago
The merged form differs slightly from the commit message quoted above. In https://github.com/ruby/ruby/pull/18546 the traversal was widened to any user box, because a plain RUBY_BOX=1 script runs in the main box after #21881, and the pushed frame became VM_FRAME_MAGIC_BLOCK instead of VM_FRAME_MAGIC_TOP following tagomoris's review.