summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornobu <nobu@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2013-03-12 13:20:50 +0000
committernobu <nobu@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2013-03-12 13:20:50 +0000
commit34a95669dad8843e3e9a4af683682ab2f50856dd (patch)
tree165cd3bd3ed70a911853f55055bce1a86632a686
parent976a3041ef38711b84c6a35db25f189d31903e38 (diff)
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. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@39735 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r--ChangeLog14
-rw-r--r--compile.c29
-rw-r--r--iseq.c14
-rw-r--r--parse.y20
-rw-r--r--test/ruby/test_keyword.rb21
-rw-r--r--vm_core.h1
-rw-r--r--vm_insnhelper.c35
7 files changed, 121 insertions, 13 deletions
diff --git a/ChangeLog b/ChangeLog
index 5cf3488807..3fc99dde1d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+Tue Mar 12 22:20:47 2013 Nobuyoshi Nakada <nobu@ruby-lang.org>
+
+ * 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@no32.tk>
* date_core.c: clearly specify operator precedence.
diff --git a/compile.c b/compile.c
index f9ed3c0038..9360f5b791 100644
--- a/compile.c
+++ b/compile.c
@@ -1183,19 +1183,31 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *optargs, NODE *node_args)
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_ANCHOR *ret, NODE * node, int poped)
}
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_ANCHOR *ret, NODE * node, int poped)
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:{
diff --git a/iseq.c b/iseq.c
index 5ef737e193..288d3bfb05 100644
--- a/iseq.c
+++ b/iseq.c
@@ -1965,8 +1965,20 @@ rb_iseq_parameters(const rb_iseq_t *iseq, int is_proc)
}
}
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]));
diff --git a/parse.y b/parse.y
index 915db67631..62782709f8 100644
--- a/parse.y
+++ b/parse.y
@@ -4613,6 +4613,16 @@ f_kw : tLABEL arg_value
$$ = 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
$$ = 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
diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb
index 45ecc6a95e..b7b4301ca1 100644
--- a/test/ruby/test_keyword.rb
+++ b/test/ruby/test_keyword.rb
@@ -290,4 +290,25 @@ class TestKeywordArguments < Test::Unit::TestCase
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
diff --git a/vm_core.h b/vm_core.h
index 1838f2a099..286831feba 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -273,6 +273,7 @@ struct rb_iseq_struct {
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 */
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index 0c447aaeda..a6b307048a 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -143,21 +143,27 @@ argument_error(const rb_iseq_t *iseq, int miss_argc, int min_argc, int max_argc)
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_iseq_t *iseq, int argc, VALUE *orig_argv, V
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_iseq_t *iseq, int argc, VALUE *orig_argv, V
}
}
}
+ 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();
}