Skip to content

Commit 0033813

Browse files
committed
Merge remote-tracking branch 'upstream/main' into TypeAliasType-extension
2 parents 9562635 + 832253d commit 0033813

File tree

3 files changed

+81
-7
lines changed

3 files changed

+81
-7
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
by PEP 649. Patches by Jelle Zijlstra and Alex Waygood.
88
- Copy the coroutine status of functions and methods wrapped
99
with `@typing_extensions.deprecated`. Patch by Sebastian Rittau.
10+
- Fix bug where `TypeAliasType` instances could be subscripted even
11+
where they were not generic. Patch by [Daraan](https://github.com/Daraan).
12+
- Fix bug where a subscripted `TypeAliasType` instance did not have all
13+
attributes of the original `TypeAliasType` instance on older Python versions.
14+
Patch by [Daraan](https://github.com/Daraan) and Alex Waygood.
1015

1116
# Release 4.12.2 (June 7, 2024)
1217

src/test_typing_extensions.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7485,6 +7485,41 @@ def test_invalid_cases_before_3_11(self):
74857485
with self.assertRaises(TypeError):
74867486
ListOrSetT[(Generic[T], )]
74877487

7488+
def test_alias_attributes(self):
7489+
T = TypeVar('T')
7490+
T2 = TypeVar('T2')
7491+
ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,))
7492+
7493+
subscripted = ListOrSetT[int]
7494+
self.assertEqual(subscripted.__module__, ListOrSetT.__module__)
7495+
self.assertEqual(subscripted.__name__, "ListOrSetT")
7496+
self.assertEqual(subscripted.__value__, Union[List[T], Set[T]])
7497+
self.assertEqual(subscripted.__type_params__, (T,))
7498+
7499+
still_generic = ListOrSetT[Iterable[T2]]
7500+
self.assertEqual(still_generic.__module__, ListOrSetT.__module__)
7501+
self.assertEqual(still_generic.__name__, "ListOrSetT")
7502+
self.assertEqual(still_generic.__value__, Union[List[T], Set[T]])
7503+
self.assertEqual(still_generic.__type_params__, (T,))
7504+
7505+
fully_subscripted = still_generic[float]
7506+
self.assertEqual(fully_subscripted.__module__, ListOrSetT.__module__)
7507+
self.assertEqual(fully_subscripted.__name__, "ListOrSetT")
7508+
self.assertEqual(fully_subscripted.__value__, Union[List[T], Set[T]])
7509+
self.assertEqual(fully_subscripted.__type_params__, (T,))
7510+
7511+
def test_subscription_without_type_params(self):
7512+
Simple = TypeAliasType("Simple", int)
7513+
with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"):
7514+
Simple[int]
7515+
7516+
# A TypeVar in the value does not allow subscription
7517+
T = TypeVar('T')
7518+
MissingTypeParamsErr = TypeAliasType("MissingTypeParamsErr", List[T])
7519+
self.assertEqual(MissingTypeParamsErr.__type_params__, ())
7520+
self.assertEqual(MissingTypeParamsErr.__parameters__, ())
7521+
with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"):
7522+
MissingTypeParamsErr[int]
74887523

74897524
def test_pickle(self):
74907525
global Alias

src/typing_extensions.py

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3452,6 +3452,37 @@ def _is_unionable(obj):
34523452
TypeAliasType,
34533453
))
34543454

3455+
if sys.version_info < (3, 10):
3456+
# Copied and pasted from https://github.com/python/cpython/blob/986a4e1b6fcae7fe7a1d0a26aea446107dd58dd2/Objects/genericaliasobject.c#L568-L582,
3457+
# so that we emulate the behaviour of `types.GenericAlias`
3458+
# on the latest versions of CPython
3459+
_ATTRIBUTE_DELEGATION_EXCLUSIONS = frozenset({
3460+
"__class__",
3461+
"__bases__",
3462+
"__origin__",
3463+
"__args__",
3464+
"__unpacked__",
3465+
"__parameters__",
3466+
"__typing_unpacked_tuple_args__",
3467+
"__mro_entries__",
3468+
"__reduce_ex__",
3469+
"__reduce__",
3470+
"__copy__",
3471+
"__deepcopy__",
3472+
})
3473+
3474+
class _TypeAliasGenericAlias(typing._GenericAlias, _root=True):
3475+
def __getattr__(self, attr):
3476+
if attr in _ATTRIBUTE_DELEGATION_EXCLUSIONS:
3477+
return object.__getattr__(self, attr)
3478+
return getattr(self.__origin__, attr)
3479+
3480+
if sys.version_info < (3, 9):
3481+
def __getitem__(self, item):
3482+
result = super().__getitem__(item)
3483+
result.__class__ = type(self)
3484+
return result
3485+
34553486
class TypeAliasType:
34563487
"""Create named, parameterized type aliases.
34573488
@@ -3543,25 +3574,28 @@ def _check_single_param(self, param, recursion=0):
35433574

35443575
def _check_parameters(self, parameters):
35453576
if sys.version_info < (3, 11):
3546-
return [
3577+
return tuple(
35473578
checked
35483579
for item in parameters
35493580
for checked in self._check_single_param(item)
3550-
]
3551-
return [typing._type_check(
3581+
)
3582+
return tuple(typing._type_check(
35523583
item, f'Subscripting {self.__name__} requires a type.'
35533584
)
35543585
for item in parameters
3555-
]
3586+
)
35563587

35573588
def __getitem__(self, parameters):
3589+
if not self.__type_params__:
3590+
raise TypeError("Only generic type aliases are subscriptable")
35583591
if not isinstance(parameters, tuple):
35593592
parameters = (parameters,)
3593+
# Using 3.9 here will create problems with Concatenate
35603594
if sys.version_info >= (3, 10):
3561-
return _types.GenericAlias(self, tuple(parameters))
3595+
return _types.GenericAlias(self, parameters)
3596+
parameters = tuple(self._check_parameters(parameters))
3597+
return typing._GenericAlias(self, tuple(parameters))
35623598
type_vars = _collect_type_vars(parameters)
3563-
parameters = self._check_parameters(parameters)
3564-
alias = typing._GenericAlias(self, tuple(parameters))
35653599
if len(alias.__parameters__) < len(type_vars):
35663600
alias.__parameters__ = tuple(type_vars)
35673601
return alias

0 commit comments

Comments
 (0)