]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-155266: Fix Argument Clinic for a lone optional group (GH-155267)
authorSerhiy Storchaka <storchaka@gmail.com>
Thu, 6 Aug 2026 09:11:57 +0000 (12:11 +0300)
committerGitHub <noreply@github.com>
Thu, 6 Aug 2026 09:11:57 +0000 (12:11 +0300)
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.

Lib/test/clinic.test.c
Lib/test/test_clinic.py
Misc/NEWS.d/next/Tools-Demos/2026-08-06-10-54-12.gh-issue-155266.NEngp9.rst [new file with mode: 0644]
Modules/_testclinic.c
Modules/clinic/_testclinic.c.h
Tools/c-analyzer/cpython/_parser.py
Tools/clinic/libclinic/parse_args.py

index 146f57a2a11342a917fee209b14361a1cf5ec18a..3dca8b8d1ed9b990474675ea5fdad09c98eee7c8 100644 (file)
@@ -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
     [
index cb4507dcac2336de6ad9e5c273bb451e708f1899..1dc1c4eaaaba1964cff49bff389a0e249aa37aa5 100644 (file)
@@ -4135,6 +4135,14 @@ class ClinicFunctionalTest(unittest.TestCase):
         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 (file)
index 0000000..2ec5fbc
--- /dev/null
@@ -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.
index c53bf4a087535864f329e337f916d6171909a647..ad4e34f640e5308289d8b462c05aac52d9cbb443 100644 (file)
@@ -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
index 3fe32d704f0140faba8b0afa03f797e751f9f9ba..12bf0639b66427dc35c75cd0d72163a78a26a2c8 100644 (file)
@@ -3477,6 +3477,41 @@ exit:
     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]*/
index 3d755765b967097524a6423643f92fba51c22a6e..489043103aa9b5b1d3b44204648dfefb3aebf43b 100644 (file)
@@ -345,7 +345,7 @@ MAX_SIZES = {
     _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),
index bca87ecd75100cefe40f8d761139d0be6da4eda6..2ad1e94ea2b4c79507f2ffe1293ab06ace04bacb 100644 (file)
@@ -303,6 +303,7 @@ class ParseArgsCodeGen:
     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