diff --git a/lib-topaz/array.rb b/lib-topaz/array.rb index d1ea4eea9..ee0504235 100644 --- a/lib-topaz/array.rb +++ b/lib-topaz/array.rb @@ -209,13 +209,39 @@ def eql?(other) return true end + def inner_hash(res) + # This is the inner part of the hash value computation where we loop over + # our contents. If we find any recursion, we throw :array_hash_recursion to + # escape to the top level and ignore any hashing we've done inside the + # recursive array. + Thread.current.recursion_guard(self) do + self.each do |x| + # We want to keep this within a fixnum range. + res = Topaz.intmask((1000003 * res) ^ x.hash.to_int) + end + return res + end + throw :array_hash_recursion + end + + private :inner_hash + def hash - res = 0x345678 - self.each do |x| - # We want to keep this within a fixnum range. - res = Topaz.intmask((1000003 * res) ^ x.hash) + # Arrays of different lengths should hash to different values. + res = 0x345678 + self.length + # We need to stop calculating the hash value at the top level of a + # recursive array so that `a' and `[a]' (which are equal) have the same + # hash value. If we're in a recursion guard already, we assume there's + # already a suitable catch block higher up the stack. Otherwise we catch + # :array_hash_recursion and just return a length-based hash value. + if Thread.current.in_recursion_guard? + return self.inner_hash(res) + else + catch(:array_hash_recursion) do + return self.inner_hash(res) + end + return res end - return res end def *(arg) diff --git a/spec/tags/core/array/hash_tags.txt b/spec/tags/core/array/hash_tags.txt index 712062405..e84ab568e 100644 --- a/spec/tags/core/array/hash_tags.txt +++ b/spec/tags/core/array/hash_tags.txt @@ -1,7 +1,3 @@ -fails:properly handles recursive arrays -fails:returns the same hash for equal recursive arrays -fails:Array#hash calls to_int on result of calling hash on each element -fails:Array#hash ignores array class differences fails:Array#hash returns same hash code for arrays with the same content fails:Array#hash returns the same value if arrays are #eql? -fails:Array#hash +fails:Array#hash returns the same hash for equal recursive arrays through hashes diff --git a/topaz/objects/threadobject.py b/topaz/objects/threadobject.py index c3e7d7b12..aff8fc0d9 100644 --- a/topaz/objects/threadobject.py +++ b/topaz/objects/threadobject.py @@ -41,3 +41,9 @@ def method_recursion_guard(self, space, w_obj, block): return space.w_true space.invoke_block(block, []) return space.w_false + + @classdef.method("in_recursion_guard?") + def method_in_recursion_guardp(self, space): + if space.getexecutioncontext().recursive_objects: + return space.w_true + return space.w_false