Repository navigation
Python 3.14.1+ annotationlib regression: ForwardRef.__hash__ raises TypeError: unhashable type: 'cell' #143831
Description
Activity
- addedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
on Jan 14, 2026 - addedstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory3.14bugs and security fixesbugs and security fixes3.15bugs and security fixesbugs and security fixes
on Jan 14, 2026 Smaller reproducer (without beartype):
import annotationlib class A: def m(self) -> A: pass A_fwdref = annotationlib.get_annotations( m, format=annotationlib.Format.FORWARDREF, )['return'] hash(A_fwdref)
Reacted by Abhinand CBisects to b1027d4 (GH-138164).
Details
git bisect start # status: waiting for both good and bad commits # bad: [d81e1ef0f3a7c63f5d246e4cf918700016b72489] gh-138189: Document type slots, and other constants, as part of Limited API (GH-138190) git bisect bad d81e1ef0f3a7c63f5d246e4cf918700016b72489 # status: waiting for good commit(s), bad commit known # good: [e34a5e33049ce845de646cf24a498766a2da3586] gh-140593: Fix a memory leak in function `my_ElementDeclHandler` of `pyexpat` (#140602) git bisect good e34a5e33049ce845de646cf24a498766a2da3586 # good: [95a3564869daa3cc083e4d4603f5f03ee5f53e31] GH-140768: Warn when the WASI SDK version doesn't match the supported version (GH-140769) git bisect good 95a3564869daa3cc083e4d4603f5f03ee5f53e31 # good: [121c219e302365e63eef0aef81b94a3de7a37fec] Remove redundant `sys.exit(2)` call in `pdb` CLI (#139948) git bisect good 121c219e302365e63eef0aef81b94a3de7a37fec # bad: [57f4d09a6fcfec229603f644c4254abdded00429] JIT: Fix compiler warning from visibility attribute in typedef (#139981) git bisect bad 57f4d09a6fcfec229603f644c4254abdded00429 # bad: [478b8dab0b40f08c3ded40bf988ec49a50f4c8fb] Docs: Fix typo in codecs documentation (GH-140883) git bisect bad 478b8dab0b40f08c3ded40bf988ec49a50f4c8fb # good: [7a9437d98641e3c3749ab2fd9fb54eac7614f9af] gh-140348: Fix using | on unusual objects plus Unions (#140383) git bisect good 7a9437d98641e3c3749ab2fd9fb54eac7614f9af # bad: [b1027d4762435b97546c122dd94290d707b3ff39] gh-138151: Fix annotationlib handling of multiple nonlocals (#138164) git bisect bad b1027d4762435b97546c122dd94290d707b3ff39 # good: [d590685297165d16df41003c3566b75a0a4ced61] gh-133879: Clean up What's New for 3.15 (#140435) git bisect good d590685297165d16df41003c3566b75a0a4ced61 # first bad commit: [b1027d4762435b97546c122dd94290d707b3ff39] gh-138151: Fix annotationlib handling of multiple nonlocals (#138164)@abhinand-c, the easiest workaround in your case with beartype is to stringify the annotation:
from beartype import beartype class A: @beartype def m(self) -> 'A': return 42 A().m() # raises BeartypeCallHintReturnViolation: Method __main__.A.m() return 42 violates type hint 'A', as int 42 not instance of <class "__main__.A">
EDIT: You found it too, nice :) beartype/beartype#610 (comment)
It makes deferred annotations blind to the class scope.
The unhashable cell in your reproduction is the transient class dict cell used in the annotation scope to resolve symbols in the class scope on the annotation level. The default
__annotate__functions of methods always consume__classdict__, so this regression will now reproduce everytime we try hash an annotationlib forward reference from any annotation in a method, because (1) this cell always appears (2) forward references are produced when eager resolution fails.Cells are not hashable.
ForwardRef.__hash__already provides an id for globals because dictionaries aren't hashable. Maybe we can do the same here?Lines 290 to 294 in 2b33b52
def __hash__(self): return hash(( self.__forward_arg__, self.__forward_module__, id(self.__globals__), # dictionaries are not hashable, so hash by identity This problem didn't occur prior to the change because
ForwardRef.__cell__was alwaysNone. There was a comment sayingForwardRef.__cell__et al. could become non-Noneif the stringifier object was manually assigned__class__:Lines 85 to 89 in d590685
# These are always set to None here but may be non-None if a ForwardRef # is created through __class__ assignment on a _Stringifier object. self.__globals__ = None self.__cell__ = None self.__extra_names__ = None However, this wasn't true for
__cell__at that time:Lines 645 to 651 in d590685
def transmogrify(self): for obj in self.stringifiers: obj.__class__ = ForwardRef obj.__stringifier_dict__ = None # not needed for ForwardRef if isinstance(obj.__ast_node__, str): obj.__arg__ = obj.__ast_node__ obj.__ast_node__ = None Only then b1027d4 delivered on that promise:
Lines 655 to 663 in b1027d4
def transmogrify(self, cell_dict): for obj in self.stringifiers: obj.__class__ = ForwardRef obj.__stringifier_dict__ = None # not needed for ForwardRef if isinstance(obj.__ast_node__, str): obj.__arg__ = obj.__ast_node__ obj.__ast_node__ = None if cell_dict is not None and obj.__cell__ is None: obj.__cell__ = cell_dict Since then cells show up while hashing and we will run into this for every cell, not just in method annotations scenario.
Reacted by Abhinand C and Savio Mak- added a commit that references this issue
on Jan 19, 2026
Bug report
Bug description:
This issue was originally reported in the beartype library (Ref: beartype/beartype#610), but investigation suggests it may stem from a change in Python’s
annotationlib.ForwardRefimplementation.Starting with Python 3.14.1, deferred annotations (PEP 649) involving self-referential return type is raising:
TypeError: unhashable type: 'cell'at decoration time. This worked correctly in Python 3.14.0 and the issue appears to persist in 3.14.2, suggesting a regression.The error originates from
annotationlib.ForwardRef.__hash__, which attempts to hash internal state containing non-hashable cell objects. Although observed via beartype, this may affects any downstream library that hashes ForwardRef objects. The following code seems to be raising the issue:tuple(sorted(self.__cell__.items())) if isinstance(self.__cell__, dict) else self.__cell__,cpython/Lib/annotationlib.py
Lines 290 to 299 in 1857a40
Code:
Observed behavior:
The program fails during decoration with the error:
TypeError: unhashable type: 'cell'Inspection shows:
Since cell objects are not hashable, this causes hash to fail.
Affected Versions:
Python
3.14.1and3.14.2OS Tested on: MacOS
CPython versions tested on:
3.14
Operating systems tested on:
macOS
Linked PRs