mirror of
https://github.com/python/cpython.git
synced 2025-11-25 04:34:37 +00:00
gh-64490: Argument Clinic: Add support for `**kwds` (#138344)
This adds a scaffold of support, initially only working with strictly positional-only arguments. The FASTCALL calling convention is not yet supported.
This commit is contained in:
parent
594bdde9df
commit
1ebd726c9b
9 changed files with 600 additions and 12 deletions
|
|
@ -357,6 +357,32 @@ class ClinicWholeFileTest(TestCase):
|
|||
"""
|
||||
self.expect_failure(block, err, lineno=6)
|
||||
|
||||
def test_double_star_after_var_keyword(self):
|
||||
err = "Function 'my_test_func' has an invalid parameter declaration (**kwargs?): '**kwds: dict'"
|
||||
block = """
|
||||
/*[clinic input]
|
||||
my_test_func
|
||||
|
||||
pos_arg: object
|
||||
**kwds: dict
|
||||
**
|
||||
[clinic start generated code]*/
|
||||
"""
|
||||
self.expect_failure(block, err, lineno=5)
|
||||
|
||||
def test_var_keyword_after_star(self):
|
||||
err = "Function 'my_test_func' has an invalid parameter declaration: '**'"
|
||||
block = """
|
||||
/*[clinic input]
|
||||
my_test_func
|
||||
|
||||
pos_arg: object
|
||||
**
|
||||
**kwds: dict
|
||||
[clinic start generated code]*/
|
||||
"""
|
||||
self.expect_failure(block, err, lineno=5)
|
||||
|
||||
def test_module_already_got_one(self):
|
||||
err = "Already defined module 'm'!"
|
||||
block = """
|
||||
|
|
@ -748,6 +774,16 @@ class ClinicWholeFileTest(TestCase):
|
|||
""")
|
||||
self.clinic.parse(raw)
|
||||
|
||||
def test_var_keyword_non_dict(self):
|
||||
err = "'var_keyword_object' is not a valid converter"
|
||||
block = """
|
||||
/*[clinic input]
|
||||
my_test_func
|
||||
|
||||
**kwds: object
|
||||
[clinic start generated code]*/
|
||||
"""
|
||||
self.expect_failure(block, err, lineno=4)
|
||||
|
||||
class ParseFileUnitTest(TestCase):
|
||||
def expect_parsing_failure(
|
||||
|
|
@ -1608,6 +1644,11 @@ class ClinicParserTest(TestCase):
|
|||
[
|
||||
a: object
|
||||
]
|
||||
""", """
|
||||
with_kwds
|
||||
[
|
||||
**kwds: dict
|
||||
]
|
||||
""")
|
||||
err = (
|
||||
"You cannot use optional groups ('[' and ']') unless all "
|
||||
|
|
@ -1991,6 +2032,44 @@ class ClinicParserTest(TestCase):
|
|||
err = "Function 'bar': '/' must precede '*'"
|
||||
self.expect_failure(block, err)
|
||||
|
||||
def test_slash_after_var_keyword(self):
|
||||
block = """
|
||||
module foo
|
||||
foo.bar
|
||||
x: int
|
||||
y: int
|
||||
**kwds: dict
|
||||
z: int
|
||||
/
|
||||
"""
|
||||
err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'"
|
||||
self.expect_failure(block, err)
|
||||
|
||||
def test_star_after_var_keyword(self):
|
||||
block = """
|
||||
module foo
|
||||
foo.bar
|
||||
x: int
|
||||
y: int
|
||||
**kwds: dict
|
||||
z: int
|
||||
*
|
||||
"""
|
||||
err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'"
|
||||
self.expect_failure(block, err)
|
||||
|
||||
def test_parameter_after_var_keyword(self):
|
||||
block = """
|
||||
module foo
|
||||
foo.bar
|
||||
x: int
|
||||
y: int
|
||||
**kwds: dict
|
||||
z: int
|
||||
"""
|
||||
err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'"
|
||||
self.expect_failure(block, err)
|
||||
|
||||
def test_depr_star_must_come_after_slash(self):
|
||||
block = """
|
||||
module foo
|
||||
|
|
@ -2079,6 +2158,16 @@ class ClinicParserTest(TestCase):
|
|||
"""
|
||||
self.expect_failure(block, err, lineno=3)
|
||||
|
||||
def test_parameters_no_more_than_one_var_keyword(self):
|
||||
err = "Encountered parameter line when not expecting parameters: **var_keyword_2: dict"
|
||||
block = """
|
||||
module foo
|
||||
foo.bar
|
||||
**var_keyword_1: dict
|
||||
**var_keyword_2: dict
|
||||
"""
|
||||
self.expect_failure(block, err, lineno=3)
|
||||
|
||||
def test_function_not_at_column_0(self):
|
||||
function = self.parse_function("""
|
||||
module foo
|
||||
|
|
@ -2513,6 +2602,14 @@ class ClinicParserTest(TestCase):
|
|||
"""
|
||||
self.expect_failure(block, err, lineno=1)
|
||||
|
||||
def test_var_keyword_cannot_take_default_value(self):
|
||||
err = "Function 'fn' has an invalid parameter declaration:"
|
||||
block = """
|
||||
fn
|
||||
**kwds: dict = None
|
||||
"""
|
||||
self.expect_failure(block, err, lineno=1)
|
||||
|
||||
def test_default_is_not_of_correct_type(self):
|
||||
err = ("int_converter: default value 2.5 for field 'a' "
|
||||
"is not of type 'int'")
|
||||
|
|
@ -2610,6 +2707,43 @@ class ClinicParserTest(TestCase):
|
|||
"""
|
||||
self.expect_failure(block, err, lineno=2)
|
||||
|
||||
def test_var_keyword_with_pos_or_kw(self):
|
||||
block = """
|
||||
module foo
|
||||
foo.bar
|
||||
x: int
|
||||
**kwds: dict
|
||||
"""
|
||||
err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'"
|
||||
self.expect_failure(block, err)
|
||||
|
||||
def test_var_keyword_with_kw_only(self):
|
||||
block = """
|
||||
module foo
|
||||
foo.bar
|
||||
x: int
|
||||
/
|
||||
*
|
||||
y: int
|
||||
**kwds: dict
|
||||
"""
|
||||
err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'"
|
||||
self.expect_failure(block, err)
|
||||
|
||||
def test_var_keyword_with_pos_or_kw_and_kw_only(self):
|
||||
block = """
|
||||
module foo
|
||||
foo.bar
|
||||
x: int
|
||||
/
|
||||
y: int
|
||||
*
|
||||
z: int
|
||||
**kwds: dict
|
||||
"""
|
||||
err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'"
|
||||
self.expect_failure(block, err)
|
||||
|
||||
def test_allow_negative_accepted_by_py_ssize_t_converter_only(self):
|
||||
errmsg = re.escape("converter_init() got an unexpected keyword argument 'allow_negative'")
|
||||
unsupported_converters = [converter_name for converter_name in converters.keys()
|
||||
|
|
@ -3954,6 +4088,49 @@ class ClinicFunctionalTest(unittest.TestCase):
|
|||
check("a", b="b", c="c", d="d", e="e", f="f", g="g")
|
||||
self.assertRaises(TypeError, fn, a="a", b="b", c="c", d="d", e="e", f="f", g="g")
|
||||
|
||||
def test_lone_kwds(self):
|
||||
with self.assertRaises(TypeError):
|
||||
ac_tester.lone_kwds(1, 2)
|
||||
self.assertEqual(ac_tester.lone_kwds(), ({},))
|
||||
self.assertEqual(ac_tester.lone_kwds(y='y'), ({'y': 'y'},))
|
||||
kwds = {'y': 'y', 'z': 'z'}
|
||||
self.assertEqual(ac_tester.lone_kwds(y='y', z='z'), (kwds,))
|
||||
self.assertEqual(ac_tester.lone_kwds(**kwds), (kwds,))
|
||||
|
||||
def test_kwds_with_pos_only(self):
|
||||
with self.assertRaises(TypeError):
|
||||
ac_tester.kwds_with_pos_only()
|
||||
with self.assertRaises(TypeError):
|
||||
ac_tester.kwds_with_pos_only(y='y')
|
||||
with self.assertRaises(TypeError):
|
||||
ac_tester.kwds_with_pos_only(1, y='y')
|
||||
self.assertEqual(ac_tester.kwds_with_pos_only(1, 2), (1, 2, {}))
|
||||
self.assertEqual(ac_tester.kwds_with_pos_only(1, 2, y='y'), (1, 2, {'y': 'y'}))
|
||||
kwds = {'y': 'y', 'z': 'z'}
|
||||
self.assertEqual(ac_tester.kwds_with_pos_only(1, 2, y='y', z='z'), (1, 2, kwds))
|
||||
self.assertEqual(ac_tester.kwds_with_pos_only(1, 2, **kwds), (1, 2, kwds))
|
||||
|
||||
def test_kwds_with_stararg(self):
|
||||
self.assertEqual(ac_tester.kwds_with_stararg(), ((), {}))
|
||||
self.assertEqual(ac_tester.kwds_with_stararg(1, 2), ((1, 2), {}))
|
||||
self.assertEqual(ac_tester.kwds_with_stararg(y='y'), ((), {'y': 'y'}))
|
||||
args = (1, 2)
|
||||
kwds = {'y': 'y', 'z': 'z'}
|
||||
self.assertEqual(ac_tester.kwds_with_stararg(1, 2, y='y', z='z'), (args, kwds))
|
||||
self.assertEqual(ac_tester.kwds_with_stararg(*args, **kwds), (args, kwds))
|
||||
|
||||
def test_kwds_with_pos_only_and_stararg(self):
|
||||
with self.assertRaises(TypeError):
|
||||
ac_tester.kwds_with_pos_only_and_stararg()
|
||||
with self.assertRaises(TypeError):
|
||||
ac_tester.kwds_with_pos_only_and_stararg(y='y')
|
||||
self.assertEqual(ac_tester.kwds_with_pos_only_and_stararg(1, 2), (1, 2, (), {}))
|
||||
self.assertEqual(ac_tester.kwds_with_pos_only_and_stararg(1, 2, y='y'), (1, 2, (), {'y': 'y'}))
|
||||
args = ('lobster', 'thermidor')
|
||||
kwds = {'y': 'y', 'z': 'z'}
|
||||
self.assertEqual(ac_tester.kwds_with_pos_only_and_stararg(1, 2, 'lobster', 'thermidor', y='y', z='z'), (1, 2, args, kwds))
|
||||
self.assertEqual(ac_tester.kwds_with_pos_only_and_stararg(1, 2, *args, **kwds), (1, 2, args, kwds))
|
||||
|
||||
|
||||
class LimitedCAPIOutputTests(unittest.TestCase):
|
||||
|
||||
|
|
|
|||
|
|
@ -2308,6 +2308,88 @@ depr_multi_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c,
|
|||
#undef _SAVED_PY_VERSION
|
||||
|
||||
|
||||
/*[clinic input]
|
||||
output pop
|
||||
[clinic start generated code]*/
|
||||
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=e7c7c42daced52b0]*/
|
||||
|
||||
|
||||
/*[clinic input]
|
||||
output push
|
||||
destination kwarg new file '{dirname}/clinic/_testclinic_kwds.c.h'
|
||||
output everything kwarg
|
||||
output docstring_prototype suppress
|
||||
output parser_prototype suppress
|
||||
output impl_definition block
|
||||
[clinic start generated code]*/
|
||||
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=02965b54b3981cc4]*/
|
||||
|
||||
#include "clinic/_testclinic_kwds.c.h"
|
||||
|
||||
|
||||
/*[clinic input]
|
||||
lone_kwds
|
||||
**kwds: dict
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
lone_kwds_impl(PyObject *module, PyObject *kwds)
|
||||
/*[clinic end generated code: output=572549c687a0432e input=6ef338b913ecae17]*/
|
||||
{
|
||||
return pack_arguments_newref(1, kwds);
|
||||
}
|
||||
|
||||
|
||||
/*[clinic input]
|
||||
kwds_with_pos_only
|
||||
a: object
|
||||
b: object
|
||||
/
|
||||
**kwds: dict
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
kwds_with_pos_only_impl(PyObject *module, PyObject *a, PyObject *b,
|
||||
PyObject *kwds)
|
||||
/*[clinic end generated code: output=573096d3a7efcce5 input=da081a5d9ae8878a]*/
|
||||
{
|
||||
return pack_arguments_newref(3, a, b, kwds);
|
||||
}
|
||||
|
||||
|
||||
/*[clinic input]
|
||||
kwds_with_stararg
|
||||
*args: tuple
|
||||
**kwds: dict
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
kwds_with_stararg_impl(PyObject *module, PyObject *args, PyObject *kwds)
|
||||
/*[clinic end generated code: output=d4b0064626a25208 input=1be404572d685859]*/
|
||||
{
|
||||
return pack_arguments_newref(2, args, kwds);
|
||||
}
|
||||
|
||||
|
||||
/*[clinic input]
|
||||
kwds_with_pos_only_and_stararg
|
||||
a: object
|
||||
b: object
|
||||
/
|
||||
*args: tuple
|
||||
**kwds: dict
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
kwds_with_pos_only_and_stararg_impl(PyObject *module, PyObject *a,
|
||||
PyObject *b, PyObject *args,
|
||||
PyObject *kwds)
|
||||
/*[clinic end generated code: output=af7df7640c792246 input=2fe330c7981f0829]*/
|
||||
{
|
||||
return pack_arguments_newref(4, a, b, args, kwds);
|
||||
}
|
||||
|
||||
|
||||
/*[clinic input]
|
||||
output pop
|
||||
[clinic start generated code]*/
|
||||
|
|
@ -2404,6 +2486,12 @@ static PyMethodDef tester_methods[] = {
|
|||
DEPR_KWD_NOINLINE_METHODDEF
|
||||
DEPR_KWD_MULTI_METHODDEF
|
||||
DEPR_MULTI_METHODDEF
|
||||
|
||||
LONE_KWDS_METHODDEF
|
||||
KWDS_WITH_POS_ONLY_METHODDEF
|
||||
KWDS_WITH_STARARG_METHODDEF
|
||||
KWDS_WITH_POS_ONLY_AND_STARARG_METHODDEF
|
||||
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
|
|
|||
184
Modules/clinic/_testclinic_kwds.c.h
generated
Normal file
184
Modules/clinic/_testclinic_kwds.c.h
generated
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
/*[clinic input]
|
||||
preserve
|
||||
[clinic start generated code]*/
|
||||
|
||||
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
|
||||
# include "pycore_gc.h" // PyGC_Head
|
||||
#endif
|
||||
#include "pycore_abstract.h" // _PyNumber_Index()
|
||||
#include "pycore_long.h" // _PyLong_UnsignedShort_Converter()
|
||||
#include "pycore_modsupport.h" // _PyArg_CheckPositional()
|
||||
#include "pycore_runtime.h" // _Py_ID()
|
||||
#include "pycore_tuple.h" // _PyTuple_FromArray()
|
||||
|
||||
PyDoc_STRVAR(lone_kwds__doc__,
|
||||
"lone_kwds($module, /, **kwds)\n"
|
||||
"--\n"
|
||||
"\n");
|
||||
|
||||
#define LONE_KWDS_METHODDEF \
|
||||
{"lone_kwds", _PyCFunction_CAST(lone_kwds), METH_VARARGS|METH_KEYWORDS, lone_kwds__doc__},
|
||||
|
||||
static PyObject *
|
||||
lone_kwds_impl(PyObject *module, PyObject *kwds);
|
||||
|
||||
static PyObject *
|
||||
lone_kwds(PyObject *module, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
PyObject *return_value = NULL;
|
||||
PyObject *__clinic_kwds = NULL;
|
||||
|
||||
if (!_PyArg_NoPositional("lone_kwds", args)) {
|
||||
goto exit;
|
||||
}
|
||||
if (kwargs == NULL) {
|
||||
__clinic_kwds = PyDict_New();
|
||||
if (__clinic_kwds == NULL) {
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
else {
|
||||
__clinic_kwds = Py_NewRef(kwargs);
|
||||
}
|
||||
return_value = lone_kwds_impl(module, __clinic_kwds);
|
||||
|
||||
exit:
|
||||
/* Cleanup for kwds */
|
||||
Py_XDECREF(__clinic_kwds);
|
||||
|
||||
return return_value;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(kwds_with_pos_only__doc__,
|
||||
"kwds_with_pos_only($module, a, b, /, **kwds)\n"
|
||||
"--\n"
|
||||
"\n");
|
||||
|
||||
#define KWDS_WITH_POS_ONLY_METHODDEF \
|
||||
{"kwds_with_pos_only", _PyCFunction_CAST(kwds_with_pos_only), METH_VARARGS|METH_KEYWORDS, kwds_with_pos_only__doc__},
|
||||
|
||||
static PyObject *
|
||||
kwds_with_pos_only_impl(PyObject *module, PyObject *a, PyObject *b,
|
||||
PyObject *kwds);
|
||||
|
||||
static PyObject *
|
||||
kwds_with_pos_only(PyObject *module, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
PyObject *return_value = NULL;
|
||||
PyObject *a;
|
||||
PyObject *b;
|
||||
PyObject *__clinic_kwds = NULL;
|
||||
|
||||
if (!_PyArg_CheckPositional("kwds_with_pos_only", PyTuple_GET_SIZE(args), 2, 2)) {
|
||||
goto exit;
|
||||
}
|
||||
a = PyTuple_GET_ITEM(args, 0);
|
||||
b = PyTuple_GET_ITEM(args, 1);
|
||||
if (kwargs == NULL) {
|
||||
__clinic_kwds = PyDict_New();
|
||||
if (__clinic_kwds == NULL) {
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
else {
|
||||
__clinic_kwds = Py_NewRef(kwargs);
|
||||
}
|
||||
return_value = kwds_with_pos_only_impl(module, a, b, __clinic_kwds);
|
||||
|
||||
exit:
|
||||
/* Cleanup for kwds */
|
||||
Py_XDECREF(__clinic_kwds);
|
||||
|
||||
return return_value;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(kwds_with_stararg__doc__,
|
||||
"kwds_with_stararg($module, /, *args, **kwds)\n"
|
||||
"--\n"
|
||||
"\n");
|
||||
|
||||
#define KWDS_WITH_STARARG_METHODDEF \
|
||||
{"kwds_with_stararg", _PyCFunction_CAST(kwds_with_stararg), METH_VARARGS|METH_KEYWORDS, kwds_with_stararg__doc__},
|
||||
|
||||
static PyObject *
|
||||
kwds_with_stararg_impl(PyObject *module, PyObject *args, PyObject *kwds);
|
||||
|
||||
static PyObject *
|
||||
kwds_with_stararg(PyObject *module, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
PyObject *return_value = NULL;
|
||||
PyObject *__clinic_args = NULL;
|
||||
PyObject *__clinic_kwds = NULL;
|
||||
|
||||
__clinic_args = Py_NewRef(args);
|
||||
if (kwargs == NULL) {
|
||||
__clinic_kwds = PyDict_New();
|
||||
if (__clinic_kwds == NULL) {
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
else {
|
||||
__clinic_kwds = Py_NewRef(kwargs);
|
||||
}
|
||||
return_value = kwds_with_stararg_impl(module, __clinic_args, __clinic_kwds);
|
||||
|
||||
exit:
|
||||
/* Cleanup for args */
|
||||
Py_XDECREF(__clinic_args);
|
||||
/* Cleanup for kwds */
|
||||
Py_XDECREF(__clinic_kwds);
|
||||
|
||||
return return_value;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(kwds_with_pos_only_and_stararg__doc__,
|
||||
"kwds_with_pos_only_and_stararg($module, a, b, /, *args, **kwds)\n"
|
||||
"--\n"
|
||||
"\n");
|
||||
|
||||
#define KWDS_WITH_POS_ONLY_AND_STARARG_METHODDEF \
|
||||
{"kwds_with_pos_only_and_stararg", _PyCFunction_CAST(kwds_with_pos_only_and_stararg), METH_VARARGS|METH_KEYWORDS, kwds_with_pos_only_and_stararg__doc__},
|
||||
|
||||
static PyObject *
|
||||
kwds_with_pos_only_and_stararg_impl(PyObject *module, PyObject *a,
|
||||
PyObject *b, PyObject *args,
|
||||
PyObject *kwds);
|
||||
|
||||
static PyObject *
|
||||
kwds_with_pos_only_and_stararg(PyObject *module, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
PyObject *return_value = NULL;
|
||||
PyObject *a;
|
||||
PyObject *b;
|
||||
PyObject *__clinic_args = NULL;
|
||||
PyObject *__clinic_kwds = NULL;
|
||||
|
||||
if (!_PyArg_CheckPositional("kwds_with_pos_only_and_stararg", PyTuple_GET_SIZE(args), 2, PY_SSIZE_T_MAX)) {
|
||||
goto exit;
|
||||
}
|
||||
a = PyTuple_GET_ITEM(args, 0);
|
||||
b = PyTuple_GET_ITEM(args, 1);
|
||||
__clinic_args = PyTuple_GetSlice(args, 2, PY_SSIZE_T_MAX);
|
||||
if (!__clinic_args) {
|
||||
goto exit;
|
||||
}
|
||||
if (kwargs == NULL) {
|
||||
__clinic_kwds = PyDict_New();
|
||||
if (__clinic_kwds == NULL) {
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
else {
|
||||
__clinic_kwds = Py_NewRef(kwargs);
|
||||
}
|
||||
return_value = kwds_with_pos_only_and_stararg_impl(module, a, b, __clinic_args, __clinic_kwds);
|
||||
|
||||
exit:
|
||||
/* Cleanup for args */
|
||||
Py_XDECREF(__clinic_args);
|
||||
/* Cleanup for kwds */
|
||||
Py_XDECREF(__clinic_kwds);
|
||||
|
||||
return return_value;
|
||||
}
|
||||
/*[clinic end generated code: output=e4dea1070e003f5d input=a9049054013a1b77]*/
|
||||
|
|
@ -84,6 +84,7 @@ CLINIC_PREFIXED_ARGS: Final = frozenset(
|
|||
"argsbuf",
|
||||
"fastargs",
|
||||
"kwargs",
|
||||
"kwds",
|
||||
"kwnames",
|
||||
"nargs",
|
||||
"noptargs",
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ class CConverter(metaclass=CConverterAutoRegister):
|
|||
data.modifications.append('/* modifications for ' + name + ' */\n' + modifications.rstrip())
|
||||
|
||||
# keywords
|
||||
if parameter.is_vararg():
|
||||
if parameter.is_variable_length():
|
||||
pass
|
||||
elif parameter.is_positional_only():
|
||||
data.keywords.append('')
|
||||
|
|
|
|||
|
|
@ -1300,3 +1300,37 @@ class varpos_array_converter(VarPosCConverter):
|
|||
{paramname} = {start};
|
||||
{self.length_name} = {size};
|
||||
"""
|
||||
|
||||
|
||||
# Converters for var-keyword parameters.
|
||||
|
||||
class VarKeywordCConverter(CConverter):
|
||||
format_unit = ''
|
||||
|
||||
def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
|
||||
raise AssertionError('should never be called')
|
||||
|
||||
def parse_var_keyword(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class var_keyword_dict_converter(VarKeywordCConverter):
|
||||
type = 'PyObject *'
|
||||
c_default = 'NULL'
|
||||
|
||||
def cleanup(self) -> str:
|
||||
return f'Py_XDECREF({self.parser_name});\n'
|
||||
|
||||
def parse_var_keyword(self) -> str:
|
||||
param_name = self.parser_name
|
||||
return f"""
|
||||
if (kwargs == NULL) {{{{
|
||||
{param_name} = PyDict_New();
|
||||
if ({param_name} == NULL) {{{{
|
||||
goto exit;
|
||||
}}}}
|
||||
}}}}
|
||||
else {{{{
|
||||
{param_name} = Py_NewRef(kwargs);
|
||||
}}}}
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -246,6 +246,7 @@ class IndentStack:
|
|||
class DSLParser:
|
||||
function: Function | None
|
||||
state: StateKeeper
|
||||
expecting_parameters: bool
|
||||
keyword_only: bool
|
||||
positional_only: bool
|
||||
deprecated_positional: VersionTuple | None
|
||||
|
|
@ -285,6 +286,7 @@ class DSLParser:
|
|||
def reset(self) -> None:
|
||||
self.function = None
|
||||
self.state = self.state_dsl_start
|
||||
self.expecting_parameters = True
|
||||
self.keyword_only = False
|
||||
self.positional_only = False
|
||||
self.deprecated_positional = None
|
||||
|
|
@ -876,6 +878,10 @@ class DSLParser:
|
|||
def parse_parameter(self, line: str) -> None:
|
||||
assert self.function is not None
|
||||
|
||||
if not self.expecting_parameters:
|
||||
fail('Encountered parameter line when not expecting '
|
||||
f'parameters: {line}')
|
||||
|
||||
match self.parameter_state:
|
||||
case ParamState.START | ParamState.REQUIRED:
|
||||
self.to_required()
|
||||
|
|
@ -909,27 +915,40 @@ class DSLParser:
|
|||
if len(function_args.args) > 1:
|
||||
fail(f"Function {self.function.name!r} has an "
|
||||
f"invalid parameter declaration (comma?): {line!r}")
|
||||
if function_args.kwarg:
|
||||
fail(f"Function {self.function.name!r} has an "
|
||||
f"invalid parameter declaration (**kwargs?): {line!r}")
|
||||
|
||||
is_vararg = is_var_keyword = False
|
||||
if function_args.vararg:
|
||||
self.check_previous_star()
|
||||
self.check_remaining_star()
|
||||
is_vararg = True
|
||||
parameter = function_args.vararg
|
||||
elif function_args.kwarg:
|
||||
# If the existing parameters are all positional only or ``*args``
|
||||
# (var-positional), then we allow ``**kwds`` (var-keyword).
|
||||
# Currently, pos-or-keyword or keyword-only arguments are not
|
||||
# allowed with the ``**kwds`` converter.
|
||||
has_non_positional_param = any(
|
||||
p.is_positional_or_keyword() or p.is_keyword_only()
|
||||
for p in self.function.parameters.values()
|
||||
)
|
||||
if has_non_positional_param:
|
||||
fail(f"Function {self.function.name!r} has an "
|
||||
f"invalid parameter declaration (**kwargs?): {line!r}")
|
||||
is_var_keyword = True
|
||||
parameter = function_args.kwarg
|
||||
else:
|
||||
is_vararg = False
|
||||
parameter = function_args.args[0]
|
||||
|
||||
parameter_name = parameter.arg
|
||||
name, legacy, kwargs = self.parse_converter(parameter.annotation)
|
||||
if is_vararg:
|
||||
name = 'varpos_' + name
|
||||
name = f'varpos_{name}'
|
||||
elif is_var_keyword:
|
||||
name = f'var_keyword_{name}'
|
||||
|
||||
value: object
|
||||
if not function_args.defaults:
|
||||
if is_vararg:
|
||||
if is_vararg or is_var_keyword:
|
||||
value = NULL
|
||||
else:
|
||||
if self.parameter_state is ParamState.OPTIONAL:
|
||||
|
|
@ -1065,6 +1084,8 @@ class DSLParser:
|
|||
kind: inspect._ParameterKind
|
||||
if is_vararg:
|
||||
kind = inspect.Parameter.VAR_POSITIONAL
|
||||
elif is_var_keyword:
|
||||
kind = inspect.Parameter.VAR_KEYWORD
|
||||
elif self.keyword_only:
|
||||
kind = inspect.Parameter.KEYWORD_ONLY
|
||||
else:
|
||||
|
|
@ -1118,6 +1139,8 @@ class DSLParser:
|
|||
|
||||
if is_vararg:
|
||||
self.keyword_only = True
|
||||
if is_var_keyword:
|
||||
self.expecting_parameters = False
|
||||
|
||||
@staticmethod
|
||||
def parse_converter(
|
||||
|
|
@ -1159,6 +1182,9 @@ class DSLParser:
|
|||
The 'version' parameter signifies the future version from which
|
||||
the marker will take effect (None means it is already in effect).
|
||||
"""
|
||||
if not self.expecting_parameters:
|
||||
fail("Encountered '*' when not expecting parameters")
|
||||
|
||||
if version is None:
|
||||
self.check_previous_star()
|
||||
self.check_remaining_star()
|
||||
|
|
@ -1214,6 +1240,9 @@ class DSLParser:
|
|||
The 'version' parameter signifies the future version from which
|
||||
the marker will take effect (None means it is already in effect).
|
||||
"""
|
||||
if not self.expecting_parameters:
|
||||
fail("Encountered '/' when not expecting parameters")
|
||||
|
||||
if version is None:
|
||||
if self.deprecated_keyword:
|
||||
fail(f"Function {function.name!r}: '/' must precede '/ [from ...]'")
|
||||
|
|
@ -1450,11 +1479,13 @@ class DSLParser:
|
|||
if p.is_vararg():
|
||||
p_lines.append("*")
|
||||
added_star = True
|
||||
if p.is_var_keyword():
|
||||
p_lines.append("**")
|
||||
|
||||
name = p.converter.signature_name or p.name
|
||||
p_lines.append(name)
|
||||
|
||||
if not p.is_vararg() and p.converter.is_optional():
|
||||
if not p.is_variable_length() and p.converter.is_optional():
|
||||
p_lines.append('=')
|
||||
value = p.converter.py_default
|
||||
if not value:
|
||||
|
|
@ -1583,8 +1614,11 @@ class DSLParser:
|
|||
|
||||
for p in reversed(self.function.parameters.values()):
|
||||
if self.keyword_only:
|
||||
if (p.kind == inspect.Parameter.KEYWORD_ONLY or
|
||||
p.kind == inspect.Parameter.VAR_POSITIONAL):
|
||||
if p.kind in {
|
||||
inspect.Parameter.KEYWORD_ONLY,
|
||||
inspect.Parameter.VAR_POSITIONAL,
|
||||
inspect.Parameter.VAR_KEYWORD
|
||||
}:
|
||||
return
|
||||
elif self.deprecated_positional:
|
||||
if p.deprecated_positional == self.deprecated_positional:
|
||||
|
|
|
|||
|
|
@ -220,9 +220,18 @@ class Parameter:
|
|||
def is_positional_only(self) -> bool:
|
||||
return self.kind == inspect.Parameter.POSITIONAL_ONLY
|
||||
|
||||
def is_positional_or_keyword(self) -> bool:
|
||||
return self.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
|
||||
|
||||
def is_vararg(self) -> bool:
|
||||
return self.kind == inspect.Parameter.VAR_POSITIONAL
|
||||
|
||||
def is_var_keyword(self) -> bool:
|
||||
return self.kind == inspect.Parameter.VAR_KEYWORD
|
||||
|
||||
def is_variable_length(self) -> bool:
|
||||
return self.is_vararg() or self.is_var_keyword()
|
||||
|
||||
def is_optional(self) -> bool:
|
||||
return not self.is_vararg() and (self.default is not unspecified)
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ def declare_parser(
|
|||
|
||||
num_keywords = len([
|
||||
p for p in f.parameters.values()
|
||||
if not p.is_positional_only() and not p.is_vararg()
|
||||
if p.is_positional_or_keyword() or p.is_keyword_only()
|
||||
])
|
||||
|
||||
condition = '#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)'
|
||||
|
|
@ -220,6 +220,7 @@ class ParseArgsCodeGen:
|
|||
max_pos: int = 0
|
||||
min_kw_only: int = 0
|
||||
varpos: Parameter | None = None
|
||||
var_keyword: Parameter | None = None
|
||||
|
||||
docstring_prototype: str
|
||||
docstring_definition: str
|
||||
|
|
@ -255,13 +256,24 @@ class ParseArgsCodeGen:
|
|||
del self.parameters[i]
|
||||
break
|
||||
|
||||
for i, p in enumerate(self.parameters):
|
||||
if p.is_var_keyword():
|
||||
self.var_keyword = p
|
||||
del self.parameters[i]
|
||||
break
|
||||
|
||||
self.converters = [p.converter for p in self.parameters]
|
||||
|
||||
if self.func.critical_section:
|
||||
self.codegen.add_include('pycore_critical_section.h',
|
||||
'Py_BEGIN_CRITICAL_SECTION()')
|
||||
|
||||
# Use fastcall if not disabled, except if in a __new__ or
|
||||
# __init__ method, or if there is a **kwargs parameter.
|
||||
if self.func.disable_fastcall:
|
||||
self.fastcall = False
|
||||
elif self.var_keyword is not None:
|
||||
self.fastcall = False
|
||||
else:
|
||||
self.fastcall = not self.is_new_or_init()
|
||||
|
||||
|
|
@ -469,6 +481,12 @@ class ParseArgsCodeGen:
|
|||
fastcall=self.fastcall,
|
||||
limited_capi=self.limited_capi)
|
||||
|
||||
def _parse_kwarg(self) -> str:
|
||||
assert self.var_keyword is not None
|
||||
c = self.var_keyword.converter
|
||||
assert isinstance(c, libclinic.converters.VarKeywordCConverter)
|
||||
return c.parse_var_keyword()
|
||||
|
||||
def parse_pos_only(self) -> None:
|
||||
if self.fastcall:
|
||||
# positional-only, but no option groups
|
||||
|
|
@ -564,6 +582,8 @@ class ParseArgsCodeGen:
|
|||
parser_code.append("skip_optional:")
|
||||
if self.varpos:
|
||||
parser_code.append(libclinic.normalize_snippet(self._parse_vararg(), indent=4))
|
||||
elif self.var_keyword:
|
||||
parser_code.append(libclinic.normalize_snippet(self._parse_kwarg(), indent=4))
|
||||
else:
|
||||
for parameter in self.parameters:
|
||||
parameter.converter.use_converter()
|
||||
|
|
@ -590,6 +610,45 @@ class ParseArgsCodeGen:
|
|||
""", indent=4)]
|
||||
self.parser_body(*parser_code)
|
||||
|
||||
def parse_var_keyword(self) -> None:
|
||||
self.flags = "METH_VARARGS|METH_KEYWORDS"
|
||||
self.parser_prototype = PARSER_PROTOTYPE_KEYWORD
|
||||
nargs = 'PyTuple_GET_SIZE(args)'
|
||||
|
||||
parser_code = []
|
||||
max_args = NO_VARARG if self.varpos else self.max_pos
|
||||
if self.varpos is None and self.min_pos == self.max_pos == 0:
|
||||
self.codegen.add_include('pycore_modsupport.h',
|
||||
'_PyArg_NoPositional()')
|
||||
parser_code.append(libclinic.normalize_snippet("""
|
||||
if (!_PyArg_NoPositional("{name}", args)) {{
|
||||
goto exit;
|
||||
}}
|
||||
""", indent=4))
|
||||
elif self.min_pos or max_args != NO_VARARG:
|
||||
self.codegen.add_include('pycore_modsupport.h',
|
||||
'_PyArg_CheckPositional()')
|
||||
parser_code.append(libclinic.normalize_snippet(f"""
|
||||
if (!_PyArg_CheckPositional("{{name}}", {nargs}, {self.min_pos}, {max_args})) {{{{
|
||||
goto exit;
|
||||
}}}}
|
||||
""", indent=4))
|
||||
|
||||
for i, p in enumerate(self.parameters):
|
||||
parse_arg = p.converter.parse_arg(
|
||||
f'PyTuple_GET_ITEM(args, {i})',
|
||||
p.get_displayname(i+1),
|
||||
limited_capi=self.limited_capi,
|
||||
)
|
||||
assert parse_arg is not None
|
||||
parser_code.append(libclinic.normalize_snippet(parse_arg, indent=4))
|
||||
|
||||
if self.varpos:
|
||||
parser_code.append(libclinic.normalize_snippet(self._parse_vararg(), indent=4))
|
||||
if self.var_keyword:
|
||||
parser_code.append(libclinic.normalize_snippet(self._parse_kwarg(), indent=4))
|
||||
self.parser_body(*parser_code)
|
||||
|
||||
def parse_general(self, clang: CLanguage) -> None:
|
||||
parsearg: str | None
|
||||
deprecated_positionals: dict[int, Parameter] = {}
|
||||
|
|
@ -921,12 +980,14 @@ class ParseArgsCodeGen:
|
|||
# previous call to parser_body. this is used for an awful hack.
|
||||
self.parser_body_fields: tuple[str, ...] = ()
|
||||
|
||||
if not self.parameters and not self.varpos:
|
||||
if not self.parameters and not self.varpos and not self.var_keyword:
|
||||
self.parse_no_args()
|
||||
elif self.use_meth_o():
|
||||
self.parse_one_arg()
|
||||
elif self.has_option_groups():
|
||||
self.parse_option_groups()
|
||||
elif self.var_keyword is not None:
|
||||
self.parse_var_keyword()
|
||||
elif (not self.requires_defining_class
|
||||
and self.pos_only == len(self.parameters)):
|
||||
self.parse_pos_only()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue