Skip to content

Commit 2c142a1

Browse files
authored
gh-114258: Use Argument Clinic for __len__ (GH-160012)
1 parent 641ef0a commit 2c142a1

9 files changed

Lines changed: 110 additions & 115 deletions

File tree

‎Lib/test/test_clinic.py‎

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@
2323
import libclinic
2424
from libclinic import ClinicError, unspecified, NULL, fail
2525
from libclinic.converters import (
26-
int_converter, object_converter, str_converter, self_converter,
27-
Py_ssize_t_converter)
26+
int_converter, object_converter, str_converter, self_converter)
2827
from libclinic.function import (
2928
Module, Class, Function, FunctionKind, Parameter,
3029
permute_optional_groups, permute_right_option_groups,
@@ -35,7 +34,7 @@
3534
from libclinic.return_converters import return_converters, int_return_converter
3635
from libclinic.block_parser import Block, BlockParser
3736
from libclinic.codegen import BlockPrinter, Destination
38-
from libclinic.dsl_parser import DSLParser
37+
from libclinic.dsl_parser import DSLParser, unsupported_special_methods
3938
from libclinic.slots import SlotSpec, SLOTS
4039
from libclinic.cli import parse_file, Clinic
4140

@@ -858,11 +857,10 @@ class Foo "FooObject *" "&Foo_Type"
858857

859858
def test_slot_with_parameters_and_return_type(self):
860859
# Slots which take arguments or return something else than an
861-
# object are not supported yet; check the machinery with fake ones.
860+
# object are not supported yet; check the machinery with them.
862861
slots = {
863-
'__contains__': SlotSpec('__contains__', 'int', 'int',
864-
{(object_converter,): 'sq_contains'}),
865-
'__bool__': SlotSpec('__bool__', 'int', 'int', {(): 'nb_bool'}),
862+
'__contains__': SlotSpec(return_converter='int'),
863+
'__bool__': SlotSpec(return_converter='int'),
866864
}
867865
block = """
868866
/*[clinic input]
@@ -3353,42 +3351,38 @@ class Foo "FooObject *" ""
33533351
Foo.__next__
33543352
""", signatures_in_block=3, function_index=2)
33553353
self.assertIs(function.kind, FunctionKind.SLOT)
3356-
self.assertEqual(function.slot.name, "__next__")
3354+
self.assertIs(function.slot, SLOTS["__next__"])
33573355
self.assertEqual(function.c_basename, "Foo_next")
33583356
self.assertEqual(function.return_converter.type, "PyObject *")
33593357
self.assertEqual(list(function.parameters), ["self"])
33603358

33613359
def test_slot_parameters(self):
3362-
slot = SlotSpec('__contains__', 'int', 'int', {
3363-
(Py_ssize_t_converter,): 'sq_fake',
3364-
(object_converter,): 'sq_contains'})
3360+
# __getitem__ is not supported yet; its parameter gives the slot
3361+
# (Py_ssize_t for sq_item, object for mp_subscript) and is not parsed.
33653362
block = """
33663363
module foo
33673364
class Foo "FooObject *" ""
3368-
Foo.__contains__
3369-
value: {converter}
3365+
Foo.__getitem__
3366+
key: {converter}
33703367
/
33713368
"""
33723369
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"),
3370+
("Foo.__getitem__\n key: object",
3371+
"The parameters of '__getitem__' must be positional-only"),
3372+
("Foo.__getitem__\n key: object = None\n /",
3373+
"The parameters of '__getitem__' cannot have a default value"),
33823374
]
3383-
with mock.patch.dict(SLOTS, {'__contains__': slot}):
3375+
supported = unsupported_special_methods - {'__getitem__'}
3376+
with (mock.patch.dict(SLOTS, {'__getitem__': SlotSpec(return_converter='object')}),
3377+
mock.patch.object(libclinic.dsl_parser,
3378+
'unsupported_special_methods', supported)):
33843379
for converter in "Py_ssize_t", "object":
33853380
with self.subTest(converter=converter):
33863381
function = self.parse_function(
33873382
block.format(converter=converter),
33883383
signatures_in_block=3, function_index=2)
33893384
self.assertIs(function.kind, FunctionKind.SLOT)
3390-
self.assertEqual(function.return_converter.type, "int")
3391-
self.assertEqual(list(function.parameters), ["self", "value"])
3385+
self.assertEqual(list(function.parameters), ["self", "key"])
33923386
for body, err in errors:
33933387
with self.subTest(block=body):
33943388
body = 'class Foo "FooObject *" "&Foo_Type"\n' + body
@@ -3406,8 +3400,6 @@ def test_slot_errors(self):
34063400
"@coexist cannot be applied to '__next__'"),
34073401
("@vectorcall\nFoo.__next__",
34083402
"@vectorcall can only be used with __init__ and __new__"),
3409-
("Foo.__next__\n a: int",
3410-
"'__next__' methods cannot define parameters"),
34113403
("Foo.__next__ -> int",
34123404
"'Foo.__next__' cannot define a return type"),
34133405
("Foo.__next__\n\nReturn the next item.",
@@ -3660,10 +3652,10 @@ def test_illegal_c_identifier(self):
36603652
self.expect_failure(block, err, lineno=2)
36613653

36623654
def test_cannot_convert_special_method(self):
3663-
err = "'__len__' is a special method and cannot be converted"
3655+
err = "'__iter__' is a special method and cannot be converted"
36643656
block = """
36653657
class T "" ""
3666-
T.__len__
3658+
T.__iter__
36673659
"""
36683660
self.expect_failure(block, err, lineno=1)
36693661

‎Modules/_dbmmodule.c‎

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,16 @@ dbm_dealloc(PyObject *self)
116116
Py_DECREF(tp);
117117
}
118118

119+
/*[clinic input]
120+
@critical_section
121+
_dbm.dbm.__len__ as dbm_length
122+
[clinic start generated code]*/
123+
119124
static Py_ssize_t
120-
dbm_length_lock_held(PyObject *self)
125+
dbm_length_impl(dbmobject *self)
126+
/*[clinic end generated code: output=d7ea9510562e5f86 input=f912d65e9b06fef5]*/
121127
{
122-
dbmobject *dp = dbmobject_CAST(self);
128+
dbmobject *dp = self;
123129
_dbm_state *state = PyType_GetModuleState(Py_TYPE(dp));
124130
assert(state != NULL);
125131
if (dp->di_dbm == NULL) {
@@ -139,16 +145,6 @@ dbm_length_lock_held(PyObject *self)
139145
return dp->di_size;
140146
}
141147

142-
static Py_ssize_t
143-
dbm_length(PyObject *self)
144-
{
145-
Py_ssize_t result;
146-
Py_BEGIN_CRITICAL_SECTION(self);
147-
result = dbm_length_lock_held(self);
148-
Py_END_CRITICAL_SECTION();
149-
return result;
150-
}
151-
152148
static int
153149
dbm_bool_lock_held(PyObject *self)
154150
{

‎Modules/_gdbmmodule.c‎

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,16 @@ gdbm_dealloc(PyObject *op)
136136
Py_DECREF(tp);
137137
}
138138

139+
/*[clinic input]
140+
@critical_section
141+
_gdbm.gdbm.__len__ as gdbm_length
142+
[clinic start generated code]*/
143+
139144
static Py_ssize_t
140-
gdbm_length_lock_held(PyObject *op)
145+
gdbm_length_impl(gdbmobject *self)
146+
/*[clinic end generated code: output=3ccb956974ed73b5 input=57878f60d264ed98]*/
141147
{
142-
gdbmobject *dp = _gdbmobject_CAST(op);
148+
gdbmobject *dp = self;
143149
_gdbm_state *state = PyType_GetModuleState(Py_TYPE(dp));
144150
if (dp->di_dbm == NULL) {
145151
PyErr_SetString(state->gdbm_error, "GDBM object has already been closed");
@@ -183,16 +189,6 @@ gdbm_length_lock_held(PyObject *op)
183189
return dp->di_size;
184190
}
185191

186-
static Py_ssize_t
187-
gdbm_length(PyObject *op)
188-
{
189-
Py_ssize_t result;
190-
Py_BEGIN_CRITICAL_SECTION(op);
191-
result = gdbm_length_lock_held(op);
192-
Py_END_CRITICAL_SECTION();
193-
return result;
194-
}
195-
196192
static int
197193
gdbm_bool_lock_held(PyObject *op)
198194
{

‎Modules/clinic/_dbmmodule.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/clinic/_gdbmmodule.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/clinic/mmapmodule.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/mmapmodule.c‎

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1487,24 +1487,19 @@ mmap_buffer_releasebuf(PyObject *op, Py_buffer *Py_UNUSED(view))
14871487
Py_END_CRITICAL_SECTION();
14881488
}
14891489

1490+
/*[clinic input]
1491+
@critical_section
1492+
mmap.mmap.__len__ as mmap_length
1493+
[clinic start generated code]*/
1494+
14901495
static Py_ssize_t
1491-
mmap_length_lock_held(PyObject *op)
1496+
mmap_length_impl(mmap_object *self)
1497+
/*[clinic end generated code: output=a63e62615cdd9960 input=b7bbf695dc9b2b66]*/
14921498
{
1493-
mmap_object *self = mmap_object_CAST(op);
14941499
CHECK_VALID(-1);
14951500
return self->size;
14961501
}
14971502

1498-
static Py_ssize_t
1499-
mmap_length(PyObject *op)
1500-
{
1501-
Py_ssize_t result;
1502-
Py_BEGIN_CRITICAL_SECTION(op);
1503-
result = mmap_length_lock_held(op);
1504-
Py_END_CRITICAL_SECTION();
1505-
return result;
1506-
}
1507-
15081503
static PyObject *
15091504
mmap_item_lock_held(PyObject *op, Py_ssize_t i)
15101505
{

‎Tools/clinic/libclinic/dsl_parser.py‎

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@
7272
__itruediv__
7373
__ixor__
7474
__le__
75-
__len__
7675
__lshift__
7776
__lt__
7877
__matmul__
@@ -695,7 +694,7 @@ def resolve_return_converter(
695694
return int_return_converter()
696695
if self.kind is SLOT:
697696
assert self.slot is not None
698-
return return_converters[self.slot.return_converter]()
697+
return self.slot.make_return_converter()
699698
return CReturnConverter()
700699

701700
def parse_cloned_function(self, names: FunctionNames, existing: str) -> None:
@@ -920,11 +919,6 @@ def state_parameters_start(self, line: str) -> None:
920919
assert self.function is not None
921920
if self.function.kind is GETTER:
922921
fail("@getter methods cannot define parameters")
923-
if self.function.kind is SLOT:
924-
assert self.function.slot is not None
925-
if not self.function.slot.takes_parameters:
926-
fail(f"{self.function.name!r} methods cannot define parameters")
927-
928922
self.parameter_continuation = ''
929923
return self.next(self.state_parameter, line)
930924

@@ -1677,19 +1671,12 @@ def check_vectorcall_parameters(self, lineno: int) -> None:
16771671
line_number=lineno)
16781672

16791673
def check_slot_parameters(self, func: Function) -> None:
1680-
"""Check that the parameters of a slot method select a C slot."""
1681-
assert func.slot is not None
1682-
params = list(func.parameters.values())[1:]
1683-
for p in params:
1674+
"""Check that the parameters of a slot method fit a C signature."""
1675+
for p in list(func.parameters.values())[1:]:
16841676
if not p.is_positional_only():
16851677
fail(f"The parameters of {func.name!r} must be positional-only")
16861678
if p.default is not unspecified:
16871679
fail(f"The parameters of {func.name!r} cannot have a default value")
1688-
converters = [p.converter for p in params]
1689-
if func.slot.c_slot(converters) is None:
1690-
got = ', '.join(c.dsl_name() for c in converters)
1691-
fail(f"{func.name!r} must declare "
1692-
f"{func.slot.describe_signatures()}; got ({got})")
16931680

16941681
def do_post_block_processing_cleanup(self, lineno: int) -> None:
16951682
"""

0 commit comments

Comments
 (0)