"""
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 = """
""")
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(
[
a: object
]
+ """, """
+ with_kwds
+ [
+ **kwds: dict
+ ]
""")
err = (
"You cannot use optional groups ('[' and ']') unless all "
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
"""
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
"""
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'")
"""
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()
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):
#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]*/
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}
};
--- /dev/null
+/*[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]*/
"argsbuf",
"fastargs",
"kwargs",
+ "kwds",
"kwnames",
"nargs",
"noptargs",
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('')
{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);
+ }}}}
+ """
class DSLParser:
function: Function | None
state: StateKeeper
+ expecting_parameters: bool
keyword_only: bool
positional_only: bool
deprecated_positional: VersionTuple | None
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
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()
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:
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:
if is_vararg:
self.keyword_only = True
+ if is_var_keyword:
+ self.expecting_parameters = False
@staticmethod
def parse_converter(
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()
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 ...]'")
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:
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:
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)
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)'
max_pos: int = 0
min_kw_only: int = 0
varpos: Parameter | None = None
+ var_keyword: Parameter | None = None
docstring_prototype: str
docstring_definition: str
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()
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
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()
""", 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] = {}
# 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()