Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions Modules/_testclinic.c
Original file line number Diff line number Diff line change
Expand Up @@ -2303,6 +2303,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]*/
{
Py_RETURN_NONE;
}


/*[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]*/
{
Py_RETURN_NONE;
}


/*[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]*/
{
Py_RETURN_NONE;
}


/*[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]*/
{
Py_RETURN_NONE;
}


/*[clinic input]
output pop
[clinic start generated code]*/
Expand Down Expand Up @@ -2399,6 +2481,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}
};

Expand Down
151 changes: 151 additions & 0 deletions Modules/clinic/_testclinic_kwds.c.h

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

2 changes: 1 addition & 1 deletion Tools/clinic/libclinic/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ def _render_non_self(
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('')
Expand Down
27 changes: 27 additions & 0 deletions Tools/clinic/libclinic/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -1279,3 +1279,30 @@ def parse_vararg(self, *, pos_only: int, min_pos: int, max_pos: int,
{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 *'
format_unit = ''
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"""
{param_name} = (kwargs != NULL) ? Py_NewRef(kwargs) : PyDict_New();
"""
37 changes: 27 additions & 10 deletions Tools/clinic/libclinic/dsl_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -909,27 +909,37 @@ def parse_parameter(self, line: str) -> None:
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.
if not all(p.is_positional_only() or p.is_vararg()
for p in self.function.parameters.values()):
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:
Expand Down Expand Up @@ -1065,6 +1075,8 @@ def bad_node(self, node: ast.AST) -> None:
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:
Expand Down Expand Up @@ -1116,7 +1128,7 @@ def bad_node(self, node: ast.AST) -> None:
key = f"{parameter_name}_as_{c_name}" if c_name else parameter_name
self.function.parameters[key] = p

if is_vararg:
if is_vararg or is_var_keyword:
self.keyword_only = True

@staticmethod
Expand Down Expand Up @@ -1450,11 +1462,13 @@ def add_parameter(text: str) -> None:
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:
Expand Down Expand Up @@ -1583,8 +1597,11 @@ def check_remaining_star(self, lineno: int | None = None) -> None:

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:
Expand Down
Loading
Loading