Skip to content
174 changes: 174 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,121 @@ 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_zero_arg(self):
block = """
module m
class Foo "FooObject *" "Foo_Type"
@classmethod
@vectorcall zero_arg=_PyFoo_GetEmpty()
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)
self.assertEqual(func.vectorcall_zero_arg, '_PyFoo_GetEmpty()')

def test_vectorcall_zero_arg_with_exact(self):
block = """
module m
class Foo "FooObject *" "Foo_Type"
@classmethod
@vectorcall exact_only zero_arg=get_cached()
Foo.__new__
x: object = NULL
/
"""
func = self.parse_function(block, signatures_in_block=3,
function_index=2)
self.assertTrue(func.vectorcall)
self.assertTrue(func.vectorcall_exact_only)
self.assertEqual(func.vectorcall_zero_arg, 'get_cached()')

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 +4433,64 @@ 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)

def test_vc_new_zeroarg_no_args(self):
# zero_arg returns Py_None when called with no arguments
result = ac_tester.VcNewZeroArg()
self.assertIs(result, None)

def test_vc_new_zeroarg_with_args(self):
self.assertIsInstance(ac_tester.VcNewZeroArg(1), ac_tester.VcNewZeroArg)
self.assertIsInstance(ac_tester.VcNewZeroArg(b=2), ac_tester.VcNewZeroArg)
self.assertIsInstance(ac_tester.VcNewZeroArg(1, b=2), ac_tester.VcNewZeroArg)

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


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.
136 changes: 136 additions & 0 deletions Modules/_testclinic.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ 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;
static PyTypeObject VcInit_Type;
static PyTypeObject VcNewExact_Type;
static PyTypeObject VcNewZeroArg_Type;

#include "clinic/_testclinic.c.h"


Expand Down Expand Up @@ -2315,6 +2322,123 @@ 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,
};


/* VcNewZeroArg: __new__ with zero_arg; returns Py_None when called with no args */

/*[clinic input]
class _testclinic.VcNewZeroArg "PyObject *" "&VcNewZeroArg_Type"
@classmethod
@vectorcall zero_arg=Py_NewRef(Py_None)
_testclinic.VcNewZeroArg.__new__ as vc_zeroarg_new
a: object = None
/
*
b: object = None
[clinic start generated code]*/

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

static PyTypeObject VcNewZeroArg_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "_testclinic.VcNewZeroArg",
.tp_basicsize = sizeof(PyObject),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = vc_zeroarg_new,
.tp_vectorcall = vc_zeroarg_vectorcall,
};


/*[clinic input]
output push
destination kwarg new file '{dirname}/clinic/_testclinic_kwds.c.h'
Expand Down Expand Up @@ -2534,6 +2658,18 @@ 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;
}
if (PyModule_AddType(m, &VcNewZeroArg_Type) < 0) {
goto error;
}
return m;

error:
Expand Down
Loading
Loading