Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 182 additions & 1 deletion Lib/test/test_clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@
import re
import sys
import unittest
from unittest import mock
import warnings

test_tools.skip_if_missing('clinic')
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,
Expand All @@ -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


Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Argument Clinic now supports the ``__next__`` type slot (``tp_iternext``),
including ``@critical_section``.

27 changes: 11 additions & 16 deletions Modules/_csv.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -124,8 +125,6 @@ typedef struct {
bool nonascii_special;
} DialectObj;

#include "clinic/_csv.c.h"

typedef struct {
PyObject_HEAD

Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down
21 changes: 8 additions & 13 deletions Modules/_io/bytesio.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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
Expand Down
17 changes: 16 additions & 1 deletion Modules/_io/clinic/bytesio.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion Modules/_io/clinic/stringio.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading