diff -r bb6cababa5bd -r 552af46d26c2 Lib/test/test_dict_jy.py --- a/Lib/test/test_dict_jy.py Sun May 17 09:10:22 2015 +0100 +++ b/Lib/test/test_dict_jy.py Thu Jun 18 19:57:46 2015 -0700 @@ -257,6 +257,102 @@ return newdict +class Issue2367DictTest(unittest.TestCase): + + class HashDict(dict): + """Dict with __hash__ and __eq__ that checks both type and dict values.""" + def __hash__(self): + return hash(frozenset(self.items())) + + def __eq__(self, other): + if type(self) is type(other): + return dict.__eq__(self, other) + return False + + def __ne__(self, other): + return not(self == other) + + class Dict1(HashDict): + pass + + class Dict2(HashDict): + pass + + class UnhashableDict(dict): + pass + + a = Dict1() + b = Dict2() + x = UnhashableDict() + + def test_equal_hashes(self): + """Ensure that we are in fact dealing with a hash collision. + + """ + self.assertEqual(hash(self.a), hash(self.b)) + + def test_unequal_objects(self): + """Test that the objects do in fact compare unequal, and should be + simultaneously usable as keys. + + """ + # Ensures no funny business is going on with __ne__ and __eq__ + self.assertTrue(self.a != self.b) + self.assertTrue(self.b != self.a) + self.assertFalse(self.a == self.b) + self.assertFalse(self.b == self.a) + self.assertFalse(self.a.__eq__(self.b)) + self.assertFalse(self.b.__eq__(self.a)) + + def test_distinct_as_keys(self): + """Test that both elements may be used as different keys in a dict. + + """ + # Python should first compare by hash. As the hashes are the same, it + # should proceed to compare for equality. As Dict1 and Dict2 have + # different types, their __eq__ methods will return false, thus we + # should have a hash collision, and both elements should be in the + # dict. + # + # In CPython and PyPy, this is what happens. Jython seems to ignore + # the custom __eq__. + d = {} + d[self.a] = 1 + d[self.b] = 2 + self.assertEqual(len(d), 2) + + def test_both_in_set(self): + """That both objects may be placed in a set. + + """ + s = {self.a, self.b} + self.assertIn(self.a, s) + self.assertIn(self.b, s) + + def test_distinct_in_set(self): + """Test that both elements are viewd as distinct in a set. + + """ + s = {self.a, self.b} + self.assertEqual(len(s), 2) + + def test_hash_collisions_on_same_type(self): + """Test that hash collisions on same type are resolved normally. + + """ + d1 = self.Dict1({2**128: 1}) + d2 = self.Dict1({hash(2**128): 1}) + self.assertEqual(hash(d1), hash(d2)) + d = {} + d[d1] = 1 + d[d2] = 2 + self.assertEqual(2, len(d)) + + def test_unhashable_dict(self): + with self.assertRaises(TypeError): + s = {self.x,} + + def test_main(): test_support.run_unittest( DictInitTest, @@ -265,7 +361,8 @@ DerivedDictTest, JavaIntegrationTest, JavaDictTest, - PyStringMapTest) + PyStringMapTest, + Issue2367DictTest) if __name__ == '__main__': test_main() diff -r bb6cababa5bd -r 552af46d26c2 src/org/python/core/PyDictionaryDerived.java --- a/src/org/python/core/PyDictionaryDerived.java Sun May 17 09:10:22 2015 +0100 +++ b/src/org/python/core/PyDictionaryDerived.java Thu Jun 18 19:57:46 2015 -0700 @@ -590,7 +590,7 @@ public PyObject __eq__(PyObject other) { PyType self_type=getType(); PyObject impl=self_type.lookup("__eq__"); - if (impl!=null) { + if (impl != Py.None) { PyObject res=impl.__get__(this,self_type).__call__(other); if (res==Py.NotImplemented) return null; @@ -803,10 +803,11 @@ return super.__long__(); } + @Override public int hashCode() { PyType self_type=getType(); PyObject impl=self_type.lookup("__hash__"); - if (impl!=null) { + if (impl != Py.None) { PyObject res=impl.__get__(this,self_type).__call__(); if (res instanceof PyInteger) { return((PyInteger)res).getValue(); @@ -822,6 +823,23 @@ return super.hashCode(); } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof PyObject) { + PyObject res = __eq__((PyObject) obj); + if (res == null) { + // the other object does not implement __eq__! + // CPython's implementation seems to treat this as "equal" + return true; + } + return res.__nonzero__(); + } + return false; + } + public PyUnicode __unicode__() { PyType self_type=getType(); PyObject impl=self_type.lookup("__unicode__");