diff options
-rw-r--r-- | test/ruby/test_keyword.rb | 24 | ||||
-rw-r--r-- | vm_args.c | 74 |
2 files changed, 86 insertions, 12 deletions
diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index d0d64520e4..791d60b70a 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -284,6 +284,30 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(expect.values_at(0, -1), pr.call(expect), bug8463) end + def opt_plus_keyword(x=1, **h) + [x, h] + end + + def splat_plus_keyword(*a, **h) + [a, h] + end + + def test_keyword_split + assert_equal([1, {:a=>1}], opt_plus_keyword(:a=>1)) + assert_equal([1, {"a"=>1}], opt_plus_keyword("a"=>1)) + assert_equal([1, {"a"=>1, :a=>1}], opt_plus_keyword("a"=>1, :a=>1)) + assert_equal([1, {:a=>1}], opt_plus_keyword({:a=>1})) + assert_equal([{"a"=>1}, {}], opt_plus_keyword({"a"=>1})) + assert_equal([{"a"=>1}, {:a=>1}], opt_plus_keyword({"a"=>1, :a=>1})) + + assert_equal([[], {:a=>1}], splat_plus_keyword(:a=>1)) + assert_equal([[], {"a"=>1}], splat_plus_keyword("a"=>1)) + assert_equal([[], {"a"=>1, :a=>1}], splat_plus_keyword("a"=>1, :a=>1)) + assert_equal([[], {:a=>1}], splat_plus_keyword({:a=>1})) + assert_equal([[{"a"=>1}], {}], splat_plus_keyword({"a"=>1})) + assert_equal([[{"a"=>1}], {:a=>1}], splat_plus_keyword({"a"=>1, :a=>1})) + end + def test_bare_kwrest # valid syntax, but its semantics is undefined assert_valid_syntax("def bug7662(**) end") @@ -183,23 +183,47 @@ args_rest_array(struct args_info *args) return ary; } +#define KW_HASH_HAS_NO_KEYS 0 +#define KW_HASH_HAS_SYMBOL_KEY 1 +#define KW_HASH_HAS_OTHER_KEY 2 +#define KW_HASH_HAS_BOTH_KEYS 3 + static int -keyword_hash_symbol_p(st_data_t key, st_data_t val, st_data_t arg) +keyword_hash_symbol_other_iter(st_data_t key, st_data_t val, st_data_t arg) { - if (SYMBOL_P((VALUE)key)) { - return ST_CONTINUE; + *(int*)arg |= SYMBOL_P((VALUE)key) ? KW_HASH_HAS_SYMBOL_KEY : KW_HASH_HAS_OTHER_KEY; + + if ((*(int*)arg & KW_HASH_HAS_BOTH_KEYS) == KW_HASH_HAS_BOTH_KEYS) { + return ST_STOP; } - *(VALUE*)arg = (VALUE)0; - return ST_STOP; + return ST_CONTINUE; +} + +static int +keyword_hash_symbol_other(VALUE hash) +{ + int symbol_other = KW_HASH_HAS_NO_KEYS; + rb_hash_stlike_foreach(hash, keyword_hash_symbol_other_iter, (st_data_t)(&symbol_other)); + return symbol_other; } static int -keyword_hash_only_symbol_p(VALUE hash) +keyword_hash_split_iter(st_data_t key, st_data_t val, st_data_t arg) +{ + if (SYMBOL_P((VALUE)key)) { + rb_hash_aset((VALUE)arg, (VALUE)key, (VALUE)val); + return ST_DELETE; + } + + return ST_CONTINUE; +} + +void +keyword_hash_split(VALUE *kw_hash_ptr, VALUE *rest_hash_ptr) { - VALUE all_symbols = (VALUE)(1); - rb_hash_stlike_foreach(hash, keyword_hash_symbol_p, (st_data_t)(&all_symbols)); - return (int)all_symbols; + *kw_hash_ptr = rb_hash_new(); + rb_hash_stlike_foreach(*rest_hash_ptr, keyword_hash_split_iter, (st_data_t)(*kw_hash_ptr)); } static int @@ -208,9 +232,18 @@ keyword_hash_p(VALUE *kw_hash_ptr, VALUE *rest_hash_ptr, int check_only_symbol) *rest_hash_ptr = rb_check_hash_type(*kw_hash_ptr); if (!NIL_P(*rest_hash_ptr)) { - if (check_only_symbol && !keyword_hash_only_symbol_p(*rest_hash_ptr)) { - *kw_hash_ptr = Qnil; - return FALSE; + if (check_only_symbol) { + switch(keyword_hash_symbol_other(*rest_hash_ptr)) { + case KW_HASH_HAS_NO_KEYS: + case KW_HASH_HAS_SYMBOL_KEY: + break; + case KW_HASH_HAS_OTHER_KEY: + *kw_hash_ptr = Qnil; + return FALSE; + case KW_HASH_HAS_BOTH_KEYS: + keyword_hash_split(kw_hash_ptr, rest_hash_ptr); + return TRUE; + } } *kw_hash_ptr = *rest_hash_ptr; *rest_hash_ptr = Qfalse; @@ -552,6 +585,20 @@ get_loc(struct rb_calling_info *calling, const struct rb_call_info *ci) } static inline void +rb_warn_split_last_hash_to_keyword(struct rb_calling_info *calling, const struct rb_call_info *ci) +{ + if (calling->recv == Qundef) return; + VALUE loc = get_loc(calling, ci); + if (NIL_P(loc)) { + rb_warn("The last argument for `%s' is split into positional and keyword parameters", rb_id2name(ci->mid)); + } + else { + rb_warn("The last argument for `%s' (defined at %s:%d) is split into positional and keyword parameters", + rb_id2name(ci->mid), RSTRING_PTR(RARRAY_AREF(loc, 0)), FIX2INT(RARRAY_AREF(loc, 1))); + } +} + +static inline void rb_warn_last_hash_to_keyword(struct rb_calling_info *calling, const struct rb_call_info *ci) { if (calling->recv == Qundef) return; @@ -710,6 +757,9 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co } given_argc--; } + else if (keyword_hash != Qnil && ec->cfp->iseq) { + rb_warn_split_last_hash_to_keyword(calling, ci); + } } if (given_argc > max_argc && max_argc != UNLIMITED_ARGUMENTS) { |