Skip to content

Commit

Permalink
pythongh-108494: Fix AC limited C API for kwargs
Browse files Browse the repository at this point in the history
Fix the limited C API code path in Argument Clinic to parse
"positional or keywords" arguments.

Add an unit test in _testclinic_limited.
  • Loading branch information
vstinner committed Aug 26, 2023
1 parent e407cea commit de98b3f
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 16 deletions.
14 changes: 14 additions & 0 deletions Lib/test/test_clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3534,6 +3534,7 @@ class LimitedCAPIFunctionalTest(unittest.TestCase):
for name in dir(_testclinic_limited) if name.startswith('test_'))

def test_my_int_func(self):
# METH_O
with self.assertRaises(TypeError):
_testclinic_limited.my_int_func()
self.assertEqual(_testclinic_limited.my_int_func(3), 3)
Expand All @@ -3543,6 +3544,7 @@ def test_my_int_func(self):
_testclinic_limited.my_int_func("xyz")

def test_my_int_sum(self):
# PyArg_ParseTuple() with "ii:my_int_sum" format
with self.assertRaises(TypeError):
_testclinic_limited.my_int_sum()
with self.assertRaises(TypeError):
Expand All @@ -3553,6 +3555,18 @@ def test_my_int_sum(self):
with self.assertRaises(TypeError):
_testclinic_limited.my_int_sum(1, "str")

def test_my_obj_func(self):
# PyArg_ParseTupleAndKeywords() with "OO:my_obj_func" format
arg1 = object()
arg2 = object()
with self.assertRaises(TypeError):
_testclinic_limited.my_obj_func()
with self.assertRaises(TypeError):
_testclinic_limited.my_obj_func(arg1)
self.assertIs(_testclinic_limited.my_obj_func(arg1, arg2), arg1)
with self.assertRaises(TypeError):
_testclinic_limited.my_obj_func(arg1, arg2, "arg3")



class PermutationTests(unittest.TestCase):
Expand Down
17 changes: 17 additions & 0 deletions Modules/_testclinic_limited.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@ my_int_func_impl(PyObject *module, int arg)
}


/*[clinic input]
my_obj_func
arg1: object
arg2: object
[clinic start generated code]*/

static PyObject *
my_obj_func_impl(PyObject *module, PyObject *arg1, PyObject *arg2)
/*[clinic end generated code: output=7f07d359d2e50436 input=c6f78c836ecab1d6]*/
{
return Py_NewRef(arg1);
}


/*[clinic input]
my_int_sum -> int
Expand All @@ -66,6 +82,7 @@ static PyMethodDef tester_methods[] = {
TEST_EMPTY_FUNCTION_METHODDEF
MY_INT_FUNC_METHODDEF
MY_INT_SUM_METHODDEF
MY_OBJ_FUNC_METHODDEF
{NULL, NULL}
};

Expand Down
30 changes: 29 additions & 1 deletion Modules/clinic/_testclinic_limited.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 19 additions & 15 deletions Tools/clinic/clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1251,7 +1251,8 @@ def parser_body(
parser_definition = parser_body(parser_prototype, ' {option_group_parsing}')

elif not requires_defining_class and pos_only == len(parameters) - pseudo_args and clinic.limited_capi:
# positional-only for the limited C API
# positional-only for the limited C API:
# PyArg_ParseTuple()
flags = "METH_VARARGS"

parser_prototype = self.PARSER_PROTOTYPE_VARARGS
Expand All @@ -1266,6 +1267,22 @@ def parser_body(
parser_definition = parser_body(parser_prototype, *parser_code,
declarations=declarations)

elif clinic.limited_capi:
# positional-or-keyword arguments for the limited C API:
# PyArg_ParseTupleAndKeywords()
flags = "METH_VARARGS|METH_KEYWORDS"

parser_prototype = self.PARSER_PROTOTYPE_KEYWORD
parser_code = [normalize_snippet("""
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "{format_units}:{name}", _keywords,
{parse_arguments}))
goto exit;
""", indent=4)]
declarations = "char* _keywords[] = {{{keywords_c} NULL}};"

parser_definition = parser_body(parser_prototype, *parser_code,
declarations=declarations)

elif not requires_defining_class and pos_only == len(parameters) - pseudo_args:
if not new_or_init:
# positional-only, but no option groups
Expand Down Expand Up @@ -1384,20 +1401,7 @@ def parser_body(
)
nargs = f"Py_MIN(nargs, {max_pos})" if max_pos else "0"

if clinic.limited_capi:
# positional-or-keyword arguments
flags = "METH_VARARGS|METH_KEYWORDS"

parser_prototype = self.PARSER_PROTOTYPE_KEYWORD
parser_code = [normalize_snippet("""
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "{format_units}:{name}", _keywords,
{parse_arguments}))
goto exit;
""", indent=4)]
argname_fmt = 'args[%d]'
declarations = ""

elif not new_or_init:
if not new_or_init:
flags = "METH_FASTCALL|METH_KEYWORDS"
parser_prototype = self.PARSER_PROTOTYPE_FASTCALL_KEYWORDS
argname_fmt = 'args[%d]'
Expand Down

0 comments on commit de98b3f

Please sign in to comment.