From 73d33c1a3078c5f2588c89d61e1a17a1b2a26c34 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sat, 26 Aug 2023 00:01:10 +0200 Subject: [PATCH] gh-107603: Argument Clinic can emit includes (#108486) * Add Clinic.add_include() method * Add CConverter.include and CConverter.add_include() * Printer.print_block() gets a second parameter: clinic. * Remove duplicated declaration of "clinic" global variable. --- Tools/clinic/clinic.py | 58 +++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 898347fd7120a2..3d3ab4bf81a5b5 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1075,6 +1075,11 @@ def output_templates( del parameters[0] converters = [p.converter for p in parameters] + # Copy includes from parameters to Clinic + for converter in converters: + if converter.include: + clinic.add_include(*converter.include) + has_option_groups = parameters and (parameters[0].group or parameters[-1].group) default_return_converter = f.return_converter.type == 'PyObject *' new_or_init = f.kind.new_or_init @@ -2126,8 +2131,8 @@ def print_block( self, block: Block, *, + clinic: Clinic, core_includes: bool = False, - clinic: Clinic | None = None, ) -> None: input = block.input output = block.output @@ -2156,18 +2161,22 @@ def print_block( write("\n") output = '' - 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 - # include "pycore_runtime.h" // _Py_ID() - #endif - - """) + if core_includes: + if not clinic.limited_capi: + output += textwrap.dedent(""" + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # include "pycore_gc.h" // PyGC_Head + # include "pycore_runtime.h" // _Py_ID() + #endif + + """) + + if clinic is not None: + # Emit optional includes + for include, reason in sorted(clinic.includes.items()): + line = f'#include "{include}"' + line = line.ljust(35) + f'// {reason}\n' + output += line input = ''.join(block.input) output += ''.join(block.output) @@ -2311,7 +2320,7 @@ def __init__(self, clinic: Clinic) -> None: ... def parse(self, block: Block) -> None: ... -clinic = None +clinic: Clinic | None = None class Clinic: presets_text = """ @@ -2379,6 +2388,9 @@ def __init__( self.modules: ModuleDict = {} self.classes: ClassDict = {} self.functions: list[Function] = [] + # dict: include name => reason + # Example: 'pycore_long.h' => '_PyLong_UnsignedShort_Converter()' + self.includes: dict[str, str] = {} self.line_prefix = self.line_suffix = '' @@ -2437,6 +2449,12 @@ def __init__( global clinic clinic = self + def add_include(self, name: str, reason: str) -> None: + if name in self.includes: + # Mention a single reason is enough, no need to list all of them + return + self.includes[name] = reason + def add_destination( self, name: str, @@ -3066,6 +3084,10 @@ class CConverter(metaclass=CConverterAutoRegister): # Only set by self_converter. signature_name: str | None = None + # Optional (name, reason) include which generate a line like: + # "#include "name" // reason" + include: tuple[str, str] | None = None + # keep in sync with self_converter.__init__! def __init__(self, # Positional args: @@ -3370,6 +3392,11 @@ def parser_name(self) -> str: else: return self.name + def add_include(self, name: str, reason: str) -> None: + if self.include is not None: + raise ValueError("a converter only supports a single include") + self.include = (name, reason) + type_checks = { '&PyLong_Type': ('PyLong_Check', 'int'), '&PyTuple_Type': ('PyTuple_Check', 'tuple'), @@ -5989,9 +6016,6 @@ def do_post_block_processing_cleanup(self, lineno: int) -> None: } -clinic = None - - def create_cli() -> argparse.ArgumentParser: cmdline = argparse.ArgumentParser( prog="clinic.py",