summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Evans <code@jeremyevans.net>2019-09-26 09:10:42 -0700
committerJeremy Evans <code@jeremyevans.net>2019-09-26 15:30:51 -0700
commit37f9213f8957e0c6dffee7d8803890907f97bdbb (patch)
tree3f864d0c39f3360d62b8a7c90bbeb1c92823c4eb
parentdd2068ac8d4016f43c1f3cc1aa81decb504db5b6 (diff)
Fix keyword argument separation issues in Enumerator::Generator#each
This requires adding rb_proc_call_kw to pass the keyword flag.
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/2491
-rw-r--r--enumerator.c2
-rw-r--r--include/ruby/intern.h1
-rw-r--r--proc.c18
-rw-r--r--test/ruby/test_keyword.rb77
4 files changed, 97 insertions, 1 deletions
diff --git a/enumerator.c b/enumerator.c
index af9dc0fd2b..63322197d6 100644
--- a/enumerator.c
+++ b/enumerator.c
@@ -1506,7 +1506,7 @@ generator_each(int argc, VALUE *argv, VALUE obj)
rb_ary_cat(args, argv, argc);
}
- return rb_proc_call(ptr->proc, args);
+ return rb_proc_call_kw(ptr->proc, args, RB_PASS_CALLED_KEYWORDS);
}
/* Lazy Enumerator methods */
diff --git a/include/ruby/intern.h b/include/ruby/intern.h
index 4af254d920..14543a9f4d 100644
--- a/include/ruby/intern.h
+++ b/include/ruby/intern.h
@@ -454,6 +454,7 @@ VALUE rb_block_lambda(void);
VALUE rb_proc_new(rb_block_call_func_t, VALUE);
VALUE rb_obj_is_proc(VALUE);
VALUE rb_proc_call(VALUE, VALUE);
+VALUE rb_proc_call_kw(VALUE, VALUE, int);
VALUE rb_proc_call_with_block(VALUE, int argc, const VALUE *argv, VALUE);
VALUE rb_proc_call_with_block_kw(VALUE, int argc, const VALUE *argv, VALUE, int);
int rb_proc_arity(VALUE);
diff --git a/proc.c b/proc.c
index f03e470151..7c82cc63c8 100644
--- a/proc.c
+++ b/proc.c
@@ -935,6 +935,24 @@ check_argc(long argc)
#endif
VALUE
+rb_proc_call_kw(VALUE self, VALUE args, int kw_splat)
+{
+ VALUE vret;
+ rb_proc_t *proc;
+ VALUE v;
+ int argc = check_argc(RARRAY_LEN(args));
+ const VALUE *argv = RARRAY_CONST_PTR(args);
+ GetProcPtr(self, proc);
+ v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat);
+ vret = rb_vm_invoke_proc(GET_EC(), proc, argc, argv,
+ kw_splat, VM_BLOCK_HANDLER_NONE);
+ rb_free_tmp_buffer(&v);
+ RB_GC_GUARD(self);
+ RB_GC_GUARD(args);
+ return vret;
+}
+
+VALUE
rb_proc_call(VALUE self, VALUE args)
{
VALUE vret;
diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb
index 4bda9d5fb8..da502ac10f 100644
--- a/test/ruby/test_keyword.rb
+++ b/test/ruby/test_keyword.rb
@@ -841,6 +841,83 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal([1, h3], t.new(&f).resume(a: 1, **h2))
end
+ def test_Enumerator_Generator_each_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ g = Enumerator::Generator
+ f = ->(_) { true }
+ assert_equal(true, g.new(&f).each(**{}))
+ assert_equal(true, g.new(&f).each(**kw))
+ assert_raise(ArgumentError) { g.new(&f).each(**h) }
+ assert_raise(ArgumentError) { g.new(&f).each(a: 1) }
+ assert_raise(ArgumentError) { g.new(&f).each(**h2) }
+ assert_raise(ArgumentError) { g.new(&f).each(**h3) }
+
+ f = ->(_, a) { a }
+ assert_warn(/The keyword argument is passed as the last hash parameter/m) do
+ assert_equal(kw, g.new(&f).each(**{}))
+ end
+ assert_warn(/The keyword argument is passed as the last hash parameter/m) do
+ assert_equal(kw, g.new(&f).each(**kw))
+ end
+ assert_equal(h, g.new(&f).each(**h))
+ assert_equal(h, g.new(&f).each(a: 1))
+ assert_equal(h2, g.new(&f).each(**h2))
+ assert_equal(h3, g.new(&f).each(**h3))
+ assert_equal(h3, g.new(&f).each(a: 1, **h2))
+
+ f = ->(_, **x) { x }
+ assert_equal(kw, g.new(&f).each(**{}))
+ assert_equal(kw, g.new(&f).each(**kw))
+ assert_equal(h, g.new(&f).each(**h))
+ assert_equal(h, g.new(&f).each(a: 1))
+ assert_equal(h2, g.new(&f).each(**h2))
+ assert_equal(h3, g.new(&f).each(**h3))
+ assert_equal(h3, g.new(&f).each(a: 1, **h2))
+ assert_warn(/The last argument is used as the keyword parameter.*for method/m) do
+ assert_equal(h, g.new(&f).each(h))
+ end
+ assert_raise(ArgumentError) { g.new(&f).each(h2) }
+ assert_warn(/The last argument is split into positional and keyword parameters.*for method/m) do
+ assert_raise(ArgumentError) { g.new(&f).each(h3) }
+ end
+
+ f = ->(_, a, **x) { [a,x] }
+ assert_warn(/The keyword argument is passed as the last hash parameter/) do
+ assert_equal([{}, {}], g.new(&f).each(**{}))
+ end
+ assert_warn(/The keyword argument is passed as the last hash parameter/) do
+ assert_equal([{}, {}], g.new(&f).each(**kw))
+ end
+ assert_warn(/The keyword argument is passed as the last hash parameter/) do
+ assert_equal([h, {}], g.new(&f).each(**h))
+ end
+ assert_warn(/The keyword argument is passed as the last hash parameter/) do
+ assert_equal([h, {}], g.new(&f).each(a: 1))
+ end
+ assert_warn(/The keyword argument is passed as the last hash parameter/) do
+ assert_equal([h2, {}], g.new(&f).each(**h2))
+ end
+ assert_warn(/The keyword argument is passed as the last hash parameter/) do
+ assert_equal([h3, {}], g.new(&f).each(**h3))
+ end
+ assert_warn(/The keyword argument is passed as the last hash parameter/) do
+ assert_equal([h3, {}], g.new(&f).each(a: 1, **h2))
+ end
+
+ f = ->(_, a=1, **x) { [a, x] }
+ assert_equal([1, kw], g.new(&f).each(**{}))
+ assert_equal([1, kw], g.new(&f).each(**kw))
+ assert_equal([1, h], g.new(&f).each(**h))
+ assert_equal([1, h], g.new(&f).each(a: 1))
+ assert_equal([1, h2], g.new(&f).each(**h2))
+ assert_equal([1, h3], g.new(&f).each(**h3))
+ assert_equal([1, h3], g.new(&f).each(a: 1, **h2))
+ end
+
def test_Class_new_kwsplat_call
kw = {}
h = {:a=>1}