diff options
-rw-r--r-- | hash.c | 30 | ||||
-rw-r--r-- | internal/hash.h | 1 | ||||
-rw-r--r-- | test/ruby/test_hash.rb | 10 |
3 files changed, 37 insertions, 4 deletions
@@ -2906,14 +2906,38 @@ rb_hash_replace(VALUE hash, VALUE hash2) else { RHASH_ST_CLEAR(hash); } - hash_copy(hash, hash2); + + if (RHASH_AR_TABLE_P(hash2)) { + if (RHASH_AR_TABLE_P(hash)) { + ar_copy(hash, hash2); + } + else { + st_table *tab = RHASH_ST_TABLE(hash); + rb_st_init_existing_table_with_size(tab, &objhash, RHASH_AR_TABLE_SIZE(hash2)); + + int bound = RHASH_AR_TABLE_BOUND(hash2); + for (int i = 0; i < bound; i++) { + if (ar_cleared_entry(hash2, i)) continue; + + ar_table_pair *pair = RHASH_AR_TABLE_REF(hash2, i); + st_add_direct(tab, pair->key, pair->val); + RB_OBJ_WRITTEN(hash, Qundef, pair->key); + RB_OBJ_WRITTEN(hash, Qundef, pair->val); + } + } + } + else { + HASH_ASSERT(sizeof(st_table) <= sizeof(ar_table)); + + RHASH_ST_TABLE_SET(hash, st_copy(RHASH_ST_TABLE(hash2))); + rb_gc_writebarrier_remember(hash); + } + if (RHASH_EMPTY_P(hash2) && RHASH_ST_TABLE_P(hash2)) { /* ident hash */ hash_st_table_init(hash, RHASH_TYPE(hash2), 0); } - rb_gc_writebarrier_remember(hash); - return hash; } diff --git a/internal/hash.h b/internal/hash.h index 248a53429d..3874abeaa5 100644 --- a/internal/hash.h +++ b/internal/hash.h @@ -179,7 +179,6 @@ static inline void RHASH_ST_CLEAR(VALUE h) { memset(RHASH_ST_TABLE(h), 0, sizeof(st_table)); - FL_UNSET_RAW(h, RHASH_ST_TABLE_FLAG); } static inline unsigned diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb index 8b6065146c..d9d1ca7dde 100644 --- a/test/ruby/test_hash.rb +++ b/test/ruby/test_hash.rb @@ -850,6 +850,16 @@ class TestHash < Test::Unit::TestCase assert(true) end + def test_replace_st_with_ar + # ST hash + h1 = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9 } + # AR hash + h2 = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 } + # Replace ST hash with AR hash + h1.replace(h2) + assert_equal(h2, h1) + end + def test_shift h = @h.dup |