Bug #16187
closedHash#replace no longer rehashes keys for small (array table) hashes
Description
Here is a script that shows the problem
mutable_key = [1]
h = { mutable_key => 'a' }
mutable_key[0] = 2
p(h == {}.replace(h))
which outputs true
in ruby 2.5 and lower versions and false
in ruby 2.6 and later versions.
This is because ruby 2.6 introduced array table backed hashes and rb_hash_replace uses ar_copy(hash, hash2)
when both hashes use an array table. If either hash used a symbol table, then the it would rehash the keys. This can be shown by forcing an symbol table to be used using Hash#compare_by_identity
mutable_key = [1]
h = { mutable_key => 'a' }
h.compare_by_identity
mutable_key[0] = 2
p(h == {}.replace(h))
which returns true
in all versions of ruby.
I think Hash#replace should definitely be consistent about whether it rehashes or not.
In https://bugs.ruby-lang.org/issues/16121 ko1 (Koichi Sasada) suggested using rb_hash_replace to implement Hash#initialize_copy. Since Hash#dup already has tests to ensure that it rehashes, I think it makes sense to have Hash#replace go back to always rehashing so we can share an implementation between these methods. I've opened https://github.com/ruby/ruby/pull/2489 which does this and addresses https://bugs.ruby-lang.org/issues/16121