https://github.com/python/cpython/commit/f9c0e1ea27a8731fecc3395ae31fc88277ff24b3
commit: f9c0e1ea27a8731fecc3395ae31fc88277ff24b3
branch: main
author: Donghee Na <[email protected]>
committer: corona10 <[email protected]>
date: 2026-10-21T00:25:02Z
summary:
gh-114258: Update AC to support @critical_section for tp_iternext (gh-159962)
files:
A Misc/NEWS.d/next/Tools-Demos/2026-10-20-12-50-00.gh-issue-114258.slotNx.rst
A Tools/clinic/libclinic/slots.py
M Lib/test/test_clinic.py
M Modules/_csv.c
M Modules/_io/bytesio.c
M Modules/_io/clinic/bytesio.c.h
M Modules/_io/clinic/stringio.c.h
M Modules/_io/clinic/textio.c.h
M Modules/_io/stringio.c
M Modules/_io/textio.c
M Modules/clinic/_csv.c.h
M Modules/clinic/itertoolsmodule.c.h
M Modules/itertoolsmodule.c
M Objects/clinic/odictobject.c.h
M Objects/clinic/rangeobject.c.h
M Objects/odictobject.c
M Objects/rangeobject.c
M Tools/clinic/libclinic/clanguage.py
M Tools/clinic/libclinic/converter.py
M Tools/clinic/libclinic/converters.py
M Tools/clinic/libclinic/dsl_parser.py
M Tools/clinic/libclinic/function.py
M Tools/clinic/libclinic/parse_args.py
diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py
index 0274edd384da439..8d2aced0b9caaf0 100644
--- a/Lib/test/test_clinic.py
+++ b/Lib/test/test_clinic.py
@@ -15,6 +15,7 @@
import re
import sys
import unittest
+from unittest import mock
import warnings
test_tools.skip_if_missing('clinic')
@@ -22,7 +23,8 @@
import libclinic
from libclinic import ClinicError, unspecified, NULL, fail
from libclinic.converters import (
- int_converter, object_converter, str_converter, self_converter)
+ int_converter, object_converter, str_converter, self_converter,
+ Py_ssize_t_converter)
from libclinic.function import (
Module, Class, Function, FunctionKind, Parameter,
permute_optional_groups, permute_right_option_groups,
@@ -34,6 +36,7 @@
from libclinic.block_parser import Block, BlockParser
from libclinic.codegen import BlockPrinter, Destination
from libclinic.dsl_parser import DSLParser
+ from libclinic.slots import SlotSpec, SLOTS
from libclinic.cli import parse_file, Clinic
@@ -807,6 +810,112 @@ def test_var_keyword_non_dict(self):
"""
self.expect_failure(block, err, lineno=4)
+ def test_slot_next(self):
+ block = """
+ /*[clinic input]
+ output everything block
+ class Foo "FooObject *" "&Foo_Type"
+ [clinic start generated code]*/
+ /*[clinic input]
+ Foo.__next__
+ [clinic start generated code]*/
+ /*[clinic input]
+ @critical_section
+ Foo.__next__ as foo_iternext
+ [clinic start generated code]*/
+ #ifdef CONDITION
+ /*[clinic input]
+ Foo.__next__ as foo_iternext_cond
+ [clinic start generated code]*/
+ #endif
+ """
+ generated = self.clinic.parse(dedent(block))
+ self.assertIn(dedent("""
+ static PyObject *
+ Foo_next(PyObject *self)
+ {
+ return Foo_next_impl((FooObject *)self);
+ }
+ """), generated)
+ self.assertIn(dedent("""
+ static PyObject *
+ foo_iternext(PyObject *self)
+ {
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = foo_iternext_impl((FooObject *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+ }
+ """), generated)
+ self.assertIn("#if defined(CONDITION)\n\nstatic PyObject *\n"
+ "foo_iternext_cond_impl(FooObject *self);", generated)
+ # A slot function is not stored in a PyMethodDef.
+ for text in "METHODDEF", "PyDoc_", "#ifndef":
+ self.assertNotIn(text, generated)
+
+ def test_slot_with_parameters_and_return_type(self):
+ # Slots which take arguments or return something else than an
+ # object are not supported yet; check the machinery with fake ones.
+ slots = {
+ '__contains__': SlotSpec('__contains__', 'int', 'int',
+ {(object_converter,): 'sq_contains'}),
+ '__bool__': SlotSpec('__bool__', 'int', 'int', {(): 'nb_bool'}),
+ }
+ block = """
+ /*[clinic input]
+ output everything block
+ class Foo "FooObject *" "&Foo_Type"
+ [clinic start generated code]*/
+ /*[clinic input]
+ Foo.__contains__
+ value: object
+ /
+ [clinic start generated code]*/
+ /*[clinic input]
+ @critical_section
+ Foo.__bool__
+ [clinic start generated code]*/
+ """
+ with mock.patch.dict(SLOTS, slots):
+ generated = self.clinic.parse(dedent(block))
+ self.assertIn(dedent("""
+ static int
+ Foo_contains(PyObject *self, PyObject *value)
+ {
+ return Foo_contains_impl((FooObject *)self, value);
+ }
+ """), generated)
+ self.assertIn(dedent("""
+ static int
+ Foo_bool(PyObject *self)
+ {
+ int return_value = -1;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = Foo_bool_impl((FooObject *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+ }
+ """), generated)
+
+ def test_slot_cannot_be_cloned(self):
+ block = """
+ /*[clinic input]
+ class Foo "FooObject *" "&Foo_Type"
+ [clinic start generated code]*/
+ /*[clinic input]
+ Foo.__next__
+ [clinic start generated code]*/
+ /*[clinic input]
+ Foo.other = Foo.__next__
+ [clinic start generated code]*/
+ """
+ self.expect_failure(block, "Type slot methods cannot be cloned",
lineno=8)
+
def test_getset_in_ifdef(self):
block = """
/*[clinic input]
@@ -3237,6 +3346,78 @@ class Foo "" ""
f"a normal callable")
self.expect_failure(block, expected_error, lineno=3)
+ def test_slot_next(self):
+ function = self.parse_function("""
+ module foo
+ class Foo "FooObject *" ""
+ Foo.__next__
+ """, signatures_in_block=3, function_index=2)
+ self.assertIs(function.kind, FunctionKind.SLOT)
+ self.assertEqual(function.slot.name, "__next__")
+ self.assertEqual(function.c_basename, "Foo_next")
+ self.assertEqual(function.return_converter.type, "PyObject *")
+ self.assertEqual(list(function.parameters), ["self"])
+
+ def test_slot_parameters(self):
+ slot = SlotSpec('__contains__', 'int', 'int', {
+ (Py_ssize_t_converter,): 'sq_fake',
+ (object_converter,): 'sq_contains'})
+ block = """
+ module foo
+ class Foo "FooObject *" ""
+ Foo.__contains__
+ value: {converter}
+ /
+ """
+ errors = [
+ ("Foo.__contains__",
+ "'__contains__' must declare (Py_ssize_t) for sq_fake or "
+ "(object) for sq_contains; got ()"),
+ ("Foo.__contains__\n value: int\n /",
+ "'__contains__' must declare"),
+ ("Foo.__contains__\n value: object",
+ "The parameters of '__contains__' must be positional-only"),
+ ("Foo.__contains__\n value: object = None\n /",
+ "The parameters of '__contains__' cannot have a default value"),
+ ]
+ with mock.patch.dict(SLOTS, {'__contains__': slot}):
+ for converter in "Py_ssize_t", "object":
+ with self.subTest(converter=converter):
+ function = self.parse_function(
+ block.format(converter=converter),
+ signatures_in_block=3, function_index=2)
+ self.assertIs(function.kind, FunctionKind.SLOT)
+ self.assertEqual(function.return_converter.type, "int")
+ self.assertEqual(list(function.parameters), ["self",
"value"])
+ for body, err in errors:
+ with self.subTest(block=body):
+ body = 'class Foo "FooObject *" "&Foo_Type"\n' + body
+ self.expect_failure(body, err)
+
+ def test_slot_errors(self):
+ cases = [
+ ("module foo\nfoo.__next__",
+ "'__next__' must be a method!"),
+ ("@classmethod\nFoo.__next__",
+ "'__next__' must be a normal method"),
+ ("@getter\nFoo.__next__",
+ "'__next__' must be a normal method"),
+ ("@coexist\nFoo.__next__",
+ "@coexist cannot be applied to '__next__'"),
+ ("@vectorcall\nFoo.__next__",
+ "@vectorcall can only be used with __init__ and __new__"),
+ ("Foo.__next__\n a: int",
+ "'__next__' methods cannot define parameters"),
+ ("Foo.__next__ -> int",
+ "'Foo.__next__' cannot define a return type"),
+ ("Foo.__next__\n\nReturn the next item.",
+ "'__next__' methods cannot have a docstring"),
+ ]
+ for block, err in cases:
+ with self.subTest(block=block):
+ block = 'class Foo "FooObject *" "&Foo_Type"\n' + block
+ self.expect_failure(block, err)
+
def test_deleter_without_setter(self):
block = """
module foo
diff --git
a/Misc/NEWS.d/next/Tools-Demos/2026-10-20-12-50-00.gh-issue-114258.slotNx.rst
b/Misc/NEWS.d/next/Tools-Demos/2026-10-20-12-50-00.gh-issue-114258.slotNx.rst
new file mode 100644
index 000000000000000..afaeda209e1bd26
--- /dev/null
+++
b/Misc/NEWS.d/next/Tools-Demos/2026-10-20-12-50-00.gh-issue-114258.slotNx.rst
@@ -0,0 +1,3 @@
+Argument Clinic now supports the ``__next__`` type slot (``tp_iternext``),
+including ``@critical_section``.
+
diff --git a/Modules/_csv.c b/Modules/_csv.c
index 34548b943856931..484f7bc0f9d2d49 100644
--- a/Modules/_csv.c
+++ b/Modules/_csv.c
@@ -22,8 +22,9 @@ module instead.
/*[clinic input]
module _csv
class _csv.Dialect "DialectObj *" "clinic_state()->dialect_type"
+class _csv.Reader "ReaderObj *" "clinic_state()->reader_type"
[clinic start generated code]*/
-/*[clinic end generated code: output=da39a3ee5e6b4b0d input=042fcc14c7d541cc]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=7bdf66dc7d68ed90]*/
#define NOT_SET ((Py_UCS4)-1)
#define EOL ((Py_UCS4)-2)
@@ -124,8 +125,6 @@ typedef struct {
bool nonascii_special;
} DialectObj;
-#include "clinic/_csv.c.h"
-
typedef struct {
PyObject_HEAD
@@ -161,6 +160,8 @@ typedef struct {
#define _ReaderObj_CAST(op) ((ReaderObj *)(op))
#define _WriterObj_CAST(op) ((WriterObj *)(op))
+#include "clinic/_csv.c.h"
+
/*
* DIALECT class
*/
@@ -1020,11 +1021,15 @@ parse_reset(ReaderObj *self)
return 0;
}
+/*[clinic input]
+@critical_section
+_csv.Reader.__next__ as Reader_iternext
+[clinic start generated code]*/
+
static PyObject *
-Reader_iternext_lock_held(PyObject *op)
+Reader_iternext_impl(ReaderObj *self)
+/*[clinic end generated code: output=3a02b0254a76e1fc input=97c70a4e2fe621e4]*/
{
- ReaderObj *self = _ReaderObj_CAST(op);
-
PyObject *fields = NULL;
Py_UCS4 c;
Py_ssize_t pos, linelen;
@@ -1094,16 +1099,6 @@ Reader_iternext_lock_held(PyObject *op)
return fields;
}
-static PyObject *
-Reader_iternext(PyObject *op)
-{
- PyObject *result;
- Py_BEGIN_CRITICAL_SECTION(op);
- result = Reader_iternext_lock_held(op);
- Py_END_CRITICAL_SECTION();
- return result;
-}
-
static void
Reader_dealloc(PyObject *op)
{
diff --git a/Modules/_io/bytesio.c b/Modules/_io/bytesio.c
index e725e863a0740a3..2374a43ef74eab7 100644
--- a/Modules/_io/bytesio.c
+++ b/Modules/_io/bytesio.c
@@ -768,13 +768,18 @@ _io_BytesIO_truncate_impl(bytesio *self, PyObject *size)
return PyLong_FromSsize_t(new_size);
}
+/*[clinic input]
+@critical_section
+_io.BytesIO.__next__ as bytesio_iternext
+[clinic start generated code]*/
+
static PyObject *
-bytesio_iternext_lock_held(PyObject *op)
+bytesio_iternext_impl(bytesio *self)
+/*[clinic end generated code: output=f530bdee3a720d48 input=7fb3a763efe11a21]*/
{
- _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
Py_ssize_t n;
- bytesio *self = bytesio_CAST(op);
CHECK_CLOSED(self);
@@ -786,16 +791,6 @@ bytesio_iternext_lock_held(PyObject *op)
return read_bytes_lock_held(self, n);
}
-static PyObject *
-bytesio_iternext(PyObject *op)
-{
- PyObject *ret;
- Py_BEGIN_CRITICAL_SECTION(op);
- ret = bytesio_iternext_lock_held(op);
- Py_END_CRITICAL_SECTION();
- return ret;
-}
-
/*[clinic input]
@critical_section
_io.BytesIO.seek
diff --git a/Modules/_io/clinic/bytesio.c.h b/Modules/_io/clinic/bytesio.c.h
index afdb60e506a2ce2..b2305e35ca81e75 100644
--- a/Modules/_io/clinic/bytesio.c.h
+++ b/Modules/_io/clinic/bytesio.c.h
@@ -511,6 +511,21 @@ _io_BytesIO_truncate(PyObject *self, PyObject *const
*args, Py_ssize_t nargs)
return return_value;
}
+static PyObject *
+bytesio_iternext_impl(bytesio *self);
+
+static PyObject *
+bytesio_iternext(PyObject *self)
+{
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = bytesio_iternext_impl((bytesio *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+
PyDoc_STRVAR(_io_BytesIO_seek__doc__,
"seek($self, pos, whence=0, /)\n"
"--\n"
@@ -780,4 +795,4 @@ _io_BytesIO___sizeof__(PyObject *self, PyObject
*Py_UNUSED(ignored))
}
#define _IO_BYTESIO_CLOSED_GETSETDEF {"closed",
(getter)_io_BytesIO_closed_get, (setter)NULL, _io_BytesIO_closed__doc__},
-/*[clinic end generated code: output=a3dc7a416dae6ae9 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=a2bc96c358d6e240 input=a9049054013a1b77]*/
diff --git a/Modules/_io/clinic/stringio.c.h b/Modules/_io/clinic/stringio.c.h
index 66b7a57cdf93337..bda87205e6bb718 100644
--- a/Modules/_io/clinic/stringio.c.h
+++ b/Modules/_io/clinic/stringio.c.h
@@ -135,6 +135,21 @@ _io_StringIO_readline(PyObject *self, PyObject *const
*args, Py_ssize_t nargs)
return return_value;
}
+static PyObject *
+stringio_iternext_impl(stringio *self);
+
+static PyObject *
+stringio_iternext(PyObject *self)
+{
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = stringio_iternext_impl((stringio *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+
PyDoc_STRVAR(_io_StringIO_truncate__doc__,
"truncate($self, pos=None, /)\n"
"--\n"
@@ -539,4 +554,4 @@ _io_StringIO_newlines_get(PyObject *self, void
*Py_UNUSED(context))
#define _IO_STRINGIO_NEWLINES_GETSETDEF {"newlines",
(getter)_io_StringIO_newlines_get, (setter)NULL, NULL},
-/*[clinic end generated code: output=4100afa5e4f295d7 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=38a0385529d9cbef input=a9049054013a1b77]*/
diff --git a/Modules/_io/clinic/textio.c.h b/Modules/_io/clinic/textio.c.h
index 6d63d4706f609c3..51e75d050617991 100644
--- a/Modules/_io/clinic/textio.c.h
+++ b/Modules/_io/clinic/textio.c.h
@@ -1096,6 +1096,21 @@ _io_TextIOWrapper_close(PyObject *self, PyObject
*Py_UNUSED(ignored))
return return_value;
}
+static PyObject *
+textiowrapper_iternext_impl(textio *self);
+
+static PyObject *
+textiowrapper_iternext(PyObject *self)
+{
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = textiowrapper_iternext_impl((textio *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+
static PyObject *
_io_TextIOWrapper_name_get_impl(textio *self);
@@ -1246,4 +1261,4 @@ _io_TextIOWrapper_buffer_get(PyObject *self, void
*Py_UNUSED(context))
#define _IO_TEXTIOWRAPPER_BUFFER_GETSETDEF {"buffer",
(getter)_io_TextIOWrapper_buffer_get, (setter)NULL, NULL},
-/*[clinic end generated code: output=89de9ab341df9f66 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=f3dd3fff36cd2142 input=a9049054013a1b77]*/
diff --git a/Modules/_io/stringio.c b/Modules/_io/stringio.c
index 93a82bcc0c6e3e9..e9a46167cff5963 100644
--- a/Modules/_io/stringio.c
+++ b/Modules/_io/stringio.c
@@ -420,13 +420,18 @@ _io_StringIO_readline_impl(stringio *self, Py_ssize_t
size)
return _stringio_readline(self, size);
}
+/*[clinic input]
+@critical_section
+_io.StringIO.__next__ as stringio_iternext
+[clinic start generated code]*/
+
static PyObject *
-stringio_iternext_lock_held(PyObject *op)
+stringio_iternext_impl(stringio *self)
+/*[clinic end generated code: output=51eb3991b1c79c41 input=b6cf3e73fab6b42e]*/
{
- _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
PyObject *line;
- stringio *self = stringio_CAST(op);
CHECK_INITIALIZED(self);
CHECK_CLOSED(self);
@@ -438,7 +443,7 @@ stringio_iternext_lock_held(PyObject *op)
}
else {
/* XXX is subclassing StringIO really supported? */
- line = PyObject_CallMethodNoArgs(op, &_Py_ID(readline));
+ line = PyObject_CallMethodNoArgs((PyObject *)self, &_Py_ID(readline));
if (line && !PyUnicode_Check(line)) {
PyErr_Format(PyExc_OSError,
"readline() should have returned a str object, "
@@ -460,16 +465,6 @@ stringio_iternext_lock_held(PyObject *op)
return line;
}
-static PyObject *
-stringio_iternext(PyObject *op)
-{
- PyObject *ret;
- Py_BEGIN_CRITICAL_SECTION(op);
- ret = stringio_iternext_lock_held(op);
- Py_END_CRITICAL_SECTION();
- return ret;
-}
-
/*[clinic input]
@critical_section
_io.StringIO.truncate
diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c
index 3736179807f45dd..f33ab46b10fd2e9 100644
--- a/Modules/_io/textio.c
+++ b/Modules/_io/textio.c
@@ -3287,12 +3287,17 @@ _io_TextIOWrapper_close_impl(textio *self)
}
}
+/*[clinic input]
+@critical_section
+_io.TextIOWrapper.__next__ as textiowrapper_iternext
+[clinic start generated code]*/
+
static PyObject *
-textiowrapper_iternext_lock_held(PyObject *op)
+textiowrapper_iternext_impl(textio *self)
+/*[clinic end generated code: output=f365c4287cd43a1f input=c5b4c79faf0c98cb]*/
{
- _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
PyObject *line;
- textio *self = textio_CAST(op);
CHECK_ATTACHED(self);
@@ -3302,7 +3307,7 @@ textiowrapper_iternext_lock_held(PyObject *op)
line = _textiowrapper_readline(self, -1);
}
else {
- line = PyObject_CallMethodNoArgs(op, &_Py_ID(readline));
+ line = PyObject_CallMethodNoArgs((PyObject *)self, &_Py_ID(readline));
if (line && !PyUnicode_Check(line)) {
PyErr_Format(PyExc_OSError,
"readline() should have returned a str object, "
@@ -3326,16 +3331,6 @@ textiowrapper_iternext_lock_held(PyObject *op)
return line;
}
-static PyObject *
-textiowrapper_iternext(PyObject *op)
-{
- PyObject *result;
- Py_BEGIN_CRITICAL_SECTION(op);
- result = textiowrapper_iternext_lock_held(op);
- Py_END_CRITICAL_SECTION();
- return result;
-}
-
/*[clinic input]
@critical_section
@getter
diff --git a/Modules/clinic/_csv.c.h b/Modules/clinic/_csv.c.h
index b25f30893224cd4..df3bdbadf29aed6 100644
--- a/Modules/clinic/_csv.c.h
+++ b/Modules/clinic/_csv.c.h
@@ -6,6 +6,7 @@ preserve
# include "pycore_gc.h" // PyGC_Head
# include "pycore_runtime.h" // _Py_ID()
#endif
+#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION()
#include "pycore_modsupport.h" // _PyArg_NoPositional()
PyDoc_STRVAR(_csv_Dialect___reduce____doc__,
@@ -87,6 +88,21 @@ _csv_Dialect___replace__(PyObject *self, PyObject *args,
PyObject *kwargs)
return return_value;
}
+static PyObject *
+Reader_iternext_impl(ReaderObj *self);
+
+static PyObject *
+Reader_iternext(PyObject *self)
+{
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = Reader_iternext_impl((ReaderObj *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+
PyDoc_STRVAR(_csv_reader__doc__,
"reader($module, iterable, dialect=\'excel\', /, **fmtparams)\n"
"--\n"
@@ -449,4 +465,4 @@ _csv_field_size_limit(PyObject *module, PyObject *const
*args, Py_ssize_t nargs,
exit:
return return_value;
}
-/*[clinic end generated code: output=1a90b8a8ed82497d input=a9049054013a1b77]*/
+/*[clinic end generated code: output=7e3a92d524d88023 input=a9049054013a1b77]*/
diff --git a/Modules/clinic/itertoolsmodule.c.h
b/Modules/clinic/itertoolsmodule.c.h
index d9917b47dc90376..d6365b46f04b260 100644
--- a/Modules/clinic/itertoolsmodule.c.h
+++ b/Modules/clinic/itertoolsmodule.c.h
@@ -7,6 +7,7 @@ preserve
# include "pycore_runtime.h" // _Py_ID()
#endif
#include "pycore_abstract.h" // _PyNumber_Index()
+#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION()
#include "pycore_modsupport.h" // _PyArg_UnpackKeywords()
PyDoc_STRVAR(batched_new__doc__,
@@ -508,6 +509,36 @@ itertools_chain_from_iterable(PyObject *type, PyObject
*arg)
return return_value;
}
+static PyObject *
+chain_next_impl(chainobject *self);
+
+static PyObject *
+chain_next(PyObject *self)
+{
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = chain_next_impl((chainobject *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+
+static PyObject *
+product_next_impl(productobject *self);
+
+static PyObject *
+product_next(PyObject *self)
+{
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = product_next_impl((productobject *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+
PyDoc_STRVAR(itertools_combinations__doc__,
"combinations(iterable, r)\n"
"--\n"
@@ -586,6 +617,21 @@ itertools_combinations(PyTypeObject *type, PyObject *args,
PyObject *kwargs)
return return_value;
}
+static PyObject *
+combinations_next_impl(combinationsobject *self);
+
+static PyObject *
+combinations_next(PyObject *self)
+{
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = combinations_next_impl((combinationsobject *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+
PyDoc_STRVAR(itertools_combinations_with_replacement__doc__,
"combinations_with_replacement(iterable, r)\n"
"--\n"
@@ -665,6 +711,21 @@ itertools_combinations_with_replacement(PyTypeObject
*type, PyObject *args, PyOb
return return_value;
}
+static PyObject *
+cwr_next_impl(cwrobject *self);
+
+static PyObject *
+cwr_next(PyObject *self)
+{
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = cwr_next_impl((cwrobject *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+
PyDoc_STRVAR(itertools_permutations__doc__,
"permutations(iterable, r=None)\n"
"--\n"
@@ -732,6 +793,21 @@ itertools_permutations(PyTypeObject *type, PyObject *args,
PyObject *kwargs)
return return_value;
}
+static PyObject *
+permutations_next_impl(permutationsobject *self);
+
+static PyObject *
+permutations_next(PyObject *self)
+{
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = permutations_next_impl((permutationsobject *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+
PyDoc_STRVAR(itertools_accumulate__doc__,
"accumulate(iterable, func=None, *, initial=None)\n"
"--\n"
@@ -808,6 +884,21 @@ itertools_accumulate(PyTypeObject *type, PyObject *args,
PyObject *kwargs)
return return_value;
}
+static PyObject *
+accumulate_next_impl(accumulateobject *self);
+
+static PyObject *
+accumulate_next(PyObject *self)
+{
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = accumulate_next_impl((accumulateobject *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+
PyDoc_STRVAR(itertools_compress__doc__,
"compress(data, selectors)\n"
"--\n"
@@ -980,4 +1071,19 @@ itertools_count(PyTypeObject *type, PyObject *args,
PyObject *kwargs)
exit:
return return_value;
}
-/*[clinic end generated code: output=a34a31f60100e0ff input=a9049054013a1b77]*/
+
+static PyObject *
+zip_longest_next_impl(ziplongestobject *self);
+
+static PyObject *
+zip_longest_next(PyObject *self)
+{
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = zip_longest_next_impl((ziplongestobject *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+/*[clinic end generated code: output=4b55088ac24582b7 input=a9049054013a1b77]*/
diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c
index c0023c839ca7fe3..636b28fa06ef2e0 100644
--- a/Modules/itertoolsmodule.c
+++ b/Modules/itertoolsmodule.c
@@ -65,6 +65,16 @@ find_state_by_type(PyTypeObject *tp)
return get_module_state(mod);
}
+// The generated code (clinic/itertoolsmodule.c.h) needs the object types
+// of the type slots implemented with Argument Clinic.
+typedef struct chainobject chainobject;
+typedef struct productobject productobject;
+typedef struct combinationsobject combinationsobject;
+typedef struct cwrobject cwrobject;
+typedef struct permutationsobject permutationsobject;
+typedef struct accumulateobject accumulateobject;
+typedef struct ziplongestobject ziplongestobject;
+
/*[clinic input]
module itertools
class itertools.groupby "groupbyobject *" "clinic_state()->groupby_type"
@@ -77,16 +87,18 @@ class itertools.dropwhile "dropwhileobject *"
"clinic_state()->dropwhile_type"
class itertools.takewhile "takewhileobject *" "clinic_state()->takewhile_type"
class itertools.starmap "starmapobject *" "clinic_state()->starmap_type"
class itertools.chain "chainobject *" "clinic_state()->chain_type"
+class itertools.product "productobject *" "clinic_state()->product_type"
class itertools.combinations "combinationsobject *"
"clinic_state()->combinations_type"
-class itertools.combinations_with_replacement "cwr_object *"
"clinic_state()->cwr_type"
+class itertools.combinations_with_replacement "cwrobject *"
"clinic_state()->cwr_type"
class itertools.permutations "permutationsobject *"
"clinic_state()->permutations_type"
class itertools.accumulate "accumulateobject *"
"clinic_state()->accumulate_type"
+class itertools.zip_longest "ziplongestobject *"
"clinic_state()->ziplongest_type"
class itertools.compress "compressobject *" "clinic_state()->compress_type"
class itertools.filterfalse "filterfalseobject *"
"clinic_state()->filterfalse_type"
class itertools.count "countobject *" "clinic_state()->count_type"
class itertools.pairwise "pairwiseobject *" "clinic_state()->pairwise_type"
[clinic start generated code]*/
-/*[clinic end generated code: output=da39a3ee5e6b4b0d input=aa48fe4de9d4080f]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=1086fb2d308d75d2]*/
#define clinic_state() (find_state_by_type(type))
#define clinic_state_by_cls() (get_module_state_by_cls(base_tp))
@@ -1877,11 +1889,11 @@ static PyType_Spec starmap_spec = {
/* chain object **************************************************************/
-typedef struct {
+struct chainobject {
PyObject_HEAD
PyObject *source; /* Iterator over input iterables */
PyObject *active; /* Currently running input iterator */
-} chainobject;
+};
#define chainobject_CAST(op) ((chainobject *)(op))
@@ -1963,10 +1975,16 @@ chain_traverse(PyObject *op, visitproc visit, void *arg)
return 0;
}
-static inline PyObject *
-chain_next_lock_held(PyObject *op)
+/*[clinic input]
+@critical_section
+itertools.chain.__next__ as chain_next
+[clinic start generated code]*/
+
+static PyObject *
+chain_next_impl(chainobject *self)
+/*[clinic end generated code: output=91e716f45def7fa9 input=e17098e9dc88706b]*/
{
- chainobject *lz = chainobject_CAST(op);
+ chainobject *lz = self;
PyObject *item;
/* lz->source is the iterator of iterables. If it's NULL, we've already
@@ -2002,16 +2020,6 @@ chain_next_lock_held(PyObject *op)
return NULL;
}
-static PyObject *
-chain_next(PyObject *op)
-{
- PyObject *result;
- Py_BEGIN_CRITICAL_SECTION(op);
- result = chain_next_lock_held(op);
- Py_END_CRITICAL_SECTION()
- return result;
-}
-
PyDoc_STRVAR(chain_doc,
"chain(*iterables)\n\
--\n\
@@ -2055,13 +2063,13 @@ static PyType_Spec chain_spec = {
/* product object ************************************************************/
-typedef struct {
+struct productobject {
PyObject_HEAD
PyObject *pools; /* tuple of pool tuples */
Py_ssize_t *indices; /* one index per pool */
PyObject *result; /* most recently returned result tuple */
int stopped; /* set to 1 when the iterator is exhausted */
-} productobject;
+};
#define productobject_CAST(op) ((productobject *)(op))
@@ -2182,10 +2190,16 @@ product_traverse(PyObject *op, visitproc visit, void
*arg)
return 0;
}
+/*[clinic input]
+@critical_section
+itertools.product.__next__ as product_next
+[clinic start generated code]*/
+
static PyObject *
-product_next_lock_held(PyObject *op)
+product_next_impl(productobject *self)
+/*[clinic end generated code: output=d20b8571608c2043 input=54266fe7ed4a857a]*/
{
- productobject *lz = productobject_CAST(op);
+ productobject *lz = self;
PyObject *pool;
PyObject *elem;
PyObject *oldelem;
@@ -2269,16 +2283,6 @@ product_next_lock_held(PyObject *op)
return NULL;
}
-static PyObject *
-product_next(PyObject *op)
-{
- PyObject *result;
- Py_BEGIN_CRITICAL_SECTION(op);
- result = product_next_lock_held(op);
- Py_END_CRITICAL_SECTION()
- return result;
-}
-
static PyMethodDef product_methods[] = {
{"__sizeof__", product_sizeof, METH_NOARGS, sizeof_doc},
{NULL, NULL} /* sentinel */
@@ -2323,14 +2327,14 @@ static PyType_Spec product_spec = {
/* combinations object *******************************************************/
-typedef struct {
+struct combinationsobject {
PyObject_HEAD
PyObject *pool; /* input converted to a tuple */
Py_ssize_t *indices; /* one index per result element */
PyObject *result; /* most recently returned result tuple */
Py_ssize_t r; /* size of result tuple */
int stopped; /* set to 1 when the iterator is exhausted */
-} combinationsobject;
+};
#define combinationsobject_CAST(op) ((combinationsobject *)(op))
@@ -2421,10 +2425,16 @@ combinations_traverse(PyObject *op, visitproc visit,
void *arg)
return 0;
}
+/*[clinic input]
+@critical_section
+itertools.combinations.__next__ as combinations_next
+[clinic start generated code]*/
+
static PyObject *
-combinations_next_lock_held(PyObject *op)
+combinations_next_impl(combinationsobject *self)
+/*[clinic end generated code: output=19965c1a1f4b8f2a input=93000f6ec48380d7]*/
{
- combinationsobject *co = combinationsobject_CAST(op);
+ combinationsobject *co = self;
PyObject *elem;
PyObject *oldelem;
PyObject *pool = co->pool;
@@ -2507,16 +2517,6 @@ combinations_next_lock_held(PyObject *op)
return NULL;
}
-static PyObject *
-combinations_next(PyObject *op)
-{
- PyObject *result;
- Py_BEGIN_CRITICAL_SECTION(op);
- result = combinations_next_lock_held(op);
- Py_END_CRITICAL_SECTION()
- return result;
-}
-
static PyMethodDef combinations_methods[] = {
{"__sizeof__", combinations_sizeof, METH_NOARGS, sizeof_doc},
{NULL, NULL} /* sentinel */
@@ -2572,14 +2572,14 @@ static PyType_Spec combinations_spec = {
if sorted(indices) == list(indices):
yield tuple(pool[i] for i in indices)
*/
-typedef struct {
+struct cwrobject {
PyObject_HEAD
PyObject *pool; /* input converted to a tuple */
Py_ssize_t *indices; /* one index per result element */
PyObject *result; /* most recently returned result tuple */
Py_ssize_t r; /* size of result tuple */
int stopped; /* set to 1 when the cwr iterator is exhausted */
-} cwrobject;
+};
#define cwrobject_CAST(op) ((cwrobject *)(op))
@@ -2673,10 +2673,16 @@ cwr_traverse(PyObject *op, visitproc visit, void *arg)
return 0;
}
+/*[clinic input]
+@critical_section
+itertools.combinations_with_replacement.__next__ as cwr_next
+[clinic start generated code]*/
+
static PyObject *
-cwr_next_lock_held(PyObject *op)
+cwr_next_impl(cwrobject *self)
+/*[clinic end generated code: output=760ddcfbf905ee37 input=d9ed46a28a5bb29a]*/
{
- cwrobject *co = cwrobject_CAST(op);
+ cwrobject *co = self;
PyObject *elem;
PyObject *oldelem;
PyObject *pool = co->pool;
@@ -2753,16 +2759,6 @@ cwr_next_lock_held(PyObject *op)
return NULL;
}
-static PyObject *
-cwr_next(PyObject *op)
-{
- PyObject *result;
- Py_BEGIN_CRITICAL_SECTION(op);
- result = cwr_next_lock_held(op);
- Py_END_CRITICAL_SECTION()
- return result;
-}
-
static PyMethodDef cwr_methods[] = {
{"__sizeof__", cwr_sizeof, METH_NOARGS, sizeof_doc},
{NULL, NULL} /* sentinel */
@@ -2818,7 +2814,7 @@ def permutations(iterable, r=None):
return
*/
-typedef struct {
+struct permutationsobject {
PyObject_HEAD
PyObject *pool; /* input converted to a tuple */
Py_ssize_t *indices; /* one index per element in the pool */
@@ -2826,7 +2822,7 @@ typedef struct {
PyObject *result; /* most recently returned result tuple */
Py_ssize_t r; /* size of result tuple */
int stopped; /* set to 1 when the iterator is exhausted */
-} permutationsobject;
+};
#define permutationsobject_CAST(op) ((permutationsobject *)(op))
@@ -2942,10 +2938,16 @@ permutations_traverse(PyObject *op, visitproc visit,
void *arg)
return 0;
}
+/*[clinic input]
+@critical_section
+itertools.permutations.__next__ as permutations_next
+[clinic start generated code]*/
+
static PyObject *
-permutations_next_lock_held(PyObject *op)
+permutations_next_impl(permutationsobject *self)
+/*[clinic end generated code: output=17d8db4465bd3ee5 input=d4c1a1eed76a35e0]*/
{
- permutationsobject *po = permutationsobject_CAST(op);
+ permutationsobject *po = self;
PyObject *elem;
PyObject *oldelem;
PyObject *pool = po->pool;
@@ -3033,16 +3035,6 @@ permutations_next_lock_held(PyObject *op)
return NULL;
}
-static PyObject *
-permutations_next(PyObject *op)
-{
- PyObject *result;
- Py_BEGIN_CRITICAL_SECTION(op);
- result = permutations_next_lock_held(op);
- Py_END_CRITICAL_SECTION()
- return result;
-}
-
static PyMethodDef permuations_methods[] = {
{"__sizeof__", permutations_sizeof, METH_NOARGS, sizeof_doc},
{NULL, NULL} /* sentinel */
@@ -3072,14 +3064,14 @@ static PyType_Spec permutations_spec = {
/* accumulate object ********************************************************/
-typedef struct {
+struct accumulateobject {
PyObject_HEAD
PyObject *total;
PyObject *it;
PyObject *binop;
PyObject *initial;
itertools_state *state;
-} accumulateobject;
+};
#define accumulateobject_CAST(op) ((accumulateobject *)(op))
@@ -3149,10 +3141,16 @@ accumulate_traverse(PyObject *op, visitproc visit, void
*arg)
return 0;
}
+/*[clinic input]
+@critical_section
+itertools.accumulate.__next__ as accumulate_next
+[clinic start generated code]*/
+
static PyObject *
-accumulate_next_lock_held(PyObject *op)
+accumulate_next_impl(accumulateobject *self)
+/*[clinic end generated code: output=30d3f9b40248d8a6 input=758a144b7cfb1ef4]*/
{
- accumulateobject *lz = accumulateobject_CAST(op);
+ accumulateobject *lz = self;
PyObject *val, *newtotal;
if (lz->initial != Py_None) {
@@ -3182,16 +3180,6 @@ accumulate_next_lock_held(PyObject *op)
return newtotal;
}
-static PyObject *
-accumulate_next(PyObject *op)
-{
- PyObject *result;
- Py_BEGIN_CRITICAL_SECTION(op);
- result = accumulate_next_lock_held(op);
- Py_END_CRITICAL_SECTION()
- return result;
-}
-
static PyType_Slot accumulate_slots[] = {
{Py_tp_dealloc, accumulate_dealloc},
{Py_tp_getattro, PyObject_GenericGetAttr},
@@ -3852,14 +3840,14 @@ static PyType_Spec repeat_spec = {
/* ziplongest object *********************************************************/
-typedef struct {
+struct ziplongestobject {
PyObject_HEAD
Py_ssize_t tuplesize;
Py_ssize_t numactive;
PyObject *ittuple; /* tuple of iterators */
PyObject *result;
PyObject *fillvalue;
-} ziplongestobject;
+};
#define ziplongestobject_CAST(op) ((ziplongestobject *)(op))
@@ -3955,10 +3943,16 @@ zip_longest_traverse(PyObject *op, visitproc visit,
void *arg)
return 0;
}
+/*[clinic input]
+@critical_section
+itertools.zip_longest.__next__ as zip_longest_next
+[clinic start generated code]*/
+
static PyObject *
-zip_longest_next_lock_held(PyObject *op)
+zip_longest_next_impl(ziplongestobject *self)
+/*[clinic end generated code: output=11bc756cf896c576 input=5b1e3b52098852a9]*/
{
- ziplongestobject *lz = ziplongestobject_CAST(op);
+ ziplongestobject *lz = self;
Py_ssize_t i;
Py_ssize_t tuplesize = lz->tuplesize;
PyObject *result = lz->result;
@@ -4027,16 +4021,6 @@ zip_longest_next_lock_held(PyObject *op)
return result;
}
-static PyObject *
-zip_longest_next(PyObject *op)
-{
- PyObject *result;
- Py_BEGIN_CRITICAL_SECTION(op);
- result = zip_longest_next_lock_held(op);
- Py_END_CRITICAL_SECTION()
- return result;
-}
-
PyDoc_STRVAR(zip_longest_doc,
"zip_longest(*iterables, fillvalue=None)\n\
--\n\
diff --git a/Objects/clinic/odictobject.c.h b/Objects/clinic/odictobject.c.h
index 92129e6444810f4..ebef5dad597c548 100644
--- a/Objects/clinic/odictobject.c.h
+++ b/Objects/clinic/odictobject.c.h
@@ -452,4 +452,19 @@ OrderedDict_move_to_end(PyObject *self, PyObject *const
*args, Py_ssize_t nargs,
exit:
return return_value;
}
-/*[clinic end generated code: output=89f7e92de998f9a4 input=a9049054013a1b77]*/
+
+static PyObject *
+odictiter_iternext_impl(PyObject *self);
+
+static PyObject *
+odictiter_iternext(PyObject *self)
+{
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = odictiter_iternext_impl(self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+/*[clinic end generated code: output=b729b3f1d40afcd9 input=a9049054013a1b77]*/
diff --git a/Objects/clinic/rangeobject.c.h b/Objects/clinic/rangeobject.c.h
index d9142eddde4b176..1cdc2e43b50dbc0 100644
--- a/Objects/clinic/rangeobject.c.h
+++ b/Objects/clinic/rangeobject.c.h
@@ -147,4 +147,19 @@ longrange_iterator___setstate__(PyObject *r, PyObject
*state)
return return_value;
}
-/*[clinic end generated code: output=719c0e4c81fe0f4a input=a9049054013a1b77]*/
+
+static PyObject *
+longrangeiter_next_impl(longrangeiterobject *self);
+
+static PyObject *
+longrangeiter_next(PyObject *self)
+{
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = longrangeiter_next_impl((longrangeiterobject *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+/*[clinic end generated code: output=e31cc65008f9db4f input=a9049054013a1b77]*/
diff --git a/Objects/odictobject.c b/Objects/odictobject.c
index 5eaeaaf87a58563..4b3b3237e82476b 100644
--- a/Objects/odictobject.c
+++ b/Objects/odictobject.c
@@ -478,8 +478,9 @@ Potential Optimizations
/*[clinic input]
class OrderedDict "PyODictObject *" "&PyODict_Type"
+class odictiter "PyObject *" "&PyODictIter_Type"
[clinic start generated code]*/
-/*[clinic end generated code: output=da39a3ee5e6b4b0d input=ca0641cf6143d4af]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=ae5599eeb14d29ae]*/
typedef struct _odictnode _ODictNode;
@@ -1804,10 +1805,16 @@ odictiter_nextkey(odictiterobject *di)
return res;
}
+/*[clinic input]
+@critical_section
+odictiter.__next__ as odictiter_iternext
+[clinic start generated code]*/
+
static PyObject *
-odictiter_iternext_lock_held(PyObject *op)
+odictiter_iternext_impl(PyObject *self)
+/*[clinic end generated code: output=71aef8eec9e1a133 input=bc8bec6c85481b93]*/
{
- odictiterobject *di = (odictiterobject*)op;
+ odictiterobject *di = (odictiterobject*)self;
PyObject *result, *value;
PyObject *key = odictiter_nextkey(di); /* new reference */
@@ -1862,16 +1869,6 @@ odictiter_iternext_lock_held(PyObject *op)
return NULL;
}
-static PyObject *
-odictiter_iternext(PyObject *op)
-{
- PyObject *res;
- Py_BEGIN_CRITICAL_SECTION(op);
- res = odictiter_iternext_lock_held(op);
- Py_END_CRITICAL_SECTION();
- return res;
-}
-
/* No need for tp_clear because odictiterobject is not mutable. */
diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c
index 55b7f108730728d..2279d4ce17d2f6f 100644
--- a/Objects/rangeobject.c
+++ b/Objects/rangeobject.c
@@ -1158,10 +1158,16 @@ longrangeiter_dealloc(PyObject *op)
PyObject_Free(r);
}
+/*[clinic input]
+@critical_section
+longrange_iterator.__next__ as longrangeiter_next
+[clinic start generated code]*/
+
static PyObject *
-longrangeiter_next_lock_held(PyObject *op)
+longrangeiter_next_impl(longrangeiterobject *self)
+/*[clinic end generated code: output=49bf9a9b89c07ba5 input=2c4ac537c23a92d3]*/
{
- longrangeiterobject *r = (longrangeiterobject*)op;
+ longrangeiterobject *r = self;
if (PyObject_RichCompareBool(r->len, _PyLong_GetZero(), Py_GT) != 1)
return NULL;
@@ -1180,16 +1186,6 @@ longrangeiter_next_lock_held(PyObject *op)
return result;
}
-static PyObject *
-longrangeiter_next(PyObject *op)
-{
- PyObject *result;
- Py_BEGIN_CRITICAL_SECTION(op);
- result = longrangeiter_next_lock_held(op);
- Py_END_CRITICAL_SECTION();
- return result;
-}
-
PyTypeObject PyLongRangeIter_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"longrange_iterator", /* tp_name */
diff --git a/Tools/clinic/libclinic/clanguage.py
b/Tools/clinic/libclinic/clanguage.py
index 1f71c6cda81b81b..f8ab2fd25154614 100644
--- a/Tools/clinic/libclinic/clanguage.py
+++ b/Tools/clinic/libclinic/clanguage.py
@@ -12,7 +12,7 @@
from libclinic.function import (
Module, Class, Property, Function, Parameter,
group_to_variable_name,
- METHOD_INIT, METHOD_NEW,
+ METHOD_INIT, METHOD_NEW, SLOT,
ACCESSORS, SETTERS)
from libclinic.converters import self_converter
from libclinic.parse_args import (
@@ -383,6 +383,10 @@ def render_function(
if ("METH_O" in templates['methoddef_define'] and
'{parser_parameters}' in templates['parser_prototype']):
data.declarations.pop(0)
+ # A slot function receives its arguments as parameters, see
+ # parse_slot(): there is nothing to declare or parse.
+ if f.kind is SLOT:
+ data.declarations.clear()
full_name = f.full_name
template_dict = {'full_name': full_name}
@@ -404,12 +408,17 @@ def render_function(
for converter in converters:
converter.set_template_dict(template_dict)
- if f.kind not in SETTERS | {METHOD_INIT}:
+ if f.kind not in SETTERS | {METHOD_INIT, SLOT}:
f.return_converter.render(f, data)
template_dict['impl_return_type'] = f.return_converter.type
- # tp_init returns int; every other parser returns an object.
- template_dict['return_type'] = (
- 'int' if f.kind is METHOD_INIT else 'PyObject *')
+ # tp_init returns int, a slot function returns what its slot
+ # declares, every other parser returns an object.
+ if f.kind is SLOT:
+ assert f.slot is not None
+ template_dict['return_type'] = f.slot.return_type
+ else:
+ template_dict['return_type'] = (
+ 'int' if f.kind is METHOD_INIT else 'PyObject *')
template_dict['declarations'] =
libclinic.format_escape("\n".join(data.declarations))
template_dict['initializers'] = "\n\n".join(data.initializers)
diff --git a/Tools/clinic/libclinic/converter.py
b/Tools/clinic/libclinic/converter.py
index 0caa8a8956f9994..2f9f54f7e29df41 100644
--- a/Tools/clinic/libclinic/converter.py
+++ b/Tools/clinic/libclinic/converter.py
@@ -275,6 +275,11 @@ def __getattr__(self, attr):
else: # pragma: no cover
pass
+ @classmethod
+ def dsl_name(cls) -> str:
+ """The name of the converter in the DSL, e.g. 'object'."""
+ return cls.__name__.removesuffix('_converter')
+
def converter_init(self) -> None:
pass
diff --git a/Tools/clinic/libclinic/converters.py
b/Tools/clinic/libclinic/converters.py
index 8db4c5fe22e26d5..c54dab302b66586 100644
--- a/Tools/clinic/libclinic/converters.py
+++ b/Tools/clinic/libclinic/converters.py
@@ -8,7 +8,7 @@
from libclinic import fail, NullType, unspecified, NULL, c_bytes_repr,
c_unichar_repr
from libclinic.function import (
Function, Parameter,
- CALLABLE, STATIC_METHOD, CLASS_METHOD, METHOD_INIT, METHOD_NEW,
+ CALLABLE, STATIC_METHOD, CLASS_METHOD, METHOD_INIT, METHOD_NEW, SLOT,
ACCESSORS)
from libclinic.codegen import CRenderData, TemplateDict
from libclinic.converter import (
@@ -1197,7 +1197,7 @@ def correct_name_for_self(
f: Function,
parser: bool = False
) -> tuple[str, str]:
- if f.kind in {CALLABLE, METHOD_INIT} | ACCESSORS:
+ if f.kind in {CALLABLE, METHOD_INIT, SLOT} | ACCESSORS:
if f.cls:
return "PyObject *", "self"
return "PyObject *", "module"
diff --git a/Tools/clinic/libclinic/dsl_parser.py
b/Tools/clinic/libclinic/dsl_parser.py
index 514844af1d05b72..60730b8828d8438 100644
--- a/Tools/clinic/libclinic/dsl_parser.py
+++ b/Tools/clinic/libclinic/dsl_parser.py
@@ -18,7 +18,7 @@
Module, Class, Property, Function, Parameter,
FunctionKind,
CALLABLE, STATIC_METHOD, CLASS_METHOD, METHOD_INIT, METHOD_NEW,
- GETTER, SETTER, SETTER_AND_DELETER,
+ GETTER, SETTER, SETTER_AND_DELETER, SLOT,
ACCESSORS, SETTERS)
from libclinic.converter import (
converters, legacy_converters)
@@ -28,12 +28,15 @@
from libclinic.return_converters import (
CReturnConverter, return_converters,
int_return_converter)
+from libclinic.slots import SlotSpec, SLOTS
from libclinic.parser import create_parser_namespace
if TYPE_CHECKING:
from libclinic.block_parser import Block
from libclinic.app import Clinic
+# Special methods which Argument Clinic cannot convert. The type slots
+# which it supports are listed in libclinic.slots.
unsupported_special_methods: set[str] = set("""
__abs__
@@ -76,7 +79,6 @@
__mod__
__mul__
__neg__
-__next__
__or__
__pos__
__pow__
@@ -265,6 +267,7 @@ class DSLParser:
critical_section: bool
target_critical_section: list[str]
disable_fastcall: bool
+ slot: SlotSpec | None
# Line of the file which is being parsed.
line_number: int | None
from_version_re = re.compile(r'([*/]) +\[from +(.+)\]')
@@ -312,6 +315,7 @@ def reset(self) -> None:
self.target_critical_section = []
self.disable_fastcall = False
self.vectorcall: bool = False
+ self.slot = None
self.permit_long_summary = False
self.permit_long_docstring_body = False
@@ -603,6 +607,9 @@ def parse_function_names(self, line: str) -> FunctionNames:
fields = full_name.split(".")
if fields[-1] == '__new__':
fields.pop()
+ elif fields[-1] in SLOTS:
+ # list.__repr__ -> list_repr
+ fields[-1] = fields[-1].strip('_')
c_basename = "_".join(fields)
if not libclinic.is_legal_py_identifier(full_name):
fail(f"Illegal function name: {full_name!r}")
@@ -627,12 +634,23 @@ def normalize_function_kind(self, fullname: str) -> None:
fail("'__new__' must be a class method!")
if self.kind in ACCESSORS and not cls:
fail("@getter and @setter must be methods")
+ slot = SLOTS.get(name)
+ if slot is not None:
+ if self.kind is not CALLABLE:
+ fail(f"{name!r} must be a normal method; got '{self.kind}'!")
+ if not cls:
+ fail(f"{name!r} must be a method!")
+ if self.coexist:
+ fail(f"@coexist cannot be applied to {name!r}")
# Normalise self.kind.
if name == '__new__':
self.kind = METHOD_NEW
elif name == '__init__':
self.kind = METHOD_INIT
+ elif slot is not None:
+ self.kind = SLOT
+ self.slot = slot
# Validate @vectorcall usage.
if self.vectorcall:
@@ -653,6 +671,9 @@ def resolve_return_converter(
fail("@setter methods cannot define a return type")
if self.kind is METHOD_INIT:
fail("__init__ methods cannot define a return type")
+ if self.kind is SLOT:
+ fail(f"{full_name!r} cannot define a return type; "
+ "its type slot defines it")
ast_input = f"def x() -> {forced_converter}: pass"
try:
module_node = ast.parse(ast_input)
@@ -672,6 +693,9 @@ def resolve_return_converter(
if self.kind in {METHOD_INIT} | SETTERS:
return int_return_converter()
+ if self.kind is SLOT:
+ assert self.slot is not None
+ return return_converters[self.slot.return_converter]()
return CReturnConverter()
def parse_cloned_function(self, names: FunctionNames, existing: str) ->
None:
@@ -703,6 +727,8 @@ def parse_cloned_function(self, names: FunctionNames,
existing: str) -> None:
"docstring_line_number": None,
"line_number": self.line_number,
}
+ if existing_function.kind is SLOT or self.slot is not None:
+ fail("Type slot methods cannot be cloned")
if not (existing_function.kind is self.kind and
existing_function.coexist == self.coexist):
# Allow __new__ or __init__ methods.
@@ -777,6 +803,7 @@ def state_modulename_name(self, line: str) -> None:
forced_text_signature=self.forced_text_signature,
line_number=self.line_number,
vectorcall=self.vectorcall,
+ slot=self.slot,
)
self.add_function(func)
@@ -893,6 +920,10 @@ def state_parameters_start(self, line: str) -> None:
assert self.function is not None
if self.function.kind is GETTER:
fail("@getter methods cannot define parameters")
+ if self.function.kind is SLOT:
+ assert self.function.slot is not None
+ if not self.function.slot.takes_parameters:
+ fail(f"{self.function.name!r} methods cannot define
parameters")
self.parameter_continuation = ''
return self.next(self.state_parameter, line)
@@ -1498,6 +1529,12 @@ def format_docstring(self) -> str:
assert self.function is not None
f = self.function
# For the following special cases, it does not make sense to render a
docstring.
+ if f.kind is SLOT:
+ # A slot function has no docstring (there is no PyMethodDef).
+ if f.docstring:
+ fail(f"{f.name!r} methods cannot have a docstring",
+ line_number=f.docstring_line_number)
+ return f.docstring
if f.kind in {METHOD_INIT, METHOD_NEW} | ACCESSORS and not f.docstring:
return f.docstring
@@ -1639,6 +1676,21 @@ def check_vectorcall_parameters(self, lineno: int) ->
None:
f"parse_arg(); parameter {p.name!r} does not",
line_number=lineno)
+ def check_slot_parameters(self, func: Function) -> None:
+ """Check that the parameters of a slot method select a C slot."""
+ assert func.slot is not None
+ params = list(func.parameters.values())[1:]
+ for p in params:
+ if not p.is_positional_only():
+ fail(f"The parameters of {func.name!r} must be
positional-only")
+ if p.default is not unspecified:
+ fail(f"The parameters of {func.name!r} cannot have a default
value")
+ converters = [p.converter for p in params]
+ if func.slot.c_slot(converters) is None:
+ got = ', '.join(c.dsl_name() for c in converters)
+ fail(f"{func.name!r} must declare "
+ f"{func.slot.describe_signatures()}; got ({got})")
+
def do_post_block_processing_cleanup(self, lineno: int) -> None:
"""
Called when processing the block is done.
@@ -1647,6 +1699,8 @@ def do_post_block_processing_cleanup(self, lineno: int)
-> None:
return
func = self.function
+ if func.kind is SLOT:
+ self.check_slot_parameters(func)
if func.kind in (SETTER, SETTER_AND_DELETER):
# The new value is the only parameter of a setter. The setter
# of a deletable attribute is also called with NULL to delete it,
diff --git a/Tools/clinic/libclinic/function.py
b/Tools/clinic/libclinic/function.py
index e5d7469bb732aa9..5270f4520180d41 100644
--- a/Tools/clinic/libclinic/function.py
+++ b/Tools/clinic/libclinic/function.py
@@ -11,6 +11,7 @@
from libclinic.converters import self_converter
from libclinic.return_converters import CReturnConverter
from libclinic.app import Clinic
+ from libclinic.slots import SlotSpec
from libclinic import VersionTuple, unspecified
@@ -94,6 +95,8 @@ class FunctionKind(enum.Enum):
GETTER = enum.auto()
SETTER = enum.auto()
SETTER_AND_DELETER = enum.auto()
+ # A type slot ("tp slot"), see libclinic.slots.
+ SLOT = enum.auto()
@functools.cached_property
def new_or_init(self) -> bool:
@@ -111,6 +114,7 @@ def __repr__(self) -> str:
GETTER: Final = FunctionKind.GETTER
SETTER: Final = FunctionKind.SETTER
SETTER_AND_DELETER: Final = FunctionKind.SETTER_AND_DELETER
+SLOT: Final = FunctionKind.SLOT
# The kinds which implement the setter of an entry of PyGetSetDef.
SETTERS: Final = frozenset({SETTER, SETTER_AND_DELETER})
@@ -156,6 +160,8 @@ class Function:
# Line on which the docstring starts (`None` if there is no docstring).
docstring_line_number: int | None = None
vectorcall: bool = False
+ # The type slot which the function implements (kind is SLOT).
+ slot: SlotSpec | None = None
def __post_init__(self) -> None:
self.parent = self.cls or self.module
@@ -224,7 +230,8 @@ def render_parameters(self) -> list[Parameter]:
@property
def methoddef_flags(self) -> str | None:
- if self.kind.new_or_init:
+ if self.kind.new_or_init or self.kind is SLOT:
+ # Slot functions are not stored in a PyMethodDef.
return None
flags = []
match self.kind:
diff --git a/Tools/clinic/libclinic/parse_args.py
b/Tools/clinic/libclinic/parse_args.py
index a3ab39376320de7..73414e897e26151 100644
--- a/Tools/clinic/libclinic/parse_args.py
+++ b/Tools/clinic/libclinic/parse_args.py
@@ -6,7 +6,7 @@
from libclinic.function import (
Function, Parameter, ParamTuple,
count_required, group_to_variable_name, permute_optional_groups,
- GETTER, SETTER, SETTER_AND_DELETER, METHOD_INIT,
+ GETTER, SETTER, SETTER_AND_DELETER, METHOD_INIT, SLOT,
ACCESSORS, SETTERS)
from libclinic.converter import CConverter
from libclinic.converters import (
@@ -142,6 +142,14 @@ def declare_parser(
static int
{c_basename}({self_type}{self_name}, PyObject *arg, void
*Py_UNUSED(context))
""")
+PARSER_PROTOTYPE_SLOT: Final[str] = libclinic.normalize_snippet("""
+ static {return_type}
+ {c_basename}({self_type}{self_name})
+""")
+PARSER_PROTOTYPE_SLOT_ARGS: Final[str] = libclinic.normalize_snippet("""
+ static {return_type}
+ {c_basename}({self_type}{self_name}, {parser_parameters})
+""")
METH_O_PROTOTYPE: Final[str] = libclinic.normalize_snippet("""
static PyObject *
{c_basename}({self_type}{self_name}, {parser_parameters})
@@ -393,8 +401,11 @@ def use_meth_o(self) -> bool:
and not self.is_new_or_init())
def use_simple_return(self) -> bool:
+ if self.func.critical_section:
+ return False
+ # A slot function returns the result of the impl unchanged.
return (self.func.return_converter.type == 'PyObject *'
- and not self.func.critical_section)
+ or self.func.kind is SLOT)
def use_pyobject_self(self) -> bool:
return self.self_parameter_converter.use_pyobject_self(self.func)
@@ -407,6 +418,19 @@ def select_prototypes(self) -> None:
if self.is_new_or_init() and not self.func.docstring:
pass
+ elif self.func.kind is SLOT:
+ # A slot function is not stored in a PyMethodDef and has no
+ # docstring (the DSL parser rejects one). It returns what its
+ # slot declares.
+ assert not self.func.docstring
+ assert self.func.slot is not None
+ self.methoddef_define = ''
+ return_type = self.func.slot.return_type
+ if not return_type.endswith('*'):
+ return_type += ' '
+ self.return_value_declaration = (
+ f"{return_type}{{parser_retval}} = "
+ f"{self.func.slot.error_value};")
elif self.func.kind is GETTER:
# Only an accessor compiled conditionally announces itself.
self.methoddef_define = (GETTERDEF_PROTOTYPE_DEFINE
@@ -482,6 +506,18 @@ def render_setter_value(self) -> str:
libclinic.normalize_snippet("}}", indent=4),
])
+ def parse_slot(self) -> None:
+ """Generate the slot function.
+
+ It does not parse anything: it forwards its arguments to the impl
+ and, with @critical_section, holds the lock while doing so.
+ """
+ if self.parameters:
+ self.parser_prototype = PARSER_PROTOTYPE_SLOT_ARGS
+ else:
+ self.parser_prototype = PARSER_PROTOTYPE_SLOT
+ self.finish_parser_body([])
+
def parse_accessor(self) -> None:
"""Generate the code of a getter or a setter."""
parser_code: list[str] = []
@@ -1744,7 +1780,9 @@ def parse_args(self, clang: CLanguage) -> dict[str, str]:
# previous call to parser_body. this is used for an awful hack.
self.parser_body_fields: tuple[str, ...] = ()
- if self.func.kind in ACCESSORS:
+ if self.func.kind is SLOT:
+ self.parse_slot()
+ elif self.func.kind in ACCESSORS:
self.parse_accessor()
elif not self.parameters and not self.varpos and not self.var_keyword:
self.parse_no_args()
diff --git a/Tools/clinic/libclinic/slots.py b/Tools/clinic/libclinic/slots.py
new file mode 100644
index 000000000000000..ee38b06c2ce4a8a
--- /dev/null
+++ b/Tools/clinic/libclinic/slots.py
@@ -0,0 +1,59 @@
+"""Type slots ("tp slots") for which Argument Clinic generates the slot
function.
+
+A slot function does not parse arguments: Argument Clinic generates a thin
+wrapper with the C signature of the slot, which forwards its arguments to
+the implementation function. The wrapper is the place where
+``@critical_section`` acquires the lock.
+
+The parameters declared in the DSL only select the C signature; they must be
+positional-only, without a default value. When a special method maps to
+several slots (``__getitem__`` is ``sq_item`` or ``mp_subscript``),
+the converters of its parameters determine which one it implements.
+"""
+from __future__ import annotations
+import dataclasses as dc
+from typing import Final
+
+from libclinic.converter import CConverter
+
+
[email protected](frozen=True, slots=True)
+class SlotSpec:
+ # The special method name, e.g. '__next__'.
+ name: str
+ # The C return type of the slot function.
+ return_type: str
+ # The name of the return converter which declares `return_type`.
+ return_converter: str
+ # Maps the converter classes of the parameters (excluding self) to
+ # the C slot.
+ signatures: dict[tuple[type[CConverter], ...], str]
+
+ @property
+ def error_value(self) -> str:
+ """The value returned by the slot function on error."""
+ return 'NULL' if self.return_type.endswith('*') else '-1'
+
+ @property
+ def takes_parameters(self) -> bool:
+ return any(self.signatures)
+
+ def c_slot(self, converters: list[CConverter]) -> str | None:
+ """Return the C slot for the parameter converters, or None."""
+ classes = tuple(type(c) for c in converters)
+ return self.signatures.get(classes)
+
+ def describe_signatures(self) -> str:
+ return " or ".join(
+ f"({', '.join(c.dsl_name() for c in sig) or 'no parameters'})"
+ f" for {slot}"
+ for sig, slot in self.signatures.items())
+
+
+# Maps the name of a special method to its slot. A name listed here must
+# not be in dsl_parser.unsupported_special_methods.
+SLOTS: Final[dict[str, SlotSpec]] = {
+ '__next__': SlotSpec('__next__', 'PyObject *', 'object', {
+ (): 'tp_iternext',
+ }),
+}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]