Skip to content

Commit 7dcaebf

Browse files
annotationlib: Move ForwardRef tests to test_annotationlib (#132571)
I started with just moving ForwardRefTests to test_annotationlib, but found that it contained a number of tests for no_type_check, which I moved to a new class in test_typing, as well as a number of tests that are more appropriately classified as tests for get_type_hints(). One test, test_forward_equality_namespace(), was somewhat accidentally depending on a global class A in test_typing. I added a class A in the annotationlib tests instead. Also add a useful comment in annotationlib.
1 parent 10a7761 commit 7dcaebf

File tree

3 files changed

+320
-314
lines changed

3 files changed

+320
-314
lines changed

Lib/annotationlib.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,15 @@ def __init__(
7777
self.__forward_is_argument__ = is_argument
7878
self.__forward_is_class__ = is_class
7979
self.__forward_module__ = module
80+
self.__owner__ = owner
81+
# These are always set to None here but may be non-None if a ForwardRef
82+
# is created through __class__ assignment on a _Stringifier object.
8083
self.__globals__ = None
84+
self.__cell__ = None
85+
# These are initially None but serve as a cache and may be set to a non-None
86+
# value later.
8187
self.__code__ = None
8288
self.__ast_node__ = None
83-
self.__cell__ = None
84-
self.__owner__ = owner
8589

8690
def __init_subclass__(cls, /, *args, **kwds):
8791
raise TypeError("Cannot subclass ForwardRef")

Lib/test/test_annotationlib.py

Lines changed: 160 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import functools
77
import itertools
88
import pickle
9+
import typing
910
import unittest
1011
from annotationlib import (
1112
Format,
@@ -15,7 +16,12 @@
1516
annotations_to_string,
1617
type_repr,
1718
)
18-
from typing import Unpack
19+
from typing import (
20+
Unpack,
21+
get_type_hints,
22+
List,
23+
Union,
24+
)
1925

2026
from test import support
2127
from test.test_inspect import inspect_stock_annotations
@@ -1205,6 +1211,159 @@ def test_annotations_to_string(self):
12051211
)
12061212

12071213

1214+
class A:
1215+
pass
1216+
1217+
1218+
class ForwardRefTests(unittest.TestCase):
1219+
def test_forwardref_instance_type_error(self):
1220+
fr = ForwardRef('int')
1221+
with self.assertRaises(TypeError):
1222+
isinstance(42, fr)
1223+
1224+
def test_forwardref_subclass_type_error(self):
1225+
fr = ForwardRef('int')
1226+
with self.assertRaises(TypeError):
1227+
issubclass(int, fr)
1228+
1229+
def test_forwardref_only_str_arg(self):
1230+
with self.assertRaises(TypeError):
1231+
ForwardRef(1) # only `str` type is allowed
1232+
1233+
def test_forward_equality(self):
1234+
fr = ForwardRef('int')
1235+
self.assertEqual(fr, ForwardRef('int'))
1236+
self.assertNotEqual(List['int'], List[int])
1237+
self.assertNotEqual(fr, ForwardRef('int', module=__name__))
1238+
frm = ForwardRef('int', module=__name__)
1239+
self.assertEqual(frm, ForwardRef('int', module=__name__))
1240+
self.assertNotEqual(frm, ForwardRef('int', module='__other_name__'))
1241+
1242+
def test_forward_equality_get_type_hints(self):
1243+
c1 = ForwardRef('C')
1244+
c1_gth = ForwardRef('C')
1245+
c2 = ForwardRef('C')
1246+
c2_gth = ForwardRef('C')
1247+
1248+
class C:
1249+
pass
1250+
def foo(a: c1_gth, b: c2_gth):
1251+
pass
1252+
1253+
self.assertEqual(get_type_hints(foo, globals(), locals()), {'a': C, 'b': C})
1254+
self.assertEqual(c1, c2)
1255+
self.assertEqual(c1, c1_gth)
1256+
self.assertEqual(c1_gth, c2_gth)
1257+
self.assertEqual(List[c1], List[c1_gth])
1258+
self.assertNotEqual(List[c1], List[C])
1259+
self.assertNotEqual(List[c1_gth], List[C])
1260+
self.assertEqual(Union[c1, c1_gth], Union[c1])
1261+
self.assertEqual(Union[c1, c1_gth, int], Union[c1, int])
1262+
1263+
def test_forward_equality_hash(self):
1264+
c1 = ForwardRef('int')
1265+
c1_gth = ForwardRef('int')
1266+
c2 = ForwardRef('int')
1267+
c2_gth = ForwardRef('int')
1268+
1269+
def foo(a: c1_gth, b: c2_gth):
1270+
pass
1271+
get_type_hints(foo, globals(), locals())
1272+
1273+
self.assertEqual(hash(c1), hash(c2))
1274+
self.assertEqual(hash(c1_gth), hash(c2_gth))
1275+
self.assertEqual(hash(c1), hash(c1_gth))
1276+
1277+
c3 = ForwardRef('int', module=__name__)
1278+
c4 = ForwardRef('int', module='__other_name__')
1279+
1280+
self.assertNotEqual(hash(c3), hash(c1))
1281+
self.assertNotEqual(hash(c3), hash(c1_gth))
1282+
self.assertNotEqual(hash(c3), hash(c4))
1283+
self.assertEqual(hash(c3), hash(ForwardRef('int', module=__name__)))
1284+
1285+
def test_forward_equality_namespace(self):
1286+
def namespace1():
1287+
a = ForwardRef('A')
1288+
def fun(x: a):
1289+
pass
1290+
get_type_hints(fun, globals(), locals())
1291+
return a
1292+
1293+
def namespace2():
1294+
a = ForwardRef('A')
1295+
1296+
class A:
1297+
pass
1298+
def fun(x: a):
1299+
pass
1300+
1301+
get_type_hints(fun, globals(), locals())
1302+
return a
1303+
1304+
self.assertEqual(namespace1(), namespace1())
1305+
self.assertEqual(namespace1(), namespace2())
1306+
1307+
def test_forward_repr(self):
1308+
self.assertEqual(repr(List['int']), "typing.List[ForwardRef('int')]")
1309+
self.assertEqual(repr(List[ForwardRef('int', module='mod')]),
1310+
"typing.List[ForwardRef('int', module='mod')]")
1311+
1312+
def test_forward_recursion_actually(self):
1313+
def namespace1():
1314+
a = ForwardRef('A')
1315+
A = a
1316+
def fun(x: a): pass
1317+
1318+
ret = get_type_hints(fun, globals(), locals())
1319+
return a
1320+
1321+
def namespace2():
1322+
a = ForwardRef('A')
1323+
A = a
1324+
def fun(x: a): pass
1325+
1326+
ret = get_type_hints(fun, globals(), locals())
1327+
return a
1328+
1329+
r1 = namespace1()
1330+
r2 = namespace2()
1331+
self.assertIsNot(r1, r2)
1332+
self.assertEqual(r1, r2)
1333+
1334+
def test_syntax_error(self):
1335+
1336+
with self.assertRaises(SyntaxError):
1337+
typing.Generic['/T']
1338+
1339+
def test_delayed_syntax_error(self):
1340+
1341+
def foo(a: 'Node[T'):
1342+
pass
1343+
1344+
with self.assertRaises(SyntaxError):
1345+
get_type_hints(foo)
1346+
1347+
def test_syntax_error_empty_string(self):
1348+
for form in [typing.List, typing.Set, typing.Type, typing.Deque]:
1349+
with self.subTest(form=form):
1350+
with self.assertRaises(SyntaxError):
1351+
form['']
1352+
1353+
def test_or(self):
1354+
X = ForwardRef('X')
1355+
# __or__/__ror__ itself
1356+
self.assertEqual(X | "x", Union[X, "x"])
1357+
self.assertEqual("x" | X, Union["x", X])
1358+
1359+
def test_multiple_ways_to_create(self):
1360+
X1 = Union["X"]
1361+
self.assertIsInstance(X1, ForwardRef)
1362+
X2 = ForwardRef("X")
1363+
self.assertIsInstance(X2, ForwardRef)
1364+
self.assertEqual(X1, X2)
1365+
1366+
12081367
class TestAnnotationLib(unittest.TestCase):
12091368
def test__all__(self):
12101369
support.check__all__(self, annotationlib)

0 commit comments

Comments
 (0)