hash.c: Add a feature to manipulate ruby2_keywords flag

It was found that a feature to check and add ruby2_keywords flag to an
existing Hash is needed when arguments are serialized and deserialized.
It is possible to do the same without explicit APIs, but it would be
good to provide them as a core feature.

https://github.com/rails/rails/pull/38105#discussion_r361863767

Hash.ruby2_keywords_hash?(hash) checks if hash is flagged or not.
Hash.ruby2_keywords_hash(hash) returns a duplicated hash that has a
ruby2_keywords flag,

[Bug #16486]
This commit is contained in:
Yusuke Endoh 2020-01-06 18:22:43 +09:00
Родитель b23fd59cbb
Коммит 7cfe93c028
2 изменённых файлов: 73 добавлений и 0 удалений

49
hash.c
Просмотреть файл

@ -1874,6 +1874,52 @@ rb_hash_s_try_convert(VALUE dummy, VALUE hash)
return rb_check_hash_type(hash);
}
/*
* call-seq:
* Hash.ruby2_keywords_hash?(hash) -> true or false
*
* Checks if a given hash is flagged by Module#ruby2_keywords (or
* Proc#ruby2_keywords).
* This method is not for casual use; debugging, researching, and
* some truly necessary cases like serialization of arguments.
*
* ruby2_keywords def foo(*args)
* Hash.ruby2_keywords_hash?(args.last)
* end
* foo(k: 1) #=> true
* foo({k: 1}) #=> false
*/
static VALUE
rb_hash_s_ruby2_keywords_hash_p(VALUE dummy, VALUE hash)
{
Check_Type(hash, T_HASH);
return (RHASH(hash)->basic.flags & RHASH_PASS_AS_KEYWORDS) ? Qtrue : Qfalse;
}
/*
* call-seq:
* Hash.ruby2_keywords_hash(hash) -> hash
*
* Duplicates a given hash and adds a ruby2_keywords flag.
* This method is not for casual use; debugging, researching, and
* some truly necessary cases like deserialization of arguments.
*
* h = {k: 1}
* h = Hash.ruby2_keywords_hash(h)
* def foo(k: 42)
* k
* end
* foo(*[h]) #=> 1 with neither a warning or an error
*/
static VALUE
rb_hash_s_ruby2_keywords_hash(VALUE dummy, VALUE hash)
{
Check_Type(hash, T_HASH);
hash = rb_hash_dup(hash);
RHASH(hash)->basic.flags |= RHASH_PASS_AS_KEYWORDS;
return hash;
}
struct rehash_arg {
VALUE hash;
st_table *tbl;
@ -6415,6 +6461,9 @@ Init_Hash(void)
rb_define_method(rb_cHash, "deconstruct_keys", rb_hash_deconstruct_keys, 1);
rb_define_singleton_method(rb_cHash, "ruby2_keywords_hash?", rb_hash_s_ruby2_keywords_hash_p, 1);
rb_define_singleton_method(rb_cHash, "ruby2_keywords_hash", rb_hash_s_ruby2_keywords_hash, 1);
/* Document-class: ENV
*
* ENV is a hash-like accessor for environment variables.

Просмотреть файл

@ -1756,4 +1756,28 @@ class TestHash < Test::Unit::TestCase
super
end
end
ruby2_keywords def get_flagged_hash(*args)
args.last
end
def check_flagged_hash(k: :NG)
k
end
def test_ruby2_keywords_hash?
flagged_hash = get_flagged_hash(k: 1)
assert_equal(true, Hash.ruby2_keywords_hash?(flagged_hash))
assert_equal(false, Hash.ruby2_keywords_hash?({}))
assert_raise(TypeError) { Hash.ruby2_keywords_hash?(1) }
end
def test_ruby2_keywords_hash!
hash = {k: 1}
assert_equal(false, Hash.ruby2_keywords_hash?(hash))
hash = Hash.ruby2_keywords_hash(hash)
assert_equal(true, Hash.ruby2_keywords_hash?(hash))
assert_equal(1, check_flagged_hash(*[hash]))
assert_raise(TypeError) { Hash.ruby2_keywords_hash(1) }
end
end