diff --git a/CHANGELOG.md b/CHANGELOG.md index f127ada0..2ddac4b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ subscripted objects) had wrong parameters if they were directly subscripted with an `Unpack` object. Patch by [Daraan](https://github.com/Daraan). +- Backport to Python 3.10 the ability to substitute `...` in generic `Callable` +aliases that have a `Concatenate` special form as their argument. + Patch by [Daraan](https://github.com/Daraan). - Fix error in subscription of `Unpack` aliases causing nested Unpacks to not be resolved correctly. Patch by [Daraan](https://github.com/Daraan). diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 528763d6..dfea3e3a 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -5401,6 +5401,18 @@ def test_invalid_uses(self): ): Concatenate[1, P] + @skipUnless(TYPING_3_10_0, "Missing backport to <=3.9. See issue #48") + def test_alias_subscription_with_ellipsis(self): + P = ParamSpec('P') + X = Callable[Concatenate[int, P], Any] + + C1 = X[...] + self.assertEqual(C1.__parameters__, ()) + with self.subTest("Compare Concatenate[int, ...]"): + if sys.version_info[:2] == (3, 10): + self.skipTest("Needs Issue #110 | PR #481: construct Concatenate with ...") + self.assertEqual(get_args(C1), (Concatenate[int, ...], Any)) + def test_basic_introspection(self): P = ParamSpec('P') C1 = Concatenate[int, P] @@ -6130,7 +6142,7 @@ def test_typing_extensions_defers_when_possible(self): if sys.version_info < (3, 10, 1): exclude |= {"Literal"} if sys.version_info < (3, 11): - exclude |= {'final', 'Any', 'NewType', 'overload'} + exclude |= {'final', 'Any', 'NewType', 'overload', 'Concatenate'} if sys.version_info < (3, 12): exclude |= { 'SupportsAbs', 'SupportsBytes', diff --git a/src/typing_extensions.py b/src/typing_extensions.py index d194d623..b02510e9 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -1795,28 +1795,52 @@ def __parameters__(self): return tuple( tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec)) ) +# 3.10+ +else: + _ConcatenateGenericAlias = typing._ConcatenateGenericAlias + # 3.10 + if sys.version_info < (3, 11): + _typing_ConcatenateGenericAlias = _ConcatenateGenericAlias -# 3.8-3.9 + class _ConcatenateGenericAlias(_typing_ConcatenateGenericAlias, _root=True): + # needed for checks in collections.abc.Callable to accept this class + __module__ = "typing" + + def copy_with(self, params): + if isinstance(params[-1], (list, tuple)): + return (*params[:-1], *params[-1]) + if isinstance(params[-1], _ConcatenateGenericAlias): + params = (*params[:-1], *params[-1].__args__) + elif not (params[-1] is ... or isinstance(params[-1], ParamSpec)): + raise TypeError("The last parameter to Concatenate should be a " + "ParamSpec variable or ellipsis.") + return super(_typing_ConcatenateGenericAlias, self).copy_with(params) + + +# 3.8-3.10 @typing._tp_cache def _concatenate_getitem(self, parameters): if parameters == (): raise TypeError("Cannot take a Concatenate of no types.") if not isinstance(parameters, tuple): parameters = (parameters,) - if not isinstance(parameters[-1], ParamSpec): + elif not (parameters[-1] is ... or isinstance(parameters[-1], ParamSpec)): raise TypeError("The last parameter to Concatenate should be a " - "ParamSpec variable.") + "ParamSpec variable or ellipsis.") msg = "Concatenate[arg, ...]: each arg must be a type." parameters = tuple(typing._type_check(p, msg) for p in parameters) + if (3, 10, 2) < sys.version_info < (3, 11): + return _ConcatenateGenericAlias(self, parameters, + _typevar_types=(TypeVar, ParamSpec), + _paramspec_tvars=True) return _ConcatenateGenericAlias(self, parameters) -# 3.10+ -if hasattr(typing, 'Concatenate'): +# 3.11+ +if sys.version_info >= (3, 11): Concatenate = typing.Concatenate - _ConcatenateGenericAlias = typing._ConcatenateGenericAlias -# 3.9 +# 3.9-3.10 elif sys.version_info[:2] >= (3, 9): @_ExtensionsSpecialForm def Concatenate(self, parameters):