https://github.com/python/cpython/commit/6e1bd24e4d88a5b691779ae74e09fa044140891f
commit: 6e1bd24e4d88a5b691779ae74e09fa044140891f
branch: main
author: Donghee Na <[email protected]>
committer: corona10 <[email protected]>
date: 2026-10-21T23:22:03Z
summary:
gh-114258: Update Argument Clinic for __contain__ slots (gh-160093)
files:
M Lib/test/test_clinic.py
M Modules/_collectionsmodule.c
M Modules/_dbmmodule.c
M Modules/_gdbmmodule.c
M Modules/clinic/_collectionsmodule.c.h
M Modules/clinic/_dbmmodule.c.h
M Modules/clinic/_gdbmmodule.c.h
M Tools/clinic/libclinic/dsl_parser.py
M Tools/clinic/libclinic/slots.py
diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py
index e3dd4a2be9e410..8293003f8ce925 100644
--- a/Lib/test/test_clinic.py
+++ b/Lib/test/test_clinic.py
@@ -858,10 +858,7 @@ class Foo "FooObject *" "&Foo_Type"
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 them.
- slots = {
- '__contains__': SlotSpec(return_converter='int'),
- '__bool__': SlotSpec(return_converter='int'),
- }
+ slots = {'__bool__': SlotSpec(return_converter='int')}
block = """
/*[clinic input]
output everything block
@@ -3356,6 +3353,20 @@ class Foo "FooObject *" ""
self.assertEqual(function.return_converter.type, "PyObject *")
self.assertEqual(list(function.parameters), ["self"])
+ def test_slot_coexist_is_a_method(self):
+ # dict.__contains__ is a METH_COEXIST method, not the slot.
+ function = self.parse_function("""
+ module foo
+ class Foo "FooObject *" ""
+ @coexist
+ Foo.__contains__
+ key: object
+ /
+ """, signatures_in_block=3, function_index=2)
+ self.assertIs(function.kind, FunctionKind.CALLABLE)
+ self.assertIsNone(function.slot)
+ self.assertIn("METH_COEXIST", function.methoddef_flags)
+
def test_slot_parameters(self):
# __getitem__ is not supported yet; its parameter gives the slot
# (Py_ssize_t for sq_item, object for mp_subscript) and is not parsed.
@@ -3396,8 +3407,6 @@ def test_slot_errors(self):
"'__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__ -> int",
diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c
index f62972ac8e4901..1d8181e9299f14 100644
--- a/Modules/_collectionsmodule.c
+++ b/Modules/_collectionsmodule.c
@@ -1190,8 +1190,17 @@ deque_count_impl(dequeobject *deque, PyObject *v)
return PyLong_FromSsize_t(count);
}
+/*[clinic input]
+@critical_section
+_collections.deque.__contains__ as deque_contains
+ deque: dequeobject
+ v: object
+ /
+[clinic start generated code]*/
+
static int
-deque_contains_lock_held(dequeobject *deque, PyObject *v)
+deque_contains_impl(dequeobject *deque, PyObject *v)
+/*[clinic end generated code: output=5e7718757d3063bf input=8dd115a2cdaf0f23]*/
{
block *b = deque->leftblock;
Py_ssize_t index = deque->leftindex;
@@ -1222,17 +1231,6 @@ deque_contains_lock_held(dequeobject *deque, PyObject *v)
return 0;
}
-static int
-deque_contains(PyObject *self, PyObject *v)
-{
- dequeobject *deque = dequeobject_CAST(self);
- int result;
- Py_BEGIN_CRITICAL_SECTION(deque);
- result = deque_contains_lock_held(deque, v);
- Py_END_CRITICAL_SECTION();
- return result;
-}
-
static Py_ssize_t
deque_len(PyObject *self)
{
diff --git a/Modules/_dbmmodule.c b/Modules/_dbmmodule.c
index cfc8340d26b79c..7201973bf7f6ba 100644
--- a/Modules/_dbmmodule.c
+++ b/Modules/_dbmmodule.c
@@ -348,10 +348,18 @@ _dbm_dbm_keys_impl(dbmobject *self, PyTypeObject *cls)
return v;
}
+/*[clinic input]
+@critical_section
+_dbm.dbm.__contains__ as dbm_contains
+ arg: object
+ /
+[clinic start generated code]*/
+
static int
-dbm_contains_lock_held(PyObject *self, PyObject *arg)
+dbm_contains_impl(dbmobject *self, PyObject *arg)
+/*[clinic end generated code: output=6c42b797ada528bc input=94134e7abf3eeb6e]*/
{
- dbmobject *dp = dbmobject_CAST(self);
+ dbmobject *dp = self;
datum key, val;
Py_ssize_t size;
@@ -382,16 +390,6 @@ dbm_contains_lock_held(PyObject *self, PyObject *arg)
return val.dptr != NULL;
}
-static int
-dbm_contains(PyObject *self, PyObject *arg)
-{
- int result;
- Py_BEGIN_CRITICAL_SECTION(self);
- result = dbm_contains_lock_held(self, arg);
- Py_END_CRITICAL_SECTION();
- return result;
-}
-
/*[clinic input]
@critical_section
_dbm.dbm.get
diff --git a/Modules/_gdbmmodule.c b/Modules/_gdbmmodule.c
index fefbb59848c638..69bad7e137e24a 100644
--- a/Modules/_gdbmmodule.c
+++ b/Modules/_gdbmmodule.c
@@ -466,10 +466,18 @@ _gdbm_gdbm_keys_impl(gdbmobject *self, PyTypeObject *cls)
return v;
}
+/*[clinic input]
+@critical_section
+_gdbm.gdbm.__contains__ as gdbm_contains
+ arg: object
+ /
+[clinic start generated code]*/
+
static int
-gdbm_contains_lock_held(PyObject *self, PyObject *arg)
+gdbm_contains_impl(gdbmobject *self, PyObject *arg)
+/*[clinic end generated code: output=ad951f5a9f4194df input=411dc2039fdd7277]*/
{
- gdbmobject *dp = (gdbmobject *)self;
+ gdbmobject *dp = self;
datum key;
Py_ssize_t size;
_gdbm_state *state = PyType_GetModuleState(Py_TYPE(dp));
@@ -498,16 +506,6 @@ gdbm_contains_lock_held(PyObject *self, PyObject *arg)
return gdbm_exists(dp->di_dbm, key);
}
-static int
-gdbm_contains(PyObject *self, PyObject *arg)
-{
- int result;
- Py_BEGIN_CRITICAL_SECTION(self);
- result = gdbm_contains_lock_held(self, arg);
- Py_END_CRITICAL_SECTION();
- return result;
-}
-
/*[clinic input]
@critical_section
_gdbm.gdbm.firstkey
diff --git a/Modules/clinic/_collectionsmodule.c.h
b/Modules/clinic/_collectionsmodule.c.h
index 7545d22b21e655..a5f4962ccf4cb8 100644
--- a/Modules/clinic/_collectionsmodule.c.h
+++ b/Modules/clinic/_collectionsmodule.c.h
@@ -349,6 +349,21 @@ deque_count(PyObject *deque, PyObject *v)
return return_value;
}
+static int
+deque_contains_impl(dequeobject *deque, PyObject *v);
+
+static int
+deque_contains(PyObject *deque, PyObject *v)
+{
+ int return_value = -1;
+
+ Py_BEGIN_CRITICAL_SECTION(deque);
+ return_value = deque_contains_impl((dequeobject *)deque, v);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+
PyDoc_STRVAR(deque_index__doc__,
"index($self, value, [start, [stop]])\n"
"--\n"
@@ -677,4 +692,4 @@ tuplegetter_new(PyTypeObject *type, PyObject *args,
PyObject *kwargs)
exit:
return return_value;
}
-/*[clinic end generated code: output=761d85edf8291792 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=fca551851f7a658e input=a9049054013a1b77]*/
diff --git a/Modules/clinic/_dbmmodule.c.h b/Modules/clinic/_dbmmodule.c.h
index 1287254421a432..d71aa1362a4d34 100644
--- a/Modules/clinic/_dbmmodule.c.h
+++ b/Modules/clinic/_dbmmodule.c.h
@@ -89,6 +89,21 @@ _dbm_dbm_keys(PyObject *self, PyTypeObject *cls, PyObject
*const *args, Py_ssize
return return_value;
}
+static int
+dbm_contains_impl(dbmobject *self, PyObject *arg);
+
+static int
+dbm_contains(PyObject *self, PyObject *arg)
+{
+ int return_value = -1;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = dbm_contains_impl((dbmobject *)self, arg);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+
PyDoc_STRVAR(_dbm_dbm_get__doc__,
"get($self, key, default=None, /)\n"
"--\n"
@@ -279,4 +294,4 @@ dbmopen(PyObject *module, PyObject *const *args, Py_ssize_t
nargs)
exit:
return return_value;
}
-/*[clinic end generated code: output=da6b5d201f8d3853 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=6ff4681a893c22ad input=a9049054013a1b77]*/
diff --git a/Modules/clinic/_gdbmmodule.c.h b/Modules/clinic/_gdbmmodule.c.h
index 7549d3001d0f3e..062b6b0374664a 100644
--- a/Modules/clinic/_gdbmmodule.c.h
+++ b/Modules/clinic/_gdbmmodule.c.h
@@ -160,6 +160,21 @@ _gdbm_gdbm_keys(PyObject *self, PyTypeObject *cls,
PyObject *const *args, Py_ssi
return return_value;
}
+static int
+gdbm_contains_impl(gdbmobject *self, PyObject *arg);
+
+static int
+gdbm_contains(PyObject *self, PyObject *arg)
+{
+ int return_value = -1;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = gdbm_contains_impl((gdbmobject *)self, arg);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+
PyDoc_STRVAR(_gdbm_gdbm_firstkey__doc__,
"firstkey($self, /)\n"
"--\n"
@@ -431,4 +446,4 @@ dbmopen(PyObject *module, PyObject *const *args, Py_ssize_t
nargs)
exit:
return return_value;
}
-/*[clinic end generated code: output=6f91f162b0f822d3 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=10c193b33298877a input=a9049054013a1b77]*/
diff --git a/Tools/clinic/libclinic/dsl_parser.py
b/Tools/clinic/libclinic/dsl_parser.py
index a3512b9522322a..c27a883de36a41 100644
--- a/Tools/clinic/libclinic/dsl_parser.py
+++ b/Tools/clinic/libclinic/dsl_parser.py
@@ -600,7 +600,7 @@ def parse_function_names(self, line: str) -> FunctionNames:
fields = full_name.split(".")
if fields[-1] == '__new__':
fields.pop()
- elif fields[-1] in SLOTS:
+ elif fields[-1] in SLOTS and not self.coexist:
# list.__repr__ -> list_repr
fields[-1] = fields[-1].strip('_')
c_basename = "_".join(fields)
@@ -627,14 +627,14 @@ 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)
+ # A @coexist method named like a slot is a method which coexists
+ # with the slot (e.g. dict.__contains__), not the slot itself.
+ slot = None if self.coexist else 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__':
diff --git a/Tools/clinic/libclinic/slots.py b/Tools/clinic/libclinic/slots.py
index c5158cc57fc940..2138a6e780b245 100644
--- a/Tools/clinic/libclinic/slots.py
+++ b/Tools/clinic/libclinic/slots.py
@@ -38,12 +38,13 @@ def make_return_converter(self) -> CReturnConverter:
# 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(return_converter='object'),
- '__len__': SlotSpec(return_converter='Py_ssize_t'),
'__add__': SlotSpec(return_converter='object'),
+ '__contains__': SlotSpec(return_converter='int'),
'__iadd__': SlotSpec(return_converter='object'),
- '__mul__': SlotSpec(return_converter='object'),
'__imul__': SlotSpec(return_converter='object'),
+ '__len__': SlotSpec(return_converter='Py_ssize_t'),
+ '__mul__': SlotSpec(return_converter='object'),
+ '__next__': SlotSpec(return_converter='object'),
'__repr__': SlotSpec(return_converter='object'),
'__setitem__': SlotSpec(return_converter='int'),
}
_______________________________________________
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]