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