summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--test/ruby/test_keyword.rb24
-rw-r--r--vm_args.c74
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")
diff --git a/vm_args.c b/vm_args.c
index 9d5626ce97..4e404870da 100644
--- a/vm_args.c
+++ b/vm_args.c
@@ -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) {