Skip to content

Commit 4b9a1ca

Browse files
gh-156219: Fix Argument Clinic for an optional argument with **kwds (GH-156220)
The generated code read an optional positional argument even if it was not passed.
1 parent 918fb3a commit 4b9a1ca

5 files changed

Lines changed: 90 additions & 1 deletion

File tree

Lib/test/test_clinic.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4981,6 +4981,18 @@ def test_kwds_with_pos_only(self):
49814981
self.assertEqual(ac_tester.kwds_with_pos_only(1, 2, y='y', z='z'), (1, 2, kwds))
49824982
self.assertEqual(ac_tester.kwds_with_pos_only(1, 2, **kwds), (1, 2, kwds))
49834983

4984+
def test_kwds_with_optional_pos_only(self):
4985+
with self.assertRaises(TypeError):
4986+
ac_tester.kwds_with_optional_pos_only()
4987+
with self.assertRaises(TypeError):
4988+
ac_tester.kwds_with_optional_pos_only(y='y')
4989+
self.assertEqual(ac_tester.kwds_with_optional_pos_only(1), (1, None, {}))
4990+
self.assertEqual(ac_tester.kwds_with_optional_pos_only(1, 2), (1, 2, {}))
4991+
self.assertEqual(ac_tester.kwds_with_optional_pos_only(1, y='y'),
4992+
(1, None, {'y': 'y'}))
4993+
self.assertEqual(ac_tester.kwds_with_optional_pos_only(1, 2, y='y'),
4994+
(1, 2, {'y': 'y'}))
4995+
49844996
def test_kwds_with_stararg(self):
49854997
self.assertEqual(ac_tester.kwds_with_stararg(), ((), {}))
49864998
self.assertEqual(ac_tester.kwds_with_stararg(1, 2), ((1, 2), {}))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix Argument Clinic generating code which reads an optional positional
2+
argument which was not passed, if the function has a ``**kwds`` parameter.

Modules/_testclinic.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2474,6 +2474,23 @@ kwds_with_pos_only_impl(PyObject *module, PyObject *a, PyObject *b,
24742474
}
24752475

24762476

2477+
/*[clinic input]
2478+
kwds_with_optional_pos_only
2479+
a: object
2480+
b: object = None
2481+
/
2482+
**kwds: dict
2483+
[clinic start generated code]*/
2484+
2485+
static PyObject *
2486+
kwds_with_optional_pos_only_impl(PyObject *module, PyObject *a, PyObject *b,
2487+
PyObject *kwds)
2488+
/*[clinic end generated code: output=25a8458f5acc1a07 input=0b18b9e1670904ec]*/
2489+
{
2490+
return pack_arguments_newref(3, a, b, kwds);
2491+
}
2492+
2493+
24772494
/*[clinic input]
24782495
kwds_with_stararg
24792496
*args: tuple
@@ -2611,6 +2628,7 @@ static PyMethodDef tester_methods[] = {
26112628

26122629
LONE_KWDS_METHODDEF
26132630
KWDS_WITH_POS_ONLY_METHODDEF
2631+
KWDS_WITH_OPTIONAL_POS_ONLY_METHODDEF
26142632
KWDS_WITH_STARARG_METHODDEF
26152633
KWDS_WITH_POS_ONLY_AND_STARARG_METHODDEF
26162634

Modules/clinic/_testclinic_kwds.c.h

Lines changed: 48 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tools/clinic/libclinic/parse_args.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,14 +655,24 @@ def parse_var_keyword(self) -> None:
655655
}}}}
656656
""", indent=4))
657657

658+
has_optional = False
658659
for i, p in enumerate(self.parameters):
659660
parse_arg = p.converter.parse_arg(
660661
f'PyTuple_GET_ITEM(args, {i})',
661662
p.get_displayname(i+1),
662663
limited_capi=self.limited_capi,
663664
)
664665
assert parse_arg is not None
666+
if has_optional or p.is_optional():
667+
has_optional = True
668+
parser_code.append(libclinic.normalize_snippet("""
669+
if (%s < %d) {{
670+
goto skip_optional;
671+
}}
672+
""", indent=4) % (nargs, i + 1))
665673
parser_code.append(libclinic.normalize_snippet(parse_arg, indent=4))
674+
if has_optional:
675+
parser_code.append("skip_optional:")
666676

667677
if self.varpos:
668678
parser_code.append(libclinic.normalize_snippet(self._parse_vararg(), indent=4))

0 commit comments

Comments
 (0)