Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
240 changes: 240 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 @@ -2496,6 +2497,162 @@ 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_init_with_kwargs(self):
block = """
module m
class Foo "FooObject *" "Foo_Type"
@vectorcall
Foo.__init__
source: object = NULL
encoding: str = NULL
errors: str = NULL
"""
func = self.parse_function(block, signatures_in_block=3,
function_index=2)
self.assertTrue(func.vectorcall)

def test_vectorcall_new_with_kwargs(self):
block = """
module m
class Foo "FooObject *" "Foo_Type"
@classmethod
@vectorcall
Foo.__new__
source: object = NULL
*
encoding: str = NULL
errors: str = NULL
"""
func = self.parse_function(block, signatures_in_block=3,
function_index=2)
self.assertTrue(func.vectorcall)

def test_vectorcall_init_no_args(self):
block = """
module m
class Foo "FooObject *" "Foo_Type"
@vectorcall
Foo.__init__
"""
func = self.parse_function(block, signatures_in_block=3,
function_index=2)
self.assertTrue(func.vectorcall)

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 @@ -4136,6 +4293,89 @@ 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_no_args(self):
obj = ac_tester.VcNew()
self.assertIsInstance(obj, ac_tester.VcNew)

def test_vc_new_with_arg(self):
obj = ac_tester.VcNew(1)
self.assertIsInstance(obj, ac_tester.VcNew)

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

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

def test_vc_init_required_pos_only(self):
obj = ac_tester.VcInit(1)
self.assertIsInstance(obj, ac_tester.VcInit)

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

def test_vc_init_with_positional_optional(self):
obj = ac_tester.VcInit(1, 2)
self.assertIsInstance(obj, 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_one_arg(self):
obj = ac_tester.VcNewExact(1)
self.assertIsInstance(obj, ac_tester.VcNewExact)

def test_vc_new_exact_two_args(self):
obj = ac_tester.VcNewExact(1, 2)
self.assertIsInstance(obj, 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_pos(self):
obj = ac_tester.VcNewZeroArg(1)
self.assertIsInstance(obj, ac_tester.VcNewZeroArg)

def test_vc_new_zeroarg_with_kwonly(self):
obj = ac_tester.VcNewZeroArg(b=2)
self.assertIsInstance(obj, ac_tester.VcNewZeroArg)

def test_vc_new_zeroarg_with_both(self):
obj = ac_tester.VcNewZeroArg(1, b=2)
self.assertIsInstance(obj, 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.
Loading