Skip to content

Commit 3207792

Browse files
Merge remote-tracking branch 'upstream/3.15' into mi-156592
2 parents 71b3c77 + 343b3b4 commit 3207792

7 files changed

Lines changed: 278 additions & 11 deletions

File tree

‎Lib/test/clinic.test.c‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5814,6 +5814,67 @@ Test___init___impl(TestObj *self, PyObject *a, int group_right_1,
58145814
/*[clinic end generated code: output=2bbb8ea60e8f57a6 input=10f5d0f1e8e466ef]*/
58155815

58165816

5817+
/*[clinic input]
5818+
group_and_optional_parameter
5819+
[
5820+
a: object
5821+
b: object
5822+
]
5823+
c: object = None
5824+
/
5825+
The optional parameter can be omitted with or without the group.
5826+
[clinic start generated code]*/
5827+
5828+
PyDoc_STRVAR(group_and_optional_parameter__doc__,
5829+
"group_and_optional_parameter([a, b,] c=None)\n"
5830+
"The optional parameter can be omitted with or without the group.");
5831+
5832+
#define GROUP_AND_OPTIONAL_PARAMETER_METHODDEF \
5833+
{"group_and_optional_parameter", (PyCFunction)group_and_optional_parameter, METH_VARARGS, group_and_optional_parameter__doc__},
5834+
5835+
static PyObject *
5836+
group_and_optional_parameter_impl(PyObject *module, int group_left_1,
5837+
PyObject *a, PyObject *b, PyObject *c);
5838+
5839+
static PyObject *
5840+
group_and_optional_parameter(PyObject *module, PyObject *args)
5841+
{
5842+
PyObject *return_value = NULL;
5843+
int group_left_1 = 0;
5844+
PyObject *a = NULL;
5845+
PyObject *b = NULL;
5846+
PyObject *c = Py_None;
5847+
5848+
switch (PyTuple_GET_SIZE(args)) {
5849+
case 0:
5850+
case 1:
5851+
if (!PyArg_ParseTuple(args, "|O:group_and_optional_parameter", &c)) {
5852+
goto exit;
5853+
}
5854+
break;
5855+
case 2:
5856+
case 3:
5857+
if (!PyArg_ParseTuple(args, "OO|O:group_and_optional_parameter", &a, &b, &c)) {
5858+
goto exit;
5859+
}
5860+
group_left_1 = 1;
5861+
break;
5862+
default:
5863+
PyErr_SetString(PyExc_TypeError, "group_and_optional_parameter requires 0 to 3 arguments");
5864+
goto exit;
5865+
}
5866+
return_value = group_and_optional_parameter_impl(module, group_left_1, a, b, c);
5867+
5868+
exit:
5869+
return return_value;
5870+
}
5871+
5872+
static PyObject *
5873+
group_and_optional_parameter_impl(PyObject *module, int group_left_1,
5874+
PyObject *a, PyObject *b, PyObject *c)
5875+
/*[clinic end generated code: output=3faea69eafd5bbbe input=7f0fbb6124f5a972]*/
5876+
5877+
58175878
/*[clinic input]
58185879
Test._pyarg_parsestackandkeywords
58195880
cls: defining_class

‎Lib/test/test_clinic.py‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,24 @@ def __init__(self):
330330
"""
331331
self.expect_failure(block, err, lineno=8)
332332

333+
def test_ambiguous_group_and_optional_parameters(self):
334+
err = ("Function 'my_test_func' has an ambiguous group configuration: "
335+
"a call with 2 argument(s) can be parsed in more than one way.")
336+
block = """
337+
/*[clinic input]
338+
my_test_func
339+
340+
[
341+
a: object
342+
b: object
343+
]
344+
c: object = None
345+
d: object = None
346+
/
347+
[clinic start generated code]*/
348+
"""
349+
self.expect_failure(block, err)
350+
333351
def test_star_after_vararg(self):
334352
err = "'my_test_func' uses '*' more than once."
335353
block = """
@@ -4003,6 +4021,27 @@ def test_varpos_kwonly_req_opt(self):
40034021
self.assertEqual(fn(1, a=2, b=3), ((1,), 2, 3, False))
40044022
self.assertEqual(fn(1, a=2, b=3, c=4), ((1,), 2, 3, 4))
40054023

4024+
def test_group_and_opt(self):
4025+
# fn([a, b,] c=None)
4026+
fn = ac_tester.group_and_opt
4027+
self.assertEqual(fn(), (False, None, None, None))
4028+
self.assertEqual(fn(1), (False, None, None, 1))
4029+
self.assertEqual(fn(1, 2), (True, 1, 2, None))
4030+
self.assertEqual(fn(1, 2, 3), (True, 1, 2, 3))
4031+
self.assertRaises(TypeError, fn, 1, 2, 3, 4)
4032+
self.assertRaises(TypeError, fn, c=1)
4033+
4034+
def test_group_and_two_opt(self):
4035+
# fn([a, b, c,] d=None, e=None)
4036+
fn = ac_tester.group_and_two_opt
4037+
self.assertEqual(fn(), (False, None, None, None, None, None))
4038+
self.assertEqual(fn(1), (False, None, None, None, 1, None))
4039+
self.assertEqual(fn(1, 2), (False, None, None, None, 1, 2))
4040+
self.assertEqual(fn(1, 2, 3), (True, 1, 2, 3, None, None))
4041+
self.assertEqual(fn(1, 2, 3, 4), (True, 1, 2, 3, 4, None))
4042+
self.assertEqual(fn(1, 2, 3, 4, 5), (True, 1, 2, 3, 4, 5))
4043+
self.assertRaises(TypeError, fn, 1, 2, 3, 4, 5, 6)
4044+
40064045
def test_gh_32092_oob(self):
40074046
ac_tester.gh_32092_oob(1, 2, 3, 4, kw1=5, kw2=6)
40084047

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix Argument Clinic support of parameters with a default value used together
2+
with optional groups.
3+
Such parameters were always required in the generated parsing code.

‎Modules/_testclinic.c‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,6 +1237,52 @@ posonly_poskw_varpos_array_impl(PyObject *module, PyObject *a, PyObject *b,
12371237
}
12381238

12391239

1240+
/*[clinic input]
1241+
group_and_opt
1242+
1243+
[
1244+
a: object
1245+
b: object
1246+
]
1247+
c: object = None
1248+
/
1249+
1250+
[clinic start generated code]*/
1251+
1252+
static PyObject *
1253+
group_and_opt_impl(PyObject *module, int group_left_1, PyObject *a,
1254+
PyObject *b, PyObject *c)
1255+
/*[clinic end generated code: output=23413ec545526111 input=8a84d8f44bc8bd0b]*/
1256+
{
1257+
return pack_arguments_newref(4, group_left_1 ? Py_True : Py_False,
1258+
a, b, c);
1259+
}
1260+
1261+
1262+
/*[clinic input]
1263+
group_and_two_opt
1264+
1265+
[
1266+
a: object
1267+
b: object
1268+
c: object
1269+
]
1270+
d: object = None
1271+
e: object = None
1272+
/
1273+
1274+
[clinic start generated code]*/
1275+
1276+
static PyObject *
1277+
group_and_two_opt_impl(PyObject *module, int group_left_1, PyObject *a,
1278+
PyObject *b, PyObject *c, PyObject *d, PyObject *e)
1279+
/*[clinic end generated code: output=1427c4b3c35f24ff input=cdda98eec1e365ea]*/
1280+
{
1281+
return pack_arguments_newref(6, group_left_1 ? Py_True : Py_False,
1282+
a, b, c, d, e);
1283+
}
1284+
1285+
12401286

12411287
/*[clinic input]
12421288
gh_32092_oob
@@ -2455,6 +2501,8 @@ static PyMethodDef tester_methods[] = {
24552501
POSONLY_VARPOS_ARRAY_METHODDEF
24562502
POSONLY_REQ_OPT_VARPOS_ARRAY_METHODDEF
24572503
POSONLY_POSKW_VARPOS_ARRAY_METHODDEF
2504+
GROUP_AND_OPT_METHODDEF
2505+
GROUP_AND_TWO_OPT_METHODDEF
24582506

24592507
GH_32092_OOB_METHODDEF
24602508
GH_32092_KW_PASS_METHODDEF

‎Modules/clinic/_testclinic.c.h‎

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

‎Tools/c-analyzer/cpython/_parser.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ def format_tsv_lines(lines):
321321
_abs('Modules/_remote_debugging/debug_offsets_validation.h'): (25_000, 1000),
322322
_abs('Modules/_remote_debugging/*.h'): (20_000, 1000),
323323
_abs('Modules/_testcapimodule.c'): (20_000, 400),
324+
_abs('Modules/_testclinic.c'): (20_000, 400),
324325
_abs('Modules/expat/expat.h'): (10_000, 500),
325326
_abs('Objects/stringlib/unicode_format.h'): (10_000, 400),
326327
_abs('Objects/typeobject.c'): (380_000, 13_000),

‎Tools/clinic/libclinic/clanguage.py‎

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22
import itertools
3-
import sys
43
import textwrap
54
from typing import TYPE_CHECKING, Literal, Final
65
from operator import attrgetter
@@ -12,7 +11,7 @@
1211
from libclinic.codegen import CRenderData, TemplateDict, CodeGen
1312
from libclinic.language import Language
1413
from libclinic.function import (
15-
Module, Class, Function, Parameter,
14+
Module, Class, Function, Parameter, ParamTuple,
1615
permute_optional_groups,
1716
GETTER, METHOD_INIT,
1817
ACCESSORS, SETTERS)
@@ -22,6 +21,20 @@
2221
from libclinic.app import Clinic
2322

2423

24+
def count_required(subset: ParamTuple) -> int:
25+
"""Return the number of arguments which cannot be omitted.
26+
27+
A parameter in an optional group is passed together with its group,
28+
so only trailing parameters with a default value can be omitted.
29+
"""
30+
count = len(subset)
31+
for p in reversed(subset):
32+
if p.group or not p.is_optional():
33+
break
34+
count -= 1
35+
return count
36+
37+
2538
def c_id(name: str) -> str:
2639
if len(name) == 1 and ord(name) < 256:
2740
if name.isalnum():
@@ -302,18 +315,26 @@ def render_option_group_parsing(
302315
assert group is not None
303316
group.append(p)
304317

305-
count_min = sys.maxsize
306-
count_max = -1
318+
# Map the number of arguments to the subset which accepts it.
319+
subsets: dict[int, ParamTuple] = {}
320+
for subset in permute_optional_groups(left, required, right):
321+
for count in range(count_required(subset), len(subset) + 1):
322+
if count in subsets:
323+
fail(f"Function {f.full_name!r} has an ambiguous group "
324+
f"configuration: a call with {count} argument(s) "
325+
f"can be parsed in more than one way.")
326+
subsets[count] = subset
307327

308328
if limited_capi:
309329
nargs = 'PyTuple_Size(args)'
310330
else:
311331
nargs = 'PyTuple_GET_SIZE(args)'
312332
out.append(f"switch ({nargs}) {{\n")
313-
for subset in permute_optional_groups(left, required, right):
314-
count = len(subset)
315-
count_min = min(count_min, count)
316-
count_max = max(count_max, count)
333+
for count, subset in sorted(subsets.items()):
334+
if count < len(subset):
335+
# The omitted parameters are parsed by the following case.
336+
out.append(f" case {count}:\n")
337+
continue
317338

318339
if count == 0:
319340
out.append(""" case 0:
@@ -327,7 +348,11 @@ def render_option_group_parsing(
327348
d: dict[str, str | int] = {}
328349
d['count'] = count
329350
d['name'] = f.name
330-
d['format_units'] = "".join(p.converter.format_unit for p in subset)
351+
format_units = [p.converter.format_unit for p in subset]
352+
n_required = count_required(subset)
353+
if n_required < count:
354+
format_units.insert(n_required, '|')
355+
d['format_units'] = "".join(format_units)
331356

332357
parse_arguments: list[str] = []
333358
for p in subset:
@@ -354,7 +379,7 @@ def render_option_group_parsing(
354379

355380
out.append(" default:\n")
356381
s = ' PyErr_SetString(PyExc_TypeError, "{} requires {} to {} arguments");\n'
357-
out.append(s.format(f.full_name, count_min, count_max))
382+
out.append(s.format(f.full_name, min(subsets), max(subsets)))
358383
out.append(' goto exit;\n')
359384
out.append("}")
360385

0 commit comments

Comments
 (0)