8000 Concatenate support for Ellipsis in python3.9-3.10 · python/typing_extensions@100be12 · GitHub
[go: up one dir, main page]

Skip to content

Commit 100be12

Browse files
committed
Concatenate support for Ellipsis in python3.9-3.10
1 parent 70cec91 commit 100be12

File tree

2 files changed

+125
-23
lines changed

2 files changed

+125
-23
lines changed

src/test_typing_extensions.py

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1681,12 +1681,16 @@ class C(Generic[T]): pass
16811681
# In 3.9 and lower we use typing_extensions's hacky implementation
16821682
# of ParamSpec, which gets incorrectly wrapped in a list
16831683
self.assertIn(get_args(Callable[P, int]), [(P, int), ([P], int)])
1684-
self.assertEqual(get_args(Callable[Concatenate[int, P], int]),
1685-
(Concatenate[int, P], int))
16861684
self.assertEqual(get_args(Required[int]), (int,))
16871685
self.assertEqual(get_args(NotRequired[int]), (int,))
16881686
self.assertEqual(get_args(Unpack[Ts]), (Ts,))
16891687
self.assertEqual(get_args(Unpack), ())
1688+
self.assertEqual(get_args(Callable[Concatenate[int, P], int]),
1689+
(Concatenate[int, P], int))
1690+
if sys.version_info >= (3, 9, 2):
1691+
# Cannot construct Callable[Concatenate[int, ...] with non-types
1692+
self.assertEqual(get_args(Callable[Concatenate[int, ...], int]),
1693+
(Concatenate[int, ...], int))
16901694

16911695

16921696
class CollectionsAbcTests(BaseTestCase):
@@ -5228,6 +5232,10 @@ class Y(Protocol[T, P]):
52285232
self.assertEqual(G2.__args__, (int, Concatenate[int, P_2]))
52295233
self.assertEqual(G2.__parameters__, (P_2,))
52305234

5235+
G3 = klass[int, Concatenate[int, ...]]
5236+
self.assertEqual(G3.__args__, (int, Concatenate[int, ...]))
5237+
self.assertEqual(G3.__parameters__, ())
5238+
52315239
# The following are some valid uses cases in PEP 612 that don't work:
52325240
# These do not work in 3.9, _type_check blocks the list and ellipsis.
52335241
# G3 = X[int, [int, bool]]
@@ -5323,6 +5331,11 @@ class MyClass: ...
53235331
c = Concatenate[MyClass, P]
53245332
self.assertNotEqual(c, Concatenate)
53255333

5334+
# Test Ellipsis Concatenation
5335+
d = Concatenate[MyClass, ...]
5336+
self.assertNotEqual(d, c)
5337+
self.assertNotEqual(d, Concatenate)
5338+
53265339
def test_valid_uses(self):
53275340
P = ParamSpec('P')
53285341
T = TypeVar('T')
@@ -5339,6 +5352,21 @@ def test_valid_uses(self):
53395352
self.assertEqual(C3.__origin__, C4.__origin__)
53405353
self.assertNotEqual(C3, C4)
53415354

5355+
# Callable with Ellipsis cannot be constructed in Python3.8 and below 3.9.2
5356+
if sys.version_info[:2] <= (3, 9, 2):
5357+
return
5358+
5359+
C5 = Callable[Concatenate[int, ...], int]
5360+
C6 = Callable[Concatenate[int, T, ...], T]
5361+
self.assertEqual(C5.__origin__, C6.__origin__)
5362+
self.assertNotEqual(C5, C6)
5363+
5364+
# Test collections.abc.Callable too.
5365+
C7 = collections.abc.Callable[Concatenate[int, ...], int]
5366+
C8 = collections.abc.Callable[Concatenate[int, T, ...], T]
5367+
self.assertEqual(C7.__origin__, C8.__origin__)
5368+
self.assertNotEqual(C7, C8)
5369+
53425370
def test_invalid_uses(self):
53435371
P = ParamSpec('P')
53445372
T = TypeVar('T')
@@ -5351,25 +5379,58 @@ def test_invalid_uses(self):
53515379

53525380
with self.assertRaisesRegex(
53535381
TypeError,
5354-
'The last parameter to Concatenate should be a ParamSpec variable',
5382+
'The last parameter to Concatenate should be a ParamSpec variable or ellipsis',
53555383
):
53565384
Concatenate[P, T]
53575385

5386+
5387+
if sys.version_info[:2] >= (3, 9, 2):
5388+
# Cannot construct a Callable with Ellipsis in Python3.8 as args must be types
5389+
with self.assertRaisesRegex(
5390+
TypeError,
5391+
'is not a generic class',
5392+
):
5393+
Callable[Concatenate[int, ...], Any][T]
5394+
5395+
53585396
if not TYPING_3_11_0:
53595397
with self.assertRaisesRegex(
53605398
TypeError,
53615399
'each arg must be a type',
53625400
):
53635401
Concatenate[1, P]
53645402

5403+
with self.assertRaisesRegex(
5404+
TypeError,
5405+
'each arg must be a type.',
5406+
):
5407+
Concatenate[1, ..., P]
5408+
5409+
@skipUnless(TYPING_3_11_0, "Cannot be backported to <=3.9"
5410+
"Cannot use typing._ConcatenateGenericAlias in 3.10")
5411+
def test_alias(self):
5412+
P = ParamSpec("P")
5413+
C1 = Callable[Concatenate[int, P], Any]
5414+
# Python <= 3.9 fails because parameters to generic types must be types.
5415+
# For Python 3.10 & typing._ConcatenateGenericAlias will
5416+
# as Ellipsis is not supported for ParamSpec
5417+
# Fallback to 3.10 & typing_extensions._ConcatenateGenericAlias not implemented
5418+
C1[...]
5419+
53655420
def test_basic_introspection(self):
53665421
P = ParamSpec('P')
53675422
C1 = Concatenate[int, P]
53685423
C2 = Concatenate[int, T, P]
5424+
C3 = Concatenate[int, ...]
5425+
C4 = Concatenate[int, T, ...]
53695426
self.assertEqual(C1.__origin__, Concatenate)
53705427
self.assertEqual(C1.__args__, (int, P))
53715428
self.assertEqual(C2.__origin__, Concatenate)
53725429
self.assertEqual(C2.__args__, (int, T, P))
5430+
self.assertEqual(C3.__origin__, Concatenate)
5431+
self.assertEqual(C3.__args__, (int, Ellipsis))
5432+
self.assertEqual(C4.__origin__, Concatenate)
5433+
self.assertEqual(C4.__args__, (int, T, Ellipsis))
53735434

53745435
def test_eq(self):
53755436
P = ParamSpec('P')
@@ -5380,6 +5441,13 @@ def test_eq(self):
53805441
self.assertEqual(hash(C1), hash(C2))
53815442
self.assertNotEqual(C1, C3)
53825443

5444+
C4 = Concatenate[int, ...]
5445+
C5 = Concatenate[int, ...]
5446+
C6 = Concatenate[int, T, ...]
5447+
self.assertEqual(C4, C5)
5448+
self.assertEqual(hash(C4), hash(C5))
5449+
self.assertNotEqual(C4, C6)
5450+
53835451

53845452
class TypeGuardTests(BaseTestCase):
53855453
def test_basics(self):
@@ -6050,7 +6118,7 @@ def test_typing_extensions_defers_when_possible(self):
60506118
if sys.version_info < (3, 10, 1):
60516119
exclude |= {"Literal"}
60526120
if sys.version_info < (3, 11):
6053-
exclude |= {'final', 'Any', 'NewType', 'overload'}
6121+
exclude |= {'final', 'Any', 'NewType', 'overload', 'Concatenate'}
60546122
if sys.version_info < (3, 12):
60556123
exclude |= {
60566124
'SupportsAbs', 'SupportsBytes',

src/typing_extensions.py

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1795,28 +1795,62 @@ 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.2+
1803+
if sys.version_info[:3] >= (3, 10, 2):
1804+
_ellipsis_dummy = ParamSpec('_ellipsis_dummy')
17991805

1800-
# 3.8-3.9
1801-
@typing._tp_cache
1802-
def _concatenate_getitem(self, parameters):
1803-
if parameters == ():
1804-
raise TypeError("Cannot take a Concatenate of no types.")
1805-
if not isinstance(parameters, tuple):
1806-
parameters = (parameters,)
1807-
if not isinstance(parameters[-1], ParamSpec):
1808-
raise TypeError("The last parameter to Concatenate should be a "
1809-
"ParamSpec variable.")
1810-
msg = "Concatenate[arg, ...]: each arg must be a type."
1811-
parameters = tuple(typing._type_check(p, msg) for p in parameters)
1812-
return _ConcatenateGenericAlias(self, parameters)
1813-
1814-
1815-
# 3.10+
1816-
if hasattr(typing, 'Concatenate'):
1806+
@typing._tp_cache
1807+
def _concatenate_getitem(self, parameters):
1808+
if parameters == ():
1809+
raise TypeError("Cannot take a Concatenate of no types.")
1810+
if not isinstance(parameters, tuple):
1811+
parameters = (parameters,)
1812+
if not (parameters[-1] is ... or isinstance(parameters[-1], ParamSpec)):
1813+
raise TypeError("The last parameter to Concatenate should be a "
1814+
"ParamSpec variable or ellipsis.")
1815+
msg = "Concatenate[arg, ...]: each arg must be a type."
1816+
parameters = (*(typing._type_check(p, msg) for p in parameters[:-1]),
1817+
parameters[-1])
1818+
if parameters[-1] is Ellipsis:
1819+
# Hack: Need ParamSpec as last parameter when passing to typing class in 3.10
1820+
parameters = parameters[:-1] + (_ellipsis_dummy,)
1821+
concatenate = _ConcatenateGenericAlias(self, parameters,
1822+
_typevar_types=(TypeVar, ParamSpec),
1823+
_paramspec_tvars=True)
1824+
# Remove dummy and replace with Ellipsis again
1825+
concatenate.__args__ = tuple(p if p is not _ellipsis_dummy else ...
1826+
for p in concatenate.__args__)
1827+
concatenate.__parameters__ = tuple(p for p in concatenate.__parameters__
1828+
if p is not _ellipsis_dummy)
1829+
return concatenate
1830+
return _ConcatenateGenericAlias(self, parameters,
1831+
_typevar_types=(TypeVar, ParamSpec),
1832+
_paramspec_tvars=True)
1833+
1834+
# 3.8-3.10.0
1835+
else:
1836+
@typing._tp_cache
1837+
def _concatenate_getitem(self, parameters):
1838+
if parameters == ():
1839+
raise TypeError("Cannot take a Concatenate of no types.")
1840+
if not isinstance(parameters, tuple):
1841+
parameters = (parameters,)
1842+
if not (parameters[-1] is ... or isinstance(parameters[-1], ParamSpec)):
1843+
raise TypeError("The last parameter to Concatenate should be a "
1844+
"ParamSpec variable or ellipsis.")
1845+
msg = "Concatenate[arg, ...]: each arg must be a type."
1846+
parameters = (*(typing._type_check(p, msg) for p in parameters[:-1]),
1847+
parameters[-1])
1848+
return _ConcatenateGenericAlias(self, parameters)
1849+
1850+
# 3.11+; Concatenate does not accept ellipsis in 3.10
1851+
if hasattr(typing, 'Concatenate') and sys.version_info[:2] >= (3, 11):
18171852
Concatenate = typing.Concatenate
1818-
_ConcatenateGenericAlias = typing._ConcatenateGenericAlias
1819-
# 3.9
1853+
# 3.9-3.10
18201854
elif sys.version_info[:2] >= (3, 9):
18211855
@_ExtensionsSpecialForm
18221856
def Concatenate(self, parameters):

0 commit comments

Comments
 (0)
0