Skip to content

Commit 36a2497

Browse files
bpo-44524: Fix an issue wherein _GenericAlias._name was not properly set for specialforms (GH-27614)
Co-authored-by: Ken Jin <[email protected]> Co-authored-by: Łukasz Langa <[email protected]> (cherry picked from commit 8bdf12e) Co-authored-by: Bas van Beek <[email protected]>
1 parent 2ae2235 commit 36a2497

File tree

3 files changed

+191
-61
lines changed

3 files changed

+191
-61
lines changed

Lib/test/test_typing.py

Lines changed: 173 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4751,66 +4751,185 @@ def test_no_isinstance(self):
47514751
issubclass(int, TypeGuard)
47524752

47534753

4754+
SpecialAttrsP = typing.ParamSpec('SpecialAttrsP')
4755+
SpecialAttrsT = typing.TypeVar('SpecialAttrsT', int, float, complex)
4756+
4757+
47544758
class SpecialAttrsTests(BaseTestCase):
4759+
47554760
def test_special_attrs(self):
4756-
cls_to_check = (
4761+
cls_to_check = {
47574762
# ABC classes
4758-
typing.AbstractSet,
4759-
typing.AsyncContextManager,
4760-
typing.AsyncGenerator,
4761-
typing.AsyncIterable,
4762-
typing.AsyncIterator,
4763-
typing.Awaitable,
4764-
typing.ByteString,
4765-
typing.Callable,
4766-
typing.ChainMap,
4767-
typing.Collection,
4768-
typing.Container,
4769-
typing.ContextManager,
4770-
typing.Coroutine,
4771-
typing.Counter,
4772-
typing.DefaultDict,
4773-
typing.Deque,
4774-
typing.Dict,
4775-
typing.FrozenSet,
4776-
typing.Generator,
4777-
typing.Hashable,
4778-
typing.ItemsView,
4779-
typing.Iterable,
4780-
typing.Iterator,
4781-
typing.KeysView,
4782-
typing.List,
4783-
typing.Mapping,
4784-
typing.MappingView,
4785-
typing.MutableMapping,
4786-
typing.MutableSequence,
4787-
typing.MutableSet,
4788-
typing.OrderedDict,
4789-
typing.Reversible,
4790-
typing.Sequence,
4791-
typing.Set,
4792-
typing.Sized,
4793-
typing.Tuple,
4794-
typing.Type,
4795-
typing.ValuesView,
4763+
typing.AbstractSet: 'AbstractSet',
4764+
typing.AsyncContextManager: 'AsyncContextManager',
4765+
typing.AsyncGenerator: 'AsyncGenerator',
4766+
typing.AsyncIterable: 'AsyncIterable',
4767+
typing.AsyncIterator: 'AsyncIterator',
4768+
typing.Awaitable: 'Awaitable',
4769+
typing.ByteString: 'ByteString',
4770+
typing.Callable: 'Callable',
4771+
typing.ChainMap: 'ChainMap',
4772+
typing.Collection: 'Collection',
4773+
typing.Container: 'Container',
4774+
typing.ContextManager: 'ContextManager',
4775+
typing.Coroutine: 'Coroutine',
4776+
typing.Counter: 'Counter',
4777+
typing.DefaultDict: 'DefaultDict',
4778+
typing.Deque: 'Deque',
4779+
typing.Dict: 'Dict',
4780+
typing.FrozenSet: 'FrozenSet',
4781+
typing.Generator: 'Generator',
4782+
typing.Hashable: 'Hashable',
4783+
typing.ItemsView: 'ItemsView',
4784+
typing.Iterable: 'Iterable',
4785+
typing.Iterator: 'Iterator',
4786+
typing.KeysView: 'KeysView',
4787+
typing.List: 'List',
4788+
typing.Mapping: 'Mapping',
4789+
typing.MappingView: 'MappingView',
4790+
typing.MutableMapping: 'MutableMapping',
4791+
typing.MutableSequence: 'MutableSequence',
4792+
typing.MutableSet: 'MutableSet',
4793+
typing.OrderedDict: 'OrderedDict',
4794+
typing.Reversible: 'Reversible',
4795+
typing.Sequence: 'Sequence',
4796+
typing.Set: 'Set',
4797+
typing.Sized: 'Sized',
4798+
typing.Tuple: 'Tuple',
4799+
typing.Type: 'Type',
4800+
typing.ValuesView: 'ValuesView',
4801+
# Subscribed ABC classes
4802+
typing.AbstractSet[Any]: 'AbstractSet',
4803+
typing.AsyncContextManager[Any]: 'AsyncContextManager',
4804+
typing.AsyncGenerator[Any, Any]: 'AsyncGenerator',
4805+
typing.AsyncIterable[Any]: 'AsyncIterable',
4806+
typing.AsyncIterator[Any]: 'AsyncIterator',
4807+
typing.Awaitable[Any]: 'Awaitable',
4808+
typing.Callable[[], Any]: 'Callable',
4809+
typing.Callable[..., Any]: 'Callable',
4810+
typing.ChainMap[Any, Any]: 'ChainMap',
4811+
typing.Collection[Any]: 'Collection',
4812+
typing.Container[Any]: 'Container',
4813+
typing.ContextManager[Any]: 'ContextManager',
4814+
typing.Coroutine[Any, Any, Any]: 'Coroutine',
4815+
typing.Counter[Any]: 'Counter',
4816+
typing.DefaultDict[Any, Any]: 'DefaultDict',
4817+
typing.Deque[Any]: 'Deque',
4818+
typing.Dict[Any, Any]: 'Dict',
4819+
typing.FrozenSet[Any]: 'FrozenSet',
4820+
typing.Generator[Any, Any, Any]: 'Generator',
4821+
typing.ItemsView[Any, Any]: 'ItemsView',
4822+
typing.Iterable[Any]: 'Iterable',
4823+
typing.Iterator[Any]: 'Iterator',
4824+
typing.KeysView[Any]: 'KeysView',
4825+
typing.List[Any]: 'List',
4826+
typing.Mapping[Any, Any]: 'Mapping',
4827+
typing.MappingView[Any]: 'MappingView',
4828+
typing.MutableMapping[Any, Any]: 'MutableMapping',
4829+
typing.MutableSequence[Any]: 'MutableSequence',
4830+
typing.MutableSet[Any]: 'MutableSet',
4831+
typing.OrderedDict[Any, Any]: 'OrderedDict',
4832+
typing.Reversible[Any]: 'Reversible',
4833+
typing.Sequence[Any]: 'Sequence',
4834+
typing.Set[Any]: 'Set',
4835+
typing.Tuple[Any]: 'Tuple',
4836+
typing.Tuple[Any, ...]: 'Tuple',
4837+
typing.Type[Any]: 'Type',
4838+
typing.ValuesView[Any]: 'ValuesView',
47964839
# Special Forms
4797-
typing.Any,
4798-
typing.NoReturn,
4799-
typing.ClassVar,
4800-
typing.Final,
4801-
typing.Union,
4802-
typing.Optional,
4803-
typing.Literal,
4804-
typing.TypeAlias,
4805-
typing.Concatenate,
4806-
typing.TypeGuard,
4807-
)
4840+
typing.Annotated: 'Annotated',
4841+
typing.Any: 'Any',
4842+
typing.ClassVar: 'ClassVar',
4843+
typing.Concatenate: 'Concatenate',
4844+
typing.Final: 'Final',
4845+
typing.ForwardRef: 'ForwardRef',
4846+
typing.Literal: 'Literal',
4847+
typing.NewType: 'NewType',
4848+
typing.NoReturn: 'NoReturn',
4849+
typing.Optional: 'Optional',
4850+
typing.TypeAlias: 'TypeAlias',
4851+
typing.TypeGuard: 'TypeGuard',
4852+
typing.TypeVar: 'TypeVar',
4853+
typing.Union: 'Union',
4854+
# Subscribed special forms
4855+
typing.Annotated[Any, "Annotation"]: 'Annotated',
4856+
typing.ClassVar[Any]: 'ClassVar',
4857+
typing.Concatenate[Any, SpecialAttrsP]: 'Concatenate',
4858+
typing.Final[Any]: 'Final',
4859+
typing.Literal[Any]: 'Literal',
4860+
typing.Optional[Any]: 'Optional',
4861+
typing.TypeGuard[Any]: 'TypeGuard',
4862+
typing.Union[Any]: 'Any',
4863+
typing.Union[int, float]: 'Union',
4864+
# Incompatible special forms (tested in test_special_attrs2)
4865+
# - typing.ForwardRef('set[Any]')
4866+
# - typing.NewType('TypeName', Any)
4867+
# - typing.ParamSpec('SpecialAttrsP')
4868+
# - typing.TypeVar('T')
4869+
}
48084870

4809-
for cls in cls_to_check:
4871+
for cls, name in cls_to_check.items():
48104872
with self.subTest(cls=cls):
4811-
self.assertEqual(cls.__name__, cls._name)
4812-
self.assertEqual(cls.__qualname__, cls._name)
4813-
self.assertEqual(cls.__module__, 'typing')
4873+
self.assertEqual(cls.__name__, name, str(cls))
4874+
self.assertEqual(cls.__qualname__, name, str(cls))
4875+
self.assertEqual(cls.__module__, 'typing', str(cls))
4876+
self.assertEqual(getattr(cls, '_name', name), name, str(cls))
4877+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
4878+
s = pickle.dumps(cls, proto)
4879+
loaded = pickle.loads(s)
4880+
self.assertIs(cls, loaded)
4881+
4882+
TypeName = typing.NewType('SpecialAttrsTests.TypeName', Any)
4883+
4884+
def test_special_attrs2(self):
4885+
# Forward refs provide a different introspection API. __name__ and
4886+
# __qualname__ make little sense for forward refs as they can store
4887+
# complex typing expressions.
4888+
fr = typing.ForwardRef('set[Any]')
4889+
self.assertFalse(hasattr(fr, '__name__'))
4890+
self.assertFalse(hasattr(fr, '__qualname__'))
4891+
self.assertEqual(fr.__module__, 'typing')
4892+
# Forward refs are currently unpicklable.
4893+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
4894+
with self.assertRaises(TypeError) as exc:
4895+
pickle.dumps(fr, proto)
4896+
4897+
self.assertEqual(SpecialAttrsTests.TypeName.__name__, 'TypeName')
4898+
self.assertEqual(
4899+
SpecialAttrsTests.TypeName.__qualname__,
4900+
'SpecialAttrsTests.TypeName',
4901+
)
4902+
self.assertEqual(
4903+
SpecialAttrsTests.TypeName.__module__,
4904+
'test.test_typing',
4905+
)
4906+
# NewTypes are picklable assuming correct qualname information.
4907+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
4908+
s = pickle.dumps(SpecialAttrsTests.TypeName, proto)
4909+
loaded = pickle.loads(s)
4910+
self.assertIs(SpecialAttrsTests.TypeName, loaded)
4911+
4912+
# Type variables don't support non-global instantiation per PEP 484
4913+
# restriction that "The argument to TypeVar() must be a string equal
4914+
# to the variable name to which it is assigned". Thus, providing
4915+
# __qualname__ is unnecessary.
4916+
self.assertEqual(SpecialAttrsT.__name__, 'SpecialAttrsT')
4917+
self.assertFalse(hasattr(SpecialAttrsT, '__qualname__'))
4918+
self.assertEqual(SpecialAttrsT.__module__, 'test.test_typing')
4919+
# Module-level type variables are picklable.
4920+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
4921+
s = pickle.dumps(SpecialAttrsT, proto)
4922+
loaded = pickle.loads(s)
4923+
self.assertIs(SpecialAttrsT, loaded)
4924+
4925+
self.assertEqual(SpecialAttrsP.__name__, 'SpecialAttrsP')
4926+
self.assertFalse(hasattr(SpecialAttrsP, '__qualname__'))
4927+
self.assertEqual(SpecialAttrsP.__module__, 'test.test_typing')
4928+
# Module-level ParamSpecs are picklable.
4929+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
4930+
s = pickle.dumps(SpecialAttrsP, proto)
4931+
loaded = pickle.loads(s)
4932+
self.assertIs(SpecialAttrsP, loaded)
48144933

48154934
class AllTests(BaseTestCase):
48164935
"""Tests for __all__."""

Lib/typing.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ class Starship:
453453
be used with isinstance() or issubclass().
454454
"""
455455
item = _type_check(parameters, f'{self} accepts only single type.')
456-
return _GenericAlias(self, (item,))
456+
return _GenericAlias(self, (item,), name="ClassVar")
457457

458458
@_SpecialForm
459459
def Final(self, parameters):
@@ -474,7 +474,7 @@ class FastConnector(Connection):
474474
There is no runtime checking of these properties.
475475
"""
476476
item = _type_check(parameters, f'{self} accepts only single type.')
477-
return _GenericAlias(self, (item,))
477+
return _GenericAlias(self, (item,), name="Final")
478478

479479
@_SpecialForm
480480
def Union(self, parameters):
@@ -512,7 +512,12 @@ def Union(self, parameters):
512512
parameters = _remove_dups_flatten(parameters)
513513
if len(parameters) == 1:
514514
return parameters[0]
515-
return _UnionGenericAlias(self, parameters)
515+
516+
if len(parameters) == 2 and type(None) in parameters:
517+
name = "Optional"
518+
else:
519+
name = "Union"
520+
return _UnionGenericAlias(self, parameters, name=name)
516521

517522
@_SpecialForm
518523
def Optional(self, parameters):
@@ -557,7 +562,7 @@ def open_helper(file: str, mode: MODE) -> str:
557562
except TypeError: # unhashable parameters
558563
pass
559564

560-
return _LiteralGenericAlias(self, parameters)
565+
return _LiteralGenericAlias(self, parameters, name="Literal")
561566

562567

563568
@_SpecialForm
@@ -596,7 +601,7 @@ def Concatenate(self, parameters):
596601
"ParamSpec variable.")
597602
msg = "Concatenate[arg, ...]: each arg must be a type."
598603
parameters = tuple(_type_check(p, msg) for p in parameters)
599-
return _ConcatenateGenericAlias(self, parameters)
604+
return _ConcatenateGenericAlias(self, parameters, name="Concatenate")
600605

601606

602607
@_SpecialForm
@@ -644,7 +649,7 @@ def is_str(val: Union[str, float]):
644649
PEP 647 (User-Defined Type Guards).
645650
"""
646651
item = _type_check(parameters, f'{self} accepts only single type.')
647-
return _GenericAlias(self, (item,))
652+
return _GenericAlias(self, (item,), name="TypeGuard")
648653

649654

650655
class ForwardRef(_Final, _root=True):
@@ -1235,6 +1240,10 @@ def __subclasscheck__(self, cls):
12351240
if issubclass(cls, arg):
12361241
return True
12371242

1243+
def __reduce__(self):
1244+
func, (origin, args) = super().__reduce__()
1245+
return func, (Union, args)
1246+
12381247

12391248
def _value_and_type_iter(parameters):
12401249
return ((p, type(p)) for p in parameters)
@@ -1567,7 +1576,7 @@ def __init__(self, origin, metadata):
15671576
if isinstance(origin, _AnnotatedAlias):
15681577
metadata = origin.__metadata__ + metadata
15691578
origin = origin.__origin__
1570-
super().__init__(origin, origin)
1579+
super().__init__(origin, origin, name="Annotated")
15711580
self.__metadata__ = metadata
15721581

15731582
def copy_with(self, params):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixed an issue wherein the ``__name__`` and ``__qualname__`` attributes of
2+
subscribed specialforms could be ``None``.

0 commit comments

Comments
 (0)