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]

Reply via email to