diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c17c1ba..0d3cafa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ with `@typing_extensions.deprecated`. Patch by Sebastian Rittau. - Fix bug where `TypeAliasType` instances could be subscripted even where they were not generic. Patch by [Daraan](https://github.com/Daraan). +- Fix bug where a subscripted `TypeAliasType` instance did not have all + attributes of the original `TypeAliasType` instance on older Python versions. + Patch by [Daraan](https://github.com/Daraan) and Alex Waygood. # Release 4.12.2 (June 7, 2024) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 79f01901..05b3083f 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7247,6 +7247,29 @@ def test_getitem(self): self.assertEqual(get_args(fully_subscripted), (Iterable[float],)) self.assertIs(get_origin(fully_subscripted), ListOrSetT) + def test_alias_attributes(self): + T = TypeVar('T') + T2 = TypeVar('T2') + ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) + + subscripted = ListOrSetT[int] + self.assertEqual(subscripted.__module__, ListOrSetT.__module__) + self.assertEqual(subscripted.__name__, "ListOrSetT") + self.assertEqual(subscripted.__value__, Union[List[T], Set[T]]) + self.assertEqual(subscripted.__type_params__, (T,)) + + still_generic = ListOrSetT[Iterable[T2]] + self.assertEqual(still_generic.__module__, ListOrSetT.__module__) + self.assertEqual(still_generic.__name__, "ListOrSetT") + self.assertEqual(still_generic.__value__, Union[List[T], Set[T]]) + self.assertEqual(still_generic.__type_params__, (T,)) + + fully_subscripted = still_generic[float] + self.assertEqual(fully_subscripted.__module__, ListOrSetT.__module__) + self.assertEqual(fully_subscripted.__name__, "ListOrSetT") + self.assertEqual(fully_subscripted.__value__, Union[List[T], Set[T]]) + self.assertEqual(fully_subscripted.__type_params__, (T,)) + def test_subscription_without_type_params(self): Simple = TypeAliasType("Simple", int) with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): @@ -7260,7 +7283,6 @@ def test_subscription_without_type_params(self): with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): MissingTypeParamsErr[int] - def test_pickle(self): global Alias Alias = TypeAliasType("Alias", int) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 3b9239d1..13bd5442 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3452,6 +3452,37 @@ def _is_unionable(obj): TypeAliasType, )) + if sys.version_info < (3, 10): + # Copied and pasted from https://github.com/python/cpython/blob/986a4e1b6fcae7fe7a1d0a26aea446107dd58dd2/Objects/genericaliasobject.c#L568-L582, + # so that we emulate the behaviour of `types.GenericAlias` + # on the latest versions of CPython + _ATTRIBUTE_DELEGATION_EXCLUSIONS = frozenset({ + "__class__", + "__bases__", + "__origin__", + "__args__", + "__unpacked__", + "__parameters__", + "__typing_unpacked_tuple_args__", + "__mro_entries__", + "__reduce_ex__", + "__reduce__", + "__copy__", + "__deepcopy__", + }) + + class _TypeAliasGenericAlias(typing._GenericAlias, _root=True): + def __getattr__(self, attr): + if attr in _ATTRIBUTE_DELEGATION_EXCLUSIONS: + return object.__getattr__(self, attr) + return getattr(self.__origin__, attr) + + if sys.version_info < (3, 9): + def __getitem__(self, item): + result = super().__getitem__(item) + result.__class__ = type(self) + return result + class TypeAliasType: """Create named, parameterized type aliases. @@ -3529,13 +3560,16 @@ def __getitem__(self, parameters): raise TypeError("Only generic type aliases are subscriptable") if not isinstance(parameters, tuple): parameters = (parameters,) - parameters = [ + # Using 3.9 here will create problems with Concatenate + if sys.version_info >= (3, 10): + return _types.GenericAlias(self, parameters) + parameters = tuple( typing._type_check( item, f'Subscripting {self.__name__} requires a type.' ) for item in parameters - ] - return typing._GenericAlias(self, tuple(parameters)) + ) + return _TypeAliasGenericAlias(self, parameters) def __reduce__(self): return self.__name__