FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

gh-138949: Fix non-generic children of generic TypedDicts with future annotations by sobolevn · Pull Request #150762 · python/cpython · GitHub

/ cpython Public
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .py  (3) .rst  (1) All 2 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
37 changes: 37 additions & 0 deletions Lib/test/test_typing.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -5364,6 +5364,43 @@ def test_pep_695_generics_with_future_annotations_nested_in_function(self):
set(results.generic_func.__type_params__)
)

def test_pep695_generic_class_with_future_typed_dicts(self):
# gh-138949
td1_hints = get_type_hints(ann_module695.TD1)
self.assertEqual(td1_hints, {'a': ann_module695.TD1.__type_params__[0]})

td2_hints = get_type_hints(ann_module695.TD2) # used to fail with `NameError`
self.assertEqual(
td2_hints,
{'a': ann_module695.TD1.__type_params__[0], 'b': int},
)

td3_hints = get_type_hints(ann_module695.TD3)
self.assertEqual(
td3_hints,
{
'a': ann_module695.TD1.__type_params__[0],
'b': int,
'c': ann_module695.TD3.__type_params__[0],
},
)

td4_hints = get_type_hints(ann_module695.TD4)
self.assertEqual(
td4_hints,
{
# Type param `TD4.T` must have a higher precedence over `TD1.T`:
'a': ann_module695.TD4.__type_params__[0],
'b': int,
'c': ann_module695.TD3.__type_params__[0],
'd': ann_module695.TD4.__type_params__[0],
'e': ann_module695.TD4.__type_params__[1],
},
)

with self.assertRaisesRegex(NameError, "name 'T' is not defined"):
get_type_hints(ann_module695.TD1_2)

def test_extended_generic_rules_subclassing(self):
class T1(Tuple[T, KT]): ...
class T2(Tuple[T, ...]): ...
Expand Down
21 changes: 20 additions & 1 deletion Lib/test/typinganndata/ann_module695.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from __future__ import annotations
from typing import Callable
from typing import Callable, TypedDict


class A[T, *Ts, **P]:
Expand Down Expand Up @@ -70,3 +70,22 @@ def generic_function[Eggs, **Spam](x: Eggs, y: Spam): pass
generic_func=generic_function,
hints_for_generic_func=get_type_hints(generic_function)
)

# gh-138949
class TD1[T](TypedDict):
a: T

class TD2(TD1):
b: int

class TD3[CT](TD2):
c: CT

class TD4[T, E](TD3):
d: T
e: E

class TD1_2(TD1):
# This must raise a `NameError`, because `T` is only defined for a parent
# keys scope.
b: T
30 changes: 27 additions & 3 deletions Lib/typing.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -449,9 +449,33 @@ def _eval_type(t, globalns, localns, type_params, *, recursive_guard=frozenset()
# prefer_fwd_module flag), so that the default behavior remains more straightforward.
if prefer_fwd_module and t.__forward_module__ is not None:
globalns = None
# If there are type params on the owner, we need to add them back, because
# annotationlib won't.
if owner_type_params := getattr(owner, "__type_params__", None):
# # If there are type params on the owner, we need to add them back, because
# # annotationlib won't.
owner_type_params = getattr(owner, "__type_params__", ())
# TypedDict classes copy the parent type annotations, but do not
# copy parent type params / mro. So, we need to collect them manually here.
if is_typeddict(owner):
owner_type_params = list(owner_type_params)
mro_stack = list(owner.__orig_bases__)
seen = {tp.__name__ for tp in owner_type_params}
while mro_stack:
typ = mro_stack.pop(0)
if is_typeddict(typ):
mro_stack.extend(typ.__orig_bases__)
if t not in typ.__annotations__.values():
# We only copy __type_params__ for types that own
# this annotation. So, it won't be possible to use
# undeclared type parameters from parent types in children.
continue

base_type_params = getattr(typ, "__type_params__", ())
for btp in base_type_params:
if btp.__name__ in seen:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

this seems wrong, base classes can have another type param with the same name

continue
owner_type_params.append(btp)
seen.add(btp.__name__)
owner_type_params = tuple(owner_type_params)
if owner_type_params:
globalns = getattr(
sys.modules.get(t.__forward_module__, None), "__dict__", None
)
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :exc:`NameError` in :func:`typing.get_type_hints` on non-generic
children of generic typed dicts with future annotations flag enabled.
Loading

Back | FazBrowse Home | New Git URL