Skip to content

Commit c76bfc0

Browse files
committed
pythongh-114053: Fix bad interaction of PEP-695, PEP-563 and get_type_hints
1 parent a4b44d3 commit c76bfc0

File tree

4 files changed

+72
-9
lines changed

4 files changed

+72
-9
lines changed

Lib/test/test_typing.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
import types
4747

4848
from test.support import captured_stderr, cpython_only, infinite_recursion
49-
from test.typinganndata import mod_generics_cache, _typed_dict_helper
49+
from test.typinganndata import ann_module695, mod_generics_cache, _typed_dict_helper
5050

5151

5252
CANNOT_SUBCLASS_TYPE = 'Cannot subclass special typing classes'
@@ -4641,6 +4641,28 @@ def f(x: X): ...
46414641
{'x': list[list[ForwardRef('X')]]}
46424642
)
46434643

4644+
def test_pep695_generic_with_future_annotations(self):
4645+
hints_for_A = get_type_hints(ann_module695.A)
4646+
self.assertEqual(hints_for_A.keys(), {"x", "y", "z"})
4647+
self.assertEqual(tuple(hints_for_A.values()), ann_module695.A.__type_params__)
4648+
4649+
hints_for_B = get_type_hints(ann_module695.B)
4650+
self.assertEqual(hints_for_B.keys(), {"x", "y", "z"})
4651+
self.assertEqual(
4652+
set(hints_for_B.values()) ^ set(ann_module695.B.__type_params__),
4653+
set()
4654+
)
4655+
4656+
hints_for_generic_function = get_type_hints(ann_module695.generic_function)
4657+
func_t_params = ann_module695.generic_function.__type_params__
4658+
self.assertEqual(
4659+
hints_for_generic_function.keys(), {"x", "y", "z", "zz", "return"}
4660+
)
4661+
self.assertIs(hints_for_generic_function["x"], func_t_params[0])
4662+
self.assertEqual(hints_for_generic_function["y"], Unpack[func_t_params[1]])
4663+
self.assertIs(hints_for_generic_function["z"].__origin__, func_t_params[2])
4664+
self.assertIs(hints_for_generic_function["zz"].__origin__, func_t_params[2])
4665+
46444666
def test_extended_generic_rules_subclassing(self):
46454667
class T1(Tuple[T, KT]): ...
46464668
class T2(Tuple[T, ...]): ...
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from __future__ import annotations
2+
3+
4+
class A[T, *Ts, **P]:
5+
x: T
6+
y: Ts
7+
z: P
8+
9+
10+
class B[T, *Ts, **P]:
11+
T = int
12+
Ts = str
13+
P = bytes
14+
x: T
15+
y: Ts
16+
z: P
17+
18+
19+
def generic_function[T, *Ts, **P](
20+
x: T, *y: *Ts, z: P.args, zz: P.kwargs
21+
) -> None: ...

Lib/typing.py

+24-8
Original file line numberDiff line numberDiff line change
@@ -399,15 +399,16 @@ def inner(*args, **kwds):
399399

400400
return decorator
401401

402-
def _eval_type(t, globalns, localns, recursive_guard=frozenset()):
402+
403+
def _eval_type(t, globalns, localns, type_params, *, recursive_guard=frozenset()):
403404
"""Evaluate all forward references in the given type t.
404405
405406
For use of globalns and localns see the docstring for get_type_hints().
406407
recursive_guard is used to prevent infinite recursion with a recursive
407408
ForwardRef.
408409
"""
409410
if isinstance(t, ForwardRef):
410-
return t._evaluate(globalns, localns, recursive_guard)
411+
return t._evaluate(globalns, localns, type_params, recursive_guard=recursive_guard)
411412
if isinstance(t, (_GenericAlias, GenericAlias, types.UnionType)):
412413
if isinstance(t, GenericAlias):
413414
args = tuple(
@@ -421,7 +422,13 @@ def _eval_type(t, globalns, localns, recursive_guard=frozenset()):
421422
t = t.__origin__[args]
422423
if is_unpacked:
423424
t = Unpack[t]
424-
ev_args = tuple(_eval_type(a, globalns, localns, recursive_guard) for a in t.__args__)
425+
426+
ev_args = tuple(
427+
_eval_type(
428+
a, globalns, localns, type_params, recursive_guard=recursive_guard
429+
)
430+
for a in t.__args__
431+
)
425432
if ev_args == t.__args__:
426433
return t
427434
if isinstance(t, GenericAlias):
@@ -974,7 +981,7 @@ def __init__(self, arg, is_argument=True, module=None, *, is_class=False):
974981
self.__forward_is_class__ = is_class
975982
self.__forward_module__ = module
976983

977-
def _evaluate(self, globalns, localns, recursive_guard):
984+
def _evaluate(self, globalns, localns, type_params, *, recursive_guard):
978985
if self.__forward_arg__ in recursive_guard:
979986
return self
980987
if not self.__forward_evaluated__ or localns is not globalns:
@@ -989,13 +996,21 @@ def _evaluate(self, globalns, localns, recursive_guard):
989996
sys.modules.get(self.__forward_module__, None), '__dict__', globalns
990997
)
991998
type_ = _type_check(
992-
eval(self.__forward_code__, globalns, localns),
999+
eval(
1000+
self.__forward_code__,
1001+
globalns,
1002+
{param.__name__: param for param in type_params} | localns
1003+
),
9931004
"Forward references must evaluate to types.",
9941005
is_argument=self.__forward_is_argument__,
9951006
allow_special_forms=self.__forward_is_class__,
9961007
)
9971008
self.__forward_value__ = _eval_type(
998-
type_, globalns, localns, recursive_guard | {self.__forward_arg__}
1009+
type_,
1010+
globalns,
1011+
localns,
1012+
type_params,
1013+
recursive_guard=(recursive_guard | {self.__forward_arg__}),
9991014
)
10001015
self.__forward_evaluated__ = True
10011016
return self.__forward_value__
@@ -2334,7 +2349,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False):
23342349
value = type(None)
23352350
if isinstance(value, str):
23362351
value = ForwardRef(value, is_argument=False, is_class=True)
2337-
value = _eval_type(value, base_globals, base_locals)
2352+
value = _eval_type(value, base_globals, base_locals, base.__type_params__)
23382353
hints[name] = value
23392354
return hints if include_extras else {k: _strip_annotations(t) for k, t in hints.items()}
23402355

@@ -2360,6 +2375,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False):
23602375
raise TypeError('{!r} is not a module, class, method, '
23612376
'or function.'.format(obj))
23622377
hints = dict(hints)
2378+
type_params = getattr(obj, "__type_params__", ())
23632379
for name, value in hints.items():
23642380
if value is None:
23652381
value = type(None)
@@ -2371,7 +2387,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False):
23712387
is_argument=not isinstance(obj, types.ModuleType),
23722388
is_class=False,
23732389
)
2374-
hints[name] = _eval_type(value, globalns, localns)
2390+
hints[name] = _eval_type(value, globalns, localns, type_params)
23752391
return hints if include_extras else {k: _strip_annotations(t) for k, t in hints.items()}
23762392

23772393

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix erroneous :exc:`NameError` when calling :func:`typing.get_type_hints` on
2+
a class that made use of :pep:`695` type parameters in a module that had
3+
``from __future__ import annotations`` at the top of the file. Patch by Alex
4+
Waygood.

0 commit comments

Comments
 (0)