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:
parent
976a3041ef
commit
34a95669da
7 changed files with 121 additions and 13 deletions
14
ChangeLog
14
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.
|
||||
|
|
29
compile.c
29
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:{
|
||||
|
|
14
iseq.c
14
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]));
|
||||
|
|
20
parse.y
20
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue