Skip to content

Commit 0785951

Browse files
committed
change hash
1 parent eea6eca commit 0785951

File tree

3 files changed

+50
-16
lines changed

3 files changed

+50
-16
lines changed

Lib/test/test_types.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -723,10 +723,39 @@ class B(metaclass=UnhashableMeta): ...
723723

724724
self.assertEqual((A | B).__args__, (A, B))
725725
union1 = A | B
726+
with self.assertRaisesRegex(TypeError, "unhashable type: 'UnhashableMeta'"):
727+
hash(union1)
728+
726729
union2 = int | B
730+
with self.assertRaisesRegex(TypeError, "unhashable type: 'UnhashableMeta'"):
731+
hash(union2)
732+
727733
union3 = A | int
734+
with self.assertRaisesRegex(TypeError, "unhashable type: 'UnhashableMeta'"):
735+
hash(union3)
736+
737+
def test_unhashable_becomes_hashable(self):
738+
is_hashable = False
739+
class UnhashableMeta(type):
740+
def __hash__(self):
741+
if is_hashable:
742+
return 1
743+
else:
744+
raise TypeError("not hashable")
745+
746+
class A(metaclass=UnhashableMeta): ...
747+
class B(metaclass=UnhashableMeta): ...
748+
749+
union = A | B
750+
self.assertEqual(union.__args__, (A, B))
751+
752+
with self.assertRaisesRegex(TypeError, "not hashable"):
753+
hash(union)
754+
755+
is_hashable = True
728756

729-
self.assertEqual(len({union1, union2, union3}), 3)
757+
with self.assertRaisesRegex(TypeError, "union contains 2 unhashable elements"):
758+
hash(union)
730759

731760
def test_instancecheck_and_subclasscheck(self):
732761
for x in (int | str, typing.Union[int, str]):

Lib/test/test_typing.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2062,10 +2062,16 @@ class B(metaclass=UnhashableMeta): ...
20622062

20632063
self.assertEqual(Union[A, B].__args__, (A, B))
20642064
union1 = Union[A, B]
2065+
with self.assertRaisesRegex(TypeError, "unhashable type: 'UnhashableMeta'"):
2066+
hash(union1)
2067+
20652068
union2 = Union[int, B]
2066-
union3 = Union[A, int]
2069+
with self.assertRaisesRegex(TypeError, "unhashable type: 'UnhashableMeta'"):
2070+
hash(union2)
20672071

2068-
self.assertEqual(len({union1, union2, union3}), 3)
2072+
union3 = Union[A, int]
2073+
with self.assertRaisesRegex(TypeError, "unhashable type: 'UnhashableMeta'"):
2074+
hash(union3)
20692075

20702076
def test_repr(self):
20712077
u = Union[Employee, int]

Objects/unionobject.c

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,26 +46,25 @@ static Py_hash_t
4646
union_hash(PyObject *self)
4747
{
4848
unionobject *alias = (unionobject *)self;
49-
Py_hash_t hash;
50-
if (alias->hashable_args) {
51-
hash = PyObject_Hash(alias->hashable_args);
52-
if (hash == -1) {
53-
return -1;
54-
}
55-
}
56-
else {
57-
hash = 604;
58-
}
59-
// Mix in the ids of all the unhashable args.
49+
// If there are any unhashable args, treat this union as unhashable.
50+
// Otherwise, two unions might compare equal but have different hashes.
6051
if (alias->unhashable_args) {
52+
// Attempt to get an error from one of the values.
6153
assert(PyTuple_CheckExact(alias->unhashable_args));
6254
Py_ssize_t n = PyTuple_GET_SIZE(alias->unhashable_args);
6355
for (Py_ssize_t i = 0; i < n; i++) {
6456
PyObject *arg = PyTuple_GET_ITEM(alias->unhashable_args, i);
65-
hash ^= (Py_hash_t)arg;
57+
Py_hash_t hash = PyObject_Hash(arg);
58+
if (hash == -1) {
59+
return -1;
60+
}
6661
}
62+
// The unhashable values somehow became hashable again. Still raise
63+
// an error.
64+
PyErr_Format(PyExc_TypeError, "union contains %d unhashable elements", n);
65+
return -1;
6766
}
68-
return hash;
67+
return PyObject_Hash(alias->hashable_args);
6968
}
7069

7170
static int

0 commit comments

Comments
 (0)