From 3151cd7bd0260f1aa297ac5a64f8faf874c17e6d Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Sat, 14 Jul 2018 08:22:38 -0700 Subject: [PATCH] Add BasicObject#instance_exec_with_block This operates similarly to #instance_exec, except that the final argument is passed as the block argument to the provided block. This allows you to get the behavior of #instance_exec when using blocks that expect block arguments (where you might not have control over the block). --- test/ruby/test_object.rb | 17 ++++++++++++++ vm.c | 9 ++++++++ vm_eval.c | 50 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb index c25dcf9c37..172e6a32fb 100644 --- a/test/ruby/test_object.rb +++ b/test/ruby/test_object.rb @@ -759,6 +759,23 @@ def test_instance_exec end end + def test_instance_exec_with_block + x = 1.instance_exec_with_block(42, 2, proc{|x| self * x}) {|a, b, &blk| (self + a).instance_exec(b, &blk) } + assert_equal(86, x) + + assert_raise(ArgumentError) do + x.instance_exec_with_block{} + end + + assert_raise(TypeError) do + x.instance_exec_with_block(1){} + end + + assert_raise(LocalJumpError) do + x.instance_exec_with_block(proc{}) + end + end + def test_extend assert_raise(ArgumentError) do 1.extend diff --git a/vm.c b/vm.c index 36c8833439..35caf0c173 100644 --- a/vm.c +++ b/vm.c @@ -1105,6 +1105,15 @@ check_block_handler(rb_execution_context_t *ec) return block_handler; } +static VALUE +vm_yield_with_cref_and_block(rb_execution_context_t *ec, int argc, const VALUE *argv, const rb_cref_t *cref, int is_lambda, VALUE block_handler) +{ + vm_block_handler_verify(block_handler); + return invoke_block_from_c_bh(ec, check_block_handler(ec), + argc, argv, block_handler, + cref, is_lambda, FALSE); +} + static VALUE vm_yield_with_cref(rb_execution_context_t *ec, int argc, const VALUE *argv, const rb_cref_t *cref, int is_lambda) { diff --git a/vm_eval.c b/vm_eval.c index df93d000dd..d6a91c8202 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -17,6 +17,7 @@ struct local_var_list { static inline VALUE method_missing(VALUE obj, ID id, int argc, const VALUE *argv, enum method_missing_reason call_status); static inline VALUE vm_yield_with_cref(rb_execution_context_t *ec, int argc, const VALUE *argv, const rb_cref_t *cref, int is_lambda); +static inline VALUE vm_yield_with_cref_and_block(rb_execution_context_t *ec, int argc, const VALUE *argv, const rb_cref_t *cref, int is_lambda, VALUE block_handler); static inline VALUE vm_yield(rb_execution_context_t *ec, int argc, const VALUE *argv); static inline VALUE vm_yield_with_block(rb_execution_context_t *ec, int argc, const VALUE *argv, VALUE block_handler); static inline VALUE vm_yield_force_blockarg(rb_execution_context_t *ec, VALUE args); @@ -1526,7 +1527,7 @@ rb_eval_cmd(VALUE cmd, VALUE arg, int level) /* block eval under the class/module context */ static VALUE -yield_under(VALUE under, VALUE self, int argc, const VALUE *argv) +yield_under(VALUE under, VALUE self, int argc, const VALUE *argv, VALUE passed_block) { rb_execution_context_t *ec = GET_EC(); rb_control_frame_t *cfp = ec->cfp; @@ -1567,7 +1568,7 @@ yield_under(VALUE under, VALUE self, int argc, const VALUE *argv) } cref = vm_cref_push(ec, under, ep, TRUE); - return vm_yield_with_cref(ec, argc, argv, cref, is_lambda); + return vm_yield_with_cref_and_block(ec, argc, argv, cref, is_lambda, passed_block); } VALUE @@ -1606,7 +1607,7 @@ specific_eval(int argc, const VALUE *argv, VALUE klass, VALUE self) { if (rb_block_given_p()) { rb_check_arity(argc, 0, 0); - return yield_under(klass, self, 1, &self); + return yield_under(klass, self, 1, &self, VM_BLOCK_HANDLER_NONE); } else { VALUE file = Qundef; @@ -1704,7 +1705,45 @@ VALUE rb_obj_instance_exec(int argc, const VALUE *argv, VALUE self) { VALUE klass = singleton_class_for_eval(self); - return yield_under(klass, self, argc, argv); + return yield_under(klass, self, argc, argv, VM_BLOCK_HANDLER_NONE); +} + +/* + * call-seq: + * obj.instance_exec_with_block(arg..., proc) {|var..., &proc| block } -> obj + * + * Executes the given block within the context of the receiver + * (_obj_). In order to set the context, the variable +self+ is set + * to _obj_ while the code is executing, giving the code access to + * _obj_'s instance variables. The final argument is the proc to pass + * as the block argument to the block. Remaining arguments are passed + * as block parameters. + * + * class KlassWithSecret + * def initialize + * @secret = "a" + * end + * end + * k = KlassWithSecret.new + * k.instance_exec_with_block("b", 2, proc{|s| capitalize * 2}) do |x, y, &blk| + * @secret << x + * @secret.instance_exec(y, &blk) + * end #=> "AbAb" + */ + +VALUE +rb_obj_instance_exec_with_block(int argc, const VALUE *argv, VALUE self) +{ + VALUE block; + VALUE klass; + + rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS); + block = argv[--argc]; + if (!rb_obj_is_proc(block)) { + rb_raise(rb_eTypeError, "last argument to instance_exec_with_block must be a Proc"); + } + klass = singleton_class_for_eval(self); + return yield_under(klass, self, argc, argv, block); } /* @@ -1765,7 +1804,7 @@ rb_mod_module_eval(int argc, const VALUE *argv, VALUE mod) VALUE rb_mod_module_exec(int argc, const VALUE *argv, VALUE mod) { - return yield_under(mod, mod, argc, argv); + return yield_under(mod, mod, argc, argv, VM_BLOCK_HANDLER_NONE); } /* @@ -2155,6 +2194,7 @@ Init_vm_eval(void) rb_define_method(rb_cBasicObject, "instance_eval", rb_obj_instance_eval, -1); rb_define_method(rb_cBasicObject, "instance_exec", rb_obj_instance_exec, -1); + rb_define_method(rb_cBasicObject, "instance_exec_with_block", rb_obj_instance_exec_with_block, -1); rb_define_private_method(rb_cBasicObject, "method_missing", rb_method_missing, -1); #if 1 -- 2.17.1