Skip to content

gh-149313: Keep hash error details for dict keys and set elements - #161536

Open
WakaTaira wants to merge 1 commit into
python:mainfrom
WakaTaira:gh-149313-preserve-hash-exception-info
Open

WakaTaira wants to merge 1 commit into
python:mainfrom
WakaTaira:gh-149313-preserve-hash-exception-info

Conversation

@WakaTaira

@WakaTaira WakaTaira commented Nov 7, 2026 •

Copy link
Copy Markdown

dict and set replace the TypeError from a failed key hash with one naming the container, which has dropped the original traceback, cause and notes since 3.14. The original exception now survives when it holds something worth keeping, and {[]: 1} keeps its single-line message.

The original TypeError has Result
A traceback or a cause (a Python __hash__) Chained as __cause__
Only notes (a C-level failure, such as the tuple note in #161496) Notes copied to the replacement
Neither Same output as today

Output for a __hash__ that raises a TypeError with the note within Key.first:

Traceback (most recent call last):
  File "ks.py", line 5, in __hash__
    raise exc
TypeError: unhashable field
within Key.first

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "ks.py", line 7, in <module>
    {Key(): 1}
TypeError: cannot use 'Key' as a dict key (unhashable field)

Before this change, only the last line and its traceback appear.

Start with _PyErr_ReplaceTypeError in Python/errors.c. Three decisions are worth checking:

  • __context__ does not trigger chaining. A C-level TypeError raised inside an except block gets the handled exception as its context, so checking it brings the duplicated unhashable type: 'list' message back. The replacement gets the same context anyway.
  • Always chaining would print the inner message twice for {([1],): 1}, as described in the Exception notes discarded from TypeError within __hash__() #149313 discussion.
  • Formatting errors stay untouched. If building the new message fails, for example because __str__ raises, that error propagates with its own cause.

Verified on Linux with a debug build: ./python -m test -j0, where only test_perf_profiler and test_profiling fail because of the local NixOS on WSL environment, and -R 3:3 on test_dict and test_set. I did not test the free-threaded build locally; it passes in this PR's CI.

A personal note: gh-161433 was my first issue on a project this large. I had a tuple-note prototype ready locally and planned to wait for comments before opening a PR. When I woke up, @localzet had already opened #161496, and @anujbharambe's #149505 and @localzet's fork experiment were there to learn from. This PR builds on both of them, and I wrote it now because I wanted to see this change through to a PR myself.

I used Claude Code to help with the implementation and tests, and I reviewed the result and can explain it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YSkzESiXGfCM1jUTyLpxHo

The TypeError raised when hashing a dict key or a set element is replaced
with a TypeError describing the container, which discards the traceback,
the cause and the notes of the original exception.

Chain the original exception as the cause of the replacement if it has a
traceback or a cause.  Otherwise, chaining would only repeat its message,
so copy its notes instead.

__context__ is intentionally not checked: a TypeError raised from C code
inside an except block gets the handled exception as its context, and the
replacement gets the same context anyway.

An error raised when formatting the new message is left unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YSkzESiXGfCM1jUTyLpxHo
@python-cla-bot

python-cla-bot Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

All commit authors signed the Contributor License Agreement.

CLA signed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant