Skip to content

Commit 1ebd726

Browse files
authored
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.
1 parent 594bdde commit 1ebd726

File tree

9 files changed

+600
-12
lines changed

9 files changed

+600
-12
lines changed

Lib/test/test_clinic.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,32 @@ def test_vararg_after_star(self):
357357
"""
358358
self.expect_failure(block, err, lineno=6)
359359

360+
def test_double_star_after_var_keyword(self):
361+
err = "Function 'my_test_func' has an invalid parameter declaration (**kwargs?): '**kwds: dict'"
362+
block = """
363+
/*[clinic input]
364+
my_test_func
365+
366+
pos_arg: object
367+
**kwds: dict
368+
**
369+
[clinic start generated code]*/
370+
"""
371+
self.expect_failure(block, err, lineno=5)
372+
373+
def test_var_keyword_after_star(self):
374+
err = "Function 'my_test_func' has an invalid parameter declaration: '**'"
375+
block = """
376+
/*[clinic input]
377+
my_test_func
378+
379+
pos_arg: object
380+
**
381+
**kwds: dict
382+
[clinic start generated code]*/
383+
"""
384+
self.expect_failure(block, err, lineno=5)
385+
360386
def test_module_already_got_one(self):
361387
err = "Already defined module 'm'!"
362388
block = """
@@ -748,6 +774,16 @@ def test_ignore_preprocessor_in_comments(self):
748774
""")
749775
self.clinic.parse(raw)
750776

777+
def test_var_keyword_non_dict(self):
778+
err = "'var_keyword_object' is not a valid converter"
779+
block = """
780+
/*[clinic input]
781+
my_test_func
782+
783+
**kwds: object
784+
[clinic start generated code]*/
785+
"""
786+
self.expect_failure(block, err, lineno=4)
751787

752788
class ParseFileUnitTest(TestCase):
753789
def expect_parsing_failure(
@@ -1608,6 +1644,11 @@ def test_disallowed_grouping__must_be_position_only(self):
16081644
[
16091645
a: object
16101646
]
1647+
""", """
1648+
with_kwds
1649+
[
1650+
**kwds: dict
1651+
]
16111652
""")
16121653
err = (
16131654
"You cannot use optional groups ('[' and ']') unless all "
@@ -1991,6 +2032,44 @@ def test_slash_after_vararg(self):
19912032
err = "Function 'bar': '/' must precede '*'"
19922033
self.expect_failure(block, err)
19932034

2035+
def test_slash_after_var_keyword(self):
2036+
block = """
2037+
module foo
2038+
foo.bar
2039+
x: int
2040+
y: int
2041+
**kwds: dict
2042+
z: int
2043+
/
2044+
"""
2045+
err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'"
2046+
self.expect_failure(block, err)
2047+
2048+
def test_star_after_var_keyword(self):
2049+
block = """
2050+
module foo
2051+
foo.bar
2052+
x: int
2053+
y: int
2054+
**kwds: dict
2055+
z: int
2056+
*
2057+
"""
2058+
err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'"
2059+
self.expect_failure(block, err)
2060+
2061+
def test_parameter_after_var_keyword(self):
2062+
block = """
2063+
module foo
2064+
foo.bar
2065+
x: int
2066+
y: int
2067+
**kwds: dict
2068+
z: int
2069+
"""
2070+
err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'"
2071+
self.expect_failure(block, err)
2072+
19942073
def test_depr_star_must_come_after_slash(self):
19952074
block = """
19962075
module foo
@@ -2079,6 +2158,16 @@ def test_parameters_no_more_than_one_vararg(self):
20792158
"""
20802159
self.expect_failure(block, err, lineno=3)
20812160

2161+
def test_parameters_no_more_than_one_var_keyword(self):
2162+
err = "Encountered parameter line when not expecting parameters: **var_keyword_2: dict"
2163+
block = """
2164+
module foo
2165+
foo.bar
2166+
**var_keyword_1: dict
2167+
**var_keyword_2: dict
2168+
"""
2169+
self.expect_failure(block, err, lineno=3)
2170+
20822171
def test_function_not_at_column_0(self):
20832172
function = self.parse_function("""
20842173
module foo
@@ -2513,6 +2602,14 @@ def test_vararg_cannot_take_default_value(self):
25132602
"""
25142603
self.expect_failure(block, err, lineno=1)
25152604

2605+
def test_var_keyword_cannot_take_default_value(self):
2606+
err = "Function 'fn' has an invalid parameter declaration:"
2607+
block = """
2608+
fn
2609+
**kwds: dict = None
2610+
"""
2611+
self.expect_failure(block, err, lineno=1)
2612+
25162613
def test_default_is_not_of_correct_type(self):
25172614
err = ("int_converter: default value 2.5 for field 'a' "
25182615
"is not of type 'int'")
@@ -2610,6 +2707,43 @@ def test_disallow_defining_class_at_module_level(self):
26102707
"""
26112708
self.expect_failure(block, err, lineno=2)
26122709

2710+
def test_var_keyword_with_pos_or_kw(self):
2711+
block = """
2712+
module foo
2713+
foo.bar
2714+
x: int
2715+
**kwds: dict
2716+
"""
2717+
err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'"
2718+
self.expect_failure(block, err)
2719+
2720+
def test_var_keyword_with_kw_only(self):
2721+
block = """
2722+
module foo
2723+
foo.bar
2724+
x: int
2725+
/
2726+
*
2727+
y: int
2728+
**kwds: dict
2729+
"""
2730+
err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'"
2731+
self.expect_failure(block, err)
2732+
2733+
def test_var_keyword_with_pos_or_kw_and_kw_only(self):
2734+
block = """
2735+
module foo
2736+
foo.bar
2737+
x: int
2738+
/
2739+
y: int
2740+
*
2741+
z: int
2742+
**kwds: dict
2743+
"""
2744+
err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'"
2745+
self.expect_failure(block, err)
2746+
26132747
def test_allow_negative_accepted_by_py_ssize_t_converter_only(self):
26142748
errmsg = re.escape("converter_init() got an unexpected keyword argument 'allow_negative'")
26152749
unsupported_converters = [converter_name for converter_name in converters.keys()
@@ -3954,6 +4088,49 @@ def test_depr_multi(self):
39544088
check("a", b="b", c="c", d="d", e="e", f="f", g="g")
39554089
self.assertRaises(TypeError, fn, a="a", b="b", c="c", d="d", e="e", f="f", g="g")
39564090

4091+
def test_lone_kwds(self):
4092+
with self.assertRaises(TypeError):
4093+
ac_tester.lone_kwds(1, 2)
4094+
self.assertEqual(ac_tester.lone_kwds(), ({},))
4095+
self.assertEqual(ac_tester.lone_kwds(y='y'), ({'y': 'y'},))
4096+
kwds = {'y': 'y', 'z': 'z'}
4097+
self.assertEqual(ac_tester.lone_kwds(y='y', z='z'), (kwds,))
4098+
self.assertEqual(ac_tester.lone_kwds(**kwds), (kwds,))
4099+
4100+
def test_kwds_with_pos_only(self):
4101+
with self.assertRaises(TypeError):
4102+
ac_tester.kwds_with_pos_only()
4103+
with self.assertRaises(TypeError):
4104+
ac_tester.kwds_with_pos_only(y='y')
4105+
with self.assertRaises(TypeError):
4106+
ac_tester.kwds_with_pos_only(1, y='y')
4107+
self.assertEqual(ac_tester.kwds_with_pos_only(1, 2), (1, 2, {}))
4108+
self.assertEqual(ac_tester.kwds_with_pos_only(1, 2, y='y'), (1, 2, {'y': 'y'}))
4109+
kwds = {'y': 'y', 'z': 'z'}
4110+
self.assertEqual(ac_tester.kwds_with_pos_only(1, 2, y='y', z='z'), (1, 2, kwds))
4111+
self.assertEqual(ac_tester.kwds_with_pos_only(1, 2, **kwds), (1, 2, kwds))
4112+
4113+
def test_kwds_with_stararg(self):
4114+
self.assertEqual(ac_tester.kwds_with_stararg(), ((), {}))
4115+
self.assertEqual(ac_tester.kwds_with_stararg(1, 2), ((1, 2), {}))
4116+
self.assertEqual(ac_tester.kwds_with_stararg(y='y'), ((), {'y': 'y'}))
4117+
args = (1, 2)
4118+
kwds = {'y': 'y', 'z': 'z'}
4119+
self.assertEqual(ac_tester.kwds_with_stararg(1, 2, y='y', z='z'), (args, kwds))
4120+
self.assertEqual(ac_tester.kwds_with_stararg(*args, **kwds), (args, kwds))
4121+
4122+
def test_kwds_with_pos_only_and_stararg(self):
4123+
with self.assertRaises(TypeError):
4124+
ac_tester.kwds_with_pos_only_and_stararg()
4125+
with self.assertRaises(TypeError):
4126+
ac_tester.kwds_with_pos_only_and_stararg(y='y')
4127+
self.assertEqual(ac_tester.kwds_with_pos_only_and_stararg(1, 2), (1, 2, (), {}))
4128+
self.assertEqual(ac_tester.kwds_with_pos_only_and_stararg(1, 2, y='y'), (1, 2, (), {'y': 'y'}))
4129+
args = ('lobster', 'thermidor')
4130+
kwds = {'y': 'y', 'z': 'z'}
4131+
self.assertEqual(ac_tester.kwds_with_pos_only_and_stararg(1, 2, 'lobster', 'thermidor', y='y', z='z'), (1, 2, args, kwds))
4132+
self.assertEqual(ac_tester.kwds_with_pos_only_and_stararg(1, 2, *args, **kwds), (1, 2, args, kwds))
4133+
39574134

39584135
class LimitedCAPIOutputTests(unittest.TestCase):
39594136

Modules/_testclinic.c

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2308,6 +2308,88 @@ depr_multi_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c,
23082308
#undef _SAVED_PY_VERSION
23092309

23102310

2311+
/*[clinic input]
2312+
output pop
2313+
[clinic start generated code]*/
2314+
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=e7c7c42daced52b0]*/
2315+
2316+
2317+
/*[clinic input]
2318+
output push
2319+
destination kwarg new file '{dirname}/clinic/_testclinic_kwds.c.h'
2320+
output everything kwarg
2321+
output docstring_prototype suppress
2322+
output parser_prototype suppress
2323+
output impl_definition block
2324+
[clinic start generated code]*/
2325+
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=02965b54b3981cc4]*/
2326+
2327+
#include "clinic/_testclinic_kwds.c.h"
2328+
2329+
2330+
/*[clinic input]
2331+
lone_kwds
2332+
**kwds: dict
2333+
[clinic start generated code]*/
2334+
2335+
static PyObject *
2336+
lone_kwds_impl(PyObject *module, PyObject *kwds)
2337+
/*[clinic end generated code: output=572549c687a0432e input=6ef338b913ecae17]*/
2338+
{
2339+
return pack_arguments_newref(1, kwds);
2340+
}
2341+
2342+
2343+
/*[clinic input]
2344+
kwds_with_pos_only
2345+
a: object
2346+
b: object
2347+
/
2348+
**kwds: dict
2349+
[clinic start generated code]*/
2350+
2351+
static PyObject *
2352+
kwds_with_pos_only_impl(PyObject *module, PyObject *a, PyObject *b,
2353+
PyObject *kwds)
2354+
/*[clinic end generated code: output=573096d3a7efcce5 input=da081a5d9ae8878a]*/
2355+
{
2356+
return pack_arguments_newref(3, a, b, kwds);
2357+
}
2358+
2359+
2360+
/*[clinic input]
2361+
kwds_with_stararg
2362+
*args: tuple
2363+
**kwds: dict
2364+
[clinic start generated code]*/
2365+
2366+
static PyObject *
2367+
kwds_with_stararg_impl(PyObject *module, PyObject *args, PyObject *kwds)
2368+
/*[clinic end generated code: output=d4b0064626a25208 input=1be404572d685859]*/
2369+
{
2370+
return pack_arguments_newref(2, args, kwds);
2371+
}
2372+
2373+
2374+
/*[clinic input]
2375+
kwds_with_pos_only_and_stararg
2376+
a: object
2377+
b: object
2378+
/
2379+
*args: tuple
2380+
**kwds: dict
2381+
[clinic start generated code]*/
2382+
2383+
static PyObject *
2384+
kwds_with_pos_only_and_stararg_impl(PyObject *module, PyObject *a,
2385+
PyObject *b, PyObject *args,
2386+
PyObject *kwds)
2387+
/*[clinic end generated code: output=af7df7640c792246 input=2fe330c7981f0829]*/
2388+
{
2389+
return pack_arguments_newref(4, a, b, args, kwds);
2390+
}
2391+
2392+
23112393
/*[clinic input]
23122394
output pop
23132395
[clinic start generated code]*/
@@ -2404,6 +2486,12 @@ static PyMethodDef tester_methods[] = {
24042486
DEPR_KWD_NOINLINE_METHODDEF
24052487
DEPR_KWD_MULTI_METHODDEF
24062488
DEPR_MULTI_METHODDEF
2489+
2490+
LONE_KWDS_METHODDEF
2491+
KWDS_WITH_POS_ONLY_METHODDEF
2492+
KWDS_WITH_STARARG_METHODDEF
2493+
KWDS_WITH_POS_ONLY_AND_STARARG_METHODDEF
2494+
24072495
{NULL, NULL}
24082496
};
24092497

0 commit comments

Comments
 (0)