Repository navigation
Description
respondsToConversion (internal/vm/vm.go) implements MRI's
check_funcall_respond_to — consult an overridden #respond_to? before
sending a conversion method — but calls it with one argument where MRI calls it
with two:
MRI : respond_to?(:to_a, true)
rbgo : respond_to?(:to_a) → the receiver's own default fills in false
Measured with this, on /usr/bin/ruby and on rbgo built from
splat-array-subclass:
class Refuser
def respond_to?(name, include_private = false)
STDERR.puts " respond_to?(#{name.inspect}, #{include_private.inspect})"
false
end
def to_a; [:wrong]; end
end
a = *Refuser.newMRI : respond_to?(:to_a, true)
rbgo : respond_to?(:to_a, false)
Why it is not only cosmetic
The second argument is include_private. An object that overrides
#respond_to? and honours that flag — the common shape being
def respond_to?(name, include_private = false); include_private && …; end, or
anything delegating to super — will answer false to rbgo where it answers
true to MRI. rbgo then wraps the object instead of converting it, silently,
with no exception to notice.
It is narrower than it first looks, and that is worth recording: a private
conversion method on an object with the DEFAULT #respond_to? is reached
correctly today, because respondsToConversion falls through to a method-table
lookup that finds private methods anyway. Verified — *SplatPriv.new with a
private #to_a gives [7, 8] on both engines. The divergence needs an
overridden #respond_to? to show up.
What matching it takes
vm_respond_to (vm_method.c) does not simply pass two arguments; it reads the
arity of the custom #respond_to? first:
- arity 1 → send one argument;
- arity 2 (or variadic) → send
(mid, priv); - arity > 2 →
ArgumentError: respond_to? must accept 1 or 2 arguments (requires N).
Without that rule, passing two arguments unconditionally would raise
ArgumentError on every object whose #respond_to? takes exactly one — which
is a perfectly ordinary definition, and would be a regression far wider than the
bug.
Blast radius, which is why it was not folded into the splat fix
respondsToConversion is the single gate for every conversion site:
to_ary, to_a, to_str, to_sym, to_hash. A change there wants its own
measurement rather than a line inside a splat fix.
Note that no spec in the gate can see this. ruby/spec asserts it —
x.should_receive(:respond_to?).with(:to_ary, true) in
language/variables_spec.rb:75 — but our minimal MSpec shim's .with does not
enforce the argument list, so the example passes either way. Whoever fixes this
should tighten .with in the shim in the same change, or the fix is unwitnessed.
🤖 Generated with Claude Code
Activity