Skip to content

Commit c121f81

Browse files
cmaloneyclaude
andcommitted
gh-87613: Support *args in Argument Clinic vectorcall
`@vectorcall` accepted `*args`, but the generated code expected an args tuple. Use fastcall form to parse the args array. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent ef1fce0 commit c121f81

2 files changed

Lines changed: 48 additions & 9 deletions

File tree

‎Lib/test/clinic.test.c‎

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5220,6 +5220,7 @@ Test___init___impl(TestObj *self, PyObject *args)
52205220
/*[clinic end generated code: output=0e5836c40dbc2397 input=a615a4485c0fc3e2]*/
52215221

52225222
/*[clinic input]
5223+
@vectorcall
52235224
@classmethod
52245225
Test.__new__
52255226
*args: tuple
@@ -5261,9 +5262,37 @@ Test(PyTypeObject *type, PyObject *args, PyObject *kwargs)
52615262
return return_value;
52625263
}
52635264

5265+
static PyObject *
5266+
Test_vectorcall(PyObject *type, PyObject *const *args,
5267+
size_t nargsf, PyObject *kwnames)
5268+
{
5269+
PyObject *return_value = NULL;
5270+
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
5271+
PyObject *__clinic_args = NULL;
5272+
5273+
assert(_PyType_CAST(type)->tp_new == Test);
5274+
/* Make sure the type object is immutable: the generated
5275+
* vectorcall doesn't deal e.g. with users reassigning __init__. */
5276+
assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE));
5277+
if (!_PyArg_NoKwnames("Test", kwnames)) {
5278+
goto exit;
5279+
}
5280+
__clinic_args = PyTuple_FromArray(args, nargs);
5281+
if (__clinic_args == NULL) {
5282+
goto exit;
5283+
}
5284+
return_value = Test_impl(_PyType_CAST(type), __clinic_args);
5285+
5286+
exit:
5287+
/* Cleanup for args */
5288+
Py_XDECREF(__clinic_args);
5289+
5290+
return return_value;
5291+
}
5292+
52645293
static PyObject *
52655294
Test_impl(PyTypeObject *type, PyObject *args)
5266-
/*[clinic end generated code: output=e6fba0c8951882fd input=8ce30adb836aeacb]*/
5295+
/*[clinic end generated code: output=07f68e3971ed24cc input=1dc824fd6cec5f8e]*/
52675296

52685297

52695298
/*[clinic input]

‎Tools/clinic/libclinic/parse_args.py‎

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -923,14 +923,14 @@ def render_option_groups_parse_tuple(
923923
parser_code.append(" }}")
924924
return parser_code
925925

926-
def _parse_vararg(self) -> str:
926+
def _parse_vararg(self, *, fastcall: bool) -> str:
927927
assert self.varpos is not None
928928
c = self.varpos.converter
929929
assert isinstance(c, libclinic.converters.VarPosCConverter)
930930
return c.parse_vararg(pos_only=self.pos_only,
931931
min_pos=self.min_pos,
932932
max_pos=self.max_pos,
933-
fastcall=self.fastcall,
933+
fastcall=fastcall,
934934
limited_capi=self.limited_capi)
935935

936936
def _parse_kwarg(self) -> str:
@@ -963,6 +963,7 @@ def _parse_positional_args(
963963
argname_fmt: str,
964964
nargs: str,
965965
limited_capi: bool,
966+
fastcall: bool,
966967
) -> list[str] | None:
967968
"""Emit per-parameter positional argument parsing.
968969
@@ -995,8 +996,8 @@ def _parse_positional_args(
995996
if self.min_pos < len(self.parameters):
996997
parser_code.append("skip_optional:")
997998
if self.varpos:
998-
parser_code.append(libclinic.normalize_snippet(self._parse_vararg(),
999-
indent=4))
999+
parser_code.append(libclinic.normalize_snippet(
1000+
self._parse_vararg(fastcall=fastcall), indent=4))
10001001
elif self.var_keyword:
10011002
parser_code.append(libclinic.normalize_snippet(self._parse_kwarg(),
10021003
indent=4))
@@ -1090,7 +1091,7 @@ def parse_pos_only(self) -> None:
10901091

10911092
pos_code = self._parse_positional_args(
10921093
argname_fmt=argname_fmt, nargs=nargs,
1093-
limited_capi=self.limited_capi)
1094+
limited_capi=self.limited_capi, fastcall=self.fastcall)
10941095
if pos_code is not None:
10951096
parser_code.extend(pos_code)
10961097
else:
@@ -1148,7 +1149,8 @@ def parse_var_keyword(self) -> None:
11481149
parser_code.append("skip_optional:")
11491150

11501151
if self.varpos:
1151-
parser_code.append(libclinic.normalize_snippet(self._parse_vararg(), indent=4))
1152+
parser_code.append(libclinic.normalize_snippet(
1153+
self._parse_vararg(fastcall=self.fastcall), indent=4))
11521154
if self.var_keyword:
11531155
parser_code.append(libclinic.normalize_snippet(self._parse_kwarg(), indent=4))
11541156
self.parser_body(*parser_code)
@@ -1376,7 +1378,12 @@ def parse_general(self, clang: CLanguage) -> None:
13761378
if add_label:
13771379
parser_code.append("%s:" % add_label)
13781380
if self.varpos:
1379-
parser_code.append(libclinic.normalize_snippet(self._parse_vararg(), indent=4))
1381+
# The vectorcall helper gets an array of arguments, not a
1382+
# tuple: use the converters' fastcall convention, which
1383+
# handles that.
1384+
fastcall = True if self.func.vectorcall else self.fastcall
1385+
parser_code.append(libclinic.normalize_snippet(
1386+
self._parse_vararg(fastcall=fastcall), indent=4))
13801387
else:
13811388
self.use_converters()
13821389

@@ -1617,8 +1624,11 @@ def _vectorcall_positional(self, *,
16171624
16181625
arity_checked: Already have a number of arguments check.
16191626
"""
1627+
# The vectorcall gets an array of arguments, not a tuple: use the
1628+
# converters' fastcall convention, which handles that.
16201629
pos_code = self._parse_positional_args(
1621-
argname_fmt='args[%d]', nargs='nargs', limited_capi=False)
1630+
argname_fmt='args[%d]', nargs='nargs', limited_capi=False,
1631+
fastcall=True)
16221632
# Converter support was validated when @vectorcall was parsed.
16231633
assert pos_code is not None
16241634
if arity_checked:

0 commit comments

Comments
 (0)