Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Prev Previous commit
Next Next commit
gh-150097: Move _testclinic to multi-phase init
Convert the static types to heap types. Move empty init vectorcall to
share with existing no-arg __init__ case. Add support for no-argument
vectorcall so the no-arg __init__ cases actually work (rather than
adding an arg). Produce the same error string as the PyArg helpers do.

The vectorcall types no longer have a C address for the generated
`Py_Is` so instead compare the slot function instead. This drops the
requirement that a class using vectorcall declare a type object.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

(cherry-picked from #157968)
  • Loading branch information
cmaloney authored and skirpichev committed Sep 26, 2026
commit 6c3d922f81b55443b15c5458dd4ee2f9e57c4f4e
5 changes: 0 additions & 5 deletions Tools/clinic/libclinic/dsl_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,11 +639,6 @@ def normalize_function_kind(self, fullname: str) -> None:
if not self.kind.new_or_init:
fail("@vectorcall can only be used with __init__ and __new__ "
"methods currently")
# Guaranteed by the __new__ / __init__ checks above.
assert cls is not None
if not cls.type_object:
fail(f"@vectorcall requires the type object of {cls.name!r}, "
f"which was declared without one")

def resolve_return_converter(
self, full_name: str, forced_converter: str
Expand Down
44 changes: 32 additions & 12 deletions Tools/clinic/libclinic/parse_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from libclinic.function import (
Function, Parameter, ParamTuple,
count_required, group_to_variable_name, permute_optional_groups,
GETTER, SETTER, SETTER_AND_DELETER, METHOD_INIT,
GETTER, SETTER, SETTER_AND_DELETER, METHOD_INIT, METHOD_NEW,
ACCESSORS, SETTERS)
from libclinic.converter import CConverter
from libclinic.converters import (
Expand Down Expand Up @@ -1537,19 +1537,22 @@ def create_template_dict(self) -> dict[str, str]:
return d2

def _vectorcall_type_check(self) -> list[str]:
"""Assert `type` is the one type this vectorcall was generated for.
"""Check dispatch function hasn't changed.

The generated code is only correct for that type: __init__ calls
tp_new with no arguments, then the impl. tp_vectorcall is not
inherited, so subclasses never reach it; the assert catches C code
installing the function on a second type.
The generated code is only correct for a type whose tp_new / tp_init is
the parser this vectorcall shadows.
"""
func = self.func
# The DSL parser rejects @vectorcall without a class and type object.
assert func.cls is not None
assert func.cls.type_object
if func.kind is METHOD_INIT:
check = f"_PyType_CAST(type)->tp_init == {func.c_basename}"
elif func.kind is METHOD_NEW:
check = f"_PyType_CAST(type)->tp_new == {func.c_basename}"
else:
raise AssertionError(
f"Unhandled function kind for vectorcall: {func.kind!r}"
)
return [libclinic.normalize_snippet(f"""
assert(Py_Is(_PyType_CAST(type), {func.cls.type_object}));
assert({check});
/* Make sure the type object is immutable: the generated
* vectorcall doesn't deal e.g. with users reassigning __init__. */
assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE));
Expand Down Expand Up @@ -1689,18 +1692,35 @@ def parse_vectorcall_pos_or_kw(self) -> None:
parser_code.extend(self._vectorcall_positional(arity_checked=True))
self.vectorcall_body(*parser_code)

def parse_vectorcall_no_args(self) -> None:
"""No keyword or positional arguments."""
parser_code = self._vectorcall_type_check()
self.codegen.add_include('pycore_modsupport.h',
'_PyArg_NoKwnames()')
parser_code.append(libclinic.normalize_snippet("""
if (nargs) {{
PyErr_SetString(PyExc_TypeError,
"{name}() takes no positional arguments");
goto exit;
}}
if (!_PyArg_NoKwnames("{name}", kwnames)) {{
goto exit;
}}
""", indent=4))
self.vectorcall_body(*parser_code)

def parse_vectorcall(self) -> None:
"""Generate the vectorcall entry point for __new__ / __init__.

Dispatch to specific parser-code builders based on parameter shape.
"""
# Branches ordered to mirror parse_args(). The DSL parser rejects
# @vectorcall with optional groups, and METH_O never applies to
# __new__/__init__. They always have arguments.
# __new__/__init__.
assert not self.has_option_groups()
assert not self.use_meth_o()
if not self.parameters and not self.varpos and not self.var_keyword:
raise NotImplementedError("No argument vectorcall")
self.parse_vectorcall_no_args()
elif self.var_keyword is not None:
self.parse_vectorcall_kw_required()
elif self.pos_only == len(self.parameters):
Expand Down