https://github.com/python/cpython/commit/4130af14b2aea0f3921ae22b63283497bc7a274a
commit: 4130af14b2aea0f3921ae22b63283497bc7a274a
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-06T12:11:57+03:00
summary:

gh-155266: Fix Argument Clinic for a lone optional group (GH-155267)

If the only parameter of a function was in an optional group, METH_O was
generated, which made the argument mandatory and did not pass the flag of
the group.

files:
A Misc/NEWS.d/next/Tools-Demos/2026-08-06-10-54-12.gh-issue-155266.NEngp9.rst
M Lib/test/clinic.test.c
M Lib/test/test_clinic.py
M Modules/_testclinic.c
M Modules/clinic/_testclinic.c.h
M Tools/c-analyzer/cpython/_parser.py
M Tools/clinic/libclinic/parse_args.py

diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c
index 146f57a2a11342..3dca8b8d1ed9b9 100644
--- a/Lib/test/clinic.test.c
+++ b/Lib/test/clinic.test.c
@@ -5769,6 +5769,56 @@ Test___init___impl(TestObj *self, PyObject *a, int 
group_right_1,
 /*[clinic end generated code: output=2bbb8ea60e8f57a6 input=10f5d0f1e8e466ef]*/
 
 
+/*[clinic input]
+only_optional_group
+    [
+    a: object
+    ]
+    /
+The only parameter is in an optional group.
+[clinic start generated code]*/
+
+PyDoc_STRVAR(only_optional_group__doc__,
+"only_optional_group([a])\n"
+"The only parameter is in an optional group.");
+
+#define ONLY_OPTIONAL_GROUP_METHODDEF    \
+    {"only_optional_group", (PyCFunction)only_optional_group, METH_VARARGS, 
only_optional_group__doc__},
+
+static PyObject *
+only_optional_group_impl(PyObject *module, int group_right_1, PyObject *a);
+
+static PyObject *
+only_optional_group(PyObject *module, PyObject *args)
+{
+    PyObject *return_value = NULL;
+    int group_right_1 = 0;
+    PyObject *a = NULL;
+
+    switch (PyTuple_GET_SIZE(args)) {
+        case 0:
+            break;
+        case 1:
+            if (!PyArg_ParseTuple(args, "O:only_optional_group", &a)) {
+                goto exit;
+            }
+            group_right_1 = 1;
+            break;
+        default:
+            PyErr_SetString(PyExc_TypeError, "only_optional_group requires 0 
to 1 arguments");
+            goto exit;
+    }
+    return_value = only_optional_group_impl(module, group_right_1, a);
+
+exit:
+    return return_value;
+}
+
+static PyObject *
+only_optional_group_impl(PyObject *module, int group_right_1, PyObject *a)
+/*[clinic end generated code: output=e7546b9441793d7d input=426c64055af7bcab]*/
+
+
 /*[clinic input]
 group_and_optional_parameter
     [
diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py
index cb4507dcac2336..1dc1c4eaaaba19 100644
--- a/Lib/test/test_clinic.py
+++ b/Lib/test/test_clinic.py
@@ -4135,6 +4135,14 @@ def test_varpos_kwonly_req_opt(self):
         self.assertEqual(fn(1, a=2, b=3), ((1,), 2, 3, False))
         self.assertEqual(fn(1, a=2, b=3, c=4), ((1,), 2, 3, 4))
 
+    def test_only_group(self):
+        # fn([a])
+        fn = ac_tester.only_group
+        self.assertEqual(fn(), (False, None))
+        self.assertEqual(fn(1), (True, 1))
+        self.assertRaises(TypeError, fn, 1, 2)
+        self.assertRaises(TypeError, fn, a=1)
+
     def test_group_and_opt(self):
         # fn([a, b,] c=None)
         fn = ac_tester.group_and_opt
diff --git 
a/Misc/NEWS.d/next/Tools-Demos/2026-08-06-10-54-12.gh-issue-155266.NEngp9.rst 
b/Misc/NEWS.d/next/Tools-Demos/2026-08-06-10-54-12.gh-issue-155266.NEngp9.rst
new file mode 100644
index 00000000000000..2ec5fbca0e012b
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Tools-Demos/2026-08-06-10-54-12.gh-issue-155266.NEngp9.rst
@@ -0,0 +1,4 @@
+Fix Argument Clinic for a function whose only parameter is in an optional
+group.
+It generated ``METH_O``, which made the argument mandatory and did not pass
+the flag of the group.
diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c
index c53bf4a0875358..ad4e34f640e530 100644
--- a/Modules/_testclinic.c
+++ b/Modules/_testclinic.c
@@ -1237,6 +1237,24 @@ posonly_poskw_varpos_array_impl(PyObject *module, 
PyObject *a, PyObject *b,
 }
 
 
+/*[clinic input]
+only_group
+
+    [
+    a: object
+    ]
+    /
+
+[clinic start generated code]*/
+
+static PyObject *
+only_group_impl(PyObject *module, int group_right_1, PyObject *a)
+/*[clinic end generated code: output=e92d6c85b72a5897 input=7aca574206712a42]*/
+{
+    return pack_arguments_newref(2, group_right_1 ? Py_True : Py_False, a);
+}
+
+
 /*[clinic input]
 group_and_opt
 
@@ -2553,6 +2571,7 @@ static PyMethodDef tester_methods[] = {
     POSONLY_VARPOS_ARRAY_METHODDEF
     POSONLY_REQ_OPT_VARPOS_ARRAY_METHODDEF
     POSONLY_POSKW_VARPOS_ARRAY_METHODDEF
+    ONLY_GROUP_METHODDEF
     GROUP_AND_OPT_METHODDEF
     GROUP_AND_TWO_OPT_METHODDEF
     TWO_GROUPS_ON_LEFT_METHODDEF
diff --git a/Modules/clinic/_testclinic.c.h b/Modules/clinic/_testclinic.c.h
index 3fe32d704f0140..12bf0639b66427 100644
--- a/Modules/clinic/_testclinic.c.h
+++ b/Modules/clinic/_testclinic.c.h
@@ -3477,6 +3477,41 @@ posonly_poskw_varpos_array(PyObject *module, PyObject 
*const *args, Py_ssize_t n
     return return_value;
 }
 
+PyDoc_STRVAR(only_group__doc__,
+"only_group([a])");
+
+#define ONLY_GROUP_METHODDEF    \
+    {"only_group", (PyCFunction)only_group, METH_VARARGS, only_group__doc__},
+
+static PyObject *
+only_group_impl(PyObject *module, int group_right_1, PyObject *a);
+
+static PyObject *
+only_group(PyObject *module, PyObject *args)
+{
+    PyObject *return_value = NULL;
+    int group_right_1 = 0;
+    PyObject *a = NULL;
+
+    switch (PyTuple_GET_SIZE(args)) {
+        case 0:
+            break;
+        case 1:
+            if (!PyArg_ParseTuple(args, "O:only_group", &a)) {
+                goto exit;
+            }
+            group_right_1 = 1;
+            break;
+        default:
+            PyErr_SetString(PyExc_TypeError, "only_group requires 0 to 1 
arguments");
+            goto exit;
+    }
+    return_value = only_group_impl(module, group_right_1, a);
+
+exit:
+    return return_value;
+}
+
 PyDoc_STRVAR(group_and_opt__doc__,
 "group_and_opt([a, b,] c=None)");
 
@@ -4804,4 +4839,4 @@ 
_testclinic_TestClass_posonly_poskw_varpos_array_no_fastcall(PyObject *type, PyO
 exit:
     return return_value;
 }
-/*[clinic end generated code: output=d9d4091b2f2ed359 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=15e6c430697bd384 input=a9049054013a1b77]*/
diff --git a/Tools/c-analyzer/cpython/_parser.py 
b/Tools/c-analyzer/cpython/_parser.py
index 3d755765b96709..489043103aa9b5 100644
--- a/Tools/c-analyzer/cpython/_parser.py
+++ b/Tools/c-analyzer/cpython/_parser.py
@@ -345,7 +345,7 @@ def format_tsv_lines(lines):
     _abs('Modules/_ssl_data_300.h'): (80_000, 10_000),
     _abs('Modules/_ssl_data_111.h'): (80_000, 10_000),
     _abs('Modules/cjkcodecs/mappings_*.h'): (160_000, 2_000),
-    _abs('Modules/clinic/_testclinic.c.h'): (125_000, 5_000),
+    _abs('Modules/clinic/_testclinic.c.h'): (135_000, 5_500),
     _abs('Modules/unicodedata_db.h'): (180_000, 3_000),
     _abs('Modules/unicodename_db.h'): (1_200_000, 15_000),
     _abs('Objects/unicodetype_db.h'): (240_000, 3_000),
diff --git a/Tools/clinic/libclinic/parse_args.py 
b/Tools/clinic/libclinic/parse_args.py
index bca87ecd75100c..2ad1e94ea2b4c7 100644
--- a/Tools/clinic/libclinic/parse_args.py
+++ b/Tools/clinic/libclinic/parse_args.py
@@ -303,6 +303,7 @@ def has_option_groups(self) -> bool:
     def use_meth_o(self) -> bool:
         return (len(self.parameters) == 1
                 and self.parameters[0].is_positional_only()
+                and not self.has_option_groups()
                 and not self.converters[0].is_optional()
                 and not self.varpos
                 and not self.requires_defining_class

_______________________________________________
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