diff --git a/compile.c b/compile.c index 63b893d7de..d11e2f9f32 100644 --- a/compile.c +++ b/compile.c @@ -1700,6 +1700,9 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *const optargs, const NODE *cons body->param.keyword = keyword; body->param.flags.has_kwrest = TRUE; } + else if (args->no_kwarg) { + body->param.flags.accepts_no_kwarg = TRUE; + } if (block_id) { body->param.block_start = arg_size++; diff --git a/node.h b/node.h index 3741e649ca..3b565fc083 100644 --- a/node.h +++ b/node.h @@ -450,6 +450,7 @@ struct rb_args_info { NODE *kw_rest_arg; NODE *opt_args; + int no_kwarg; }; struct rb_ary_pattern_info { diff --git a/parse.y b/parse.y index 4dfdd5d2a3..c7c43e3278 100644 --- a/parse.y +++ b/parse.y @@ -3245,6 +3245,10 @@ block_args_tail : f_block_kwarg ',' f_kwrest opt_f_block_arg { $$ = new_args_tail(p, Qnone, $1, $2, &@1); } + | f_no_kwarg opt_f_block_arg + { + $$ = new_args_tail(p, Qnone, rb_intern("nil"), $2, &@1); + } | f_block_arg { $$ = new_args_tail(p, Qnone, Qnone, $1, &@1); @@ -4712,6 +4716,10 @@ args_tail : f_kwarg ',' f_kwrest opt_f_block_arg { $$ = new_args_tail(p, Qnone, $1, $2, &@1); } + | f_no_kwarg opt_f_block_arg + { + $$ = new_args_tail(p, Qnone, rb_intern("nil"), $2, &@1); + } | f_block_arg { $$ = new_args_tail(p, Qnone, Qnone, $1, &@1); @@ -4968,6 +4976,9 @@ kwrest_mark : tPOW | tDSTAR ; +f_no_kwarg : kwrest_mark keyword_nil + ; + f_kwrest : kwrest_mark tIDENTIFIER { arg_var(p, shadowing_lvar(p, get_id($2))); @@ -11125,6 +11136,9 @@ new_args_tail(struct parser_params *p, NODE *kw_args, ID kw_rest_arg, ID block, args->kw_rest_arg = NEW_DVAR(kw_rest_arg, loc); args->kw_rest_arg->nd_cflag = kw_bits; } + else if (kw_rest_arg == rb_intern("nil")) { + args->no_kwarg = 1; + } else if (kw_rest_arg) { args->kw_rest_arg = NEW_DVAR(kw_rest_arg, loc); } diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index 791d60b70a..1e707170fd 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -126,6 +126,34 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(1, f10(b: 42)) end + def f11(**nil) + local_variables + end + + def test_f11 + h = {} + + assert_equal([], f11) + assert_equal([], f11(**{})) + assert_equal([], f11(**h)) + end + + def f12(**nil, &b) + [b, local_variables] + end + + def test_f12 + h = {} + b = proc{} + + assert_equal([nil, [:b]], f12) + assert_equal([nil, [:b]], f12(**{})) + assert_equal([nil, [:b]], f12(**h)) + assert_equal([b, [:b]], f12(&b)) + assert_equal([b, [:b]], f12(**{}, &b)) + assert_equal([b, [:b]], f12(**h, &b)) + end + def test_method_parameters assert_equal([[:key, :str], [:key, :num]], method(:f1).parameters); assert_equal([[:req, :x], [:key, :str], [:key, :num]], method(:f2).parameters); diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index 02a95bc60b..72a3cc2fc7 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -249,6 +249,22 @@ class TestSyntax < Test::Unit::TestCase assert_syntax_error('def o.foo(@@foo: a) end', /class variable/) end + def test_keywords_specified_and_not_accepted + assert_syntax_error('def o.foo(a:, **nil) end', /unexpected/) + assert_syntax_error('def o.foo(a:, **nil, &b) end', /unexpected/) + assert_syntax_error('def o.foo(**a, **nil) end', /unexpected/) + assert_syntax_error('def o.foo(**a, **nil, &b) end', /unexpected/) + assert_syntax_error('def o.foo(**nil, **a) end', /unexpected/) + assert_syntax_error('def o.foo(**nil, **a, &b) end', /unexpected/) + + assert_syntax_error('proc do |a:, **nil| end', /unexpected/) + assert_syntax_error('proc do |a:, **nil, &b| end', /unexpected/) + assert_syntax_error('proc do |**a, **nil| end', /unexpected/) + assert_syntax_error('proc do |**a, **nil, &b| end', /unexpected/) + assert_syntax_error('proc do |**nil, **a| end', /unexpected/) + assert_syntax_error('proc do |**nil, **a, &b| end', /unexpected/) + end + def test_optional_self_reference bug9593 = '[ruby-core:61299] [Bug #9593]' o = Object.new diff --git a/vm_args.c b/vm_args.c index 4e404870da..4eccb62da6 100644 --- a/vm_args.c +++ b/vm_args.c @@ -702,6 +702,10 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co args->rest = Qfalse; } + if (kw_flag && iseq->body->param.flags.accepts_no_kwarg) { + rb_raise(rb_eArgError, "no keywords accepted"); + } + switch (arg_setup_type) { case arg_setup_method: break; /* do nothing special */ diff --git a/vm_core.h b/vm_core.h index cb22fc4115..479698932e 100644 --- a/vm_core.h +++ b/vm_core.h @@ -385,6 +385,7 @@ struct rb_iseq_constant_body { unsigned int has_block : 1; unsigned int ambiguous_param0 : 1; /* {|a|} */ + unsigned int accepts_no_kwarg : 1; } flags; unsigned int size;