Skip to content

gh-85283: Extending Argument Clinic to support to use the limited C API. #26080

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

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
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
48 changes: 40 additions & 8 deletions Tools/clinic/clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,7 @@ def __init__(self, filename):
super().__init__(filename)
self.cpp = cpp.Monitor(filename)
self.cpp.fail = fail
self.use_limited_api = False

def parse_line(self, line):
self.cpp.writeline(line)
Expand Down Expand Up @@ -835,7 +836,7 @@ def parser_body(prototype, *fields, declarations=''):
parser_definition = parser_body(parser_prototype, ' {option_group_parsing}')

elif not requires_defining_class and pos_only == len(parameters):
if not new_or_init:
if not new_or_init and not self.use_limited_api:
# positional-only, but no option groups
# we only need one call to _PyArg_ParseStack

Expand Down Expand Up @@ -874,7 +875,14 @@ def parser_body(prototype, *fields, declarations=''):
""", indent=4) % (nargs, i + 1))
parser_code.append(normalize_snippet(parsearg, indent=4))

if parser_code is not None:
if self.use_limited_api:
parser_code = [normalize_snippet("""
if (!PyArg_ParseTuple(args, "{format_units}:{name}",
{parse_arguments})) {{
goto exit;
}}
""", indent=4)]
elif parser_code is not None:
if has_optional:
parser_code.append("skip_optional:")
else:
Expand All @@ -887,7 +895,7 @@ def parser_body(prototype, *fields, declarations=''):
""", indent=4)]
else:
parser_code = [normalize_snippet("""
if (!PyArg_ParseTuple(args, "{format_units}:{name}",
if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the limited C API when using the use_limited_api flag, otherwise it'll use the unlimited C API.

Sorry for my delay.

{parse_arguments})) {{
goto exit;
}}
Expand All @@ -896,7 +904,7 @@ def parser_body(prototype, *fields, declarations=''):

else:
has_optional_kw = (max(pos_only, min_pos) + min_kw_only < len(converters))
if not new_or_init:
if not new_or_init and not self.use_limited_api:
flags = "METH_FASTCALL|METH_KEYWORDS"
parser_prototype = parser_prototype_fastcall_keywords
argname_fmt = 'args[%d]'
Expand Down Expand Up @@ -991,7 +999,25 @@ def parser_body(prototype, *fields, declarations=''):
}}
""" % add_label, indent=4))

if parser_code is not None:
if self.use_limited_api:
if parser_prototype is parser_prototype_keyword:
declarations = 'static char *_keywords[] = {{{keywords} NULL}};'
parser_code = [normalize_snippet("""
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "{format_units}:{name}", _keywords, {parse_arguments})) {{
goto exit;
}}
""", indent=4)]
else:
declarations = (
'static const char * const _keywords[] = {{{keywords} NULL}};\n'
'static _PyArg_Parser _parser = {{"{format_units}:{name}", _keywords, 0}};')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not in the Limited API.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, you are right. Thanks.

parser_code = [normalize_snippet("""
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser{parse_arguments_comma}
{parse_arguments})) {{
goto exit;
}}
""", indent=4)]
elif parser_code is not None:
if add_label:
parser_code.append("%s:" % add_label)
else:
Expand Down Expand Up @@ -2040,7 +2066,7 @@ def _module_and_class(self, fields):
return module, cls


def parse_file(filename, *, verify=True, output=None):
def parse_file(filename, *, verify=True, output=None, use_limited_api=False):
if not output:
output = filename

Expand All @@ -2050,6 +2076,8 @@ def parse_file(filename, *, verify=True, output=None):

try:
language = extensions[extension](filename)
if isinstance(language, CLanguage):
language.use_limited_api = use_limited_api
except KeyError:
fail("Can't identify file type for file " + repr(filename))

Expand Down Expand Up @@ -5032,6 +5060,8 @@ def main(argv):
cmdline.add_argument("-o", "--output", type=str)
cmdline.add_argument("-v", "--verbose", action='store_true')
cmdline.add_argument("--converters", action='store_true')
cmdline.add_argument("--limited_api", action='store_true',
help="Using the limited C API in the generated code.")
cmdline.add_argument("--make", action='store_true',
help="Walk --srcdir to run over all relevant files.")
cmdline.add_argument("--srcdir", type=str, default=os.curdir,
Expand Down Expand Up @@ -5121,7 +5151,8 @@ def main(argv):
path = os.path.join(root, filename)
if ns.verbose:
print(path)
parse_file(path, verify=not ns.force)
parse_file(path, verify=not ns.force,
use_limited_api=ns.limited_api)
return

if not ns.filename:
Expand All @@ -5137,7 +5168,8 @@ def main(argv):
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, verify=not ns.force,
use_limited_api=ns.limited_api)


if __name__ == "__main__":
Expand Down