Skip to content

Commit 8bdf12e

Browse files
BvB93Fidget-Spinnerambv
authored
bpo-44524: Fix an issue wherein _GenericAlias._name was not properly set for specialforms (GH-27614)
Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Co-authored-by: Łukasz Langa <lukasz@langa.pl>
1 parent c24896c commit 8bdf12e

File tree

3 files changed

+191
-61
lines changed

3 files changed

+191
-61
lines changed

Lib/test/test_typing.py

+173-54
Original file line numberDiff line numberDiff line change
@@ -4791,66 +4791,185 @@ def test_no_isinstance(self):
47914791
issubclass(int, TypeGuard)
47924792

47934793

4794+
SpecialAttrsP = typing.ParamSpec('SpecialAttrsP')
4795+
SpecialAttrsT = typing.TypeVar('SpecialAttrsT', int, float, complex)
4796+
4797+
47944798
class SpecialAttrsTests(BaseTestCase):
4799+
47954800
def test_special_attrs(self):
4796-
cls_to_check = (
4801+
cls_to_check = {
47974802
# ABC classes
4798-
typing.AbstractSet,
4799-
typing.AsyncContextManager,
4800-
typing.AsyncGenerator,
4801-
typing.AsyncIterable,
4802-
typing.AsyncIterator,
4803-
typing.Awaitable,
4804-
typing.ByteString,
4805-
typing.Callable,
4806-
typing.ChainMap,
4807-
typing.Collection,
4808-
typing.Container,
4809-
typing.ContextManager,
4810-
typing.Coroutine,
4811-
typing.Counter,
4812-
typing.DefaultDict,
4813-
typing.Deque,
4814-
typing.Dict,
4815-
typing.FrozenSet,
4816-
typing.Generator,
4817-
typing.Hashable,
4818-
typing.ItemsView,
4819-
typing.Iterable,
4820-
typing.Iterator,
4821-
typing.KeysView,
4822-
typing.List,
4823-
typing.Mapping,
4824-
typing.MappingView,
4825-
typing.MutableMapping,
4826-
typing.MutableSequence,
4827-
typing.MutableSet,
4828-
typing.OrderedDict,
4829-
typing.Reversible,
4830-
typing.Sequence,
4831-
typing.Set,
4832-
typing.Sized,
4833-
typing.Tuple,
4834-
typing.Type,
4835-
typing.ValuesView,
4803+
typing.AbstractSet: 'AbstractSet',
4804+
typing.AsyncContextManager: 'AsyncContextManager',
4805+
typing.AsyncGenerator: 'AsyncGenerator',
4806+
typing.AsyncIterable: 'AsyncIterable',
4807+
typing.AsyncIterator: 'AsyncIterator',
4808+
typing.Awaitable: 'Awaitable',
4809+
typing.ByteString: 'ByteString',
4810+
typing.Callable: 'Callable',
4811+
typing.ChainMap: 'ChainMap',
4812+
typing.Collection: 'Collection',
4813+
typing.Container: 'Container',
4814+
typing.ContextManager: 'ContextManager',
4815+
typing.Coroutine: 'Coroutine',
4816+
typing.Counter: 'Counter',
4817+
typing.DefaultDict: 'DefaultDict',
4818+
typing.Deque: 'Deque',
4819+
typing.Dict: 'Dict',
4820+
typing.FrozenSet: 'FrozenSet',
4821+
typing.Generator: 'Generator',
4822+
typing.Hashable: 'Hashable',
4823+
typing.ItemsView: 'ItemsView',
4824+
typing.Iterable: 'Iterable',
4825+
typing.Iterator: 'Iterator',
4826+
typing.KeysView: 'KeysView',
4827+
typing.List: 'List',
4828+
typing.Mapping: 'Mapping',
4829+
typing.MappingView: 'MappingView',
4830+
typing.MutableMapping: 'MutableMapping',
4831+
typing.MutableSequence: 'MutableSequence',
4832+
typing.MutableSet: 'MutableSet',
4833+
typing.OrderedDict: 'OrderedDict',
4834+
typing.Reversible: 'Reversible',
4835+
typing.Sequence: 'Sequence',
4836+
typing.Set: 'Set',
4837+
typing.Sized: 'Sized',
4838+
typing.Tuple: 'Tuple',
4839+
typing.Type: 'Type',
4840+
typing.ValuesView: 'ValuesView',
4841+
# Subscribed ABC classes
4842+
typing.AbstractSet[Any]: 'AbstractSet',
4843+
typing.AsyncContextManager[Any]: 'AsyncContextManager',
4844+
typing.AsyncGenerator[Any, Any]: 'AsyncGenerator',
4845+
typing.AsyncIterable[Any]: 'AsyncIterable',
4846+
typing.AsyncIterator[Any]: 'AsyncIterator',
4847+
typing.Awaitable[Any]: 'Awaitable',
4848+
typing.Callable[[], Any]: 'Callable',
4849+
typing.Callable[..., Any]: 'Callable',
4850+
typing.ChainMap[Any, Any]: 'ChainMap',
4851+
typing.Collection[Any]: 'Collection',
4852+
typing.Container[Any]: 'Container',
4853+
typing.ContextManager[Any]: 'ContextManager',
4854+
typing.Coroutine[Any, Any, Any]: 'Coroutine',
4855+
typing.Counter[Any]: 'Counter',
4856+
typing.DefaultDict[Any, Any]: 'DefaultDict',
4857+
typing.Deque[Any]: 'Deque',
4858+
typing.Dict[Any, Any]: 'Dict',
4859+
typing.FrozenSet[Any]: 'FrozenSet',
4860+
typing.Generator[Any, Any, Any]: 'Generator',
4861+
typing.ItemsView[Any, Any]: 'ItemsView',
4862+
typing.Iterable[Any]: 'Iterable',
4863+
typing.Iterator[Any]: 'Iterator',
4864+
typing.KeysView[Any]: 'KeysView',
4865+
typing.List[Any]: 'List',
4866+
typing.Mapping[Any, Any]: 'Mapping',
4867+
typing.MappingView[Any]: 'MappingView',
4868+
typing.MutableMapping[Any, Any]: 'MutableMapping',
4869+
typing.MutableSequence[Any]: 'MutableSequence',
4870+
typing.MutableSet[Any]: 'MutableSet',
4871+
typing.OrderedDict[Any, Any]: 'OrderedDict',
4872+
typing.Reversible[Any]: 'Reversible',
4873+
typing.Sequence[Any]: 'Sequence',
4874+
typing.Set[Any]: 'Set',
4875+
typing.Tuple[Any]: 'Tuple',
4876+
typing.Tuple[Any, ...]: 'Tuple',
4877+
typing.Type[Any]: 'Type',
4878+
typing.ValuesView[Any]: 'ValuesView',
48364879
# Special Forms
4837-
typing.Any,
4838-
typing.NoReturn,
4839-
typing.ClassVar,
4840-
typing.Final,
4841-
typing.Union,
4842-
typing.Optional,
4843-
typing.Literal,
4844-
typing.TypeAlias,
4845-
typing.Concatenate,
4846-
typing.TypeGuard,
4847-
)
4880+
typing.Annotated: 'Annotated',
4881+
typing.Any: 'Any',
4882+
typing.ClassVar: 'ClassVar',
4883+
typing.Concatenate: 'Concatenate',
4884+
typing.Final: 'Final',
4885+
typing.ForwardRef: 'ForwardRef',
4886+
typing.Literal: 'Literal',
4887+
typing.NewType: 'NewType',
4888+
typing.NoReturn: 'NoReturn',
4889+
typing.Optional: 'Optional',
4890+
typing.TypeAlias: 'TypeAlias',
4891+
typing.TypeGuard: 'TypeGuard',
4892+
typing.TypeVar: 'TypeVar',
4893+
typing.Union: 'Union',
4894+
# Subscribed special forms
4895+
typing.Annotated[Any, "Annotation"]: 'Annotated',
4896+
typing.ClassVar[Any]: 'ClassVar',
4897+
typing.Concatenate[Any, SpecialAttrsP]: 'Concatenate',
4898+
typing.Final[Any]: 'Final',
4899+
typing.Literal[Any]: 'Literal',
4900+
typing.Optional[Any]: 'Optional',
4901+
typing.TypeGuard[Any]: 'TypeGuard',
4902+
typing.Union[Any]: 'Any',
4903+
typing.Union[int, float]: 'Union',
4904+
# Incompatible special forms (tested in test_special_attrs2)
4905+
# - typing.ForwardRef('set[Any]')
4906+
# - typing.NewType('TypeName', Any)
4907+
# - typing.ParamSpec('SpecialAttrsP')
4908+
# - typing.TypeVar('T')
4909+
}
48484910

4849-
for cls in cls_to_check:
4911+
for cls, name in cls_to_check.items():
48504912
with self.subTest(cls=cls):
4851-
self.assertEqual(cls.__name__, cls._name)
4852-
self.assertEqual(cls.__qualname__, cls._name)
4853-
self.assertEqual(cls.__module__, 'typing')
4913+
self.assertEqual(cls.__name__, name, str(cls))
4914+
self.assertEqual(cls.__qualname__, name, str(cls))
4915+
self.assertEqual(cls.__module__, 'typing', str(cls))
4916+
self.assertEqual(getattr(cls, '_name', name), name, str(cls))
4917+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
4918+
s = pickle.dumps(cls, proto)
4919+
loaded = pickle.loads(s)
4920+
self.assertIs(cls, loaded)
4921+
4922+
TypeName = typing.NewType('SpecialAttrsTests.TypeName', Any)
4923+
4924+
def test_special_attrs2(self):
4925+
# Forward refs provide a different introspection API. __name__ and
4926+
# __qualname__ make little sense for forward refs as they can store
4927+
# complex typing expressions.
4928+
fr = typing.ForwardRef('set[Any]')
4929+
self.assertFalse(hasattr(fr, '__name__'))
4930+
self.assertFalse(hasattr(fr, '__qualname__'))
4931+
self.assertEqual(fr.__module__, 'typing')
4932+
# Forward refs are currently unpicklable.
4933+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
4934+
with self.assertRaises(TypeError) as exc:
4935+
pickle.dumps(fr, proto)
4936+
4937+
self.assertEqual(SpecialAttrsTests.TypeName.__name__, 'TypeName')
4938+
self.assertEqual(
4939+
SpecialAttrsTests.TypeName.__qualname__,
4940+
'SpecialAttrsTests.TypeName',
4941+
)
4942+
self.assertEqual(
4943+
SpecialAttrsTests.TypeName.__module__,
4944+
'test.test_typing',
4945+
)
4946+
# NewTypes are picklable assuming correct qualname information.
4947+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
4948+
s = pickle.dumps(SpecialAttrsTests.TypeName, proto)
4949+
loaded = pickle.loads(s)
4950+
self.assertIs(SpecialAttrsTests.TypeName, loaded)
4951+
4952+
# Type variables don't support non-global instantiation per PEP 484
4953+
# restriction that "The argument to TypeVar() must be a string equal
4954+
# to the variable name to which it is assigned". Thus, providing
4955+
# __qualname__ is unnecessary.
4956+
self.assertEqual(SpecialAttrsT.__name__, 'SpecialAttrsT')
4957+
self.assertFalse(hasattr(SpecialAttrsT, '__qualname__'))
4958+
self.assertEqual(SpecialAttrsT.__module__, 'test.test_typing')
4959+
# Module-level type variables are picklable.
4960+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
4961+
s = pickle.dumps(SpecialAttrsT, proto)
4962+
loaded = pickle.loads(s)
4963+
self.assertIs(SpecialAttrsT, loaded)
4964+
4965+
self.assertEqual(SpecialAttrsP.__name__, 'SpecialAttrsP')
4966+
self.assertFalse(hasattr(SpecialAttrsP, '__qualname__'))
4967+
self.assertEqual(SpecialAttrsP.__module__, 'test.test_typing')
4968+
# Module-level ParamSpecs are picklable.
4969+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
4970+
s = pickle.dumps(SpecialAttrsP, proto)
4971+
loaded = pickle.loads(s)
4972+
self.assertIs(SpecialAttrsP, loaded)
48544973

48554974
class AllTests(BaseTestCase):
48564975
"""Tests for __all__."""

Lib/typing.py

+16-7
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ class Starship:
461461
be used with isinstance() or issubclass().
462462
"""
463463
item = _type_check(parameters, f'{self} accepts only single type.')
464-
return _GenericAlias(self, (item,))
464+
return _GenericAlias(self, (item,), name="ClassVar")
465465

466466
@_SpecialForm
467467
def Final(self, parameters):
@@ -482,7 +482,7 @@ class FastConnector(Connection):
482482
There is no runtime checking of these properties.
483483
"""
484484
item = _type_check(parameters, f'{self} accepts only single type.')
485-
return _GenericAlias(self, (item,))
485+
return _GenericAlias(self, (item,), name="Final")
486486

487487
@_SpecialForm
488488
def Union(self, parameters):
@@ -520,7 +520,12 @@ def Union(self, parameters):
520520
parameters = _remove_dups_flatten(parameters)
521521
if len(parameters) == 1:
522522
return parameters[0]
523-
return _UnionGenericAlias(self, parameters)
523+
524+
if len(parameters) == 2 and type(None) in parameters:
525+
name = "Optional"
526+
else:
527+
name = "Union"
528+
return _UnionGenericAlias(self, parameters, name=name)
524529

525530
@_SpecialForm
526531
def Optional(self, parameters):
@@ -565,7 +570,7 @@ def open_helper(file: str, mode: MODE) -> str:
565570
except TypeError: # unhashable parameters
566571
pass
567572

568-
return _LiteralGenericAlias(self, parameters)
573+
return _LiteralGenericAlias(self, parameters, name="Literal")
569574

570575

571576
@_SpecialForm
@@ -604,7 +609,7 @@ def Concatenate(self, parameters):
604609
"ParamSpec variable.")
605610
msg = "Concatenate[arg, ...]: each arg must be a type."
606611
parameters = tuple(_type_check(p, msg) for p in parameters)
607-
return _ConcatenateGenericAlias(self, parameters)
612+
return _ConcatenateGenericAlias(self, parameters, name="Concatenate")
608613

609614

610615
@_SpecialForm
@@ -652,7 +657,7 @@ def is_str(val: Union[str, float]):
652657
PEP 647 (User-Defined Type Guards).
653658
"""
654659
item = _type_check(parameters, f'{self} accepts only single type.')
655-
return _GenericAlias(self, (item,))
660+
return _GenericAlias(self, (item,), name="TypeGuard")
656661

657662

658663
class ForwardRef(_Final, _root=True):
@@ -1237,6 +1242,10 @@ def __subclasscheck__(self, cls):
12371242
if issubclass(cls, arg):
12381243
return True
12391244

1245+
def __reduce__(self):
1246+
func, (origin, args) = super().__reduce__()
1247+
return func, (Union, args)
1248+
12401249

12411250
def _value_and_type_iter(parameters):
12421251
return ((p, type(p)) for p in parameters)
@@ -1566,7 +1575,7 @@ def __init__(self, origin, metadata):
15661575
if isinstance(origin, _AnnotatedAlias):
15671576
metadata = origin.__metadata__ + metadata
15681577
origin = origin.__origin__
1569-
super().__init__(origin, origin)
1578+
super().__init__(origin, origin, name="Annotated")
15701579
self.__metadata__ = metadata
15711580

15721581
def copy_with(self, params):
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)