Repository navigation
A &block-pass that is not a Proc raises TypeError, with MRI's two messages - #906
Conversation
…sages
language/send_spec.rb: pass=74 fail=4 -> pass=75 fail=3.
S.makeproc(&Object.new)
# rbgo: NoMethodError: undefined method 'to_proc' for an instance of Object
# MRI: TypeError: no implicit conversion of Object into Proc
toBlock reached to_proc through vm.send unconditionally, so an object with no
to_proc at all surfaced the dispatch failure instead of a conversion failure.
## ⛔ The class was never NoMethodError, so this is not only a 4.0 move
Measured on three engines:
MRI 2.6.10 TypeError: wrong argument type Object (expected Proc)
MRI 4.0.7 TypeError: no implicit conversion of Object into Proc
rbgo NoMethodError: undefined method 'to_proc' ...
2.6 already answered TypeError. What 4.0 changed is the WORDING. The wrong
class was a plain defect; only the two messages belong to #799.
## Bibliography
vm_to_proc (vm_args.c at v4.0.9) looks the method up FIRST and branches, which
is why there are two messages and not one:
me = rb_callable_method_entry_with_refinements(CLASS_OF(proc), idTo_proc, NULL);
if (me) { b = rb_vm_call0(GET_EC(), proc, idTo_proc, 0, NULL, me, RB_NO_KEYWORDS); }
else { /* NOTE: calling method_missing */
b = rb_check_convert_type_with_id(proc, T_DATA, "Proc", idTo_proc); }
rb_raise(rb_eTypeError, "can't convert %"PRIsVALUE" to Proc "
"(%"PRIsVALUE"#to_proc gives %"PRIsVALUE")", ...)
rb_raise(rb_eTypeError, "no implicit conversion of %s into Proc", ...)
## ⛔ The predicate is a METHOD ENTRY, not respond_to?
The obvious spelling is `respondsToDynamic(v, "to_proc")`. It would have broken
three cases that ALREADY WORKED, measured on 4.0.7 before choosing:
a PRIVATE to_proc -> used, :priv
to_proc via method_missing + respond_to_missing? -> used, :mm
to_proc via method_missing ALONE -> used, :mmonly
respond_to? is false for the first and the third. MRI uses
rb_callable_method_entry (any visibility) and, failing that, rb_check_funcall,
which calls method_missing when it is overridden and gives up when it is still
BasicObject's. The guard mirrors that: findMethod, else a method_missing whose
owner is no longer cBasicObject. All three are controls in the test.
## An upstream report I did NOT file
ruby/spec expects "can't convert X into Proc" where MRI 4.0.9 formats "to Proc",
so the corpus and the reference disagree. That is deliberate, not a bug:
# CRuby < 4.1 has inconsistent coercion errors:
# https://bugs.ruby-lang.org/issues/21864
# This matcher ignores the message on CRuby < 4.1
if RUBY_ENGINE == "ruby" and ruby_version_is ""..."4.1"
message = nil
CRuby 4.0's wording is known-wrong and fixed in 4.1; the check is waived for
CRuby only. rbgo reports RUBY_ENGINE "ruby" and RUBY_VERSION "4.0.9", so the
waiver covers it -- as spec_helper.rb:350 already records. Implementing 4.0.9's
exact wording is therefore right, and there is nothing to report upstream.
## Measured
cmd/oracle against /opt/homebrew/opt/ruby@4.0/bin/ruby: both probes AGREE byte
for byte (9 rows). 10 table rows, 3 of them the respond_to? controls.
Refs #799
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The corpus sweep
One file moved and it is the one this PR is about. Why that is the expected shape, and not a disappointmentThe sibling example — "raises TypeError if It passed because That is worth stating rather than quietly banking the The three rows the sweep also cannot seeA private CI: 26/26 green. 🤖 Generated with Claude Code |
Closes another of the
language/send_spec.rbfailures from#799:pass=74 fail=4→pass=75 fail=3.toBlockreachedto_procthroughvm.sendunconditionally, so an object with noto_procsurfaced the dispatch failure instead of a conversion failure.⛔ The class was never
NoMethodError, so this is not only a 4.0 move&Object.newTypeError: wrong argument type Object (expected Proc)TypeError: no implicit conversion of Object into ProcNoMethodError: undefined method 'to_proc'2.6 already answered
TypeError. What 4.0 changed is the wording. So the wrong class was a plain defect of long standing, and only the two messages belong to the version move — I had this filed as "a 4.0 behaviour" and that was half wrong.Bibliography
vm_to_proclooks the method up first and branches, which is why there are two messages:⛔ The predicate is a method ENTRY, not
respond_to?The obvious spelling is
respondsToDynamic(v, "to_proc"). It would have broken three cases that already worked — measured on 4.0.7 before choosing, not after a test went red:respond_to?to_proc:privto_procviamethod_missing+respond_to_missing?:mmto_procviamethod_missingalone:mmonlyMRI uses
rb_callable_method_entry(any visibility) and, failing that,rb_check_funcall, which callsmethod_missingwhen it is overridden and gives up when it is stillBasicObject's. The guard mirrors exactly that:findMethod, else amethod_missingwhose owner is no longercBasicObject. All three are now controls in the test, so narrowing the predicate later fails loudly.An upstream report I did not file
ruby/spec expects
can't convert X into Proc; MRI 4.0.9 formatsto Proc. Corpus and reference disagree, and both readings were verified — the C source read verbatim, the 4.0.7 binary run. The conclusion would still have been wrong:A deliberate waiver, not an oversight: CRuby 4.0's wording is known wrong, fixed in 4.1, and the message check is skipped for CRuby only. rbgo reports
RUBY_ENGINE == "ruby"andRUBY_VERSION == "4.0.9", so the waiver covers it — whichspec_helper.rb:350already records. Implementing 4.0.9's exact wording is therefore correct and there is nothing to report upstream.Measured
cmd/oracleagainst/opt/homebrew/opt/ruby@4.0/bin/ruby(ruby 4.0.7 … +PRISM [arm64-darwin25]): both probes AGREE, byte for byte across nine rows, where the control differed on three.10 table rows in
internal/vm/block_pass_to_proc_test.go, three of them therespond_to?controls, plus&nil,&:symboland&procas pass-through controls.Refs #799
🤖 Generated with Claude Code