Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
947e149
gh-87613: Argument Cliic @vectorcall decorator
cmaloney Feb 22, 2026
a9d0d6f
add blurb
cmaloney Mar 1, 2026
15f74e8
Move enumerate, reversed in enum.c to AC vectorcall
cmaloney Mar 1, 2026
a243517
Move tuple to AC vectorcall
cmaloney Mar 1, 2026
3698a32
Add _testclinic vectorcall sample + testing
cmaloney Mar 18, 2026
115a524
Merge upstream/main into ac_add_vectorcall
cmaloney May 18, 2026
04a148c
Simplify some test cases
cmaloney May 18, 2026
61746eb
Merge remote-tracking branch 'upstream/main' into ac_add_vectorcall
cmaloney May 18, 2026
b5d2e40
Ignore very large _testclinic
cmaloney May 19, 2026
71b0d8e
Up limit a bit more
cmaloney May 19, 2026
b96c326
Exclude more _testclinic from globals
cmaloney May 19, 2026
379fd85
Remove zero_arg case
cmaloney May 19, 2026
e9aff2f
Fix lint
cmaloney May 19, 2026
446bce5
Move to dataclass for vc args
cmaloney May 19, 2026
420d705
Fix test failures, refactor to dedup
cmaloney May 20, 2026
e4d18aa
Simplify now there's only exact_only
cmaloney May 20, 2026
1dcc72b
Merge branch 'main' into ac_add_vectorcall
cmaloney May 21, 2026
7e089b0
Remove removed class from globals-to-fix.tsv
cmaloney May 21, 2026
7f56bb0
Refactor kwarg parsing to be a helper function when emitting two pars…
cmaloney May 22, 2026
61ada45
Add a test class where all args are keyword allowed to increase cover…
cmaloney May 22, 2026
b1cd7b6
remove unnecessary branch in keyword parsing helper
cmaloney May 22, 2026
2a7c068
de-duplicate prototypes
cmaloney May 22, 2026
a19e9a8
Exercise critical section generation in testclinic
cmaloney May 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions Lib/test/test_clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,7 @@ def test_directive_output_invalid_command(self):
- 'impl_prototype'
- 'parser_prototype'
- 'parser_definition'
- 'vectorcall_definition'
- 'cpp_endif'
- 'methoddef_ifndef'
- 'impl_definition'
Expand Down Expand Up @@ -2677,6 +2678,89 @@ def test_duplicate_coexist(self):
"""
self.expect_failure(block, err, lineno=2)

def test_duplicate_vectorcall(self):
err = "Called @vectorcall twice"
block = """
module m
class Foo "FooObject *" ""
@vectorcall
@vectorcall
Foo.__init__
"""
self.expect_failure(block, err, lineno=3)

def test_vectorcall_on_regular_method(self):
err = "@vectorcall can only be used with __init__ and __new__ methods"
block = """
module m
class Foo "FooObject *" ""
@vectorcall
Foo.some_method
"""
self.expect_failure(block, err, lineno=3)

def test_vectorcall_on_module_function(self):
err = "@vectorcall can only be used with __init__ and __new__ methods"
block = """
module m
@vectorcall
m.fn
"""
self.expect_failure(block, err, lineno=2)

def test_vectorcall_on_init(self):
block = """
module m
class Foo "FooObject *" "Foo_Type"
@vectorcall
Foo.__init__
iterable: object = NULL
/
"""
func = self.parse_function(block, signatures_in_block=3,
function_index=2)
self.assertTrue(func.vectorcall)
self.assertFalse(func.vectorcall.exact_only)

def test_vectorcall_on_new(self):
block = """
module m
class Foo "FooObject *" "Foo_Type"
@classmethod
@vectorcall
Foo.__new__
x: object = NULL
/
"""
func = self.parse_function(block, signatures_in_block=3,
function_index=2)
self.assertTrue(func.vectorcall)
self.assertFalse(func.vectorcall.exact_only)

def test_vectorcall_exact_only(self):
block = """
module m
class Foo "FooObject *" "Foo_Type"
@vectorcall exact_only
Foo.__init__
iterable: object = NULL
/
"""
func = self.parse_function(block, signatures_in_block=3,
function_index=2)
self.assertTrue(func.vectorcall)
self.assertTrue(func.vectorcall.exact_only)

def test_vectorcall_invalid_kwarg(self):
err = "unknown argument"
block = """
module m
class Foo "FooObject *" ""
@vectorcall bogus=True
Foo.__init__
"""
self.expect_failure(block, err, lineno=2)

def test_unused_param(self):
block = self.parse("""
module foo
Expand Down Expand Up @@ -4317,6 +4401,49 @@ def test_kwds_with_pos_only_and_stararg(self):
self.assertEqual(ac_tester.kwds_with_pos_only_and_stararg(1, 2, *args, **kwds), (1, 2, args, kwds))


@unittest.skipIf(ac_tester is None, "_testclinic is missing")
class VectorcallFunctionalTest(unittest.TestCase):
"""Runtime tests for @vectorcall exemplar types."""

def test_vc_new(self):
self.assertIsInstance(ac_tester.VcNew(), ac_tester.VcNew)
self.assertIsInstance(ac_tester.VcNew(1), ac_tester.VcNew)
self.assertIsInstance(ac_tester.VcNew(a=1), ac_tester.VcNew)

def test_vc_new_rejects_extra_args(self):
with self.assertRaises(TypeError):
ac_tester.VcNew(1, 2)

def test_vc_init(self):
self.assertIsInstance(ac_tester.VcInit(1), ac_tester.VcInit)
self.assertIsInstance(ac_tester.VcInit(1, 2), ac_tester.VcInit)
self.assertIsInstance(ac_tester.VcInit(1, b=2), ac_tester.VcInit)

def test_vc_init_missing_required(self):
with self.assertRaises(TypeError):
ac_tester.VcInit()

def test_vc_init_rejects_a_as_keyword(self):
# 'a' is positional-only
with self.assertRaises(TypeError):
ac_tester.VcInit(a=1)

def test_vc_new_exact(self):
self.assertIsInstance(ac_tester.VcNewExact(1), ac_tester.VcNewExact)
self.assertIsInstance(ac_tester.VcNewExact(1, 2), ac_tester.VcNewExact)

def test_vc_new_exact_missing_required(self):
with self.assertRaises(TypeError):
ac_tester.VcNewExact()

def test_vc_new_exact_subclass(self):
# exact_only: subclass goes through non-vectorcall (tp_new) path
Sub = type('Sub', (ac_tester.VcNewExact,), {})
obj = Sub(1)
self.assertIsInstance(obj, Sub)
self.assertIsInstance(obj, ac_tester.VcNewExact)


class LimitedCAPIOutputTests(unittest.TestCase):

def setUp(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add a ``@vectorcall`` decorator to Argument Clinic that can be used on
``__init__`` and ``__new__`` which generates :ref:`vectorcall` argument
parsing.
102 changes: 102 additions & 0 deletions Modules/_testclinic.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ custom_converter(PyObject *obj, custom_t *val)
}


/* Forward declarations for vectorcall exemplar types, needed because
* clinic/_testclinic.c.h is included before the type definitions. */
static PyTypeObject VcNew_Type;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created gh-150097 to track that we shouldn't need to do this

static PyTypeObject VcInit_Type;
static PyTypeObject VcNewExact_Type;
#include "clinic/_testclinic.c.h"


Expand Down Expand Up @@ -2315,6 +2320,94 @@ output pop
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=e7c7c42daced52b0]*/


/* @vectorcall test types. One type per exemplar because tp_vectorcall is a single slot. */

/* VcNew: __new__ with one optional positional-or-keyword arg */

/*[clinic input]
class _testclinic.VcNew "PyObject *" "&VcNew_Type"
@classmethod
@vectorcall
_testclinic.VcNew.__new__ as vc_plain_new
a: object = None
[clinic start generated code]*/

static PyObject *
vc_plain_new_impl(PyTypeObject *type, PyObject *a)
/*[clinic end generated code: output=55b273e9797a3013 input=e15d88606280badc]*/
{
return type->tp_alloc(type, 0);
}

static PyTypeObject VcNew_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "_testclinic.VcNew",
.tp_basicsize = sizeof(PyObject),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = vc_plain_new,
.tp_vectorcall = vc_plain_vectorcall,
};


/* VcInit: __init__ with one required positional-only and one optional keyword arg */

/*[clinic input]
class _testclinic.VcInit "PyObject *" "&VcInit_Type"
@vectorcall
_testclinic.VcInit.__init__ as vc_posorkw_init
a: object
/
b: object = None
[clinic start generated code]*/

static int
vc_posorkw_init_impl(PyObject *self, PyObject *a, PyObject *b)
/*[clinic end generated code: output=6018424ba9fb0744 input=25e4c2b792040c31]*/
{
return 0;
}

static PyTypeObject VcInit_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "_testclinic.VcInit",
.tp_basicsize = sizeof(PyObject),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyType_GenericNew,
.tp_init = vc_posorkw_init,
.tp_vectorcall = vc_posorkw_vectorcall,
};


/* VcNewExact: __new__ with exact_only; subclasses fall back to tp_new */

/*[clinic input]
class _testclinic.VcNewExact "PyObject *" "&VcNewExact_Type"
@classmethod
@vectorcall exact_only
_testclinic.VcNewExact.__new__ as vc_exact_new
a: object
/
b: object = None
[clinic start generated code]*/

static PyObject *
vc_exact_new_impl(PyTypeObject *type, PyObject *a, PyObject *b)
/*[clinic end generated code: output=e88217e36443b698 input=ea86a1ab634c93a6]*/
{
return type->tp_alloc(type, 0);
}

static PyTypeObject VcNewExact_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "_testclinic.VcNewExact",
.tp_basicsize = sizeof(PyObject),
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_new = vc_exact_new,
.tp_vectorcall = vc_exact_vectorcall,
};



/*[clinic input]
output push
destination kwarg new file '{dirname}/clinic/_testclinic_kwds.c.h'
Expand Down Expand Up @@ -2534,6 +2627,15 @@ PyInit__testclinic(void)
if (PyModule_AddType(m, &DeprKwdInitNoInline) < 0) {
goto error;
}
if (PyModule_AddType(m, &VcNew_Type) < 0) {
goto error;
}
if (PyModule_AddType(m, &VcInit_Type) < 0) {
goto error;
}
if (PyModule_AddType(m, &VcNewExact_Type) < 0) {
goto error;
}
return m;

error:
Expand Down
Loading
Loading