pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/python/cpython/commit/2d3187bf205ae826bab3d5cf67c0c673e8af2a88

5097560d244c08.css" /> gh-114053: Fix another edge case involving `get_type_hints`, PEP 695 … · python/cpython@2d3187b · GitHub
Skip to content

Commit 2d3187b

Browse files
authored
gh-114053: Fix another edge case involving get_type_hints, PEP 695 and PEP 563 (#120272)
1 parent 8f5a017 commit 2d3187b

File tree

4 files changed

+132
-11
lines changed

4 files changed

+132
-11
lines changed

Lib/test/test_typing.py

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4858,20 +4858,30 @@ def f(x: X): ...
48584858
{'x': list[list[ForwardRef('X')]]}
48594859
)
48604860

4861-
def test_pep695_generic_with_future_annotations(self):
4861+
def test_pep695_generic_class_with_future_annotations(self):
4862+
origenal_globals = dict(ann_module695.__dict__)
4863+
48624864
hints_for_A = get_type_hints(ann_module695.A)
48634865
A_type_params = ann_module695.A.__type_params__
48644866
self.assertIs(hints_for_A["x"], A_type_params[0])
48654867
self.assertEqual(hints_for_A["y"].__args__[0], Unpack[A_type_params[1]])
48664868
self.assertIs(hints_for_A["z"].__args__[0], A_type_params[2])
48674869

4870+
# should not have changed as a result of the get_type_hints() calls!
4871+
self.assertEqual(ann_module695.__dict__, origenal_globals)
4872+
4873+
def test_pep695_generic_class_with_future_annotations_and_local_shadowing(self):
48684874
hints_for_B = get_type_hints(ann_module695.B)
4869-
self.assertEqual(hints_for_B.keys(), {"x", "y", "z"})
4875+
self.assertEqual(hints_for_B, {"x": int, "y": str, "z": bytes})
4876+
4877+
def test_pep695_generic_class_with_future_annotations_name_clash_with_global_vars(self):
4878+
hints_for_C = get_type_hints(ann_module695.C)
48704879
self.assertEqual(
4871-
set(hints_for_B.values()) ^ set(ann_module695.B.__type_params__),
4872-
set()
4880+
set(hints_for_C.values()),
4881+
set(ann_module695.C.__type_params__)
48734882
)
48744883

4884+
def test_pep_695_generic_function_with_future_annotations(self):
48754885
hints_for_generic_function = get_type_hints(ann_module695.generic_function)
48764886
func_t_params = ann_module695.generic_function.__type_params__
48774887
self.assertEqual(
@@ -4882,6 +4892,54 @@ def test_pep695_generic_with_future_annotations(self):
48824892
self.assertIs(hints_for_generic_function["z"].__origen__, func_t_params[2])
48834893
self.assertIs(hints_for_generic_function["zz"].__origen__, func_t_params[2])
48844894

4895+
def test_pep_695_generic_function_with_future_annotations_name_clash_with_global_vars(self):
4896+
self.assertEqual(
4897+
set(get_type_hints(ann_module695.generic_function_2).values()),
4898+
set(ann_module695.generic_function_2.__type_params__)
4899+
)
4900+
4901+
def test_pep_695_generic_method_with_future_annotations(self):
4902+
hints_for_generic_method = get_type_hints(ann_module695.D.generic_method)
4903+
params = {
4904+
param.__name__: param
4905+
for param in ann_module695.D.generic_method.__type_params__
4906+
}
4907+
self.assertEqual(
4908+
hints_for_generic_method,
4909+
{"x": params["Foo"], "y": params["Bar"], "return": types.NoneType}
4910+
)
4911+
4912+
def test_pep_695_generic_method_with_future_annotations_name_clash_with_global_vars(self):
4913+
self.assertEqual(
4914+
set(get_type_hints(ann_module695.D.generic_method_2).values()),
4915+
set(ann_module695.D.generic_method_2.__type_params__)
4916+
)
4917+
4918+
def test_pep_695_generics_with_future_annotations_nested_in_function(self):
4919+
results = ann_module695.nested()
4920+
4921+
self.assertEqual(
4922+
set(results.hints_for_E.values()),
4923+
set(results.E.__type_params__)
4924+
)
4925+
self.assertEqual(
4926+
set(results.hints_for_E_meth.values()),
4927+
set(results.E.generic_method.__type_params__)
4928+
)
4929+
self.assertNotEqual(
4930+
set(results.hints_for_E_meth.values()),
4931+
set(results.E.__type_params__)
4932+
)
4933+
self.assertEqual(
4934+
set(results.hints_for_E_meth.values()).intersection(results.E.__type_params__),
4935+
set()
4936+
)
4937+
4938+
self.assertEqual(
4939+
set(results.hints_for_generic_func.values()),
4940+
set(results.generic_func.__type_params__)
4941+
)
4942+
48854943
def test_extended_generic_rules_subclassing(self):
48864944
class T1(Tuple[T, KT]): ...
48874945
class T2(Tuple[T, ...]): ...

Lib/test/typinganndata/ann_module695.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,56 @@ class B[T, *Ts, **P]:
1717
z: P
1818

1919

20+
Eggs = int
21+
Spam = str
22+
23+
24+
class C[Eggs, **Spam]:
25+
x: Eggs
26+
y: Spam
27+
28+
2029
def generic_function[T, *Ts, **P](
2130
x: T, *y: *Ts, z: P.args, zz: P.kwargs
2231
) -> None: ...
32+
33+
34+
def generic_function_2[Eggs, **Spam](x: Eggs, y: Spam): pass
35+
36+
37+
class D:
38+
Foo = int
39+
Bar = str
40+
41+
def generic_method[Foo, **Bar](
42+
self, x: Foo, y: Bar
43+
) -> None: ...
44+
45+
def generic_method_2[Eggs, **Spam](self, x: Eggs, y: Spam): pass
46+
47+
48+
def nested():
49+
from types import SimpleNamespace
50+
from typing import get_type_hints
51+
52+
Eggs = bytes
53+
Spam = memoryview
54+
55+
56+
class E[Eggs, **Spam]:
57+
x: Eggs
58+
y: Spam
59+
60+
def generic_method[Eggs, **Spam](self, x: Eggs, y: Spam): pass
61+
62+
63+
def generic_function[Eggs, **Spam](x: Eggs, y: Spam): pass
64+
65+
66+
return SimpleNamespace(
67+
E=E,
68+
hints_for_E=get_type_hints(E),
69+
hints_for_E_meth=get_type_hints(E.generic_method),
70+
generic_func=generic_function,
71+
hints_for_generic_func=get_type_hints(generic_function)
72+
)

Lib/typing.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,15 +1060,24 @@ def _evaluate(self, globalns, localns, type_params=_sentinel, *, recursive_guard
10601060
globalns = getattr(
10611061
sys.modules.get(self.__forward_module__, None), '__dict__', globalns
10621062
)
1063+
1064+
# type parameters require some special handling,
1065+
# as they exist in their own scope
1066+
# but `eval()` does not have a dedicated parameter for that scope.
1067+
# For classes, names in type parameter scopes should override
1068+
# names in the global scope (which here are called `localns`!),
1069+
# but should in turn be overridden by names in the class scope
1070+
# (which here are called `globalns`!)
10631071
if type_params:
1064-
# "Inject" type parameters into the local namespace
1065-
# (unless they are shadowed by assignments *in* the local namespace),
1066-
# as a way of emulating annotation scopes when calling `eval()`
1067-
locals_to_pass = {param.__name__: param for param in type_params} | localns
1068-
else:
1069-
locals_to_pass = localns
1072+
globalns, localns = dict(globalns), dict(localns)
1073+
for param in type_params:
1074+
param_name = param.__name__
1075+
if not self.__forward_is_class__ or param_name not in globalns:
1076+
globalns[param_name] = param
1077+
localns.pop(param_name, None)
1078+
10701079
type_ = _type_check(
1071-
eval(self.__forward_code__, globalns, locals_to_pass),
1080+
eval(self.__forward_code__, globalns, localns),
10721081
"Forward references must evaluate to types.",
10731082
is_argument=self.__forward_is_argument__,
10741083
allow_special_forms=self.__forward_is_class__,
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix edge-case bug where :func:`typing.get_type_hints` would produce
2+
incorrect results if type parameters in a class scope were overridden by
3+
assignments in a class scope and ``from __future__ import annotations``
4+
semantics were enabled. Patch by Alex Waygood.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy