Skip to content

Commit f9c0e1e

Browse files
authored
gh-114258: Update AC to support @critical_section for tp_iternext (gh-159962)
1 parent e07817d commit f9c0e1e

23 files changed

Lines changed: 714 additions & 204 deletions

‎Lib/test/test_clinic.py‎

Lines changed: 182 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@
1515
import re
1616
import sys
1717
import unittest
18+
from unittest import mock
1819
import warnings
1920

2021
test_tools.skip_if_missing('clinic')
2122
with test_tools.imports_under_tool('clinic'):
2223
import libclinic
2324
from libclinic import ClinicError, unspecified, NULL, fail
2425
from libclinic.converters import (
25-
int_converter, object_converter, str_converter, self_converter)
26+
int_converter, object_converter, str_converter, self_converter,
27+
Py_ssize_t_converter)
2628
from libclinic.function import (
2729
Module, Class, Function, FunctionKind, Parameter,
2830
permute_optional_groups, permute_right_option_groups,
@@ -34,6 +36,7 @@
3436
from libclinic.block_parser import Block, BlockParser
3537
from libclinic.codegen import BlockPrinter, Destination
3638
from libclinic.dsl_parser import DSLParser
39+
from libclinic.slots import SlotSpec, SLOTS
3740
from libclinic.cli import parse_file, Clinic
3841

3942

@@ -807,6 +810,112 @@ def test_var_keyword_non_dict(self):
807810
"""
808811
self.expect_failure(block, err, lineno=4)
809812

813+
def test_slot_next(self):
814+
block = """
815+
/*[clinic input]
816+
output everything block
817+
class Foo "FooObject *" "&Foo_Type"
818+
[clinic start generated code]*/
819+
/*[clinic input]
820+
Foo.__next__
821+
[clinic start generated code]*/
822+
/*[clinic input]
823+
@critical_section
824+
Foo.__next__ as foo_iternext
825+
[clinic start generated code]*/
826+
#ifdef CONDITION
827+
/*[clinic input]
828+
Foo.__next__ as foo_iternext_cond
829+
[clinic start generated code]*/
830+
#endif
831+
"""
832+
generated = self.clinic.parse(dedent(block))
833+
self.assertIn(dedent("""
834+
static PyObject *
835+
Foo_next(PyObject *self)
836+
{
837+
return Foo_next_impl((FooObject *)self);
838+
}
839+
"""), generated)
840+
self.assertIn(dedent("""
841+
static PyObject *
842+
foo_iternext(PyObject *self)
843+
{
844+
PyObject *return_value = NULL;
845+
846+
Py_BEGIN_CRITICAL_SECTION(self);
847+
return_value = foo_iternext_impl((FooObject *)self);
848+
Py_END_CRITICAL_SECTION();
849+
850+
return return_value;
851+
}
852+
"""), generated)
853+
self.assertIn("#if defined(CONDITION)\n\nstatic PyObject *\n"
854+
"foo_iternext_cond_impl(FooObject *self);", generated)
855+
# A slot function is not stored in a PyMethodDef.
856+
for text in "METHODDEF", "PyDoc_", "#ifndef":
857+
self.assertNotIn(text, generated)
858+
859+
def test_slot_with_parameters_and_return_type(self):
860+
# Slots which take arguments or return something else than an
861+
# object are not supported yet; check the machinery with fake ones.
862+
slots = {
863+
'__contains__': SlotSpec('__contains__', 'int', 'int',
864+
{(object_converter,): 'sq_contains'}),
865+
'__bool__': SlotSpec('__bool__', 'int', 'int', {(): 'nb_bool'}),
866+
}
867+
block = """
868+
/*[clinic input]
869+
output everything block
870+
class Foo "FooObject *" "&Foo_Type"
871+
[clinic start generated code]*/
872+
/*[clinic input]
873+
Foo.__contains__
874+
value: object
875+
/
876+
[clinic start generated code]*/
877+
/*[clinic input]
878+
@critical_section
879+
Foo.__bool__
880+
[clinic start generated code]*/
881+
"""
882+
with mock.patch.dict(SLOTS, slots):
883+
generated = self.clinic.parse(dedent(block))
884+
self.assertIn(dedent("""
885+
static int
886+
Foo_contains(PyObject *self, PyObject *value)
887+
{
888+
return Foo_contains_impl((FooObject *)self, value);
889+
}
890+
"""), generated)
891+
self.assertIn(dedent("""
892+
static int
893+
Foo_bool(PyObject *self)
894+
{
895+
int return_value = -1;
896+
897+
Py_BEGIN_CRITICAL_SECTION(self);
898+
return_value = Foo_bool_impl((FooObject *)self);
899+
Py_END_CRITICAL_SECTION();
900+
901+
return return_value;
902+
}
903+
"""), generated)
904+
905+
def test_slot_cannot_be_cloned(self):
906+
block = """
907+
/*[clinic input]
908+
class Foo "FooObject *" "&Foo_Type"
909+
[clinic start generated code]*/
910+
/*[clinic input]
911+
Foo.__next__
912+
[clinic start generated code]*/
913+
/*[clinic input]
914+
Foo.other = Foo.__next__
915+
[clinic start generated code]*/
916+
"""
917+
self.expect_failure(block, "Type slot methods cannot be cloned", lineno=8)
918+
810919
def test_getset_in_ifdef(self):
811920
block = """
812921
/*[clinic input]
@@ -3237,6 +3346,78 @@ class Foo "" ""
32373346
f"a normal callable")
32383347
self.expect_failure(block, expected_error, lineno=3)
32393348

3349+
def test_slot_next(self):
3350+
function = self.parse_function("""
3351+
module foo
3352+
class Foo "FooObject *" ""
3353+
Foo.__next__
3354+
""", signatures_in_block=3, function_index=2)
3355+
self.assertIs(function.kind, FunctionKind.SLOT)
3356+
self.assertEqual(function.slot.name, "__next__")
3357+
self.assertEqual(function.c_basename, "Foo_next")
3358+
self.assertEqual(function.return_converter.type, "PyObject *")
3359+
self.assertEqual(list(function.parameters), ["self"])
3360+
3361+
def test_slot_parameters(self):
3362+
slot = SlotSpec('__contains__', 'int', 'int', {
3363+
(Py_ssize_t_converter,): 'sq_fake',
3364+
(object_converter,): 'sq_contains'})
3365+
block = """
3366+
module foo
3367+
class Foo "FooObject *" ""
3368+
Foo.__contains__
3369+
value: {converter}
3370+
/
3371+
"""
3372+
errors = [
3373+
("Foo.__contains__",
3374+
"'__contains__' must declare (Py_ssize_t) for sq_fake or "
3375+
"(object) for sq_contains; got ()"),
3376+
("Foo.__contains__\n value: int\n /",
3377+
"'__contains__' must declare"),
3378+
("Foo.__contains__\n value: object",
3379+
"The parameters of '__contains__' must be positional-only"),
3380+
("Foo.__contains__\n value: object = None\n /",
3381+
"The parameters of '__contains__' cannot have a default value"),
3382+
]
3383+
with mock.patch.dict(SLOTS, {'__contains__': slot}):
3384+
for converter in "Py_ssize_t", "object":
3385+
with self.subTest(converter=converter):
3386+
function = self.parse_function(
3387+
block.format(converter=converter),
3388+
signatures_in_block=3, function_index=2)
3389+
self.assertIs(function.kind, FunctionKind.SLOT)
3390+
self.assertEqual(function.return_converter.type, "int")
3391+
self.assertEqual(list(function.parameters), ["self", "value"])
3392+
for body, err in errors:
3393+
with self.subTest(block=body):
3394+
body = 'class Foo "FooObject *" "&Foo_Type"\n' + body
3395+
self.expect_failure(body, err)
3396+
3397+
def test_slot_errors(self):
3398+
cases = [
3399+
("module foo\nfoo.__next__",
3400+
"'__next__' must be a method!"),
3401+
("@classmethod\nFoo.__next__",
3402+
"'__next__' must be a normal method"),
3403+
("@getter\nFoo.__next__",
3404+
"'__next__' must be a normal method"),
3405+
("@coexist\nFoo.__next__",
3406+
"@coexist cannot be applied to '__next__'"),
3407+
("@vectorcall\nFoo.__next__",
3408+
"@vectorcall can only be used with __init__ and __new__"),
3409+
("Foo.__next__\n a: int",
3410+
"'__next__' methods cannot define parameters"),
3411+
("Foo.__next__ -> int",
3412+
"'Foo.__next__' cannot define a return type"),
3413+
("Foo.__next__\n\nReturn the next item.",
3414+
"'__next__' methods cannot have a docstring"),
3415+
]
3416+
for block, err in cases:
3417+
with self.subTest(block=block):
3418+
block = 'class Foo "FooObject *" "&Foo_Type"\n' + block
3419+
self.expect_failure(block, err)
3420+
32403421
def test_deleter_without_setter(self):
32413422
block = """
32423423
module foo
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Argument Clinic now supports the ``__next__`` type slot (``tp_iternext``),
2+
including ``@critical_section``.
3+

‎Modules/_csv.c‎

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ module instead.
2222
/*[clinic input]
2323
module _csv
2424
class _csv.Dialect "DialectObj *" "clinic_state()->dialect_type"
25+
class _csv.Reader "ReaderObj *" "clinic_state()->reader_type"
2526
[clinic start generated code]*/
26-
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=042fcc14c7d541cc]*/
27+
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=7bdf66dc7d68ed90]*/
2728
#define NOT_SET ((Py_UCS4)-1)
2829
#define EOL ((Py_UCS4)-2)
2930

@@ -124,8 +125,6 @@ typedef struct {
124125
bool nonascii_special;
125126
} DialectObj;
126127

127-
#include "clinic/_csv.c.h"
128-
129128
typedef struct {
130129
PyObject_HEAD
131130

@@ -161,6 +160,8 @@ typedef struct {
161160
#define _ReaderObj_CAST(op) ((ReaderObj *)(op))
162161
#define _WriterObj_CAST(op) ((WriterObj *)(op))
163162

163+
#include "clinic/_csv.c.h"
164+
164165
/*
165166
* DIALECT class
166167
*/
@@ -1020,11 +1021,15 @@ parse_reset(ReaderObj *self)
10201021
return 0;
10211022
}
10221023

1024+
/*[clinic input]
1025+
@critical_section
1026+
_csv.Reader.__next__ as Reader_iternext
1027+
[clinic start generated code]*/
1028+
10231029
static PyObject *
1024-
Reader_iternext_lock_held(PyObject *op)
1030+
Reader_iternext_impl(ReaderObj *self)
1031+
/*[clinic end generated code: output=3a02b0254a76e1fc input=97c70a4e2fe621e4]*/
10251032
{
1026-
ReaderObj *self = _ReaderObj_CAST(op);
1027-
10281033
PyObject *fields = NULL;
10291034
Py_UCS4 c;
10301035
Py_ssize_t pos, linelen;
@@ -1094,16 +1099,6 @@ Reader_iternext_lock_held(PyObject *op)
10941099
return fields;
10951100
}
10961101

1097-
static PyObject *
1098-
Reader_iternext(PyObject *op)
1099-
{
1100-
PyObject *result;
1101-
Py_BEGIN_CRITICAL_SECTION(op);
1102-
result = Reader_iternext_lock_held(op);
1103-
Py_END_CRITICAL_SECTION();
1104-
return result;
1105-
}
1106-
11071102
static void
11081103
Reader_dealloc(PyObject *op)
11091104
{

‎Modules/_io/bytesio.c‎

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -768,13 +768,18 @@ _io_BytesIO_truncate_impl(bytesio *self, PyObject *size)
768768
return PyLong_FromSsize_t(new_size);
769769
}
770770

771+
/*[clinic input]
772+
@critical_section
773+
_io.BytesIO.__next__ as bytesio_iternext
774+
[clinic start generated code]*/
775+
771776
static PyObject *
772-
bytesio_iternext_lock_held(PyObject *op)
777+
bytesio_iternext_impl(bytesio *self)
778+
/*[clinic end generated code: output=f530bdee3a720d48 input=7fb3a763efe11a21]*/
773779
{
774-
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
780+
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
775781

776782
Py_ssize_t n;
777-
bytesio *self = bytesio_CAST(op);
778783

779784
CHECK_CLOSED(self);
780785

@@ -786,16 +791,6 @@ bytesio_iternext_lock_held(PyObject *op)
786791
return read_bytes_lock_held(self, n);
787792
}
788793

789-
static PyObject *
790-
bytesio_iternext(PyObject *op)
791-
{
792-
PyObject *ret;
793-
Py_BEGIN_CRITICAL_SECTION(op);
794-
ret = bytesio_iternext_lock_held(op);
795-
Py_END_CRITICAL_SECTION();
796-
return ret;
797-
}
798-
799794
/*[clinic input]
800795
@critical_section
801796
_io.BytesIO.seek

‎Modules/_io/clinic/bytesio.c.h‎

Lines changed: 16 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Modules/_io/clinic/stringio.c.h‎

Lines changed: 16 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)