ruby-changes:27683
From: nobu <ko1@a...>
Date: Tue, 12 Mar 2013 22:21:06 +0900 (JST)
Subject: [ruby-changes:27683] nobu:r39735 (trunk): required keyword arguments
nobu 2013-03-12 22:20:50 +0900 (Tue, 12 Mar 2013) New Revision: 39735 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=39735 Log: required keyword arguments * compile.c (iseq_set_arguments, iseq_compile_each): support required keyword arguments. [ruby-core:51454] [Feature #7701] * iseq.c (rb_iseq_parameters): ditto. * parse.y (f_kw, f_block_kw): ditto. this syntax is still experimental, the notation may change. * vm_core.h (rb_iseq_struct): ditto. * vm_insnhelper.c (vm_callee_setup_keyword_arg): ditto. Modified files: trunk/ChangeLog trunk/compile.c trunk/iseq.c trunk/parse.y trunk/test/ruby/test_keyword.rb trunk/vm_core.h trunk/vm_insnhelper.c Index: ChangeLog =================================================================== --- ChangeLog (revision 39734) +++ ChangeLog (revision 39735) @@ -1,3 +1,17 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1 +Tue Mar 12 22:20:47 2013 Nobuyoshi Nakada <nobu@r...> + + * compile.c (iseq_set_arguments, iseq_compile_each): support required + keyword arguments. [ruby-core:51454] [Feature #7701] + + * iseq.c (rb_iseq_parameters): ditto. + + * parse.y (f_kw, f_block_kw): ditto. this syntax is still + experimental, the notation may change. + + * vm_core.h (rb_iseq_struct): ditto. + + * vm_insnhelper.c (vm_callee_setup_keyword_arg): ditto. + Tue Mar 12 17:02:53 2013 TAKANO Mitsuhiro <tak@n...> * date_core.c: clearly specify operator precedence. Index: vm_core.h =================================================================== --- vm_core.h (revision 39734) +++ vm_core.h (revision 39735) @@ -273,6 +273,7 @@ struct rb_iseq_struct { https://github.com/ruby/ruby/blob/trunk/vm_core.h#L273 int arg_keyword; int arg_keyword_check; /* if this is true, raise an ArgumentError when unknown keyword argument is passed */ int arg_keywords; + int arg_keyword_required; ID *arg_keyword_table; size_t stack_max; /* for stack overflow check */ Index: iseq.c =================================================================== --- iseq.c (revision 39734) +++ iseq.c (revision 39735) @@ -1965,8 +1965,20 @@ rb_iseq_parameters(const rb_iseq_t *iseq https://github.com/ruby/ruby/blob/trunk/iseq.c#L1965 } } if (iseq->arg_keyword != -1) { + i = 0; + if (iseq->arg_keyword_required) { + ID keyreq; + CONST_ID(keyreq, "keyreq"); + for (; i < iseq->arg_keyword_required; i++) { + PARAM_TYPE(keyreq); + if (rb_id2str(iseq->arg_keyword_table[i])) { + rb_ary_push(a, ID2SYM(iseq->arg_keyword_table[i])); + } + rb_ary_push(args, a); + } + } CONST_ID(key, "key"); - for (i = 0; i < iseq->arg_keywords; i++) { + for (; i < iseq->arg_keywords; i++) { PARAM_TYPE(key); if (rb_id2str(iseq->arg_keyword_table[i])) { rb_ary_push(a, ID2SYM(iseq->arg_keyword_table[i])); Index: compile.c =================================================================== --- compile.c (revision 39734) +++ compile.c (revision 39735) @@ -1183,19 +1183,31 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK https://github.com/ruby/ruby/blob/trunk/compile.c#L1183 if (args->kw_args) { NODE *node = args->kw_args; VALUE keywords = rb_ary_tmp_new(1); - int i = 0, j; + VALUE required = 0; + int i = 0, j, r = 0; iseq->arg_keyword = get_dyna_var_idx_at_raw(iseq, args->kw_rest_arg->nd_vid); COMPILE(optargs, "kwarg", args->kw_rest_arg); while (node) { - rb_ary_push(keywords, INT2FIX(node->nd_body->nd_vid)); + VALUE list = keywords; + if (node->nd_body->nd_value == (NODE *)-1) { + ++r; + if (!required) required = rb_ary_tmp_new(1); + list = required; + } + rb_ary_push(list, INT2FIX(node->nd_body->nd_vid)); COMPILE_POPED(optargs, "kwarg", node); /* nd_type(node) == NODE_KW_ARG */ node = node->nd_next; i += 1; } iseq->arg_keyword_check = (args->kw_rest_arg->nd_vid & ID_SCOPE_MASK) == ID_JUNK; iseq->arg_keywords = i; + iseq->arg_keyword_required = r; iseq->arg_keyword_table = ALLOC_N(ID, i); + if (r) { + rb_ary_concat(required, keywords); + keywords = required; + } for (j = 0; j < i; j++) { iseq->arg_keyword_table[j] = FIX2INT(RARRAY_PTR(keywords)[j]); } @@ -5200,7 +5212,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ https://github.com/ruby/ruby/blob/trunk/compile.c#L5212 } case NODE_KW_ARG:{ LABEL *default_label = NEW_LABEL(line); - LABEL *end_label = NEW_LABEL(line); + LABEL *end_label = 0; int idx, lv, ls; ID id = node->nd_body->nd_vid; @@ -5224,10 +5236,15 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ https://github.com/ruby/ruby/blob/trunk/compile.c#L5236 default: rb_bug("iseq_compile_each (NODE_KW_ARG): unknown node: %s", ruby_node_name(nd_type(node->nd_body))); } - ADD_INSNL(ret, line, jump, end_label); + if (node->nd_body->nd_value != (NODE *)-1) { + end_label = NEW_LABEL(nd_line(node)); + ADD_INSNL(ret, nd_line(node), jump, end_label); + } ADD_LABEL(ret, default_label); - COMPILE_POPED(ret, "keyword default argument", node->nd_body); - ADD_LABEL(ret, end_label); + if (node->nd_body->nd_value != (NODE *)-1) { + COMPILE_POPED(ret, "keyword default argument", node->nd_body); + ADD_LABEL(ret, end_label); + } break; } case NODE_DSYM:{ Index: parse.y =================================================================== --- parse.y (revision 39734) +++ parse.y (revision 39735) @@ -4613,6 +4613,16 @@ f_kw : tLABEL arg_value https://github.com/ruby/ruby/blob/trunk/parse.y#L4613 $$ = rb_assoc_new($$, $2); %*/ } + | tLABEL + { + arg_var(formal_argument(get_id($1))); + $$ = assignable($1, (NODE *)-1); + /*%%%*/ + $$ = NEW_KW_ARG(0, $$); + /*% + $$ = rb_assoc_new($$, 0); + %*/ + } ; f_block_kw : tLABEL primary_value @@ -4625,6 +4635,16 @@ f_block_kw : tLABEL primary_value https://github.com/ruby/ruby/blob/trunk/parse.y#L4635 $$ = rb_assoc_new($$, $2); %*/ } + | tLABEL + { + arg_var(formal_argument(get_id($1))); + $$ = assignable($1, (NODE *)-1); + /*%%%*/ + $$ = NEW_KW_ARG(0, $$); + /*% + $$ = rb_assoc_new($$, 0); + %*/ + } ; f_block_kwarg : f_block_kw Index: vm_insnhelper.c =================================================================== --- vm_insnhelper.c (revision 39734) +++ vm_insnhelper.c (revision 39735) @@ -143,21 +143,27 @@ argument_error(const rb_iseq_t *iseq, in https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L143 rb_exc_raise(exc); } +NORETURN(static void keyword_error(const char *error, VALUE keys)); +static void +keyword_error(const char *error, VALUE keys) +{ + const char *msg = RARRAY_LEN(keys) == 1 ? "" : "s"; + keys = rb_ary_join(keys, rb_usascii_str_new2(", ")); + rb_raise(rb_eArgError, "%s keyword%s: %"PRIsVALUE, error, msg, keys); +} + NORETURN(static void unknown_keyword_error(const rb_iseq_t *iseq, VALUE hash)); static void unknown_keyword_error(const rb_iseq_t *iseq, VALUE hash) { - VALUE sep = rb_usascii_str_new2(", "), keys; - const char *msg; + VALUE keys; int i; for (i = 0; i < iseq->arg_keywords; i++) { rb_hash_delete(hash, ID2SYM(iseq->arg_keyword_table[i])); } keys = rb_funcall(hash, rb_intern("keys"), 0, 0); if (!RB_TYPE_P(keys, T_ARRAY)) rb_raise(rb_eArgError, "unknown keyword"); - msg = RARRAY_LEN(keys) == 1 ? "" : "s"; - keys = rb_funcall(keys, rb_intern("join"), 1, sep); - rb_raise(rb_eArgError, "unknown keyword%s: %"PRIsVALUE, msg, keys); + keyword_error("unknown", keys); } void @@ -1075,7 +1081,17 @@ vm_callee_setup_keyword_arg(const rb_ise https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L1081 argc--; keyword_hash = rb_hash_dup(keyword_hash); if (iseq->arg_keyword_check) { - for (i = j = 0; i < iseq->arg_keywords; i++) { + VALUE missing = Qnil; + for (i = 0; i < iseq->arg_keyword_required; i++) { + if (st_lookup(RHASH_TBL(keyword_hash), ID2SYM(iseq->arg_keyword_table[i]), 0)) + continue; + if (NIL_P(missing)) missing = rb_ary_tmp_new(1); + rb_ary_push(missing, ID2SYM(iseq->arg_keyword_table[i])); + } + if (!NIL_P(missing)) { + keyword_error("missing", missing); + } + for (j = i; i < iseq->arg_keywords; i++) { if (st_lookup(RHASH_TBL(keyword_hash), ID2SYM(iseq->arg_keyword_table[i]), 0)) j++; } if (RHASH_TBL(keyword_hash)->num_entries > (unsigned int) j) { @@ -1083,6 +1099,13 @@ vm_callee_setup_keyword_arg(const rb_ise https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L1099 } } } + else if (iseq->arg_keyword_check && iseq->arg_keyword_required) { + VALUE missing = rb_ary_tmp_new(iseq->arg_keyword_required); + for (i = 0; i < iseq->arg_keyword_required; i++) { + rb_ary_push(missing, ID2SYM(iseq->arg_keyword_table[i])); + } + keyword_error("missing", missing); + } else { keyword_hash = rb_hash_new(); } Index: test/ruby/test_keyword.rb =================================================================== --- test/ruby/test_keyword.rb (revision 39734) +++ test/ruby/test_keyword.rb (revision 39735) @@ -290,4 +290,25 @@ class TestKeywordArguments < Test::Unit: https://github.com/ruby/ruby/blob/trunk/test/ruby/test_keyword.rb#L290 assert_equal(1, o.bug7942(), bug7942) assert_equal(42, o.bug7942(a: 42), bug7942) end + + def test_required_keyword + feature7701 = '[ruby-core:51454] [Feature #7701] required keyword argument' + o = Object.new + assert_nothing_raised(SyntaxError, feature7701) do + eval("def o.foo(a:) a; end") + end + assert_raise(ArgumentError, feature7701) {o.foo} + assert_equal(42, o.foo(a: 42), feature7701) + assert_equal([[:keyreq, :a]], o.method(:foo).parameters, feature7701) + end + + def test_block_required_keyword + feature7701 = '[ruby-core:51454] [Feature #7701] required keyword argument' + b = assert_nothing_raised(SyntaxError, feature7701) do + break eval("proc {|a:| a}") + end + assert_raise(ArgumentError, feature7701) {b.call} + assert_equal(42, b.call(a: 42), feature7701) + assert_equal([[:keyreq, :a]], b.parameters, feature7701) + end end -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/