https://github.com/python/cpython/commit/bd83de61433a8f6e5ae49be6ec54dc9f7d9e6aaf
commit: bd83de61433a8f6e5ae49be6ec54dc9f7d9e6aaf
branch: main
author: Ivan Zorin <[email protected]>
committer: vstinner <[email protected]>
date: 2026-11-07T16:07:27+01:00
summary:

gh-161433: Identify the failing item when hashing tuples (#161496)

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-11-06-15-15-00.gh-issue-161433.A9b3Kq.rst
M Lib/test/test_tuple.py
M Misc/ACKS
M Objects/tupleobject.c

diff --git a/Lib/test/test_tuple.py b/Lib/test/test_tuple.py
index 5650dec09adef4..a3bd60eca4f6b1 100644
--- a/Lib/test/test_tuple.py
+++ b/Lib/test/test_tuple.py
@@ -450,6 +450,45 @@ def test_lexicographic_ordering(self):
         self.assertLess(a, b)
         self.assertLess(b, c)
 
+    def test_hash_unhashable_note(self):
+        with self.assertRaisesRegex(TypeError, "unhashable type: 'list'") as 
cm:
+            hash(("alice", []))
+        self.assertEqual(cm.exception.__notes__,
+                         ["when hashing tuple item 1"])
+
+        with self.assertRaisesRegex(TypeError, "unhashable type: 'list'") as 
cm:
+            hash((("alice", []), 42))
+        self.assertEqual(cm.exception.__notes__,
+                         ["when hashing tuple item 1",
+                          "when hashing tuple item 0"])
+
+        class BrokenHash:
+            def __hash__(self):
+                raise TypeError("custom failure")
+
+        with self.assertRaisesRegex(TypeError, "custom failure") as cm:
+            hash((BrokenHash(),))
+        self.assertEqual(cm.exception.__notes__, ["when hashing tuple item 0"])
+
+        class BrokenHashNote:
+            def __hash__(self):
+                exc = TypeError("custom failure")
+                exc.add_note("from custom hash")
+                raise exc
+
+        with self.assertRaisesRegex(TypeError, "custom failure") as cm:
+            hash((BrokenHashNote(),))
+        self.assertEqual(cm.exception.__notes__,
+                         ["from custom hash", "when hashing tuple item 0"])
+
+        class BrokenHashValueError:
+            def __hash__(self):
+                raise ValueError("custom failure")
+
+        with self.assertRaisesRegex(ValueError, "custom failure") as cm:
+            hash((BrokenHashValueError(),))
+        self.assertFalse(hasattr(cm.exception, "__notes__"))
+
 # Notes on testing hash codes.  The primary thing is that Python doesn't
 # care about "random" hash codes.  To the contrary, we like them to be
 # very regular when possible, so that the low-order bits are as evenly
diff --git a/Misc/ACKS b/Misc/ACKS
index 65f6f423eb7c42..4178ae3108128a 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -2048,6 +2048,7 @@ Sjoerd de Vries
 Jonas Wagner
 Daniel Wagner-Hall
 Niki W. Waibel
+WakaTaira
 Wojtek Walczak
 Charles Waldman
 Richard Walker
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-11-06-15-15-00.gh-issue-161433.A9b3Kq.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-11-06-15-15-00.gh-issue-161433.A9b3Kq.rst
new file mode 100644
index 00000000000000..251837ac6d632a
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-11-06-15-15-00.gh-issue-161433.A9b3Kq.rst
@@ -0,0 +1,2 @@
+Add context notes to :exc:`TypeError` raised while hashing a
+:class:`tuple`, identifying the index of the item whose hash failed.
diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c
index 68ca2772507295..992699756ac770 100644
--- a/Objects/tupleobject.c
+++ b/Objects/tupleobject.c
@@ -8,6 +8,7 @@
 #include "pycore_list.h"          // _Py_memory_repeat()
 #include "pycore_modsupport.h"    // _PyArg_NoKwnames()
 #include "pycore_object.h"        // _PyObject_GC_TRACK()
+#include "pycore_pyerrors.h"      // _PyException_AddNote()
 #include "pycore_stackref.h"      // PyStackRef_AsPyObjectSteal()
 #include "pycore_tuple.h"         // _PyTupleIterObject
 
@@ -392,6 +393,23 @@ tuple_hash(PyObject *op)
     for (Py_ssize_t i = 0; i < len; i++) {
         Py_uhash_t lane = PyObject_Hash(item[i]);
         if (lane == (Py_uhash_t)-1) {
+            if (PyErr_ExceptionMatches(PyExc_TypeError)) {
+                /* Preserve the original error while providing context for
+                   the element that could not be hashed. */
+                PyObject *exc = PyErr_GetRaisedException();
+                PyObject *note = PyUnicode_FromFormat(
+                    "when hashing tuple item %zd", i);
+                if (note != NULL) {
+                    if (_PyException_AddNote(exc, note) < 0) {
+                        PyErr_Clear();
+                    }
+                    Py_DECREF(note);
+                }
+                else {
+                    PyErr_Clear();
+                }
+                PyErr_SetRaisedException(exc);
+            }
             _Py_LeaveRecursiveCall();
             return -1;
         }

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to