gh-114258: Update AC to support @critical_section for tp_iternext (gh-159962)

This commit is contained in:
Donghee Na authored and GitHub committed 2026-10-21 00:25:02 +00:00
1 parent e07817d029
commit f9c0e1ea27
23 files changed
+715 -205

No files matched your search

+182 -1
View File
@@ -15,6 +15,7 @@ import os.path
import re
import sys
import unittest
from unittest import mock
import warnings
test_tools.skip_if_missing('clinic')
@@ -22,7 +23,8 @@ with test_tools.imports_under_tool('clinic'):
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 @@ with test_tools.imports_under_tool('clinic'):
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 @@ class ClinicWholeFileTest(TestCase):
"""
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 ClinicParserTest(TestCase):
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
@@ -0,0 +1,3 @@
Argument Clinic now supports the ``__next__`` type slot (``tp_iternext``),
including ``@critical_section``.
+12 -17
View File
@@ -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;
}
static PyObject *
Reader_iternext_lock_held(PyObject *op)
{
ReaderObj *self = _ReaderObj_CAST(op);
/*[clinic input]
@critical_section
_csv.Reader.__next__ as Reader_iternext
[clinic start generated code]*/
static PyObject *
Reader_iternext_impl(ReaderObj *self)
/*[clinic end generated code: output=3a02b0254a76e1fc input=97c70a4e2fe621e4]*/
{
PyObject *fields = NULL;
Py_UCS4 c;
Py_ssize_t pos, linelen;
@@ -1094,16 +1099,6 @@ err:
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)
{
+8 -13
View File
@@ -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
+16 -1
View File
@@ -511,6 +511,21 @@ exit:
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]*/
+16 -1
View File
@@ -135,6 +135,21 @@ exit:
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]*/
+16 -1
View File
@@ -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]*/
+9 -14
View File
@@ -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
+9 -14
View File
@@ -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
+17 -1
View File
@@ -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 @@ exit:
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 @@ skip_optional_pos:
exit:
return return_value;
}
/*[clinic end generated code: output=1a90b8a8ed82497d input=a9049054013a1b77]*/
/*[clinic end generated code: output=7e3a92d524d88023 input=a9049054013a1b77]*/
+107 -1
View File
@@ -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 @@ exit:
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 @@ exit:
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 @@ exit:
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 @@ exit:
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 @@ skip_optional_pos:
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]*/
+85 -101
View File
@@ -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 @@ empty:
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 @@ empty:
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 @@ empty:
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 @@ empty:
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\
+16 -1
View File
@@ -452,4 +452,19 @@ skip_optional_pos:
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]*/
+16 -1
View File
@@ -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]*/
+10 -13
View File
@@ -478,8 +478,9 @@ later:
/*[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 @@ error:
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. */
+8 -12
View File
@@ -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 */
+14 -5
View File
@@ -12,7 +12,7 @@ from libclinic.language import Language
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 @@ class CLanguage(Language):
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 @@ class CLanguage(Language):
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)
+5
View File
@@ -275,6 +275,11 @@ class CConverter(metaclass=CConverterAutoRegister):
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
+2 -2
View File
@@ -8,7 +8,7 @@ import libclinic
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"
+56 -2
View File
@@ -18,7 +18,7 @@ from libclinic.function import (
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.converters import (
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 @@ __matmul__
__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 @@ class DSLParser:
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 @@ class DSLParser:
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 @@ class DSLParser:
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 @@ class DSLParser:
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 @@ class DSLParser:
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 @@ class DSLParser:
"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 @@ class DSLParser:
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 @@ class DSLParser:
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 @@ class DSLParser:
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 @@ class DSLParser:
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 @@ class DSLParser:
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,
+8 -1
View File
@@ -11,6 +11,7 @@ if TYPE_CHECKING:
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 @@ METHOD_NEW: Final = FunctionKind.METHOD_NEW
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 @@ class Function:
@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:
+41 -3
View File
@@ -6,7 +6,7 @@ from libclinic import fail, warn, unspecified, Sentinels
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 @@ PARSER_PROTOTYPE_SETTER: Final[str] = libclinic.normalize_snippet("""
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 @@ class ParseArgsCodeGen:
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 @@ class ParseArgsCodeGen:
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 @@ class ParseArgsCodeGen:
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 @@ class ParseArgsCodeGen:
# 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()
+59
View File
@@ -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
@dc.dataclass(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',
}),
}