1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

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
This commit is contained in:
nobu 2013-03-12 13:20:50 +00:00
parent 976a3041ef
commit 34a95669da
7 changed files with 121 additions and 13 deletions

View file

@ -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.

View file

@ -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:{

14
iseq.c
View file

@ -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]));

20
parse.y
View file

@ -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

View file

@ -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

View file

@ -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 */

View file

@ -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();
}