Skip to content

Commit 80958f3

Browse files
DaraanAlexWaygoodJelleZijlstra
authored
Support subscription of Callable[Concatenate[P], Any] with ... in Python 3.10 (#479)
Co-authored-by: Alex Waygood <[email protected]> Co-authored-by: Jelle Zijlstra <[email protected]>
1 parent a2abfe6 commit 80958f3

File tree

3 files changed

+47
-8
lines changed

3 files changed

+47
-8
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
subscripted objects) had wrong parameters if they were directly
1717
subscripted with an `Unpack` object.
1818
Patch by [Daraan](https://github.com/Daraan).
19+
- Backport to Python 3.10 the ability to substitute `...` in generic `Callable`
20+
aliases that have a `Concatenate` special form as their argument.
21+
Patch by [Daraan](https://github.com/Daraan).
1922
- Fix error in subscription of `Unpack` aliases causing nested Unpacks
2023
to not be resolved correctly. Patch by [Daraan](https://github.com/Daraan).
2124

src/test_typing_extensions.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5401,6 +5401,18 @@ def test_invalid_uses(self):
54015401
):
54025402
Concatenate[1, P]
54035403

5404+
@skipUnless(TYPING_3_10_0, "Missing backport to <=3.9. See issue #48")
5405+
def test_alias_subscription_with_ellipsis(self):
5406+
P = ParamSpec('P')
5407+
X = Callable[Concatenate[int, P], Any]
5408+
5409+
C1 = X[...]
5410+
self.assertEqual(C1.__parameters__, ())
5411+
with self.subTest("Compare Concatenate[int, ...]"):
5412+
if sys.version_info[:2] == (3, 10):
5413+
self.skipTest("Needs Issue #110 | PR #481: construct Concatenate with ...")
5414+
self.assertEqual(get_args(C1), (Concatenate[int, ...], Any))
5415+
54045416
def test_basic_introspection(self):
54055417
P = ParamSpec('P')
54065418
C1 = Concatenate[int, P]
@@ -6130,7 +6142,7 @@ def test_typing_extensions_defers_when_possible(self):
61306142
if sys.version_info < (3, 10, 1):
61316143
exclude |= {"Literal"}
61326144
if sys.version_info < (3, 11):
6133-
exclude |= {'final', 'Any', 'NewType', 'overload'}
6145+
exclude |= {'final', 'Any', 'NewType', 'overload', 'Concatenate'}
61346146
if sys.version_info < (3, 12):
61356147
exclude |= {
61366148
'SupportsAbs', 'SupportsBytes',

src/typing_extensions.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1795,28 +1795,52 @@ def __parameters__(self):
17951795
return tuple(
17961796
tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec))
17971797
)
1798+
# 3.10+
1799+
else:
1800+
_ConcatenateGenericAlias = typing._ConcatenateGenericAlias
17981801

1802+
# 3.10
1803+
if sys.version_info < (3, 11):
1804+
_typing_ConcatenateGenericAlias = _ConcatenateGenericAlias
17991805

1800-
# 3.8-3.9
1806+
class _ConcatenateGenericAlias(_typing_ConcatenateGenericAlias, _root=True):
1807+
# needed for checks in collections.abc.Callable to accept this class
1808+
__module__ = "typing"
1809+
1810+
def copy_with(self, params):
1811+
if isinstance(params[-1], (list, tuple)):
1812+
return (*params[:-1], *params[-1])
1813+
if isinstance(params[-1], _ConcatenateGenericAlias):
1814+
params = (*params[:-1], *params[-1].__args__)
1815+
elif not (params[-1] is ... or isinstance(params[-1], ParamSpec)):
1816+
raise TypeError("The last parameter to Concatenate should be a "
1817+
"ParamSpec variable or ellipsis.")
1818+
return super(_typing_ConcatenateGenericAlias, self).copy_with(params)
1819+
1820+
1821+
# 3.8-3.10
18011822
@typing._tp_cache
18021823
def _concatenate_getitem(self, parameters):
18031824
if parameters == ():
18041825
raise TypeError("Cannot take a Concatenate of no types.")
18051826
if not isinstance(parameters, tuple):
18061827
parameters = (parameters,)
1807-
if not isinstance(parameters[-1], ParamSpec):
1828+
elif not (parameters[-1] is ... or isinstance(parameters[-1], ParamSpec)):
18081829
raise TypeError("The last parameter to Concatenate should be a "
1809-
"ParamSpec variable.")
1830+
"ParamSpec variable or ellipsis.")
18101831
msg = "Concatenate[arg, ...]: each arg must be a type."
18111832
parameters = tuple(typing._type_check(p, msg) for p in parameters)
1833+
if (3, 10, 2) < sys.version_info < (3, 11):
1834+
return _ConcatenateGenericAlias(self, parameters,
1835+
_typevar_types=(TypeVar, ParamSpec),
1836+
_paramspec_tvars=True)
18121837
return _ConcatenateGenericAlias(self, parameters)
18131838

18141839

1815-
# 3.10+
1816-
if hasattr(typing, 'Concatenate'):
1840+
# 3.11+
1841+
if sys.version_info >= (3, 11):
18171842
Concatenate = typing.Concatenate
1818-
_ConcatenateGenericAlias = typing._ConcatenateGenericAlias
1819-
# 3.9
1843+
# 3.9-3.10
18201844
elif sys.version_info[:2] >= (3, 9):
18211845
@_ExtensionsSpecialForm
18221846
def Concatenate(self, parameters):

0 commit comments

Comments
 (0)