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 <jelle.zijlstra@gmail.com> Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com>
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

Lines changed: 90 additions & 0 deletions
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()
Lines changed: 1 addition & 0 deletions
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

Lines changed: 75 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy