Skip to content

Commit f59d4fb

Browse files
committed
Instead implement __eq__/__hash__ correctly
1 parent 5d06ec5 commit f59d4fb

File tree

6 files changed

+209
-60
lines changed

6 files changed

+209
-60
lines changed

Lib/annotationlib.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,34 @@ def __forward_code__(self):
219219
raise SyntaxError(f"Forward reference must be an expression -- got {arg!r}")
220220
return self.__code__
221221

222+
def __eq__(self, other):
223+
if not isinstance(other, ForwardRef):
224+
return NotImplemented
225+
return (
226+
self.__forward_arg__ == other.__forward_arg__
227+
and self.__forward_module__ == other.__forward_module__
228+
and self.__forward_is_class__ == other.__forward_is_class__
229+
and self.__code__ == other.__code__
230+
and self.__ast_node__ == other.__ast_node__
231+
# Use "is" here because we use id() for this in __hash__
232+
# because dictionaries are not hashable.
233+
and self.__globals__ is other.__globals__
234+
and self.__cell__ == other.__cell__
235+
and self.__owner__ == other.__owner__
236+
)
237+
238+
def __hash__(self):
239+
return hash((
240+
self.__forward_arg__,
241+
self.__forward_module__,
242+
self.__forward_is_class__,
243+
self.__code__,
244+
self.__ast_node__,
245+
id(self.__globals__), # dictionaries are not hashable, so hash by identity
246+
self.__cell__,
247+
self.__owner__,
248+
))
249+
222250
def __or__(self, other):
223251
global _Union
224252
if _Union is None:
@@ -232,11 +260,14 @@ def __ror__(self, other):
232260
return _Union[other, self]
233261

234262
def __repr__(self):
235-
if self.__forward_module__ is None:
236-
module_repr = ""
237-
else:
238-
module_repr = f", module={self.__forward_module__!r}"
239-
return f"ForwardRef({self.__forward_arg__!r}{module_repr})"
263+
extra = []
264+
if self.__forward_module__ is not None:
265+
extra.append(f", module={self.__forward_module__!r}")
266+
if self.__forward_is_class__:
267+
extra.append(", is_class=True")
268+
if self.__owner__ is not None:
269+
extra.append(f", owner={self.__owner__!r}")
270+
return f"ForwardRef({self.__forward_arg__!r}{''.join(extra)})"
240271

241272

242273
class _Stringifier:

Lib/test/support/__init__.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
if __name__ != 'test.support':
44
raise ImportError('support must be imported from the test package')
55

6+
import annotationlib
67
import contextlib
78
import functools
89
import inspect
@@ -3012,3 +3013,44 @@ def is_libssl_fips_mode():
30123013
except ImportError:
30133014
return False # more of a maybe, unless we add this to the _ssl module.
30143015
return get_fips_mode() != 0
3016+
3017+
3018+
class EqualToForwardRef:
3019+
"""Helper to ease use of annotationlib.ForwardRef in tests.
3020+
3021+
This checks only attributes that can be set using the constructor.
3022+
3023+
"""
3024+
3025+
def __init__(
3026+
self,
3027+
arg,
3028+
*,
3029+
module=None,
3030+
owner=None,
3031+
is_class=False,
3032+
):
3033+
self.__forward_arg__ = arg
3034+
self.__forward_is_class__ = is_class
3035+
self.__forward_module__ = module
3036+
self.__owner__ = owner
3037+
3038+
def __eq__(self, other):
3039+
if not isinstance(other, (EqualToForwardRef, annotationlib.ForwardRef)):
3040+
return NotImplemented
3041+
return (
3042+
self.__forward_arg__ == other.__forward_arg__
3043+
and self.__forward_module__ == other.__forward_module__
3044+
and self.__forward_is_class__ == other.__forward_is_class__
3045+
and self.__owner__ == other.__owner__
3046+
)
3047+
3048+
def __repr__(self):
3049+
extra = []
3050+
if self.__forward_module__ is not None:
3051+
extra.append(f", module={self.__forward_module__!r}")
3052+
if self.__forward_is_class__:
3053+
extra.append(", is_class=True")
3054+
if self.__owner__ is not None:
3055+
extra.append(f", owner={self.__owner__!r}")
3056+
return f"EqualToForwardRef({self.__forward_arg__!r}{''.join(extra)})"

Lib/test/test_annotationlib.py

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,6 @@ def wrapper(a, b):
3232
return wrapper
3333

3434

35-
def assert_is_fwdref(case, obj, value):
36-
case.assertIsInstance(obj, annotationlib.ForwardRef)
37-
case.assertEqual(obj.__forward_arg__, value)
38-
39-
4035
class MyClass:
4136
def __repr__(self):
4237
return "my repr"
@@ -64,7 +59,8 @@ def inner(arg: x):
6459

6560
anno = annotationlib.get_annotations(inner, format=Format.FORWARDREF)
6661
fwdref = anno["arg"]
67-
assert_is_fwdref(self, fwdref, "x")
62+
self.assertIsInstance(fwdref, annotationlib.ForwardRef)
63+
self.assertEqual(fwdref.__forward_arg__, "x")
6864
with self.assertRaises(NameError):
6965
fwdref.evaluate()
7066

@@ -81,7 +77,8 @@ def f(x: int, y: doesntexist):
8177
anno = annotationlib.get_annotations(f, format=Format.FORWARDREF)
8278
self.assertIs(anno["x"], int)
8379
fwdref = anno["y"]
84-
assert_is_fwdref(self, fwdref, "doesntexist")
80+
self.assertIsInstance(fwdref, annotationlib.ForwardRef)
81+
self.assertEqual(fwdref.__forward_arg__, "doesntexist")
8582
with self.assertRaises(NameError):
8683
fwdref.evaluate()
8784
self.assertEqual(fwdref.evaluate(globals={"doesntexist": 1}), 1)
@@ -99,22 +96,28 @@ def f(
9996

10097
anno = annotationlib.get_annotations(f, format=Format.FORWARDREF)
10198
x_anno = anno["x"]
102-
assert_is_fwdref(self, x_anno, "some.module")
99+
self.assertIsInstance(x_anno, ForwardRef)
100+
self.assertEqual(x_anno, support.EqualToForwardRef("some.module", owner=f))
103101

104102
y_anno = anno["y"]
105-
assert_is_fwdref(self, y_anno, "some[module]")
103+
self.assertIsInstance(y_anno, ForwardRef)
104+
self.assertEqual(y_anno, support.EqualToForwardRef("some[module]", owner=f))
106105

107106
z_anno = anno["z"]
108-
assert_is_fwdref(self, z_anno, "some(module)")
107+
self.assertIsInstance(z_anno, ForwardRef)
108+
self.assertEqual(z_anno, support.EqualToForwardRef("some(module)", owner=f))
109109

110110
alpha_anno = anno["alpha"]
111-
assert_is_fwdref(self, alpha_anno, "some | obj")
111+
self.assertIsInstance(alpha_anno, ForwardRef)
112+
self.assertEqual(alpha_anno, support.EqualToForwardRef("some | obj", owner=f))
112113

113114
beta_anno = anno["beta"]
114-
assert_is_fwdref(self, beta_anno, "+some")
115+
self.assertIsInstance(beta_anno, ForwardRef)
116+
self.assertEqual(beta_anno, support.EqualToForwardRef("+some", owner=f))
115117

116118
gamma_anno = anno["gamma"]
117-
assert_is_fwdref(self, gamma_anno, "some < obj")
119+
self.assertIsInstance(gamma_anno, ForwardRef)
120+
self.assertEqual(gamma_anno, support.EqualToForwardRef("some < obj", owner=f))
118121

119122

120123
class TestSourceFormat(unittest.TestCase):
@@ -359,6 +362,14 @@ def test_fwdref_to_builtin(self):
359362
obj = object()
360363
self.assertIs(ForwardRef("int").evaluate(globals={"int": obj}), obj)
361364

365+
def test_fwdref_value_is_not_cached(self):
366+
fr = ForwardRef("hello")
367+
with self.assertRaises(NameError):
368+
fr.evaluate()
369+
self.assertIs(fr.evaluate(globals={"hello": str}), str)
370+
with self.assertRaises(NameError):
371+
fr.evaluate()
372+
362373
def test_fwdref_with_owner(self):
363374
self.assertEqual(
364375
ForwardRef("Counter[int]", owner=collections).evaluate(),
@@ -447,10 +458,12 @@ def f2(a: undefined):
447458
)
448459
self.assertEqual(annotationlib.get_annotations(f1, format=1), {"a": int})
449460

450-
for fmt in (Format.FORWARDREF, 3):
451-
annos = annotationlib.get_annotations(f2, format=fmt)
452-
self.assertEqual(list(annos), ["a"])
453-
assert_is_fwdref(self, annos["a"], "undefined")
461+
fwd = support.EqualToForwardRef("undefined", owner=f2)
462+
self.assertEqual(
463+
annotationlib.get_annotations(f2, format=Format.FORWARDREF),
464+
{"a": fwd},
465+
)
466+
self.assertEqual(annotationlib.get_annotations(f2, format=3), {"a": fwd})
454467

455468
self.assertEqual(
456469
annotationlib.get_annotations(f1, format=Format.STRING),
@@ -1000,10 +1013,10 @@ def evaluate(format, exc=NotImplementedError):
10001013

10011014
with self.assertRaises(NameError):
10021015
annotationlib.call_evaluate_function(evaluate, Format.VALUE)
1003-
1004-
fwdref = annotationlib.call_evaluate_function(evaluate, Format.FORWARDREF)
1005-
assert_is_fwdref(self, fwdref, "undefined")
1006-
1016+
self.assertEqual(
1017+
annotationlib.call_evaluate_function(evaluate, Format.FORWARDREF),
1018+
support.EqualToForwardRef("undefined"),
1019+
)
10071020
self.assertEqual(
10081021
annotationlib.call_evaluate_function(evaluate, Format.STRING),
10091022
"undefined",

Lib/test/test_inspect/test_inspect.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from annotationlib import Format, ForwardRef
1+
from annotationlib import Format
22
import asyncio
33
import builtins
44
import collections
@@ -37,7 +37,7 @@
3737

3838
from test.support import cpython_only, import_helper
3939
from test.support import MISSING_C_DOCSTRINGS, ALWAYS_EQ
40-
from test.support import run_no_yield_async_fn
40+
from test.support import run_no_yield_async_fn, EqualToForwardRef
4141
from test.support.import_helper import DirsOnSysPath, ready_to_import
4242
from test.support.os_helper import TESTFN, temp_cwd
4343
from test.support.script_helper import assert_python_ok, assert_python_failure, kill_python
@@ -48,7 +48,6 @@
4848
from test.test_inspect import inspect_fodder2 as mod2
4949
from test.test_inspect import inspect_stringized_annotations
5050
from test.test_inspect import inspect_deferred_annotations
51-
from test.test_typing import EqualToForwardRef
5251

5352

5453
# Functions tested in this suite:
@@ -4932,9 +4931,12 @@ def test_signature_annotation_format(self):
49324931
signature_func(ida.f, annotation_format=Format.STRING),
49334932
sig([par("x", PORK, annotation="undefined")])
49344933
)
4934+
s1 = signature_func(ida.f, annotation_format=Format.FORWARDREF)
4935+
s2 = sig([par("x", PORK, annotation=EqualToForwardRef("undefined", owner=ida.f))])
4936+
#breakpoint()
49354937
self.assertEqual(
49364938
signature_func(ida.f, annotation_format=Format.FORWARDREF),
4937-
sig([par("x", PORK, annotation=EqualToForwardRef("undefined"))])
4939+
sig([par("x", PORK, annotation=EqualToForwardRef("undefined", owner=ida.f))])
49384940
)
49394941
with self.assertRaisesRegex(NameError, "undefined"):
49404942
signature_func(ida.f, annotation_format=Format.VALUE)

0 commit comments

Comments
 (0)