[前][次][番号順一覧][スレッド一覧]

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/

[前][次][番号順一覧][スレッド一覧]