Skip to content

gh-107603: Argument Clinic can emit includes #108486

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

Merged
merged 3 commits into from
Aug 25, 2023
Merged
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
58 changes: 41 additions & 17 deletions Tools/clinic/clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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'
Comment on lines +2177 to +2178
Copy link
Member

Choose a reason for hiding this comment

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

Should we add a comment to explain the indent amount?

Suggested change
line = f'#include "{include}"'
line = line.ljust(35) + f'// {reason}\n'
include = f'"{include}"'
line = f'#include {include: <25} // {reason}\n'

Copy link
Member Author

Choose a reason for hiding this comment

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

35 is my arbitrary convention that I'm using in Python. I prefer to keep ljust(35) since it reminds me the 35th column :-)

output += line

input = ''.join(block.input)
output += ''.join(block.output)
Expand Down Expand Up @@ -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 = """
Expand Down Expand Up @@ -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 = ''

Expand Down Expand Up @@ -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
Comment on lines +2453 to +2454
Copy link
Member

Choose a reason for hiding this comment

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

For generated code we could list all the known reasons?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't see the point. It would generate giant lines for little value.

For #include, I like to show at least one imported symbol to explain why the include was needed. In manually written code, I use it to remove unused imports time to time. IMO here it's interesting to repeat this habit to explain why an include is needed.

return
self.includes[name] = reason

def add_destination(
self,
name: str,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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'),
Expand Down Expand Up @@ -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",
Expand Down