From 9c769b1133c6bccba6309e1912f69331c1a0235f Mon Sep 17 00:00:00 2001 From: Bhuvansh Kataria Date: Sun, 2 Aug 2026 22:03:26 +0000 Subject: [PATCH 1/2] gh-155102: Preserve set discard/remove comparison errors --- Lib/test/test_set.py | 23 +++++++++++++++++++++++ Objects/setobject.c | 32 ++++++++++++++++++-------------- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_set.py b/Lib/test/test_set.py index 9bfd4bc7d636699..27c18bc2e60c1c2 100644 --- a/Lib/test/test_set.py +++ b/Lib/test/test_set.py @@ -471,6 +471,29 @@ def test_discard(self): self.assertNotIn(self.thetype(self.word), s) s.discard(self.thetype(self.word)) + def test_discard_remove_hashable_set_subclass_eq_typeerror(self): + class Bad: + def __hash__(self): + return 1 + + def __eq__(self, other): + raise TypeError("boom from __eq__") + + class HashableSet(set): + def __hash__(self): + return 1 + + probe = HashableSet() + + with self.assertRaisesRegex(TypeError, "boom from __eq__"): + probe in {Bad()} + + with self.assertRaisesRegex(TypeError, "boom from __eq__"): + {Bad()}.discard(probe) + + with self.assertRaisesRegex(TypeError, "boom from __eq__"): + {Bad()}.remove(probe) + def test_pop(self): for i in range(len(self.s)): elem = self.s.pop() diff --git a/Objects/setobject.c b/Objects/setobject.c index 8fdd1eb26118c0a..1c101b0fceb201b 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -2653,20 +2653,22 @@ set_remove_impl(PySetObject *so, PyObject *key) { int rv; - rv = set_discard_key(so, key); - if (rv < 0) { - if (!PySet_Check(key) || !PyErr_ExceptionMatches(PyExc_TypeError)) + Py_hash_t hash = PyObject_Hash(key); + if (hash == -1) { + if (!PySet_Check(key) || !PyErr_ExceptionMatches(PyExc_TypeError)) { + set_unhashable_type(key); return NULL; + } PyErr_Clear(); - Py_hash_t hash; Py_BEGIN_CRITICAL_SECTION(key); hash = frozenset_hash_impl(key); Py_END_CRITICAL_SECTION(); - rv = set_discard_entry(so, key, hash); - if (rv < 0) - return NULL; } + rv = set_discard_entry(so, key, hash); + if (rv < 0) + return NULL; + if (rv == DISCARD_NOTFOUND) { _PyErr_SetKeyError(key); return NULL; @@ -2693,19 +2695,21 @@ set_discard_impl(PySetObject *so, PyObject *key) { int rv; - rv = set_discard_key(so, key); - if (rv < 0) { - if (!PySet_Check(key) || !PyErr_ExceptionMatches(PyExc_TypeError)) + Py_hash_t hash = PyObject_Hash(key); + if (hash == -1) { + if (!PySet_Check(key) || !PyErr_ExceptionMatches(PyExc_TypeError)) { + set_unhashable_type(key); return NULL; + } PyErr_Clear(); - Py_hash_t hash; Py_BEGIN_CRITICAL_SECTION(key); hash = frozenset_hash_impl(key); Py_END_CRITICAL_SECTION(); - rv = set_discard_entry(so, key, hash); - if (rv < 0) - return NULL; } + + rv = set_discard_entry(so, key, hash); + if (rv < 0) + return NULL; Py_RETURN_NONE; } From 144983936c2d5ac4a7f3e7d9775c8337a840d142 Mon Sep 17 00:00:00 2001 From: Bhuvansh Kataria Date: Sun, 2 Aug 2026 22:08:16 +0000 Subject: [PATCH 2/2] gh-155102: Add NEWS entry --- .../2026-08-02-22-07-48.gh-issue-155102.wYmQcU.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-08-02-22-07-48.gh-issue-155102.wYmQcU.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-02-22-07-48.gh-issue-155102.wYmQcU.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-02-22-07-48.gh-issue-155102.wYmQcU.rst new file mode 100644 index 000000000000000..cf880603678d0f8 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-02-22-07-48.gh-issue-155102.wYmQcU.rst @@ -0,0 +1,3 @@ +Preserve TypeError exceptions raised during equality comparison in +set.discard() and set.remove() when using hashable set subclasses as lookup +keys.