Skip to content

Commit 9159287

Browse files
vstinnerpicnixz
andauthored
gh-144175: Add PyArg_ParseArray() function (#144283)
Add PyArg_ParseArray() and PyArg_ParseArrayAndKeywords() functions to parse arguments of functions using the METH_FASTCALL calling convention. Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
1 parent c1d7768 commit 9159287

File tree

7 files changed

+204
-14
lines changed

7 files changed

+204
-14
lines changed

Doc/c-api/arg.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,28 @@ API Functions
516516
}
517517
518518
519+
.. c:function:: int PyArg_ParseArray(PyObject *const *args, Py_ssize_t nargs, const char *format, ...)
520+
521+
Parse the parameters of a function that takes only array parameters into
522+
local variables (that is, a function using the :c:macro:`METH_FASTCALL`
523+
calling convention).
524+
Returns true on success; on failure, it returns false and raises the
525+
appropriate exception.
526+
527+
.. versionadded:: next
528+
529+
530+
.. c:function:: int PyArg_ParseArrayAndKeywords(PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames, const char *format, const char * const *kwlist, ...)
531+
532+
Parse the parameters of a function that takes both array and keyword
533+
parameters into local variables (that is, a function using the
534+
:c:macro:`METH_FASTCALL` ``|`` :c:macro:`METH_KEYWORDS` calling convention).
535+
Returns true on success; on failure, it returns false and raises the
536+
appropriate exception.
537+
538+
.. versionadded:: next
539+
540+
519541
.. c:function:: int PyArg_UnpackTuple(PyObject *args, const char *name, Py_ssize_t min, Py_ssize_t max, ...)
520542
521543
A simpler form of parameter retrieval which does not use a format string to

Doc/whatsnew/3.15.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1607,6 +1607,11 @@ C API changes
16071607
New features
16081608
------------
16091609

1610+
* Add :c:func:`PyArg_ParseArray` and :c:func:`PyArg_ParseArrayAndKeywords`
1611+
functions to parse arguments of functions using the :c:macro:`METH_FASTCALL`
1612+
calling convention.
1613+
(Contributed by Victor Stinner in :gh:`144175`.)
1614+
16101615
* Add the following functions for the new :class:`frozendict` type:
16111616

16121617
* :c:func:`PyAnyDict_Check`

Include/cpython/modsupport.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22
# error "this header file must not be included directly"
33
#endif
44

5+
PyAPI_FUNC(int) PyArg_ParseArray(
6+
PyObject *const *args,
7+
Py_ssize_t nargs,
8+
const char *format,
9+
...);
10+
PyAPI_FUNC(int) PyArg_ParseArrayAndKeywords(
11+
PyObject *const *args,
12+
Py_ssize_t nargs,
13+
PyObject *kwnames,
14+
const char *format,
15+
const char * const *kwlist,
16+
...);
17+
518
// A data structure that can be used to run initialization code once in a
619
// thread-safe manner. The C++11 equivalent is std::call_once.
720
typedef struct {

Lib/test/test_capi/test_modsupport.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,22 @@ def test_negative_freethreading(self, modname, minor, build):
152152
msg = "only compatible with free-threaded CPython"
153153
with self.assertRaisesRegex(ImportError, msg):
154154
_testcapi.pyabiinfo_check(modname, 1, minor, ft_flag, build, 0)
155+
156+
157+
class TestModsupport(unittest.TestCase):
158+
def test_pyarg_parsearray(self):
159+
func = _testcapi.pyarg_parsearray
160+
self.assertEqual(func(1, 2), (1, 2, 0))
161+
self.assertEqual(func(1, 2, 3), (1, 2, 3))
162+
self.assertRaises(TypeError, func, 1)
163+
self.assertRaises(TypeError, func, "str", 2)
164+
165+
def test_funcandkeywords(self):
166+
func = _testcapi.pyarg_parsearrayandkeywords
167+
self.assertEqual(func(1, 2), (1, 2, 0))
168+
self.assertEqual(func(1, 2, 3), (1, 2, 3))
169+
self.assertEqual(func(1, b=2), (1, 2, 0))
170+
self.assertEqual(func(1, b=2, c=3), (1, 2, 3))
171+
self.assertRaises(TypeError, func, 1)
172+
self.assertRaises(TypeError, func, "str", 2)
173+
self.assertRaises(TypeError, func, 1, z=2)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add :c:func:`PyArg_ParseArray` and :c:func:`PyArg_ParseArrayAndKeywords`
2+
functions to parse arguments of functions using the :c:macro:`METH_FASTCALL`
3+
calling convention. Patch by Victor Stinner.

Modules/_testcapi/modsupport.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,36 @@ pyabiinfo_check(PyObject *Py_UNUSED(module), PyObject *args)
2525
Py_RETURN_NONE;
2626
}
2727

28+
static PyObject *
29+
pyarg_parsearray(PyObject* self, PyObject* const* args, Py_ssize_t nargs)
30+
{
31+
int a, b, c = 0;
32+
if (!PyArg_ParseArray(args, nargs, "ii|i", &a, &b, &c)) {
33+
return NULL;
34+
}
35+
return Py_BuildValue("iii", a, b, c);
36+
}
37+
38+
static PyObject *
39+
pyarg_parsearrayandkeywords(PyObject* self, PyObject* const* args,
40+
Py_ssize_t nargs, PyObject* kwnames)
41+
{
42+
int a, b, c = 0;
43+
const char *kwlist[] = {"a", "b", "c", NULL};
44+
if (!PyArg_ParseArrayAndKeywords(args, nargs, kwnames,
45+
"ii|i", kwlist,
46+
&a, &b, &c)) {
47+
return NULL;
48+
}
49+
return Py_BuildValue("iii", a, b, c);
50+
}
51+
2852
static PyMethodDef TestMethods[] = {
2953
{"pyabiinfo_check", pyabiinfo_check, METH_VARARGS},
54+
{"pyarg_parsearray", _PyCFunction_CAST(pyarg_parsearray), METH_FASTCALL},
55+
{"pyarg_parsearrayandkeywords",
56+
_PyCFunction_CAST(pyarg_parsearrayandkeywords),
57+
METH_FASTCALL | METH_KEYWORDS},
3058
{NULL},
3159
};
3260

Python/getargs.c

Lines changed: 114 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,15 @@ static const char *convertsimple(PyObject *, const char **, va_list *, int,
5757
static Py_ssize_t convertbuffer(PyObject *, const void **p, const char **);
5858
static int getbuffer(PyObject *, Py_buffer *, const char**);
5959

60-
static int vgetargskeywords(PyObject *, PyObject *,
61-
const char *, const char * const *, va_list *, int);
60+
static int
61+
vgetargskeywords(PyObject *args, PyObject *kwargs,
62+
const char *format, const char * const *kwlist,
63+
va_list *p_va, int flags);
64+
static int
65+
vgetargskeywords_impl(PyObject *const *args, Py_ssize_t nargs,
66+
PyObject *kwargs, PyObject *kwnames,
67+
const char *format, const char * const *kwlist,
68+
va_list *p_va, int flags);
6269
static int vgetargskeywordsfast(PyObject *, PyObject *,
6370
struct _PyArg_Parser *, va_list *, int);
6471
static int vgetargskeywordsfast_impl(PyObject *const *args, Py_ssize_t nargs,
@@ -129,6 +136,40 @@ _PyArg_ParseStack(PyObject *const *args, Py_ssize_t nargs, const char *format, .
129136
return retval;
130137
}
131138

139+
int
140+
PyArg_ParseArray(PyObject *const *args, Py_ssize_t nargs, const char *format, ...)
141+
{
142+
va_list va;
143+
va_start(va, format);
144+
int retval = vgetargs1_impl(NULL, args, nargs, format, &va, 0);
145+
va_end(va);
146+
return retval;
147+
}
148+
149+
int
150+
PyArg_ParseArrayAndKeywords(PyObject *const *args, Py_ssize_t nargs,
151+
PyObject *kwnames,
152+
const char *format,
153+
const char * const *kwlist, ...)
154+
{
155+
if ((args == NULL && nargs != 0) ||
156+
(kwnames != NULL && !PyTuple_Check(kwnames)) ||
157+
format == NULL ||
158+
kwlist == NULL)
159+
{
160+
PyErr_BadInternalCall();
161+
return 0;
162+
}
163+
164+
va_list va;
165+
va_start(va, kwlist);
166+
int retval = vgetargskeywords_impl(args, nargs, NULL, kwnames, format,
167+
kwlist, &va, 0);
168+
va_end(va);
169+
return retval;
170+
}
171+
172+
132173
int
133174
PyArg_VaParse(PyObject *args, const char *format, va_list va)
134175
{
@@ -1612,11 +1653,27 @@ PyArg_ValidateKeywordArguments(PyObject *kwargs)
16121653
static PyObject *
16131654
new_kwtuple(const char * const *keywords, int total, int pos);
16141655

1656+
static PyObject*
1657+
find_keyword_str(PyObject *kwnames, PyObject *const *kwstack, const char *key)
1658+
{
1659+
Py_ssize_t nkwargs = PyTuple_GET_SIZE(kwnames);
1660+
for (Py_ssize_t i = 0; i < nkwargs; i++) {
1661+
PyObject *kwname = PyTuple_GET_ITEM(kwnames, i);
1662+
assert(PyUnicode_Check(kwname));
1663+
if (PyUnicode_EqualToUTF8(kwname, key)) {
1664+
return Py_NewRef(kwstack[i]);
1665+
}
1666+
}
1667+
return NULL;
1668+
}
1669+
16151670
#define IS_END_OF_FORMAT(c) (c == '\0' || c == ';' || c == ':')
16161671

16171672
static int
1618-
vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
1619-
const char * const *kwlist, va_list *p_va, int flags)
1673+
vgetargskeywords_impl(PyObject *const *args, Py_ssize_t nargs,
1674+
PyObject *kwargs, PyObject *kwnames,
1675+
const char *format, const char * const *kwlist,
1676+
va_list *p_va, int flags)
16201677
{
16211678
char msgbuf[512];
16221679
int levels[32];
@@ -1625,16 +1682,18 @@ vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
16251682
int max = INT_MAX;
16261683
int i, pos, len;
16271684
int skip = 0;
1628-
Py_ssize_t nargs, nkwargs;
1685+
Py_ssize_t nkwargs;
16291686
freelistentry_t static_entries[STATIC_FREELIST_ENTRIES];
16301687
freelist_t freelist;
1688+
PyObject * const *kwstack = NULL;
16311689

16321690
freelist.entries = static_entries;
16331691
freelist.first_available = 0;
16341692
freelist.entries_malloced = 0;
16351693

1636-
assert(args != NULL && PyTuple_Check(args));
1694+
assert(args != NULL || nargs == 0);
16371695
assert(kwargs == NULL || PyDict_Check(kwargs));
1696+
assert(kwnames == NULL || PyTuple_Check(kwnames));
16381697
assert(format != NULL);
16391698
assert(kwlist != NULL);
16401699
assert(p_va != NULL);
@@ -1672,8 +1731,16 @@ vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
16721731
freelist.entries_malloced = 1;
16731732
}
16741733

1675-
nargs = PyTuple_GET_SIZE(args);
1676-
nkwargs = (kwargs == NULL) ? 0 : PyDict_GET_SIZE(kwargs);
1734+
if (kwargs != NULL) {
1735+
nkwargs = PyDict_GET_SIZE(kwargs);
1736+
}
1737+
else if (kwnames != NULL) {
1738+
nkwargs = PyTuple_GET_SIZE(kwnames);
1739+
kwstack = args + nargs;
1740+
}
1741+
else {
1742+
nkwargs = 0;
1743+
}
16771744
if (nargs + nkwargs > len) {
16781745
/* Adding "keyword" (when nargs == 0) prevents producing wrong error
16791746
messages in some special cases (see bpo-31229). */
@@ -1757,11 +1824,16 @@ vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
17571824
if (!skip) {
17581825
PyObject *current_arg;
17591826
if (i < nargs) {
1760-
current_arg = Py_NewRef(PyTuple_GET_ITEM(args, i));
1827+
current_arg = Py_NewRef(args[i]);
17611828
}
17621829
else if (nkwargs && i >= pos) {
1763-
if (PyDict_GetItemStringRef(kwargs, kwlist[i], &current_arg) < 0) {
1764-
return cleanreturn(0, &freelist);
1830+
if (kwargs != NULL) {
1831+
if (PyDict_GetItemStringRef(kwargs, kwlist[i], &current_arg) < 0) {
1832+
return cleanreturn(0, &freelist);
1833+
}
1834+
}
1835+
else {
1836+
current_arg = find_keyword_str(kwnames, kwstack, kwlist[i]);
17651837
}
17661838
if (current_arg) {
17671839
--nkwargs;
@@ -1846,8 +1918,13 @@ vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
18461918
/* make sure there are no arguments given by name and position */
18471919
for (i = pos; i < nargs; i++) {
18481920
PyObject *current_arg;
1849-
if (PyDict_GetItemStringRef(kwargs, kwlist[i], &current_arg) < 0) {
1850-
return cleanreturn(0, &freelist);
1921+
if (kwargs != NULL) {
1922+
if (PyDict_GetItemStringRef(kwargs, kwlist[i], &current_arg) < 0) {
1923+
return cleanreturn(0, &freelist);
1924+
}
1925+
}
1926+
else {
1927+
current_arg = find_keyword_str(kwnames, kwstack, kwlist[i]);
18511928
}
18521929
if (current_arg) {
18531930
Py_DECREF(current_arg);
@@ -1863,7 +1940,20 @@ vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
18631940
}
18641941
/* make sure there are no extraneous keyword arguments */
18651942
j = 0;
1866-
while (PyDict_Next(kwargs, &j, &key, NULL)) {
1943+
while (1) {
1944+
if (kwargs != NULL) {
1945+
if (!PyDict_Next(kwargs, &j, &key, NULL)) {
1946+
break;
1947+
}
1948+
}
1949+
else {
1950+
if (j >= nkwargs) {
1951+
break;
1952+
}
1953+
key = PyTuple_GET_ITEM(kwnames, j);
1954+
j++;
1955+
}
1956+
18671957
int match = 0;
18681958
if (!PyUnicode_Check(key)) {
18691959
PyErr_SetString(PyExc_TypeError,
@@ -1921,6 +2011,16 @@ vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
19212011
return cleanreturn(1, &freelist);
19222012
}
19232013

2014+
static int
2015+
vgetargskeywords(PyObject *argstuple, PyObject *kwargs,
2016+
const char *format, const char * const *kwlist,
2017+
va_list *p_va, int flags)
2018+
{
2019+
PyObject *const *args = _PyTuple_ITEMS(argstuple);
2020+
Py_ssize_t nargs = PyTuple_GET_SIZE(argstuple);
2021+
return vgetargskeywords_impl(args, nargs, kwargs, NULL,
2022+
format, kwlist, p_va, flags);
2023+
}
19242024

19252025
static int
19262026
scan_keywords(const char * const *keywords, int *ptotal, int *pposonly)

0 commit comments

Comments
 (0)