8000 bpo-41370: Evaluate strings as forward refs in PEP 585 generics (GH-3… · python/cpython@b465b60 · GitHub
[go: up one dir, main page]

Skip to content

Commit b465b60

Browse files
NiklasRosensteinJelleZijlstraAlexWaygood
authored
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>
1 parent 77446d2 commit b465b60

File tree

3 files changed

+49
-2
lines changed

3 files changed

+49
-2
lines changed

Lib/test/test_typing.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from typing import ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs
3333
from typing import TypeGuard
3434
import abc
35+
import textwrap
3536
import typing
3637
import weakref
3738
import types
@@ -2156,6 +2157,45 @@ def barfoo(x: AT): ...
21562157
def barfoo2(x: CT): ...
21572158
self.assertIs(get_type_hints(barfoo2, globals(), locals())['x'], CT)
21582159

2160+
def test_generic_pep585_forward_ref(self):
2161+
# See https://bugs.python.org/issue41370
2162+
2163+
class C1:
2164+
a: list['C1']
2165+
self.assertEqual(
2166+
get_type_hints(C1, globals(), locals()),
2167+
{'a': list[C1]}
2168+
)
2169+
2170+
class C2:
2171+
a: dict['C1', list[List[list['C2']]]]
2172+
self.assertEqual(
2173+
get_type_hints(C2, globals(), locals()),
2174+
{'a': dict[C1, list[List[list[C2]]]]}
2175+
)
2176+
2177+
# Test stringified annotations
2178+
scope = {}
2179+
exec(textwrap.dedent('''
2180+
from __future__ import annotations
2181+
class C3:
2182+
a: List[list["C2"]]
2183+
'''), 8000 scope)
2184+
C3 = scope['C3']
2185+
self.assertEqual(C3.__annotations__['a'], "List[list['C2']]")
2186+
self.assertEqual(
2187+
get_type_hints(C3, globals(), locals()),
2188+
{'a': List[list[C2]]}
2189+
)
2190+
2191+
# Test recursive types
2192+
X = list["X"]
2193+
def f(x: X): ...
2194+
self.assertEqual(
2195+
get_type_hints(f, globals(), locals()),
2196+
{'x': list[list[ForwardRef('X')]]}
2197+
)
2198+
21592199
def test_extended_generic_rules_subclassing(self):
21602200
class T1(Tuple[T, KT]): ...
21612201
class T2(Tuple[T, ...]): ...
@@ -3556,15 +3596,15 @@ def foobar(x: list[ForwardRef('X')]): ...
35563596
BA = Tuple[Annotated[T, (1, 0)], ...]
35573597
def barfoo(x: BA): ...
35583598
self.assertEqual(get_type_hints(barfoo, globals(), locals())['x'], Tuple[T, ...])
3559-
self.assertIs(
3599+
self.assertEqual(
35603600
get_type_hints(barfoo, globals(), locals(), include_extras=True)['x'],
35613601
BA
35623602
)
35633603

35643604
BA = tuple[Annotated[T, (1, 0)], ...]
35653605
def barfoo(x: BA): ...
35663606
self.assertEqual(get_type_hints(barfoo, globals(), locals())['x'], tuple[T, ...])
3567-
self.assertIs(
3607+
self.assertEqual(
35683608
get_type_hints(barfoo, globals(), locals(), include_extras=True)['x'],
35693609
BA
35703610
)

Lib/typing.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,12 @@ def _eval_type(t, globalns, localns, recursive_guard=frozenset()):
336336
if isinstance(t, ForwardRef):
337337
return t._evaluate(globalns, localns, recursive_guard)
338338
if isinstance(t, (_GenericAlias, GenericAlias, types.UnionType)):
339+
if isinstance(t, GenericAlias):
340+
args = tuple(
341+
ForwardRef(arg) if isinstance(arg, str) else arg
342+
for arg in t.__args__
343+
)
344+
t = t.__origin__[args]
339345
ev_args = tuple(_eval_type(a, globalns, localns, recursive_guard) for a in t.__args__)
340346
if ev_args == t.__args__:
341347
return t
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:func:`typing.get_type_hints` now supports evaluating strings as forward references in :ref:`PEP 585 generic aliases <types-genericalias>`.

0 commit comments

Comments
 (0)
0