mirror of https://github.com/python/cpython
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:
parent
77446d2aa5
commit
b465b60604
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
:func:`typing.get_type_hints` now supports evaluating strings as forward references in :ref:`PEP 585 generic aliases <types-genericalias>`.
|
Loading…
Reference in New Issue