-
-
Notifications
You must be signed in to change notification settings - Fork 32.1k
gh-108494: Argument Clinic partial supports of Limited C API #108495
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
:ref:`Argument Clinic <howto-clinic>` now has a partial support of the | ||
:ref:`Limited API <limited-c-api>`. Patch by Victor Stinner. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// For now, only limited C API 3.13 is supported | ||
#define Py_LIMITED_API 0x030d0000 | ||
|
||
/* Always enable assertions */ | ||
#undef NDEBUG | ||
|
||
#include "Python.h" | ||
|
||
|
||
#include "clinic/_testclinic_limited.c.h" | ||
|
||
|
||
/*[clinic input] | ||
module _testclinic_limited | ||
[clinic start generated code]*/ | ||
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=dd408149a4fc0dbb]*/ | ||
|
||
|
||
/*[clinic input] | ||
test_empty_function | ||
|
||
[clinic start generated code]*/ | ||
|
||
static PyObject * | ||
test_empty_function_impl(PyObject *module) | ||
/*[clinic end generated code: output=0f8aeb3ddced55cb input=0dd7048651ad4ae4]*/ | ||
{ | ||
Py_RETURN_NONE; | ||
} | ||
|
||
|
||
/*[clinic input] | ||
my_int_func -> int | ||
|
||
arg: int | ||
/ | ||
|
||
[clinic start generated code]*/ | ||
|
||
static int | ||
my_int_func_impl(PyObject *module, int arg) | ||
/*[clinic end generated code: output=761cd54582f10e4f input=16eb8bba71d82740]*/ | ||
{ | ||
return arg; | ||
} | ||
|
||
|
||
static PyMethodDef tester_methods[] = { | ||
TEST_EMPTY_FUNCTION_METHODDEF | ||
MY_INT_FUNC_METHODDEF | ||
{NULL, NULL} | ||
}; | ||
|
||
static struct PyModuleDef _testclinic_module = { | ||
PyModuleDef_HEAD_INIT, | ||
.m_name = "_testclinic_limited", | ||
.m_size = 0, | ||
.m_methods = tester_methods, | ||
}; | ||
|
||
PyMODINIT_FUNC | ||
PyInit__testclinic_limited(void) | ||
{ | ||
PyObject *m = PyModule_Create(&_testclinic_module); | ||
if (m == NULL) { | ||
return NULL; | ||
} | ||
return m; | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,6 +63,7 @@ | |
|
||
version = '1' | ||
|
||
DEFAULT_LIMITED_CAPI = False | ||
NO_VARARG = "PY_SSIZE_T_MAX" | ||
CLINIC_PREFIX = "__clinic_" | ||
CLINIC_PREFIXED_ARGS = { | ||
|
@@ -1360,7 +1361,21 @@ def parser_body( | |
vararg | ||
) | ||
nargs = f"Py_MIN(nargs, {max_pos})" if max_pos else "0" | ||
if not new_or_init: | ||
|
||
if clinic.limited_capi: | ||
# positional-or-keyword arguments | ||
flags = "METH_VARARGS|METH_KEYWORDS" | ||
|
||
parser_prototype = self.PARSER_PROTOTYPE_KEYWORD | ||
parser_code = [normalize_snippet(""" | ||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "{format_units}:{name}", _keywords, | ||
{parse_arguments})) | ||
goto exit; | ||
""", indent=4)] | ||
argname_fmt = 'args[%d]' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not think that it is correct. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh ok, so I suppose that it can be removed. |
||
declarations = "" | ||
|
||
elif not new_or_init: | ||
flags = "METH_FASTCALL|METH_KEYWORDS" | ||
parser_prototype = self.PARSER_PROTOTYPE_FASTCALL_KEYWORDS | ||
argname_fmt = 'args[%d]' | ||
|
@@ -2111,7 +2126,8 @@ def print_block( | |
self, | ||
block: Block, | ||
*, | ||
core_includes: bool = False | ||
core_includes: bool = False, | ||
clinic: Clinic | None = None, | ||
) -> None: | ||
input = block.input | ||
output = block.output | ||
|
@@ -2140,7 +2156,11 @@ def print_block( | |
write("\n") | ||
|
||
output = '' | ||
if core_includes: | ||
if clinic: | ||
limited_capi = clinic.limited_capi | ||
else: | ||
limited_capi = DEFAULT_LIMITED_CAPI | ||
if core_includes and not limited_capi: | ||
output += textwrap.dedent(""" | ||
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) | ||
# include "pycore_gc.h" // PyGC_Head | ||
|
@@ -2344,6 +2364,7 @@ def __init__( | |
*, | ||
filename: str, | ||
verify: bool = True, | ||
limited_capi: bool = False, | ||
) -> None: | ||
# maps strings to Parser objects. | ||
# (instantiated from the "parsers" global.) | ||
|
@@ -2353,6 +2374,7 @@ def __init__( | |
fail("Custom printers are broken right now") | ||
self.printer = printer or BlockPrinter(language) | ||
self.verify = verify | ||
self.limited_capi = limited_capi | ||
self.filename = filename | ||
self.modules: ModuleDict = {} | ||
self.classes: ClassDict = {} | ||
|
@@ -2450,7 +2472,7 @@ def parse(self, input: str) -> str: | |
self.parsers[dsl_name] = parsers[dsl_name](self) | ||
parser = self.parsers[dsl_name] | ||
parser.parse(block) | ||
printer.print_block(block) | ||
printer.print_block(block, clinic=self) | ||
|
||
# these are destinations not buffers | ||
for name, destination in self.destinations.items(): | ||
|
@@ -2465,7 +2487,7 @@ def parse(self, input: str) -> str: | |
block.input = "dump " + name + "\n" | ||
warn("Destination buffer " + repr(name) + " not empty at end of file, emptying.") | ||
printer.write("\n") | ||
printer.print_block(block) | ||
printer.print_block(block, clinic=self) | ||
continue | ||
|
||
if destination.type == 'file': | ||
|
@@ -2490,7 +2512,7 @@ def parse(self, input: str) -> str: | |
|
||
block.input = 'preserve\n' | ||
printer_2 = BlockPrinter(self.language) | ||
printer_2.print_block(block, core_includes=True) | ||
printer_2.print_block(block, core_includes=True, clinic=self) | ||
write_file(destination.filename, printer_2.f.getvalue()) | ||
continue | ||
|
||
|
@@ -2536,9 +2558,15 @@ def __repr__(self) -> str: | |
def parse_file( | ||
filename: str, | ||
*, | ||
verify: bool = True, | ||
output: str | None = None | ||
ns: argparse.Namespace, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm replying here since I dislike comments lost on a commit, I don't think that many people will notice it. I'm fine with people doing reviews after I merged changes (I like them!).
Free feel to change it in a follow-up PR. Your rationale makes sense and I don't have a strong preference for passing arguments here. Or I can do it if you prefer. I'm still struggling a lot with this code base and I tried to write the minimum working change without touching too many parts of the code. The problem is that I'm not used with the AC design, so I'm not sure if it's the right place to add code...
I understand your frustration. I didn't expect any review at all in fact. I'm always happy to get reviews, even if it's after I merge my change. Today I wanted to push multiple changes to unblock my work on removing private functions: see PR #108499 which kind-of the end of my "PR serie". I'm frequently frustrated by the fact that GitHub doesn't let me create a "patch serie" (multiple PRs depending on each others). So I have to merge my changes one by one, but I'm always bitten by a CI which dislike my change, then I have to wait 30 min for the next issue... On the AC changes, I didn't expect that I wanted to push the basis on AC work to add I'm glad that other people are now working on AC. Adding #include and supporting the limited C API was a long awaited feature for me, I started working on that in 2020 :-) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Follow-up PR: #108504 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Oops, I didn't mean to add a comment on the commit — I meant to comment on the PR. Thanks! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Come on, you know that both Alex and I are active on a daily basis on CPython ;) |
||
output: str | None = None, | ||
) -> None: | ||
verify = not ns.force | ||
limited_capi = ns.limited_capi | ||
# XXX Temporary solution | ||
if os.path.basename(filename) == '_testclinic_limited.c': | ||
print(f"{filename} uses limited C API") | ||
limited_capi = True | ||
if not output: | ||
output = filename | ||
|
||
|
@@ -2560,7 +2588,10 @@ def parse_file( | |
return | ||
|
||
assert isinstance(language, CLanguage) | ||
clinic = Clinic(language, verify=verify, filename=filename) | ||
clinic = Clinic(language, | ||
verify=verify, | ||
filename=filename, | ||
limited_capi=limited_capi) | ||
cooked = clinic.parse(raw) | ||
|
||
write_file(output, cooked) | ||
|
@@ -5987,6 +6018,8 @@ def create_cli() -> argparse.ArgumentParser: | |
cmdline.add_argument("--exclude", type=str, action="append", | ||
help=("a file to exclude in --make mode; " | ||
"can be given multiple times")) | ||
cmdline.add_argument("--limited", dest="limited_capi", action='store_true', | ||
help="use the Limited C API") | ||
cmdline.add_argument("filename", metavar="FILE", type=str, nargs="*", | ||
help="the list of files to process") | ||
return cmdline | ||
|
@@ -6077,7 +6110,7 @@ def run_clinic(parser: argparse.ArgumentParser, ns: argparse.Namespace) -> None: | |
continue | ||
if ns.verbose: | ||
print(path) | ||
parse_file(path, verify=not ns.force) | ||
parse_file(path, ns=ns) | ||
return | ||
|
||
if not ns.filename: | ||
|
@@ -6089,7 +6122,7 @@ def run_clinic(parser: argparse.ArgumentParser, ns: argparse.Namespace) -> None: | |
for filename in ns.filename: | ||
if ns.verbose: | ||
print(filename) | ||
parse_file(filename, output=ns.output, verify=not ns.force) | ||
parse_file(filename, output=ns.output, ns=ns) | ||
|
||
|
||
def main(argv: list[str] | None = None) -> NoReturn: | ||
|
Uh oh!
There was an error while loading. Please reload this page.