diff options
-rw-r--r-- | Lib/test/test_typing.py | 44 | ||||
-rw-r--r-- | Lib/typing.py | 6 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2022-01-27-11-54-16.bpo-41370.gYxCPE.rst | 1 |
3 files changed, 49 insertions, 2 deletions
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 86baed9..17d78cf 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -32,6 +32,7 @@ from typing import TypeAlias from typing import ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs from typing import TypeGuard import abc +import textwrap import typing import weakref import types @@ -2156,6 +2157,45 @@ class GenericTests(BaseTestCase): def barfoo2(x: CT): ... self.assertIs(get_type_hints(barfoo2, globals(), locals())['x'], CT) + def test_generic_pep585_forward_ref(self): + # See https://bugs.python.org/issue41370 + + class C1: + a: list['C1'] + self.assertEqual( + get_type_hints(C1, globals(), locals()), + {'a': list[C1]} + ) + + class C2: + a: dict['C1', list[List[list['C2']]]] + self.assertEqual( + get_type_hints(C2, globals(), locals()), + {'a': dict[C1, list[List[list[C2]]]]} + ) + + # Test stringified annotations + scope = {} + exec(textwrap.dedent(''' + from __future__ import annotations + class C3: + a: List[list["C2"]] + '''), scope) + C3 = scope['C3'] + self.assertEqual(C3.__annotations__['a'], "List[list['C2']]") + self.assertEqual( + get_type_hints(C3, globals(), locals()), + {'a': List[list[C2]]} + ) + + # Test recursive types + X = list["X"] + def f(x: X): ... + self.assertEqual( + get_type_hints(f, globals(), locals()), + {'x': list[list[ForwardRef('X')]]} + ) + def test_extended_generic_rules_subclassing(self): class T1(Tuple[T, KT]): ... class T2(Tuple[T, ...]): ... @@ -3556,7 +3596,7 @@ class GetTypeHintTests(BaseTestCase): BA = Tuple[Annotated[T, (1, 0)], ...] def barfoo(x: BA): ... self.assertEqual(get_type_hints(barfoo, globals(), locals())['x'], Tuple[T, ...]) - self.assertIs( + self.assertEqual( get_type_hints(barfoo, globals(), locals(), include_extras=True)['x'], BA ) @@ -3564,7 +3604,7 @@ class GetTypeHintTests(BaseTestCase): BA = tuple[Annotated[T, (1, 0)], ...] def barfoo(x: BA): ... self.assertEqual(get_type_hints(barfoo, globals(), locals())['x'], tuple[T, ...]) - self.assertIs( + self.assertEqual( get_type_hints(barfoo, globals(), locals(), include_extras=True)['x'], BA ) diff --git a/Lib/typing.py b/Lib/typing.py index 27d83c5..360129e 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -336,6 +336,12 @@ def _eval_type(t, globalns, localns, recursive_guard=frozenset()): if isinstance(t, ForwardRef): return t._evaluate(globalns, localns, recursive_guard) if isinstance(t, (_GenericAlias, GenericAlias, types.UnionType)): + if isinstance(t, GenericAlias): + args = tuple( + ForwardRef(arg) if isinstance(arg, str) else arg + for arg in t.__args__ + ) + t = t.__origin__[args] ev_args = tuple(_eval_type(a, globalns, localns, recursive_guard) for a in t.__args__) if ev_args == t.__args__: return t diff --git a/Misc/NEWS.d/next/Library/2022-01-27-11-54-16.bpo-41370.gYxCPE.rst b/Misc/NEWS.d/next/Library/2022-01-27-11-54-16.bpo-41370.gYxCPE.rst new file mode 100644 index 0000000..d9ad2af --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-01-27-11-54-16.bpo-41370.gYxCPE.rst @@ -0,0 +1 @@ +:func:`typing.get_type_hints` now supports evaluating strings as forward references in :ref:`PEP 585 generic aliases <types-genericalias>`. |