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]