/*[clinic end generated code: output=2bbb8ea60e8f57a6 input=10f5d0f1e8e466ef]*/
+/*[clinic input]
+group_and_optional_parameter
+ [
+ a: object
+ b: object
+ ]
+ c: object = None
+ /
+The optional parameter can be omitted with or without the group.
+[clinic start generated code]*/
+
+PyDoc_STRVAR(group_and_optional_parameter__doc__,
+"group_and_optional_parameter([a, b,] c=None)\n"
+"The optional parameter can be omitted with or without the group.");
+
+#define GROUP_AND_OPTIONAL_PARAMETER_METHODDEF \
+ {"group_and_optional_parameter", (PyCFunction)group_and_optional_parameter, METH_VARARGS, group_and_optional_parameter__doc__},
+
+static PyObject *
+group_and_optional_parameter_impl(PyObject *module, int group_left_1,
+ PyObject *a, PyObject *b, PyObject *c);
+
+static PyObject *
+group_and_optional_parameter(PyObject *module, PyObject *args)
+{
+ PyObject *return_value = NULL;
+ int group_left_1 = 0;
+ PyObject *a = NULL;
+ PyObject *b = NULL;
+ PyObject *c = Py_None;
+
+ switch (PyTuple_GET_SIZE(args)) {
+ case 0:
+ case 1:
+ if (!PyArg_ParseTuple(args, "|O:group_and_optional_parameter", &c)) {
+ goto exit;
+ }
+ break;
+ case 2:
+ case 3:
+ if (!PyArg_ParseTuple(args, "OO|O:group_and_optional_parameter", &a, &b, &c)) {
+ goto exit;
+ }
+ group_left_1 = 1;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "group_and_optional_parameter requires 0 to 3 arguments");
+ goto exit;
+ }
+ return_value = group_and_optional_parameter_impl(module, group_left_1, a, b, c);
+
+exit:
+ return return_value;
+}
+
+static PyObject *
+group_and_optional_parameter_impl(PyObject *module, int group_left_1,
+ PyObject *a, PyObject *b, PyObject *c)
+/*[clinic end generated code: output=3faea69eafd5bbbe input=7f0fbb6124f5a972]*/
+
+
/*[clinic input]
Test._pyarg_parsestackandkeywords
cls: defining_class
"""
self.expect_failure(block, err, lineno=8)
+ def test_ambiguous_group_and_optional_parameters(self):
+ err = ("Function 'my_test_func' has an ambiguous group configuration: "
+ "a call with 2 argument(s) can be parsed in more than one way.")
+ block = """
+ /*[clinic input]
+ my_test_func
+
+ [
+ a: object
+ b: object
+ ]
+ c: object = None
+ d: object = None
+ /
+ [clinic start generated code]*/
+ """
+ self.expect_failure(block, err)
+
def test_star_after_vararg(self):
err = "'my_test_func' uses '*' more than once."
block = """
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_group_and_opt(self):
+ # fn([a, b,] c=None)
+ fn = ac_tester.group_and_opt
+ self.assertEqual(fn(), (False, None, None, None))
+ self.assertEqual(fn(1), (False, None, None, 1))
+ self.assertEqual(fn(1, 2), (True, 1, 2, None))
+ self.assertEqual(fn(1, 2, 3), (True, 1, 2, 3))
+ self.assertRaises(TypeError, fn, 1, 2, 3, 4)
+ self.assertRaises(TypeError, fn, c=1)
+
+ def test_group_and_two_opt(self):
+ # fn([a, b, c,] d=None, e=None)
+ fn = ac_tester.group_and_two_opt
+ self.assertEqual(fn(), (False, None, None, None, None, None))
+ self.assertEqual(fn(1), (False, None, None, None, 1, None))
+ self.assertEqual(fn(1, 2), (False, None, None, None, 1, 2))
+ self.assertEqual(fn(1, 2, 3), (True, 1, 2, 3, None, None))
+ self.assertEqual(fn(1, 2, 3, 4), (True, 1, 2, 3, 4, None))
+ self.assertEqual(fn(1, 2, 3, 4, 5), (True, 1, 2, 3, 4, 5))
+ self.assertRaises(TypeError, fn, 1, 2, 3, 4, 5, 6)
+
def test_gh_32092_oob(self):
ac_tester.gh_32092_oob(1, 2, 3, 4, kw1=5, kw2=6)
--- /dev/null
+Fix Argument Clinic support of parameters with a default value used together
+with optional groups.
+Such parameters were always required in the generated parsing code.
}
+/*[clinic input]
+group_and_opt
+
+ [
+ a: object
+ b: object
+ ]
+ c: object = None
+ /
+
+[clinic start generated code]*/
+
+static PyObject *
+group_and_opt_impl(PyObject *module, int group_left_1, PyObject *a,
+ PyObject *b, PyObject *c)
+/*[clinic end generated code: output=23413ec545526111 input=8a84d8f44bc8bd0b]*/
+{
+ return pack_arguments_newref(4, group_left_1 ? Py_True : Py_False,
+ a, b, c);
+}
+
+
+/*[clinic input]
+group_and_two_opt
+
+ [
+ a: object
+ b: object
+ c: object
+ ]
+ d: object = None
+ e: object = None
+ /
+
+[clinic start generated code]*/
+
+static PyObject *
+group_and_two_opt_impl(PyObject *module, int group_left_1, PyObject *a,
+ PyObject *b, PyObject *c, PyObject *d, PyObject *e)
+/*[clinic end generated code: output=1427c4b3c35f24ff input=cdda98eec1e365ea]*/
+{
+ return pack_arguments_newref(6, group_left_1 ? Py_True : Py_False,
+ a, b, c, d, e);
+}
+
+
/*[clinic input]
gh_32092_oob
POSONLY_VARPOS_ARRAY_METHODDEF
POSONLY_REQ_OPT_VARPOS_ARRAY_METHODDEF
POSONLY_POSKW_VARPOS_ARRAY_METHODDEF
+ GROUP_AND_OPT_METHODDEF
+ GROUP_AND_TWO_OPT_METHODDEF
GH_32092_OOB_METHODDEF
GH_32092_KW_PASS_METHODDEF
return return_value;
}
+PyDoc_STRVAR(group_and_opt__doc__,
+"group_and_opt([a, b,] c=None)");
+
+#define GROUP_AND_OPT_METHODDEF \
+ {"group_and_opt", (PyCFunction)group_and_opt, METH_VARARGS, group_and_opt__doc__},
+
+static PyObject *
+group_and_opt_impl(PyObject *module, int group_left_1, PyObject *a,
+ PyObject *b, PyObject *c);
+
+static PyObject *
+group_and_opt(PyObject *module, PyObject *args)
+{
+ PyObject *return_value = NULL;
+ int group_left_1 = 0;
+ PyObject *a = NULL;
+ PyObject *b = NULL;
+ PyObject *c = Py_None;
+
+ switch (PyTuple_GET_SIZE(args)) {
+ case 0:
+ case 1:
+ if (!PyArg_ParseTuple(args, "|O:group_and_opt", &c)) {
+ goto exit;
+ }
+ break;
+ case 2:
+ case 3:
+ if (!PyArg_ParseTuple(args, "OO|O:group_and_opt", &a, &b, &c)) {
+ goto exit;
+ }
+ group_left_1 = 1;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "group_and_opt requires 0 to 3 arguments");
+ goto exit;
+ }
+ return_value = group_and_opt_impl(module, group_left_1, a, b, c);
+
+exit:
+ return return_value;
+}
+
+PyDoc_STRVAR(group_and_two_opt__doc__,
+"group_and_two_opt([a, b, c,] d=None, e=None)");
+
+#define GROUP_AND_TWO_OPT_METHODDEF \
+ {"group_and_two_opt", (PyCFunction)group_and_two_opt, METH_VARARGS, group_and_two_opt__doc__},
+
+static PyObject *
+group_and_two_opt_impl(PyObject *module, int group_left_1, PyObject *a,
+ PyObject *b, PyObject *c, PyObject *d, PyObject *e);
+
+static PyObject *
+group_and_two_opt(PyObject *module, PyObject *args)
+{
+ PyObject *return_value = NULL;
+ int group_left_1 = 0;
+ PyObject *a = NULL;
+ PyObject *b = NULL;
+ PyObject *c = NULL;
+ PyObject *d = Py_None;
+ PyObject *e = Py_None;
+
+ switch (PyTuple_GET_SIZE(args)) {
+ case 0:
+ case 1:
+ case 2:
+ if (!PyArg_ParseTuple(args, "|OO:group_and_two_opt", &d, &e)) {
+ goto exit;
+ }
+ break;
+ case 3:
+ case 4:
+ case 5:
+ if (!PyArg_ParseTuple(args, "OOO|OO:group_and_two_opt", &a, &b, &c, &d, &e)) {
+ goto exit;
+ }
+ group_left_1 = 1;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "group_and_two_opt requires 0 to 5 arguments");
+ goto exit;
+ }
+ return_value = group_and_two_opt_impl(module, group_left_1, a, b, c, d, e);
+
+exit:
+ return return_value;
+}
+
PyDoc_STRVAR(gh_32092_oob__doc__,
"gh_32092_oob($module, /, pos1, pos2, *varargs, kw1=None, kw2=None)\n"
"--\n"
exit:
return return_value;
}
-/*[clinic end generated code: output=9971dbbc5f62b8d2 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=f6a3b617130c4e3a input=a9049054013a1b77]*/
_abs('Modules/_remote_debugging/debug_offsets_validation.h'): (25_000, 1000),
_abs('Modules/_remote_debugging/*.h'): (20_000, 1000),
_abs('Modules/_testcapimodule.c'): (20_000, 400),
+ _abs('Modules/_testclinic.c'): (20_000, 400),
_abs('Modules/expat/expat.h'): (10_000, 400),
_abs('Objects/stringlib/unicode_format.h'): (10_000, 400),
_abs('Objects/typeobject.c'): (380_000, 13_000),
from __future__ import annotations
import itertools
-import sys
import textwrap
from typing import TYPE_CHECKING, Literal, Final
from operator import attrgetter
from libclinic.codegen import CRenderData, TemplateDict, CodeGen
from libclinic.language import Language
from libclinic.function import (
- Module, Class, Function, Parameter,
+ Module, Class, Function, Parameter, ParamTuple,
permute_optional_groups,
GETTER, SETTER, METHOD_INIT)
from libclinic.converters import self_converter
from libclinic.app import Clinic
+def count_required(subset: ParamTuple) -> int:
+ """Return the number of arguments which cannot be omitted.
+
+ A parameter in an optional group is passed together with its group,
+ so only trailing parameters with a default value can be omitted.
+ """
+ count = len(subset)
+ for p in reversed(subset):
+ if p.group or not p.is_optional():
+ break
+ count -= 1
+ return count
+
+
def c_id(name: str) -> str:
if len(name) == 1 and ord(name) < 256:
if name.isalnum():
assert group is not None
group.append(p)
- count_min = sys.maxsize
- count_max = -1
+ # Map the number of arguments to the subset which accepts it.
+ subsets: dict[int, ParamTuple] = {}
+ for subset in permute_optional_groups(left, required, right):
+ for count in range(count_required(subset), len(subset) + 1):
+ if count in subsets:
+ fail(f"Function {f.full_name!r} has an ambiguous group "
+ f"configuration: a call with {count} argument(s) "
+ f"can be parsed in more than one way.")
+ subsets[count] = subset
if limited_capi:
nargs = 'PyTuple_Size(args)'
else:
nargs = 'PyTuple_GET_SIZE(args)'
out.append(f"switch ({nargs}) {{\n")
- for subset in permute_optional_groups(left, required, right):
- count = len(subset)
- count_min = min(count_min, count)
- count_max = max(count_max, count)
+ for count, subset in sorted(subsets.items()):
+ if count < len(subset):
+ # The omitted parameters are parsed by the following case.
+ out.append(f" case {count}:\n")
+ continue
if count == 0:
out.append(""" case 0:
d: dict[str, str | int] = {}
d['count'] = count
d['name'] = f.name
- d['format_units'] = "".join(p.converter.format_unit for p in subset)
+ format_units = [p.converter.format_unit for p in subset]
+ n_required = count_required(subset)
+ if n_required < count:
+ format_units.insert(n_required, '|')
+ d['format_units'] = "".join(format_units)
parse_arguments: list[str] = []
for p in subset:
out.append(" default:\n")
s = ' PyErr_SetString(PyExc_TypeError, "{} requires {} to {} arguments");\n'
- out.append(s.format(f.full_name, count_min, count_max))
+ out.append(s.format(f.full_name, min(subsets), max(subsets)))
out.append(' goto exit;\n')
out.append("}")