Skip to content

Commit af2277e

Browse files
mrahtzJelleZijlstrakumaraditya303Fidget-Spinner
authored
bpo-43224: Implement PEP 646 changes to genericaliasobject.c (GH-31019)
Specifically, prepare for starring of tuples via a new genericalias iter type. GenericAlias also partially supports the iterator protocol after this change. Co-authored-by: Jelle Zijlstra <[email protected]> Co-authored-by: Kumar Aditya <[email protected]> Co-authored-by: Ken Jin <[email protected]>
1 parent 7517437 commit af2277e

File tree

3 files changed

+166
-0
lines changed

3 files changed

+166
-0
lines changed

Lib/test/test_genericalias.py

+90
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,24 @@ class MyList(list):
169169
self.assertEqual(repr(list[str]), 'list[str]')
170170
self.assertEqual(repr(list[()]), 'list[()]')
171171
self.assertEqual(repr(tuple[int, ...]), 'tuple[int, ...]')
172+
x1 = tuple[
173+
tuple( # Effectively the same as starring; TODO
174+
tuple[int]
175+
)
176+
]
177+
self.assertEqual(repr(x1), 'tuple[*tuple[int]]')
178+
x2 = tuple[
179+
tuple( # Ditto TODO
180+
tuple[int, str]
181+
)
182+
]
183+
self.assertEqual(repr(x2), 'tuple[*tuple[int, str]]')
184+
x3 = tuple[
185+
tuple( # Ditto TODO
186+
tuple[int, ...]
187+
)
188+
]
189+
self.assertEqual(repr(x3), 'tuple[*tuple[int, ...]]')
172190
self.assertTrue(repr(MyList[int]).endswith('.BaseTest.test_repr.<locals>.MyList[int]'))
173191
self.assertEqual(repr(list[str]()), '[]') # instances should keep their normal repr
174192

@@ -182,6 +200,7 @@ def test_exposed_type(self):
182200

183201
def test_parameters(self):
184202
from typing import List, Dict, Callable
203+
185204
D0 = dict[str, int]
186205
self.assertEqual(D0.__args__, (str, int))
187206
self.assertEqual(D0.__parameters__, ())
@@ -197,6 +216,7 @@ def test_parameters(self):
197216
D2b = dict[T, T]
198217
self.assertEqual(D2b.__args__, (T, T))
199218
self.assertEqual(D2b.__parameters__, (T,))
219+
200220
L0 = list[str]
201221
self.assertEqual(L0.__args__, (str,))
202222
self.assertEqual(L0.__parameters__, ())
@@ -219,6 +239,45 @@ def test_parameters(self):
219239
self.assertEqual(L5.__args__, (Callable[[K, V], K],))
220240
self.assertEqual(L5.__parameters__, (K, V))
221241

242+
T1 = tuple[
243+
tuple( # Ditto TODO
244+
tuple[int]
245+
)
246+
]
247+
self.assertEqual(
248+
T1.__args__,
249+
tuple( # Ditto TODO
250+
tuple[int]
251+
)
252+
)
253+
self.assertEqual(T1.__parameters__, ())
254+
255+
T2 = tuple[
256+
tuple( # Ditto TODO
257+
tuple[T]
258+
)
259+
]
260+
self.assertEqual(
261+
T2.__args__,
262+
tuple( # Ditto TODO
263+
tuple[T]
264+
)
265+
)
266+
self.assertEqual(T2.__parameters__, (T,))
267+
268+
T4 = tuple[
269+
tuple( # Ditto TODO
270+
tuple[int, str]
271+
)
272+
]
273+
self.assertEqual(
274+
T4.__args__,
275+
tuple( # Ditto TODO
276+
tuple[int, str]
277+
)
278+
)
279+
self.assertEqual(T4.__parameters__, ())
280+
222281
def test_parameter_chaining(self):
223282
from typing import List, Dict, Union, Callable
224283
self.assertEqual(list[T][int], list[int])
@@ -249,6 +308,19 @@ def test_parameter_chaining(self):
249308
def test_equality(self):
250309
self.assertEqual(list[int], list[int])
251310
self.assertEqual(dict[str, int], dict[str, int])
311+
self.assertEqual((*tuple[int],)[0], (*tuple[int],)[0])
312+
self.assertEqual(
313+
tuple[
314+
tuple( # Effectively the same as starring; TODO
315+
tuple[int]
316+
)
317+
],
318+
tuple[
319+
tuple( # Ditto TODO
320+
tuple[int]
321+
)
322+
]
323+
)
252324
self.assertNotEqual(dict[str, int], dict[str, str])
253325
self.assertNotEqual(list, list[int])
254326
self.assertNotEqual(list[int], list)
@@ -346,6 +418,24 @@ def __new__(cls, *args, **kwargs):
346418
with self.assertRaises(TypeError):
347419
Bad(list, int, bad=int)
348420

421+
def test_iter_creates_starred_tuple(self):
422+
t = tuple[int, str]
423+
iter_t = iter(t)
424+
x = next(iter_t)
425+
self.assertEqual(repr(x), '*tuple[int, str]')
426+
427+
def test_calling_next_twice_raises_stopiteration(self):
428+
t = tuple[int, str]
429+
iter_t = iter(t)
430+
next(iter_t)
431+
with self.assertRaises(StopIteration):
432+
next(iter_t)
433+
434+
def test_del_iter(self):
435+
t = tuple[int, str]
436+
iter_x = iter(t)
437+
del iter_x
438+
349439

350440
if __name__ == "__main__":
351441
unittest.main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow unpacking types.GenericAlias objects, e.g. ``*tuple[int, str]``.

Objects/genericaliasobject.c

+75
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,23 @@
55
#include "pycore_unionobject.h" // _Py_union_type_or, _PyGenericAlias_Check
66
#include "structmember.h" // PyMemberDef
77

8+
#include <stdbool.h>
9+
810
typedef struct {
911
PyObject_HEAD
1012
PyObject *origin;
1113
PyObject *args;
1214
PyObject *parameters;
1315
PyObject* weakreflist;
16+
// Whether we're a starred type, e.g. *tuple[int].
17+
bool starred;
1418
} gaobject;
1519

20+
typedef struct {
21+
PyObject_HEAD
22+
PyObject *obj; /* Set to NULL when iterator is exhausted */
23+
} gaiterobject;
24+
1625
static void
1726
ga_dealloc(PyObject *self)
1827
{
@@ -120,6 +129,11 @@ ga_repr(PyObject *self)
120129
_PyUnicodeWriter writer;
121130
_PyUnicodeWriter_Init(&writer);
122131

132+
if (alias->starred) {
133+
if (_PyUnicodeWriter_WriteASCIIString(&writer, "*", 1) < 0) {
134+
goto error;
135+
}
136+
}
123137
if (ga_repr_item(&writer, alias->origin) < 0) {
124138
goto error;
125139
}
@@ -603,6 +617,66 @@ static PyNumberMethods ga_as_number = {
603617
.nb_or = _Py_union_type_or, // Add __or__ function
604618
};
605619

620+
static PyObject *
621+
ga_iternext(gaiterobject *gi) {
622+
if (gi->obj == NULL) {
623+
PyErr_SetNone(PyExc_StopIteration);
624+
return NULL;
625+
}
626+
gaobject *alias = (gaobject *)gi->obj;
627+
PyObject *starred_alias = Py_GenericAlias(alias->origin, alias->args);
628+
if (starred_alias == NULL) {
629+
return NULL;
630+
}
631+
((gaobject *)starred_alias)->starred = true;
632+
Py_SETREF(gi->obj, NULL);
633+
return starred_alias;
634+
}
635+
636+
static void
637+
ga_iter_dealloc(gaiterobject *gi) {
638+
PyObject_GC_UnTrack(gi);
639+
Py_XDECREF(gi->obj);
640+
PyObject_GC_Del(gi);
641+
}
642+
643+
static int
644+
ga_iter_traverse(gaiterobject *gi, visitproc visit, void *arg)
645+
{
646+
Py_VISIT(gi->obj);
647+
return 0;
648+
}
649+
650+
static int
651+
ga_iter_clear(PyObject *self) {
652+
gaiterobject *gi = (gaiterobject *)self;
653+
Py_CLEAR(gi->obj);
654+
return 0;
655+
}
656+
657+
static PyTypeObject Py_GenericAliasIterType = {
658+
PyVarObject_HEAD_INIT(&PyType_Type, 0)
659+
.tp_name = "generic_alias_iterator",
660+
.tp_basicsize = sizeof(gaiterobject),
661+
.tp_iter = PyObject_SelfIter,
662+
.tp_iternext = (iternextfunc)ga_iternext,
663+
.tp_traverse = (traverseproc)ga_iter_traverse,
664+
.tp_dealloc = (destructor)ga_iter_dealloc,
665+
.tp_clear = (inquiry)ga_iter_clear,
666+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
667+
};
668+
669+
static PyObject *
670+
ga_iter(PyObject *self) {
671+
gaiterobject *gi = PyObject_GC_New(gaiterobject, &Py_GenericAliasIterType);
672+
if (gi == NULL) {
673+
return NULL;
674+
}
675+
gi->obj = Py_NewRef(self);
676+
PyObject_GC_Track(gi);
677+
return (PyObject *)gi;
678+
}
679+
606680
// TODO:
607681
// - argument clinic?
608682
// - __doc__?
@@ -631,6 +705,7 @@ PyTypeObject Py_GenericAliasType = {
631705
.tp_new = ga_new,
632706
.tp_free = PyObject_GC_Del,
633707
.tp_getset = ga_properties,
708+
.tp_iter = (getiterfunc)ga_iter,
634709
};
635710

636711
PyObject *

0 commit comments

Comments
 (0)