Bug #22522
open`Ractor.shareable_proc` silently drops the block of the method for `block_given?`, `defined?(yield)` and `super`
Description
yield in a block calls the block of the enclosing method. A shareable Proc cannot keep that block: the block (or the Proc it becomes) has its own self and environment, which are generally not shareable, and the copied environment of the shareable Proc has no block. That is why Ractor.shareable_proc already rejects yield:
def m = Ractor.shareable_proc { yield }
m {} # can not make a Proc shareable because it uses 'yield'. (Ractor::IsolationError)
But other constructs inside a block use the block of the enclosing method too, and are not rejected. The shareable Proc silently behaves as if the method had no block:
def m1 = Ractor.shareable_proc { block_given? }
p m1 {}.call # Ruby block semantics: true
def m2 = Ractor.shareable_proc { defined?(yield) }
p m2 {}.call # "yield"
class Parent
def foo = block_given?
end
class Child < Parent
# super without an explicit block passes the block of the method
def foo = Ractor.shareable_proc(self: self) { super() }
end
obj = Ractor.make_shareable(Child.new)
p obj.foo {}.call # true
$ ruby -v block.rb
ruby 4.1.0dev (2026-11-09T15:47:20Z master 7e5d882817) +PRISM [arm64-darwin25]
false
nil
false
Same result on 4.0.7. iterator? (the deprecated alias of block_given?) and zsuper (super) behave the same way. Since #22444 this also applies to Ractor.new blocks.
These should raise Ractor::IsolationError like yield does, for the same reason. Following [Feature #21557], it is better to raise than to silently change the semantics of the block.
Fix¶
PR: https://github.com/ruby/ruby/pull/19803
The compiler now records block_given?/iterator? calls without a receiver, defined?(yield), and super without an explicit block argument or literal block, the same way it records yield. This is done only when the enclosing scope is a method, since otherwise there is no block of the method and nothing to lose, so Ractor.new { block_given? } keeps working. The error message names the construct:
Like the existing check for yield, this check is static, so it raises even if the method was called without a block. A possible later relaxation, for yield too, would be to check at runtime instead and reject only when the method has a block which is not shareable. A method's block cannot change during the call, so without a block the shareable Proc behaves exactly like the original.
Code which currently creates such a shareable Proc gets Ractor::IsolationError after this fix, so I don't think this should be backported.
Updated by Eregon (Benoit Daloze) about 20 hours ago
- Related to Feature #21557: Ractor.shareable_proc to make sharable Proc objects, safely and flexibly added