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 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
|
||||||
)
|
)
|
||||||
|
@ -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
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
:func:`typing.get_type_hints` now supports evaluating strings as forward references in :ref:`PEP 585 generic aliases <types-genericalias>`.
|
Loading…
x
Reference in New Issue
Block a user