From 7311293ad9bc7b3b3c8b7bd21038690f17fbd8a7 Mon Sep 17 00:00:00 2001 From: Bas van Beek <43369155+BvB93@users.noreply.github.com> Date: Fri, 6 Aug 2021 15:36:35 +0200 Subject: [PATCH] bpo-44524: Fix an issue wherein `_GenericAlias._name` was not properly set for specialforms (GH-27614) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Co-authored-by: Ɓukasz Langa (cherry picked from commit 8bdf12e99a3dc7ada5f85bba79c2a9eb9931f5b0) Co-authored-by: Bas van Beek <43369155+BvB93@users.noreply.github.com> --- Lib/test/test_typing.py | 227 +++++++++++++----- Lib/typing.py | 23 +- .../2021-08-05-18-20-17.bpo-44524.9T1tfe.rst | 2 + 3 files changed, 191 insertions(+), 61 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2021-08-05-18-20-17.bpo-44524.9T1tfe.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 6e5609d723bee3..af7c343d6b69a7 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -4751,66 +4751,185 @@ def test_no_isinstance(self): issubclass(int, TypeGuard) +SpecialAttrsP = typing.ParamSpec('SpecialAttrsP') +SpecialAttrsT = typing.TypeVar('SpecialAttrsT', int, float, complex) + + class SpecialAttrsTests(BaseTestCase): + def test_special_attrs(self): - cls_to_check = ( + cls_to_check = { # ABC classes - typing.AbstractSet, - typing.AsyncContextManager, - typing.AsyncGenerator, - typing.AsyncIterable, - typing.AsyncIterator, - typing.Awaitable, - typing.ByteString, - typing.Callable, - typing.ChainMap, - typing.Collection, - typing.Container, - typing.ContextManager, - typing.Coroutine, - typing.Counter, - typing.DefaultDict, - typing.Deque, - typing.Dict, - typing.FrozenSet, - typing.Generator, - typing.Hashable, - typing.ItemsView, - typing.Iterable, - typing.Iterator, - typing.KeysView, - typing.List, - typing.Mapping, - typing.MappingView, - typing.MutableMapping, - typing.MutableSequence, - typing.MutableSet, - typing.OrderedDict, - typing.Reversible, - typing.Sequence, - typing.Set, - typing.Sized, - typing.Tuple, - typing.Type, - typing.ValuesView, + typing.AbstractSet: 'AbstractSet', + typing.AsyncContextManager: 'AsyncContextManager', + typing.AsyncGenerator: 'AsyncGenerator', + typing.AsyncIterable: 'AsyncIterable', + typing.AsyncIterator: 'AsyncIterator', + typing.Awaitable: 'Awaitable', + typing.ByteString: 'ByteString', + typing.Callable: 'Callable', + typing.ChainMap: 'ChainMap', + typing.Collection: 'Collection', + typing.Container: 'Container', + typing.ContextManager: 'ContextManager', + typing.Coroutine: 'Coroutine', + typing.Counter: 'Counter', + typing.DefaultDict: 'DefaultDict', + typing.Deque: 'Deque', + typing.Dict: 'Dict', + typing.FrozenSet: 'FrozenSet', + typing.Generator: 'Generator', + typing.Hashable: 'Hashable', + typing.ItemsView: 'ItemsView', + typing.Iterable: 'Iterable', + typing.Iterator: 'Iterator', + typing.KeysView: 'KeysView', + typing.List: 'List', + typing.Mapping: 'Mapping', + typing.MappingView: 'MappingView', + typing.MutableMapping: 'MutableMapping', + typing.MutableSequence: 'MutableSequence', + typing.MutableSet: 'MutableSet', + typing.OrderedDict: 'OrderedDict', + typing.Reversible: 'Reversible', + typing.Sequence: 'Sequence', + typing.Set: 'Set', + typing.Sized: 'Sized', + typing.Tuple: 'Tuple', + typing.Type: 'Type', + typing.ValuesView: 'ValuesView', + # Subscribed ABC classes + typing.AbstractSet[Any]: 'AbstractSet', + typing.AsyncContextManager[Any]: 'AsyncContextManager', + typing.AsyncGenerator[Any, Any]: 'AsyncGenerator', + typing.AsyncIterable[Any]: 'AsyncIterable', + typing.AsyncIterator[Any]: 'AsyncIterator', + typing.Awaitable[Any]: 'Awaitable', + typing.Callable[[], Any]: 'Callable', + typing.Callable[..., Any]: 'Callable', + typing.ChainMap[Any, Any]: 'ChainMap', + typing.Collection[Any]: 'Collection', + typing.Container[Any]: 'Container', + typing.ContextManager[Any]: 'ContextManager', + typing.Coroutine[Any, Any, Any]: 'Coroutine', + typing.Counter[Any]: 'Counter', + typing.DefaultDict[Any, Any]: 'DefaultDict', + typing.Deque[Any]: 'Deque', + typing.Dict[Any, Any]: 'Dict', + typing.FrozenSet[Any]: 'FrozenSet', + typing.Generator[Any, Any, Any]: 'Generator', + typing.ItemsView[Any, Any]: 'ItemsView', + typing.Iterable[Any]: 'Iterable', + typing.Iterator[Any]: 'Iterator', + typing.KeysView[Any]: 'KeysView', + typing.List[Any]: 'List', + typing.Mapping[Any, Any]: 'Mapping', + typing.MappingView[Any]: 'MappingView', + typing.MutableMapping[Any, Any]: 'MutableMapping', + typing.MutableSequence[Any]: 'MutableSequence', + typing.MutableSet[Any]: 'MutableSet', + typing.OrderedDict[Any, Any]: 'OrderedDict', + typing.Reversible[Any]: 'Reversible', + typing.Sequence[Any]: 'Sequence', + typing.Set[Any]: 'Set', + typing.Tuple[Any]: 'Tuple', + typing.Tuple[Any, ...]: 'Tuple', + typing.Type[Any]: 'Type', + typing.ValuesView[Any]: 'ValuesView', # Special Forms - typing.Any, - typing.NoReturn, - typing.ClassVar, - typing.Final, - typing.Union, - typing.Optional, - typing.Literal, - typing.TypeAlias, - typing.Concatenate, - typing.TypeGuard, - ) + typing.Annotated: 'Annotated', + typing.Any: 'Any', + typing.ClassVar: 'ClassVar', + typing.Concatenate: 'Concatenate', + typing.Final: 'Final', + typing.ForwardRef: 'ForwardRef', + typing.Literal: 'Literal', + typing.NewType: 'NewType', + typing.NoReturn: 'NoReturn', + typing.Optional: 'Optional', + typing.TypeAlias: 'TypeAlias', + typing.TypeGuard: 'TypeGuard', + typing.TypeVar: 'TypeVar', + typing.Union: 'Union', + # Subscribed special forms + typing.Annotated[Any, "Annotation"]: 'Annotated', + typing.ClassVar[Any]: 'ClassVar', + typing.Concatenate[Any, SpecialAttrsP]: 'Concatenate', + typing.Final[Any]: 'Final', + typing.Literal[Any]: 'Literal', + typing.Optional[Any]: 'Optional', + typing.TypeGuard[Any]: 'TypeGuard', + typing.Union[Any]: 'Any', + typing.Union[int, float]: 'Union', + # Incompatible special forms (tested in test_special_attrs2) + # - typing.ForwardRef('set[Any]') + # - typing.NewType('TypeName', Any) + # - typing.ParamSpec('SpecialAttrsP') + # - typing.TypeVar('T') + } - for cls in cls_to_check: + for cls, name in cls_to_check.items(): with self.subTest(cls=cls): - self.assertEqual(cls.__name__, cls._name) - self.assertEqual(cls.__qualname__, cls._name) - self.assertEqual(cls.__module__, 'typing') + self.assertEqual(cls.__name__, name, str(cls)) + self.assertEqual(cls.__qualname__, name, str(cls)) + self.assertEqual(cls.__module__, 'typing', str(cls)) + self.assertEqual(getattr(cls, '_name', name), name, str(cls)) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + s = pickle.dumps(cls, proto) + loaded = pickle.loads(s) + self.assertIs(cls, loaded) + + TypeName = typing.NewType('SpecialAttrsTests.TypeName', Any) + + def test_special_attrs2(self): + # Forward refs provide a different introspection API. __name__ and + # __qualname__ make little sense for forward refs as they can store + # complex typing expressions. + fr = typing.ForwardRef('set[Any]') + self.assertFalse(hasattr(fr, '__name__')) + self.assertFalse(hasattr(fr, '__qualname__')) + self.assertEqual(fr.__module__, 'typing') + # Forward refs are currently unpicklable. + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.assertRaises(TypeError) as exc: + pickle.dumps(fr, proto) + + self.assertEqual(SpecialAttrsTests.TypeName.__name__, 'TypeName') + self.assertEqual( + SpecialAttrsTests.TypeName.__qualname__, + 'SpecialAttrsTests.TypeName', + ) + self.assertEqual( + SpecialAttrsTests.TypeName.__module__, + 'test.test_typing', + ) + # NewTypes are picklable assuming correct qualname information. + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + s = pickle.dumps(SpecialAttrsTests.TypeName, proto) + loaded = pickle.loads(s) + self.assertIs(SpecialAttrsTests.TypeName, loaded) + + # Type variables don't support non-global instantiation per PEP 484 + # restriction that "The argument to TypeVar() must be a string equal + # to the variable name to which it is assigned". Thus, providing + # __qualname__ is unnecessary. + self.assertEqual(SpecialAttrsT.__name__, 'SpecialAttrsT') + self.assertFalse(hasattr(SpecialAttrsT, '__qualname__')) + self.assertEqual(SpecialAttrsT.__module__, 'test.test_typing') + # Module-level type variables are picklable. + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + s = pickle.dumps(SpecialAttrsT, proto) + loaded = pickle.loads(s) + self.assertIs(SpecialAttrsT, loaded) + + self.assertEqual(SpecialAttrsP.__name__, 'SpecialAttrsP') + self.assertFalse(hasattr(SpecialAttrsP, '__qualname__')) + self.assertEqual(SpecialAttrsP.__module__, 'test.test_typing') + # Module-level ParamSpecs are picklable. + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + s = pickle.dumps(SpecialAttrsP, proto) + loaded = pickle.loads(s) + self.assertIs(SpecialAttrsP, loaded) class AllTests(BaseTestCase): """Tests for __all__.""" diff --git a/Lib/typing.py b/Lib/typing.py index 3cbc8a42c2010a..6d7ba330acabe0 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -453,7 +453,7 @@ class Starship: be used with isinstance() or issubclass(). """ item = _type_check(parameters, f'{self} accepts only single type.') - return _GenericAlias(self, (item,)) + return _GenericAlias(self, (item,), name="ClassVar") @_SpecialForm def Final(self, parameters): @@ -474,7 +474,7 @@ class FastConnector(Connection): There is no runtime checking of these properties. """ item = _type_check(parameters, f'{self} accepts only single type.') - return _GenericAlias(self, (item,)) + return _GenericAlias(self, (item,), name="Final") @_SpecialForm def Union(self, parameters): @@ -512,7 +512,12 @@ def Union(self, parameters): parameters = _remove_dups_flatten(parameters) if len(parameters) == 1: return parameters[0] - return _UnionGenericAlias(self, parameters) + + if len(parameters) == 2 and type(None) in parameters: + name = "Optional" + else: + name = "Union" + return _UnionGenericAlias(self, parameters, name=name) @_SpecialForm def Optional(self, parameters): @@ -557,7 +562,7 @@ def open_helper(file: str, mode: MODE) -> str: except TypeError: # unhashable parameters pass - return _LiteralGenericAlias(self, parameters) + return _LiteralGenericAlias(self, parameters, name="Literal") @_SpecialForm @@ -596,7 +601,7 @@ def Concatenate(self, parameters): "ParamSpec variable.") msg = "Concatenate[arg, ...]: each arg must be a type." parameters = tuple(_type_check(p, msg) for p in parameters) - return _ConcatenateGenericAlias(self, parameters) + return _ConcatenateGenericAlias(self, parameters, name="Concatenate") @_SpecialForm @@ -644,7 +649,7 @@ def is_str(val: Union[str, float]): PEP 647 (User-Defined Type Guards). """ item = _type_check(parameters, f'{self} accepts only single type.') - return _GenericAlias(self, (item,)) + return _GenericAlias(self, (item,), name="TypeGuard") class ForwardRef(_Final, _root=True): @@ -1235,6 +1240,10 @@ def __subclasscheck__(self, cls): if issubclass(cls, arg): return True + def __reduce__(self): + func, (origin, args) = super().__reduce__() + return func, (Union, args) + def _value_and_type_iter(parameters): return ((p, type(p)) for p in parameters) @@ -1567,7 +1576,7 @@ def __init__(self, origin, metadata): if isinstance(origin, _AnnotatedAlias): metadata = origin.__metadata__ + metadata origin = origin.__origin__ - super().__init__(origin, origin) + super().__init__(origin, origin, name="Annotated") self.__metadata__ = metadata def copy_with(self, params): diff --git a/Misc/NEWS.d/next/Library/2021-08-05-18-20-17.bpo-44524.9T1tfe.rst b/Misc/NEWS.d/next/Library/2021-08-05-18-20-17.bpo-44524.9T1tfe.rst new file mode 100644 index 00000000000000..0c9e82050883d5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-08-05-18-20-17.bpo-44524.9T1tfe.rst @@ -0,0 +1,2 @@ +Fixed an issue wherein the ``__name__`` and ``__qualname__`` attributes of +subscribed specialforms could be ``None``.