bpo-41370: Evaluate strings as forward refs in PEP 585 generics (GH-30900)

This removes discrepancy between list["int"] and List["int"].

Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
Niklas Rosenstein 2022-03-07 19:02:59 +01:00 committed by GitHub
parent 77446d2aa5
commit b465b60604
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 49 additions and 2 deletions

View File

@ -32,6 +32,7 @@ from typing import TypeAlias
from typing import ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs from typing import ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs
from typing import TypeGuard from typing import TypeGuard
import abc import abc
import textwrap
import typing import typing
import weakref import weakref
import types import types
@ -2156,6 +2157,45 @@ class GenericTests(BaseTestCase):
def barfoo2(x: CT): ... def barfoo2(x: CT): ...
self.assertIs(get_type_hints(barfoo2, globals(), locals())['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): def test_extended_generic_rules_subclassing(self):
class T1(Tuple[T, KT]): ... class T1(Tuple[T, KT]): ...
class T2(Tuple[T, ...]): ... class T2(Tuple[T, ...]): ...
@ -3556,7 +3596,7 @@ class GetTypeHintTests(BaseTestCase):
BA = Tuple[Annotated[T, (1, 0)], ...] BA = Tuple[Annotated[T, (1, 0)], ...]
def barfoo(x: BA): ... def barfoo(x: BA): ...
self.assertEqual(get_type_hints(barfoo, globals(), locals())['x'], Tuple[T, ...]) 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'], get_type_hints(barfoo, globals(), locals(), include_extras=True)['x'],
BA BA
) )
@ -3564,7 +3604,7 @@ class GetTypeHintTests(BaseTestCase):
BA = tuple[Annotated[T, (1, 0)], ...] BA = tuple[Annotated[T, (1, 0)], ...]
def barfoo(x: BA): ... def barfoo(x: BA): ...
self.assertEqual(get_type_hints(barfoo, globals(), locals())['x'], tuple[T, ...]) 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'], get_type_hints(barfoo, globals(), locals(), include_extras=True)['x'],
BA BA
) )

View File

@ -336,6 +336,12 @@ def _eval_type(t, globalns, localns, recursive_guard=frozenset()):
if isinstance(t, ForwardRef): if isinstance(t, ForwardRef):
return t._evaluate(globalns, localns, recursive_guard) return t._evaluate(globalns, localns, recursive_guard)
if isinstance(t, (_GenericAlias, GenericAlias, types.UnionType)): 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__) ev_args = tuple(_eval_type(a, globalns, localns, recursive_guard) for a in t.__args__)
if ev_args == t.__args__: if ev_args == t.__args__:
return t return t

View File

@ -0,0 +1 @@
:func:`typing.get_type_hints` now supports evaluating strings as forward references in :ref:`PEP 585 generic aliases <types-genericalias>`.