Skip to content

Commit 6c3d922

Browse files
cmaloneyskirpichev
authored andcommitted
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)
1 parent a0723f1 commit 6c3d922

2 files changed

Lines changed: 32 additions & 17 deletions

File tree

‎Tools/clinic/libclinic/dsl_parser.py‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -639,11 +639,6 @@ def normalize_function_kind(self, fullname: str) -> None:
639639
if not self.kind.new_or_init:
640640
fail("@vectorcall can only be used with __init__ and __new__ "
641641
"methods currently")
642-
# Guaranteed by the __new__ / __init__ checks above.
643-
assert cls is not None
644-
if not cls.type_object:
645-
fail(f"@vectorcall requires the type object of {cls.name!r}, "
646-
f"which was declared without one")
647642

648643
def resolve_return_converter(
649644
self, full_name: str, forced_converter: str

‎Tools/clinic/libclinic/parse_args.py‎

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from libclinic.function import (
77
Function, Parameter, ParamTuple,
88
count_required, group_to_variable_name, permute_optional_groups,
9-
GETTER, SETTER, SETTER_AND_DELETER, METHOD_INIT,
9+
GETTER, SETTER, SETTER_AND_DELETER, METHOD_INIT, METHOD_NEW,
1010
ACCESSORS, SETTERS)
1111
from libclinic.converter import CConverter
1212
from libclinic.converters import (
@@ -1537,19 +1537,22 @@ def create_template_dict(self) -> dict[str, str]:
15371537
return d2
15381538

15391539
def _vectorcall_type_check(self) -> list[str]:
1540-
"""Assert `type` is the one type this vectorcall was generated for.
1540+
"""Check dispatch function hasn't changed.
15411541
1542-
The generated code is only correct for that type: __init__ calls
1543-
tp_new with no arguments, then the impl. tp_vectorcall is not
1544-
inherited, so subclasses never reach it; the assert catches C code
1545-
installing the function on a second type.
1542+
The generated code is only correct for a type whose tp_new / tp_init is
1543+
the parser this vectorcall shadows.
15461544
"""
15471545
func = self.func
1548-
# The DSL parser rejects @vectorcall without a class and type object.
1549-
assert func.cls is not None
1550-
assert func.cls.type_object
1546+
if func.kind is METHOD_INIT:
1547+
check = f"_PyType_CAST(type)->tp_init == {func.c_basename}"
1548+
elif func.kind is METHOD_NEW:
1549+
check = f"_PyType_CAST(type)->tp_new == {func.c_basename}"
1550+
else:
1551+
raise AssertionError(
1552+
f"Unhandled function kind for vectorcall: {func.kind!r}"
1553+
)
15511554
return [libclinic.normalize_snippet(f"""
1552-
assert(Py_Is(_PyType_CAST(type), {func.cls.type_object}));
1555+
assert({check});
15531556
/* Make sure the type object is immutable: the generated
15541557
* vectorcall doesn't deal e.g. with users reassigning __init__. */
15551558
assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE));
@@ -1689,18 +1692,35 @@ def parse_vectorcall_pos_or_kw(self) -> None:
16891692
parser_code.extend(self._vectorcall_positional(arity_checked=True))
16901693
self.vectorcall_body(*parser_code)
16911694

1695+
def parse_vectorcall_no_args(self) -> None:
1696+
"""No keyword or positional arguments."""
1697+
parser_code = self._vectorcall_type_check()
1698+
self.codegen.add_include('pycore_modsupport.h',
1699+
'_PyArg_NoKwnames()')
1700+
parser_code.append(libclinic.normalize_snippet("""
1701+
if (nargs) {{
1702+
PyErr_SetString(PyExc_TypeError,
1703+
"{name}() takes no positional arguments");
1704+
goto exit;
1705+
}}
1706+
if (!_PyArg_NoKwnames("{name}", kwnames)) {{
1707+
goto exit;
1708+
}}
1709+
""", indent=4))
1710+
self.vectorcall_body(*parser_code)
1711+
16921712
def parse_vectorcall(self) -> None:
16931713
"""Generate the vectorcall entry point for __new__ / __init__.
16941714
16951715
Dispatch to specific parser-code builders based on parameter shape.
16961716
"""
16971717
# Branches ordered to mirror parse_args(). The DSL parser rejects
16981718
# @vectorcall with optional groups, and METH_O never applies to
1699-
# __new__/__init__. They always have arguments.
1719+
# __new__/__init__.
17001720
assert not self.has_option_groups()
17011721
assert not self.use_meth_o()
17021722
if not self.parameters and not self.varpos and not self.var_keyword:
1703-
raise NotImplementedError("No argument vectorcall")
1723+
self.parse_vectorcall_no_args()
17041724
elif self.var_keyword is not None:
17051725
self.parse_vectorcall_kw_required()
17061726
elif self.pos_only == len(self.parameters):

0 commit comments

Comments
 (0)