Preview Ruby 4.1 is a preview. It can change before the final release. Switch to Ruby 4.0 →
Proc#refined
static VALUE
proc_refined(int argc, VALUE *argv, VALUE self)
{
rb_proc_t *src;
GetProcPtr(self, src);
if (argc == 0) {
return self;
}
if (vm_block_type(&src->block) != block_type_iseq || src->header.is_from_method) {
rb_raise(rb_eArgError, "can't apply refinements to a Proc without a Ruby block");
}
for (int i = 0; i < argc; i++) {
Check_Type(argv[i], T_MODULE);
}
const rb_iseq_t *src_iseq = src->block.as.captured.code.iseq;
VALUE src_recipe = rb_proc_refinements_recipe(self);
const rb_cref_t *src_cref, *base_cref;
if (NIL_P(src_recipe)) {
src_cref = base_cref = rb_vm_get_cref(src->block.as.captured.ep);
}
else {
/* keep asking for the modules of the whole chain, so that a chained
* call ends up with the recipe of a single call of all of them */
src_cref = (const rb_cref_t *)RARRAY_AREF(src_recipe, REFINEMENT_RECIPE_CREF);
base_cref = (const rb_cref_t *)RARRAY_AREF(src_recipe, REFINEMENT_RECIPE_BASE_CREF);
}
/* A block that is itself a copy is short-lived, so it is not memoized, and
* it has to be copied here: ISEQ_REFINED_COPY has to keep meaning "the
* copy of this Proc". */
bool copied_src = FL_TEST_RAW((VALUE)src_iseq, ISEQ_REFINED_COPY);
if (copied_src) {
rb_category_warn(
RB_WARN_CATEGORY_PERFORMANCE,
"Proc#refined on a Proc whose block was already copied by Proc#refined is not memoized"
);
}
VALUE recipe = copied_src ? Qnil :
refinement_memo_lookup(src_iseq, base_cref, src_recipe, argc, argv);
if (NIL_P(recipe)) {
rb_cref_t *cref = rb_vm_cref_dup(src_cref);
/* rb_using_module_recursive modifies shared subclass lists */
RB_VM_LOCKING() {
for (int i = 0; i < argc; i++) {
rb_using_module_recursive(cref, argv[i]);
}
}
/* Freeze the refinements table and mark it shareable so the memoized
* cref can be reused from any Ractor. */
VALUE refs = CREF_REFINEMENTS(cref);
if (!NIL_P(refs)) {
OBJ_FREEZE(refs);
RB_OBJ_SET_SHAREABLE(refs);
}
CREF_OMOD_SHARED_SET(cref);
CREF_REFINED_PROC_SET(cref);
recipe = refinement_recipe_new(base_cref, cref, src_iseq, src_recipe, argc, argv);
}
const rb_iseq_t *new_iseq = copied_src ?
rb_iseq_dup_with_independent_caches(src_iseq) : src_iseq;
return rb_proc_dup_with_iseq_and_recipe(self, new_iseq, recipe);
}
Returns a new Proc that behaves like the receiver but with the refinements activated by the given modules in effect inside its body. The receiver is left unchanged.
module StringRefinement refine String do def shout = upcase + "!" end end original = ->(s) { s.shout } refined_proc = original.refined(StringRefinement) refined_proc.call("hi") #=> "HI!" original.call("hi") #=> NoMethodError
If no modules are given, returns the receiver. Otherwise, only Procs created from a Ruby block are supported; calling this on a Proc backed by a C function, a Symbol, or a method raises ArgumentError.
When calls of this method are chained, all the given modules are activated in the order they are given, so refinements activated by a later call take precedence.
The refinement set of the returned Proc is fixed when it is created: calling using inside its body raises RuntimeError.
The refinements are in effect throughout the body, including nested blocks and methods defined with def inside it. As with a def inside a using scope, such a method keeps the refinements even when it is called later:
refined_proc = ->(s) { -> { s.shout }.call # nested block: "HI!" }.refined(StringRefinement) refined_proc = -> { obj = Object.new def obj.shout_hi = "hi".shout # the method sees the refinement obj.shout_hi #=> "HI!" }.refined(StringRefinement)
Running the returned Proc requires a copy of the instruction sequence of the block and of all of its nested blocks, so that the copy can resolve methods through the refinements without affecting the original Proc. The copy is made when the Proc is first called, and is cached and reused for the same block and the same modules, whether they were given in one call or in a chain of calls; a Proc that is never called is never copied. Applying refinements therefore increases memory use roughly in proportion to the size of the block, once the Proc runs.
Ruby version changes
-
4.1
-
Proc#refinedis added. It returns a newProcthat behaves like the receiver but with the refinements activated by the given modules in effect inside its body, without affecting the originalProc. [Feature #22097]
-