8000 Concatenate supports Ellipsis in python3.9-3.10 · python/typing_extensions@46ff492 · GitHub
[go: up one dir, main page]

Skip to content

Commit 46ff492

Browse files
committed
Concatenate supports Ellipsis in python3.9-3.10
1 parent 70cec91 commit 46ff492

File tree

2 files changed

+126
-24
lines changed

2 files changed

+126
-24
lines changed

src/test_typing_extensions.py

Lines changed: 73 additions & 5 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):
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]]
@@ -5312,7 +5320,7 @@ def run():
53125320

53135321
# The actual test:
53145322
self.assertEqual(result1, result2)
5315-
5323+
53165324

53175325
class ConcatenateTests(BaseTestCase):
53185326
def test_basics(self):
@@ -5322,6 +5330,11 @@ class MyClass: ...
53225330

53235331
c = Concatenate[MyClass, P]
53245332
self.assertNotEqual(c, Concatenate)
5333+
5334+
# Test Ellipsis Concatenation
5335+
d = Concatenate[MyClass, ...]
5336+
self.assertNotEqual(d, c)
5337+
self.assertNotEqual(d, Concatenate)
53255338

53265339
def test_valid_uses(self):
53275340
P = ParamSpec('P')
@@ -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
5356+
if sys.version_info[:2] <= (3, 8):
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]
5385+
5386+
5387+
if sys.version_info[:2] >= (3, 9):
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+
53575395

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]
5402+
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[...]
53645419

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')
@@ -5379,6 +5440,13 @@ def test_eq(self):
53795440
self.assertEqual(C1, C2)
53805441
self.assertEqual(hash(C1), hash(C2))
53815442
self.assertNotEqual(C1, C3)
5443+
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)
53825450

53835451

53845452
class TypeGuardTests(BaseTestCase):
@@ -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
@@ -1773,7 +1773,7 @@ class _ConcatenateGenericAlias(list):
17731773
# Flag in 3.8.
17741774
_special = False
17751775

1776-
def __init__(self, origin, args):
1776+
def __init__(self, origin, args, _typevar_types=(TypeVar, ParamSpec), _paramspec_tvars=True):
17771777
super().__init__(args)
17781778
self.__origin__ = origin
17791779
self.__args__ = args
@@ -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 # noqa: F811
17981801

1802+
# 3.10
1803+
if sys.version_info[:2] == (3, 10):
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-
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]), parameters[-1])
1817+
if parameters[-1] is Ellipsis:
1818+
# need ParamSpec to pass ParamSpec type check only
1819+
concatenate = _ConcatenateGenericAlias(self, parameters[:-1] + (_ellipsis_dummy,),
1820+
_typevar_types=(TypeVar, ParamSpec),
1821+
_paramspec_tvars=True)
1822+
concatenate.__args__ = tuple(p if p is not _ellipsis_dummy else ... for p in concatenate.__args__)
1823+
concatenate.__parameters__ = tuple(p for p in concatenate.__parameters__ if p is not _ellipsis_dummy)
1824+
return concatenate
18141825

1815-
# 3.10+
1816-
if hasattr(typing, 'Concatenate'):
1826+
else:
1827+
concatenate = _ConcatenateGenericAlias(self, parameters,
1828+
_typevar_types=(TypeVar, ParamSpec),
1829+
_paramspec_tvars=True)
1830+
return concatenate
1831+
# 3.8-3.9
1832+
else:
1833+
@typing._tp_cache
1834+
def _concatenate_getitem(self, parameters):
1835+
if parameters == ():
1836+
raise TypeError("Cannot take a Concatenate of no types.")
1837+
if not isinstance(parameters, tuple):
1838+
parameters = (parameters,)
1839+
if not (parameters[-1] is ... or isinstance(parameters[-1], ParamSpec)):
1840+
raise TypeError("The last parameter to Concatenate should be a "
1841+
"ParamSpec variable or ellipsis.")
1842+
msg = "Concatenate[arg, ...]: each arg must be a type."
1843+
parameters = (*(typing._type_check(p, msg) for p in parameters[:-1]), parameters[-1])
1844+
concatenate = _ConcatenateGenericAlias(self, parameters,
1845+
_typevar_types=(TypeVar, ParamSpec),
1846+
_paramspec_tvars=True)
1847+
return concatenate
1848+
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