From ee0746af7d7cfc6cc25441726034e4fea4bcf7e5 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 8 Nov 2024 14:12:15 +0200 Subject: [PATCH] gh-122943: Move code generation for var-positional parameter to converters (GH-126575) --- Tools/clinic/libclinic/converters.py | 87 +++++++++++++++++++++++++--- Tools/clinic/libclinic/parse_args.py | 77 ++++-------------------- 2 files changed, 91 insertions(+), 73 deletions(-) diff --git a/Tools/clinic/libclinic/converters.py b/Tools/clinic/libclinic/converters.py index 860ff8135fb..a65f73ba02f 100644 --- a/Tools/clinic/libclinic/converters.py +++ b/Tools/clinic/libclinic/converters.py @@ -1232,7 +1232,18 @@ class self_converter(CConverter): # Converters for var-positional parameter. -class varpos_tuple_converter(CConverter): +class VarPosCConverter(CConverter): + format_unit = '' + + def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: + raise AssertionError('should never be called') + + def parse_vararg(self, *, pos_only: int, min_pos: int, max_pos: int, + fastcall: bool, limited_capi: bool) -> str: + raise NotImplementedError + + +class varpos_tuple_converter(VarPosCConverter): type = 'PyObject *' format_unit = '' c_default = 'NULL' @@ -1240,14 +1251,76 @@ class varpos_tuple_converter(CConverter): def cleanup(self) -> str: return f"""Py_XDECREF({self.parser_name});\n""" - def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: - raise AssertionError('should never be called') + def parse_vararg(self, *, pos_only: int, min_pos: int, max_pos: int, + fastcall: bool, limited_capi: bool) -> str: + paramname = self.parser_name + if fastcall: + if limited_capi: + if min(pos_only, min_pos) < max_pos: + size = f'Py_MAX(nargs - {max_pos}, 0)' + else: + size = f'nargs - {max_pos}' if max_pos else 'nargs' + return f""" + {paramname} = PyTuple_New({size}); + if (!{paramname}) {{{{ + goto exit; + }}}} + for (Py_ssize_t i = {max_pos}; i < nargs; ++i) {{{{ + PyTuple_SET_ITEM({paramname}, i - {max_pos}, Py_NewRef(args[i])); + }}}} + """ + else: + self.add_include('pycore_tuple.h', '_PyTuple_FromArray()') + start = f'args + {max_pos}' if max_pos else 'args' + size = f'nargs - {max_pos}' if max_pos else 'nargs' + if min(pos_only, min_pos) < max_pos: + return f""" + {paramname} = nargs > {max_pos} + ? _PyTuple_FromArray({start}, {size}) + : PyTuple_New(0); + if ({paramname} == NULL) {{{{ + goto exit; + }}}} + """ + else: + return f""" + {paramname} = _PyTuple_FromArray({start}, {size}); + if ({paramname} == NULL) {{{{ + goto exit; + }}}} + """ + else: + if max_pos: + return f""" + {paramname} = PyTuple_GetSlice(args, {max_pos}, PY_SSIZE_T_MAX); + if (!{paramname}) {{{{ + goto exit; + }}}} + """ + else: + return f"{paramname} = Py_NewRef(args);\n" -class varpos_array_converter(CConverter): + +class varpos_array_converter(VarPosCConverter): type = 'PyObject * const *' - format_unit = '' length = True c_ignored_default = '' - def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: - raise AssertionError('should never be called') + def parse_vararg(self, *, pos_only: int, min_pos: int, max_pos: int, + fastcall: bool, limited_capi: bool) -> str: + paramname = self.parser_name + if not fastcall: + self.add_include('pycore_tuple.h', '_PyTuple_ITEMS()') + start = 'args' if fastcall else '_PyTuple_ITEMS(args)' + size = 'nargs' if fastcall else 'PyTuple_GET_SIZE(args)' + if max_pos: + if min(pos_only, min_pos) < max_pos: + start = f'{size} > {max_pos} ? {start} + {max_pos} : {start}' + size = f'Py_MAX(0, {size} - {max_pos})' + else: + start = f'{start} + {max_pos}' + size = f'{size} - {max_pos}' + return f""" + {paramname} = {start}; + {self.length_name} = {size}; + """ diff --git a/Tools/clinic/libclinic/parse_args.py b/Tools/clinic/libclinic/parse_args.py index 2ce4e751214..08cf697b67e 100644 --- a/Tools/clinic/libclinic/parse_args.py +++ b/Tools/clinic/libclinic/parse_args.py @@ -452,71 +452,13 @@ class ParseArgsCodeGen: def _parse_vararg(self) -> str: assert self.varpos is not None - paramname = self.varpos.converter.parser_name - if self.varpos.converter.length: - if not self.fastcall: - self.codegen.add_include('pycore_tuple.h', - '_PyTuple_ITEMS()') - start = 'args' if self.fastcall else '_PyTuple_ITEMS(args)' - size = 'nargs' if self.fastcall else 'PyTuple_GET_SIZE(args)' - if self.max_pos: - if min(self.pos_only, self.min_pos) < self.max_pos: - start = f'{size} > {self.max_pos} ? {start} + {self.max_pos} : {start}' - size = f'Py_MAX(0, {size} - {self.max_pos})' - else: - start = f'{start} + {self.max_pos}' - size = f'{size} - {self.max_pos}' - return f""" - {paramname} = {start}; - {self.varpos.converter.length_name} = {size}; - """ - - if self.fastcall: - if self.limited_capi: - if min(self.pos_only, self.min_pos) < self.max_pos: - size = f'Py_MAX(nargs - {self.max_pos}, 0)' - else: - size = f'nargs - {self.max_pos}' if self.max_pos else 'nargs' - return f""" - {paramname} = PyTuple_New({size}); - if (!{paramname}) {{{{ - goto exit; - }}}} - for (Py_ssize_t i = {self.max_pos}; i < nargs; ++i) {{{{ - PyTuple_SET_ITEM({paramname}, i - {self.max_pos}, Py_NewRef(args[i])); - }}}} - """ - else: - self.codegen.add_include('pycore_tuple.h', - '_PyTuple_FromArray()') - if min(self.pos_only, self.min_pos) < self.max_pos: - return f""" - {paramname} = nargs > {self.max_pos} - ? _PyTuple_FromArray(args + {self.max_pos}, nargs - {self.max_pos}) - : PyTuple_New(0); - if ({paramname} == NULL) {{{{ - goto exit; - }}}} - """ - else: - start = f'args + {self.max_pos}' if self.max_pos else 'args' - size = f'nargs - {self.max_pos}' if self.max_pos else 'nargs' - return f""" - {paramname} = _PyTuple_FromArray({start}, {size}); - if ({paramname} == NULL) {{{{ - goto exit; - }}}} - """ - else: - if self.max_pos: - return f""" - {paramname} = PyTuple_GetSlice(args, {self.max_pos}, PY_SSIZE_T_MAX); - if (!{paramname}) {{{{ - goto exit; - }}}} - """ - else: - return f"{paramname} = Py_NewRef(args);\n" + c = self.varpos.converter + assert isinstance(c, libclinic.converters.VarPosCConverter) + return c.parse_vararg(pos_only=self.pos_only, + min_pos=self.min_pos, + max_pos=self.max_pos, + fastcall=self.fastcall, + limited_capi=self.limited_capi) def parse_pos_only(self) -> None: if self.fastcall: @@ -839,7 +781,10 @@ class ParseArgsCodeGen: def copy_includes(self) -> None: # Copy includes from parameters to Clinic after parse_arg() # has been called above. - for converter in self.converters: + converters = self.converters + if self.varpos: + converters = converters + [self.varpos.converter] + for converter in converters: for include in converter.get_includes(): self.codegen.add_include( include.filename,