From 78a4f148faaf2cf41af8f5c7d9474b28f321c8c2 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Thu, 9 Jul 2026 19:42:11 +0100 Subject: [PATCH 01/11] #30 Combine a class's template instantiations into one wrapper file cppwg emitted one wrapper .hpp/.cpp pair per template instantiation, so a class wrapped at N instantiations produced 2N files of near-identical code. Emit a single {Class}.cppwg.hpp/.cpp per class instead, holding one shared preamble and one register_{py_name}_class function per instantiation. The registration bodies are unchanged (byte-identical), just co-located. - Split the class_cpp template into class_cpp_header (the once-per-class preamble: includes, namespace, holder, prefix code, deduplicated trampoline return typedefs) and class_cpp_register (the per-instantiation block); class_hpp now declares every instantiation's register function; add struct_enum_register. - class_writer.write() builds one file pair per class, deduplicating file-scope trampoline typedefs (so a type shared by several instantiations is not redefined) and naming files after the class (CppClassInfo.py_name_base()). - module_writer emits one include per class; register calls are unchanged. It warns if two classes would collide on the same wrapper file name. This reduces the generated file count (and the translation units re-parsing the heavy shared headers) without changing what is registered. Co-Authored-By: Claude Opus 4.8 --- cppwg/info/class_info.py | 46 +++++---- cppwg/templates/pybind11_default.py | 65 ++++++------ cppwg/writers/class_writer.py | 149 +++++++++++++++++++--------- cppwg/writers/module_writer.py | 20 +++- tests/test_class_writer.py | 122 ++++++++++++++++------- 5 files changed, 267 insertions(+), 135 deletions(-) diff --git a/cppwg/info/class_info.py b/cppwg/info/class_info.py index afcae63..dfde083 100644 --- a/cppwg/info/class_info.py +++ b/cppwg/info/class_info.py @@ -514,6 +514,29 @@ def update_from_source(self, source_file_paths: list[str]) -> None: # Update the C++ and Python class names self.update_names() + def py_name_base(self) -> str: + """ + Return the shared Python-name base for the class, before template args. + + This is the common prefix of every instantiation's Python name (e.g. + "Foo" for py_names ["Foo_2_2", "Foo_3_3"]), and is used to name the + single wrapper file shared by all of a class's instantiations. For an + untemplated class it is the (override) name unchanged; for a templated + class it is cleaned the same way as the instantiation names. + """ + base = self.name_override or self.name + if not self.template_arg_lists: + return base + + for name, replacement in self.name_replacements.items(): + base = base.replace(name, replacement) + base = base.translate( + str.maketrans({"<": None, ">": None, ",": None, " ": None}) + ) + if len(base) > 1: + base = base[0].capitalize() + base[1:] + return base + def update_py_names(self) -> None: """ Set the Python names for the class, accounting for template args. @@ -524,34 +547,17 @@ def update_py_names(self) -> None: class instantiation. For example, class "Foo" with template arguments [[2, 2], [3, 3]] will have a Python name list ["Foo_2_2", "Foo_3_3"]. """ + class_name = self.py_name_base() + # Handles untemplated classes if not self.template_arg_lists: - if self.name_override: - self.py_names.append(self.name_override) - else: - self.py_names.append(self.name) + self.py_names.append(class_name) return # Table of special characters for removal rm_chars = {"<": None, ">": None, ",": None, " ": None} rm_table = str.maketrans(rm_chars) - # Clean the class name - class_name = self.name - if self.name_override: - class_name = self.name_override - - # Do standard name replacements e.g. "unsigned int" -> "Unsigned" - for name, replacement in self.name_replacements.items(): - class_name = class_name.replace(name, replacement) - - # Remove special characters - class_name = class_name.translate(rm_table) - - # Capitalize the first letter e.g. "foo" -> "Foo" - if len(class_name) > 1: - class_name = class_name[0].capitalize() + class_name[1:] - # Create a string of template args separated by "_" e.g. 2_2 for template_arg_list in self.template_arg_lists: # Example template_arg_list : [2, 2] diff --git a/cppwg/templates/pybind11_default.py b/cppwg/templates/pybind11_default.py index cad4aa7..375db50 100644 --- a/cppwg/templates/pybind11_default.py +++ b/cppwg/templates/pybind11_default.py @@ -89,32 +89,52 @@ ) # Skeleton for a class wrapper hpp file. +# Skeleton for a class wrapper hpp file. One hpp is emitted per class (not per +# template instantiation); it forward-declares the register function for every +# instantiation via ${register_declarations}. class_hpp = Template( "${prefix_text}" - "#ifndef ${class_py_name}_hpp__" + CPPWG_EXT + "_wrapper\n" - "#define ${class_py_name}_hpp__" + CPPWG_EXT + "_wrapper\n" + "#ifndef ${class_hpp_name}_hpp__" + CPPWG_EXT + "_wrapper\n" + "#define ${class_hpp_name}_hpp__" + CPPWG_EXT + "_wrapper\n" "\n" "#include \n" "\n" + "${register_declarations}" + "#endif // ${class_hpp_name}_hpp__" + CPPWG_EXT + "_wrapper\n" +) + +# A single register-function forward declaration, one per instantiation, joined +# into ${register_declarations} above. +class_hpp_register_declaration = Template( "void register_${class_py_name}_class(pybind11::module &m);\n" - "#endif // ${class_py_name}_hpp__" + CPPWG_EXT + "_wrapper\n" ) -# Skeleton for a class wrapper cpp file. -class_cpp = Template( +# Preamble for a class wrapper cpp file, emitted once per class. The file-scope +# items that must appear only once when several instantiations share a file live +# here: the includes, the smart-pointer holder declaration, class-level prefix +# code, and the (deduplicated) trampoline return typedefs. Each instantiation's +# registration follows via one class_cpp_register block. +class_cpp_header = Template( "${prefix_text}" "#include \n" "#include \n" "${includes}" "\n" - '#include "${class_py_name}.' + CPPWG_EXT + '.hpp"\n' + '#include "${class_hpp_name}.' + CPPWG_EXT + '.hpp"\n' "\n" "namespace py = pybind11;\n" - "typedef ${class_cpp_name} ${class_py_name};\n" "${smart_ptr_handle};\n" "${prefix_code}" - "${generator_pre_code}" "${return_typedefs}" +) + +# Registration block for one template instantiation, appended once per +# instantiation after the class_cpp_header preamble. Refers to the class through +# the wrapper alias (class_py_name, typedef'd to the C++ type) so the trampoline, +# registration function name and Python-visible name all match. +class_cpp_register = Template( + "${generator_pre_code}" + "typedef ${class_cpp_name} ${class_py_name};\n" "\n" "${override_class}" "void register_${class_py_name}_class(py::module &m)\n" @@ -131,25 +151,12 @@ # Skeleton for the struct-enum special case, e.g.: # struct Foo { enum Value { A, B, C }; }; -# The header block is identical to a class cpp file; the registration body wraps -# the single nested enum. Like the class cpp, it refers to the class through the -# wrapper alias (class_py_name, typedef'd to the C++ type) so the registration -# function name matches the hpp declaration and the module's register_..._class -# call, and the Python-visible name is the wrapper name (e.g. for templated -# instantiations or name overrides where it differs from the C++ decl name). -struct_enum_cpp = Template( - "${prefix_text}" - "#include \n" - "#include \n" - "${includes}" - "\n" - '#include "${class_py_name}.' + CPPWG_EXT + '.hpp"\n' - "\n" - "namespace py = pybind11;\n" - "typedef ${class_cpp_name} ${class_py_name};\n" - "${smart_ptr_handle};\n" - "${prefix_code}" +# The registration body wraps the single nested enum. Like class_cpp_register it +# refers to the class through the wrapper alias (class_py_name), and follows the +# shared class_cpp_header preamble. +struct_enum_register = Template( "${generator_pre_code}" + "typedef ${class_cpp_name} ${class_py_name};\n" "void register_${class_py_name}_class(py::module &m){\n" ' py::class_<${class_py_name}> myclass(m, "${class_py_name}");\n' ' py::enum_<${class_py_name}::${enum_name}>(myclass, "${enum_name}")\n' @@ -187,8 +194,10 @@ "module_exception_catch": module_exception_catch, "header_collection_hpp": header_collection_hpp, "class_hpp": class_hpp, - "class_cpp": class_cpp, - "struct_enum_cpp": struct_enum_cpp, + "class_hpp_register_declaration": class_hpp_register_declaration, + "class_cpp_header": class_cpp_header, + "class_cpp_register": class_cpp_register, + "struct_enum_register": struct_enum_register, "free_function": free_function, "class_method": class_method, "class_constructor": class_constructor, diff --git a/cppwg/writers/class_writer.py b/cppwg/writers/class_writer.py index 67a8539..74d9856 100644 --- a/cppwg/writers/class_writer.py +++ b/cppwg/writers/class_writer.py @@ -301,38 +301,76 @@ def bases_block(self, class_decl: "class_t") -> str: return bases - def build_hpp(self, class_py_name: str) -> str: + def build_hpp(self, register_py_names: list[str]) -> str: """ Build the class wrapper hpp file contents. + One hpp is emitted per class, forward-declaring the register function for + every instantiation that is written. + Parameters ---------- - class_py_name : str - The Python name of the class e.g. Foo_2_2 + register_py_names : list[str] + The Python names of the instantiations that have a register function, + e.g. ["Foo_2_2", "Foo_3_3"]. Returns ------- str The hpp wrapper code. """ + decl_template = self.wrapper_templates["class_hpp_register_declaration"] + register_declarations = "".join( + decl_template.substitute(class_py_name=class_py_name) + for class_py_name in register_py_names + ) return self.wrapper_templates["class_hpp"].substitute( prefix_text=self.prefix_block(), - class_py_name=class_py_name, + class_hpp_name=self.class_info.py_name_base(), + register_declarations=register_declarations, ) - def build_class_cpp(self, template_idx: int) -> str: + def build_cpp_header(self, return_typedefs: str) -> str: """ - Build the class wrapper cpp file contents for one template instantiation. + Build the shared preamble of the class wrapper cpp file. + + Emitted once per class (not per instantiation): the includes, the smart + pointer holder declaration, class-level prefix code, and the + deduplicated trampoline return typedefs. Parameters ---------- - template_idx : int - The index of the template in the class info + return_typedefs : str + The trampoline return typedefs, deduplicated across instantiations. Returns ------- str - The cpp wrapper code. + The cpp preamble. + """ + return self.wrapper_templates["class_cpp_header"].substitute( + prefix_text=self.prefix_block(), + includes=self.includes_block(), + class_hpp_name=self.class_info.py_name_base(), + smart_ptr_handle=self.smart_ptr_handle(), + prefix_code=self.prefix_code(), + return_typedefs=return_typedefs, + ) + + def build_class_register(self, template_idx: int) -> tuple[str, str]: + """ + Build the registration block for one template instantiation. + + Parameters + ---------- + template_idx : int + The index of the template instantiation in the class info. + + Returns + ------- + tuple[str, str] + The registration block and its trampoline return typedefs (the + latter are hoisted into the shared preamble and deduplicated). """ class_cpp_name = self.class_info.cpp_names[template_idx] class_py_name = self.class_info.py_names[template_idx] @@ -382,17 +420,12 @@ def build_class_cpp(self, template_idx: int) -> str: ) ) - return self.wrapper_templates["class_cpp"].substitute( - prefix_text=self.prefix_block(), - includes=self.includes_block(), - class_py_name=class_py_name, - class_cpp_name=class_cpp_name, - smart_ptr_handle=self.smart_ptr_handle(), - prefix_code=self.prefix_code(), + block = self.wrapper_templates["class_cpp_register"].substitute( generator_pre_code=( generator.get_class_cpp_pre_code(class_py_name) if generator else "" ), - return_typedefs=return_typedefs, + class_py_name=class_py_name, + class_cpp_name=class_cpp_name, override_class=override_class, overrides_string=overrides_string, ptr_support=ptr_support, @@ -404,10 +437,11 @@ def build_class_cpp(self, template_idx: int) -> str: ), suffix_code=self.suffix_code(), ) + return block, return_typedefs - def build_struct_enum_cpp(self, template_idx: int) -> str: + def build_struct_enum_register(self, template_idx: int) -> str: """ - Build the cpp file contents for the struct-enum special case. + Build the registration block for a struct-enum instantiation. Handles a struct that wraps a single nested enum, for example: @@ -418,12 +452,12 @@ def build_struct_enum_cpp(self, template_idx: int) -> str: Parameters ---------- template_idx : int - The index of the template in the class info + The index of the template instantiation in the class info. Returns ------- str - The cpp wrapper code. + The registration block. """ class_cpp_name = self.class_info.cpp_names[template_idx] class_py_name = self.class_info.py_names[template_idx] @@ -444,23 +478,23 @@ def build_struct_enum_cpp(self, template_idx: int) -> str: for value in enum_decl.values ) - return self.wrapper_templates["struct_enum_cpp"].substitute( - prefix_text=self.prefix_block(), - includes=self.includes_block(), - class_py_name=class_py_name, - class_cpp_name=class_cpp_name, - smart_ptr_handle=self.smart_ptr_handle(), - prefix_code=self.prefix_code(), + return self.wrapper_templates["struct_enum_register"].substitute( generator_pre_code=( generator.get_class_cpp_pre_code(class_py_name) if generator else "" ), + class_py_name=class_py_name, + class_cpp_name=class_cpp_name, enum_name=enum_decl.name, enum_values=enum_values, ) def write(self, work_dir: str) -> None: """ - Write the hpp and cpp wrapper codes to file. + Write the hpp and cpp wrapper code to file. + + All of a class's template instantiations share a single + `{class}.cppwg.hpp` / `.cpp` pair. The cpp holds one shared preamble + followed by one registration block per instantiation. Parameters ---------- @@ -473,26 +507,46 @@ def write(self, work_dir: str) -> None: logger.error("Not enough class decls added to do write.") raise AssertionError() - for idx, class_py_name in enumerate(self.class_info.py_names): - class_decl = self.class_info.decls[idx] + register_blocks: list[str] = [] + register_py_names: list[str] = [] + deduped_typedefs: list[str] = [] + seen_typedefs: set[str] = set() - # Check for struct-enum pattern. For example: - # struct Foo{ - # enum Value{A, B, C}; - # }; + for idx, class_decl in enumerate(self.class_info.decls): + class_py_name = self.class_info.py_names[idx] + + # Check for the struct-enum pattern, e.g.: + # struct Foo { enum Value {A, B, C}; }; if type_traits_classes.is_struct(class_decl): enums = class_decl.enumerations(allow_empty=True) if len(enums) == 1: - self.cpp_string = self.build_struct_enum_cpp(idx) - self.hpp_string = self.build_hpp(class_py_name) - self.write_files(work_dir, class_py_name) + register_blocks.append(self.build_struct_enum_register(idx)) + register_py_names.append(class_py_name) continue - self.cpp_string = self.build_class_cpp(idx) - self.hpp_string = self.build_hpp(class_py_name) - self.write_files(work_dir, class_py_name) - - def write_files(self, work_dir: str, class_py_name: str) -> None: + block, return_typedefs = self.build_class_register(idx) + register_blocks.append(block) + register_py_names.append(class_py_name) + + # Hoist trampoline return typedefs into the shared preamble, + # deduplicated so a type shared by several instantiations (e.g. + # ::std::string) is not redefined. + for line in return_typedefs.splitlines(keepends=True): + if line not in seen_typedefs: + seen_typedefs.add(line) + deduped_typedefs.append(line) + + # Nothing to register (e.g. only structs that are not the single-enum + # pattern) - write no files, matching the previous behaviour. + if not register_blocks: + return + + self.hpp_string = self.build_hpp(register_py_names) + self.cpp_string = self.build_cpp_header("".join(deduped_typedefs)) + self.cpp_string += "\n" + "\n".join(register_blocks) + self.write_files(work_dir, self.class_info.py_name_base()) + + def write_files(self, work_dir: str, file_stem: str) -> None: """ Write the hpp and cpp wrapper code to file. @@ -500,11 +554,12 @@ def write_files(self, work_dir: str, class_py_name: str) -> None: ---------- work_dir : str The directory to write the files to - class_py_name : str - The Python name of the class e.g. Foo_2_2 + file_stem : str + The wrapper file stem shared by all of the class's + instantiations, e.g. Foo (for Foo.cppwg.hpp / Foo.cppwg.cpp). """ - hpp_filepath = os.path.join(work_dir, f"{class_py_name}.{CPPWG_EXT}.hpp") - cpp_filepath = os.path.join(work_dir, f"{class_py_name}.{CPPWG_EXT}.cpp") + hpp_filepath = os.path.join(work_dir, f"{file_stem}.{CPPWG_EXT}.hpp") + cpp_filepath = os.path.join(work_dir, f"{file_stem}.{CPPWG_EXT}.cpp") write_file_if_changed(hpp_filepath, self.hpp_string, self.overwrite) write_file_if_changed(cpp_filepath, self.cpp_string, self.overwrite) diff --git a/cppwg/writers/module_writer.py b/cppwg/writers/module_writer.py index c79e81c..2df6c44 100644 --- a/cppwg/writers/module_writer.py +++ b/cppwg/writers/module_writer.py @@ -173,12 +173,12 @@ def build_module_context(self) -> dict[str, str]: else: includes = exception_block - # Includes for class wrappers in the module - # Example: #include "Foo_2_2.cppwg.hpp" + # Includes for class wrappers in the module. All of a class's template + # instantiations share one wrapper hpp (named after the class), so this + # is one include per class, e.g. #include "Foo.cppwg.hpp". class_includes = "".join( - f'#include "{py_name}.{CPPWG_EXT}.hpp"\n' + f'#include "{class_info.py_name_base()}.{CPPWG_EXT}.hpp"\n' for class_info in non_excluded_classes - for py_name in class_info.py_names ) # Import any modules that register externally-wrapped base classes, so @@ -265,12 +265,24 @@ def write_class_wrappers(self) -> None: """Write wrappers for classes in the module.""" logger = logging.getLogger() + seen_file_stems: dict[str, str] = {} for class_info in self.module_info.class_collection: # Skip excluded classes if class_info.excluded: logger.info(f"Skipping class {class_info.name}") continue + # Each class writes one wrapper file pair named after the class; warn + # if two classes in the module would collide on that file name. + file_stem = class_info.py_name_base() + if file_stem in seen_file_stems: + logger.warning( + f"Wrapper file '{file_stem}.{CPPWG_EXT}.*' for class " + f"{class_info.name} collides with class " + f"{seen_file_stems[file_stem]}; one will overwrite the other." + ) + seen_file_stems[file_stem] = class_info.name + logger.info(f"Generating wrappers for class {class_info.name}") class_writer = CppClassWrapperWriter( diff --git a/tests/test_class_writer.py b/tests/test_class_writer.py index 6e12fb7..7741538 100644 --- a/tests/test_class_writer.py +++ b/tests/test_class_writer.py @@ -35,17 +35,31 @@ def enumerations(self, allow_empty=False): class _FakeClassInfo: """Minimal CppClassInfo stand-in for exercising the writer directly.""" - def __init__(self, name, decl, attrs, source_file, cpp_names=None, py_names=None): + def __init__( + self, + name, + decl, + attrs, + source_file, + cpp_names=None, + py_names=None, + name_base=None, + decls=None, + ): self.name = name self.cpp_names = cpp_names if cpp_names is not None else [name] self.py_names = py_names if py_names is not None else [name] - self.decls = [decl] + self.decls = decls if decls is not None else [decl] self.source_file = source_file self.prefix_code = [] self.suffix_code = [] self.custom_generator_instance = None + self._name_base = name_base or name self._attrs = attrs + def py_name_base(self): + return self._name_base + def hierarchy_attribute(self, key): return self._attrs.get(key) @@ -79,18 +93,17 @@ def test_struct_enum_wrapper_common_include(): source_file="Color.hpp", ) - output = _make_writer(class_info).build_struct_enum_cpp(0) + writer = _make_writer(class_info) + header = writer.build_cpp_header("") + block = writer.build_struct_enum_register(0) - expected = ( - "#include \n" - "#include \n" - '#include "wrapper_header_collection.cppwg.hpp"\n' - "\n" - '#include "Color.cppwg.hpp"\n' - "\n" - "namespace py = pybind11;\n" + # The shared preamble carries the includes and the holder (exactly once). + assert '#include "wrapper_header_collection.cppwg.hpp"\n' in header + assert header.count("PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr);") == 1 + + # The per-instantiation registration block wraps the enum values. + expected_block = ( "typedef Color Color;\n" - "PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr);\n" "void register_Color_class(py::module &m){\n" ' py::class_ myclass(m, "Color");\n' ' py::enum_(myclass, "Value")\n' @@ -100,7 +113,7 @@ def test_struct_enum_wrapper_common_include(): " .export_values();\n" "}\n" ) - assert output == expected + assert block == expected_block def test_struct_enum_wrapper_uses_wrapper_alias_not_cpp_decl_name(): @@ -122,21 +135,19 @@ def test_struct_enum_wrapper_uses_wrapper_alias_not_cpp_decl_name(): source_file="Color.hpp", cpp_names=["Color"], py_names=["MyColor"], + name_base="MyColor", ) writer = _make_writer(class_info) - output = writer.build_struct_enum_cpp(0) + header = writer.build_cpp_header("") + block = writer.build_struct_enum_register(0) - expected = ( - "#include \n" - "#include \n" - '#include "wrapper_header_collection.cppwg.hpp"\n' - "\n" - '#include "MyColor.cppwg.hpp"\n' - "\n" - "namespace py = pybind11;\n" + # The wrapper file and its include are named after the Python alias. + assert '#include "MyColor.cppwg.hpp"\n' in header + + # The registration refers to the class by the alias throughout. + expected_block = ( "typedef Color MyColor;\n" - "PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr);\n" "void register_MyColor_class(py::module &m){\n" ' py::class_ myclass(m, "MyColor");\n' ' py::enum_(myclass, "Value")\n' @@ -145,11 +156,11 @@ def test_struct_enum_wrapper_uses_wrapper_alias_not_cpp_decl_name(): " .export_values();\n" "}\n" ) - assert output == expected + assert block == expected_block # The cpp definition and the hpp declaration must be for the same symbol. - assert "void register_MyColor_class(" in output - assert "void register_MyColor_class(" in writer.build_hpp("MyColor") + assert "void register_MyColor_class(" in block + assert "void register_MyColor_class(" in writer.build_hpp(["MyColor"]) def test_struct_enum_wrapper_source_includes_and_prefix(): @@ -171,9 +182,11 @@ class source file) and the empty smart-pointer handle. source_file="Color.hpp", ) - output = _make_writer(class_info).build_struct_enum_cpp(0) + header = _make_writer(class_info).build_cpp_header("") - expected = ( + # Non-common-include-file path: prefix text, then the explicit source + # includes and the class source file; the empty holder renders as ";". + expected_header = ( "// auto\n" "#include \n" "#include \n" @@ -183,16 +196,53 @@ class source file) and the empty smart-pointer handle. '#include "Color.cppwg.hpp"\n' "\n" "namespace py = pybind11;\n" - "typedef Color Color;\n" ";\n" - "void register_Color_class(py::module &m){\n" - ' py::class_ myclass(m, "Color");\n' - ' py::enum_(myclass, "Value")\n' - ' .value("RED", Color::Value::RED)\n' - " .export_values();\n" - "}\n" ) - assert output == expected + assert header == expected_header + + +def test_combined_wrapper_shares_one_preamble_for_all_instantiations(): + """All instantiations of a class share one file: one preamble, N register fns. + + Pins issue #30 Phase 1 - the includes, namespace and holder appear once, the + hpp declares a register function for every instantiation, and each + instantiation contributes its own alias typedef and register function. + """ + enum2 = _FakeEnum("Value", [("A", 0)]) + enum3 = _FakeEnum("Value", [("A", 0)]) + decl2 = _FakeStructDecl("Foo", "/src/Foo.hpp", enum2) + decl3 = _FakeStructDecl("Foo", "/src/Foo.hpp", enum3) + class_info = _FakeClassInfo( + "Foo", + decl2, + attrs={"common_include_file": True, "smart_ptr_type": "std::shared_ptr"}, + source_file="Foo.hpp", + cpp_names=["Foo<2>", "Foo<3>"], + py_names=["Foo_2", "Foo_3"], + name_base="Foo", + decls=[decl2, decl3], + ) + writer = _make_writer(class_info) + + # The single hpp declares a register function for every instantiation. + hpp = writer.build_hpp(["Foo_2", "Foo_3"]) + assert "void register_Foo_2_class(" in hpp + assert "void register_Foo_3_class(" in hpp + assert hpp.count("#define Foo_hpp__cppwg_wrapper") == 1 + + # The combined cpp is one shared preamble + one register block per instantiation. + cpp = ( + writer.build_cpp_header("") + + "\n" + + "\n".join(writer.build_struct_enum_register(i) for i in range(2)) + ) + assert cpp.count("namespace py = pybind11;") == 1 + assert cpp.count("PYBIND11_DECLARE_HOLDER_TYPE") == 1 + assert cpp.count('#include "Foo.cppwg.hpp"') == 1 + assert "typedef Foo<2> Foo_2;" in cpp + assert "typedef Foo<3> Foo_3;" in cpp + assert "void register_Foo_2_class(" in cpp + assert "void register_Foo_3_class(" in cpp def test_includes_block_skips_non_string_source_include(): From 8a02ecbac35bd2123759363d254adafff6c3fb44 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Thu, 9 Jul 2026 19:42:21 +0100 Subject: [PATCH 02/11] #30 Regenerate the shapes and cells example wrappers Regenerate with the combined-per-class wrappers: each class's template instantiations now share one {Class}.cppwg.hpp/.cpp instead of one pair per instantiation. Shapes drops from 19 to 15 wrapper files, cells from 34 to 22; the module main files include one header per class and their register calls are unchanged. Co-Authored-By: Claude Opus 4.8 --- ...h_2_2.cppwg.cpp => AbstractMesh.cppwg.cpp} | 40 ++++++++++++++++- .../wrappers/all/AbstractMesh.cppwg.hpp | 10 +++++ .../wrappers/all/AbstractMesh_2_2.cppwg.hpp | 9 ---- .../wrappers/all/AbstractMesh_3_3.cppwg.cpp | 45 ------------------- .../wrappers/all/AbstractMesh_3_3.cppwg.hpp | 9 ---- .../cells/dynamic/wrappers/all/Cell.cppwg.cpp | 3 +- .../{Corner_2.cppwg.cpp => Corner.cppwg.cpp} | 5 ++- .../{Corner_2.cppwg.hpp => Corner.cppwg.hpp} | 6 +-- .../{Facet_2.cppwg.cpp => Facet.cppwg.cpp} | 5 ++- .../{Facet_2.cppwg.hpp => Facet.cppwg.hpp} | 6 +-- ...Mesh_2_2.cppwg.cpp => MacroMesh.cppwg.cpp} | 17 ++++++- .../dynamic/wrappers/all/MacroMesh.cppwg.hpp | 10 +++++ .../wrappers/all/MacroMesh_2_2.cppwg.hpp | 9 ---- .../wrappers/all/MacroMesh_3_3.cppwg.cpp | 22 --------- .../wrappers/all/MacroMesh_3_3.cppwg.hpp | 9 ---- ...Mesh_2.cppwg.cpp => MeshFactory.cppwg.cpp} | 17 ++++++- .../wrappers/all/MeshFactory.cppwg.hpp | 10 +++++ .../all/MeshFactory_PottsMesh_2.cppwg.hpp | 9 ---- .../all/MeshFactory_PottsMesh_3.cppwg.cpp | 23 ---------- .../all/MeshFactory_PottsMesh_3.cppwg.hpp | 9 ---- .../all/{Node_2.cppwg.cpp => Node.cppwg.cpp} | 25 ++++++++++- .../all/{Node_2.cppwg.hpp => Node.cppwg.hpp} | 7 +-- .../dynamic/wrappers/all/Node_3.cppwg.cpp | 31 ------------- .../dynamic/wrappers/all/Node_3.cppwg.hpp | 9 ---- .../dynamic/wrappers/all/PetscUtils.cppwg.cpp | 3 +- ...tsMesh_2.cppwg.cpp => PottsMesh.cppwg.cpp} | 31 ++++++++++++- .../dynamic/wrappers/all/PottsMesh.cppwg.hpp | 10 +++++ .../wrappers/all/PottsMesh_2.cppwg.hpp | 9 ---- .../wrappers/all/PottsMesh_3.cppwg.cpp | 36 --------------- .../wrappers/all/PottsMesh_3.cppwg.hpp | 9 ---- .../{Scene_2.cppwg.cpp => Scene.cppwg.cpp} | 20 ++++++++- .../{Scene_2.cppwg.hpp => Scene.cppwg.hpp} | 7 +-- .../dynamic/wrappers/all/Scene_3.cppwg.cpp | 26 ----------- .../dynamic/wrappers/all/Scene_3.cppwg.hpp | 9 ---- .../wrappers/all/_pycells_all.main.cppwg.cpp | 22 ++++----- .../wrapper/composites/Square.cppwg.cpp | 3 +- .../{Point_2.cppwg.cpp => Point.cppwg.cpp} | 30 ++++++++++++- .../{Point_2.cppwg.hpp => Point.cppwg.hpp} | 7 +-- .../shapes/wrapper/geometry/Point_3.cppwg.cpp | 35 --------------- .../shapes/wrapper/geometry/Point_3.cppwg.hpp | 10 ----- .../_pyshapes_geometry.main.cppwg.cpp | 3 +- .../wrapper/primitives/Cuboid.cppwg.cpp | 3 +- .../wrapper/primitives/Rectangle.cppwg.cpp | 3 +- .../{Shape_2.cppwg.cpp => Shape.cppwg.cpp} | 29 +++++++++++- .../{Shape_2.cppwg.hpp => Shape.cppwg.hpp} | 7 +-- .../wrapper/primitives/Shape_3.cppwg.cpp | 34 -------------- .../wrapper/primitives/Shape_3.cppwg.hpp | 10 ----- .../_pyshapes_primitives.main.cppwg.cpp | 3 +- 48 files changed, 281 insertions(+), 423 deletions(-) rename examples/cells/dynamic/wrappers/all/{AbstractMesh_2_2.cppwg.cpp => AbstractMesh.cppwg.cpp} (54%) create mode 100644 examples/cells/dynamic/wrappers/all/AbstractMesh.cppwg.hpp delete mode 100644 examples/cells/dynamic/wrappers/all/AbstractMesh_2_2.cppwg.hpp delete mode 100644 examples/cells/dynamic/wrappers/all/AbstractMesh_3_3.cppwg.cpp delete mode 100644 examples/cells/dynamic/wrappers/all/AbstractMesh_3_3.cppwg.hpp rename examples/cells/dynamic/wrappers/all/{Corner_2.cppwg.cpp => Corner.cppwg.cpp} (97%) rename examples/cells/dynamic/wrappers/all/{Corner_2.cppwg.hpp => Corner.cppwg.hpp} (59%) rename examples/cells/dynamic/wrappers/all/{Facet_2.cppwg.cpp => Facet.cppwg.cpp} (97%) rename examples/cells/dynamic/wrappers/all/{Facet_2.cppwg.hpp => Facet.cppwg.hpp} (60%) rename examples/cells/dynamic/wrappers/all/{MacroMesh_2_2.cppwg.cpp => MacroMesh.cppwg.cpp} (61%) create mode 100644 examples/cells/dynamic/wrappers/all/MacroMesh.cppwg.hpp delete mode 100644 examples/cells/dynamic/wrappers/all/MacroMesh_2_2.cppwg.hpp delete mode 100644 examples/cells/dynamic/wrappers/all/MacroMesh_3_3.cppwg.cpp delete mode 100644 examples/cells/dynamic/wrappers/all/MacroMesh_3_3.cppwg.hpp rename examples/cells/dynamic/wrappers/all/{MeshFactory_PottsMesh_2.cppwg.cpp => MeshFactory.cppwg.cpp} (60%) create mode 100644 examples/cells/dynamic/wrappers/all/MeshFactory.cppwg.hpp delete mode 100644 examples/cells/dynamic/wrappers/all/MeshFactory_PottsMesh_2.cppwg.hpp delete mode 100644 examples/cells/dynamic/wrappers/all/MeshFactory_PottsMesh_3.cppwg.cpp delete mode 100644 examples/cells/dynamic/wrappers/all/MeshFactory_PottsMesh_3.cppwg.hpp rename examples/cells/dynamic/wrappers/all/{Node_2.cppwg.cpp => Node.cppwg.cpp} (57%) rename examples/cells/dynamic/wrappers/all/{Node_2.cppwg.hpp => Node.cppwg.hpp} (52%) delete mode 100644 examples/cells/dynamic/wrappers/all/Node_3.cppwg.cpp delete mode 100644 examples/cells/dynamic/wrappers/all/Node_3.cppwg.hpp rename examples/cells/dynamic/wrappers/all/{PottsMesh_2.cppwg.cpp => PottsMesh.cppwg.cpp} (57%) create mode 100644 examples/cells/dynamic/wrappers/all/PottsMesh.cppwg.hpp delete mode 100644 examples/cells/dynamic/wrappers/all/PottsMesh_2.cppwg.hpp delete mode 100644 examples/cells/dynamic/wrappers/all/PottsMesh_3.cppwg.cpp delete mode 100644 examples/cells/dynamic/wrappers/all/PottsMesh_3.cppwg.hpp rename examples/cells/dynamic/wrappers/all/{Scene_2.cppwg.cpp => Scene.cppwg.cpp} (61%) rename examples/cells/dynamic/wrappers/all/{Scene_2.cppwg.hpp => Scene.cppwg.hpp} (51%) delete mode 100644 examples/cells/dynamic/wrappers/all/Scene_3.cppwg.cpp delete mode 100644 examples/cells/dynamic/wrappers/all/Scene_3.cppwg.hpp rename examples/shapes/wrapper/geometry/{Point_2.cppwg.cpp => Point.cppwg.cpp} (55%) rename examples/shapes/wrapper/geometry/{Point_2.cppwg.hpp => Point.cppwg.hpp} (53%) delete mode 100644 examples/shapes/wrapper/geometry/Point_3.cppwg.cpp delete mode 100644 examples/shapes/wrapper/geometry/Point_3.cppwg.hpp rename examples/shapes/wrapper/primitives/{Shape_2.cppwg.cpp => Shape.cppwg.cpp} (55%) rename examples/shapes/wrapper/primitives/{Shape_2.cppwg.hpp => Shape.cppwg.hpp} (53%) delete mode 100644 examples/shapes/wrapper/primitives/Shape_3.cppwg.cpp delete mode 100644 examples/shapes/wrapper/primitives/Shape_3.cppwg.hpp diff --git a/examples/cells/dynamic/wrappers/all/AbstractMesh_2_2.cppwg.cpp b/examples/cells/dynamic/wrappers/all/AbstractMesh.cppwg.cpp similarity index 54% rename from examples/cells/dynamic/wrappers/all/AbstractMesh_2_2.cppwg.cpp rename to examples/cells/dynamic/wrappers/all/AbstractMesh.cppwg.cpp index ed81b5f..bd250d5 100644 --- a/examples/cells/dynamic/wrappers/all/AbstractMesh_2_2.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/AbstractMesh.cppwg.cpp @@ -5,12 +5,13 @@ #include #include "AbstractMesh.hpp" -#include "AbstractMesh_2_2.cppwg.hpp" +#include "AbstractMesh.cppwg.hpp" namespace py = pybind11; -typedef AbstractMesh<2, 2> AbstractMesh_2_2; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); +typedef AbstractMesh<2, 2> AbstractMesh_2_2; + class AbstractMesh_2_2_Overrides : public AbstractMesh_2_2 { public: @@ -43,3 +44,38 @@ void register_AbstractMesh_2_2_class(py::module &m) " ", py::arg("factor")) ; } + +typedef AbstractMesh<3, 3> AbstractMesh_3_3; + +class AbstractMesh_3_3_Overrides : public AbstractMesh_3_3 +{ +public: + using AbstractMesh_3_3::AbstractMesh; + void Scale(double const factor) override + { + PYBIND11_OVERRIDE_PURE( + void, + AbstractMesh_3_3, + Scale, + factor); + } +}; + +void register_AbstractMesh_3_3_class(py::module &m) +{ + py::class_>(m, "AbstractMesh_3_3") + .def(py::init<>()) + .def("GetIndex", + (unsigned int(AbstractMesh_3_3::*)() const) &AbstractMesh_3_3::GetIndex, + " ") + .def("SetIndex", + (void(AbstractMesh_3_3::*)(unsigned int)) &AbstractMesh_3_3::SetIndex, + " ", py::arg("index")) + .def("AddNode", + (void(AbstractMesh_3_3::*)(::Node<3>)) &AbstractMesh_3_3::AddNode, + " ", py::arg("node")) + .def("Scale", + (void(AbstractMesh_3_3::*)(double const)) &AbstractMesh_3_3::Scale, + " ", py::arg("factor")) + ; +} diff --git a/examples/cells/dynamic/wrappers/all/AbstractMesh.cppwg.hpp b/examples/cells/dynamic/wrappers/all/AbstractMesh.cppwg.hpp new file mode 100644 index 0000000..986fc0a --- /dev/null +++ b/examples/cells/dynamic/wrappers/all/AbstractMesh.cppwg.hpp @@ -0,0 +1,10 @@ +// This file is auto-generated by cppwg; manual changes will be overwritten. + +#ifndef AbstractMesh_hpp__cppwg_wrapper +#define AbstractMesh_hpp__cppwg_wrapper + +#include + +void register_AbstractMesh_2_2_class(pybind11::module &m); +void register_AbstractMesh_3_3_class(pybind11::module &m); +#endif // AbstractMesh_hpp__cppwg_wrapper diff --git a/examples/cells/dynamic/wrappers/all/AbstractMesh_2_2.cppwg.hpp b/examples/cells/dynamic/wrappers/all/AbstractMesh_2_2.cppwg.hpp deleted file mode 100644 index c1503c6..0000000 --- a/examples/cells/dynamic/wrappers/all/AbstractMesh_2_2.cppwg.hpp +++ /dev/null @@ -1,9 +0,0 @@ -// This file is auto-generated by cppwg; manual changes will be overwritten. - -#ifndef AbstractMesh_2_2_hpp__cppwg_wrapper -#define AbstractMesh_2_2_hpp__cppwg_wrapper - -#include - -void register_AbstractMesh_2_2_class(pybind11::module &m); -#endif // AbstractMesh_2_2_hpp__cppwg_wrapper diff --git a/examples/cells/dynamic/wrappers/all/AbstractMesh_3_3.cppwg.cpp b/examples/cells/dynamic/wrappers/all/AbstractMesh_3_3.cppwg.cpp deleted file mode 100644 index c56583b..0000000 --- a/examples/cells/dynamic/wrappers/all/AbstractMesh_3_3.cppwg.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// This file is auto-generated by cppwg; manual changes will be overwritten. - -#include -#include -#include -#include "AbstractMesh.hpp" - -#include "AbstractMesh_3_3.cppwg.hpp" - -namespace py = pybind11; -typedef AbstractMesh<3, 3> AbstractMesh_3_3; -PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - -class AbstractMesh_3_3_Overrides : public AbstractMesh_3_3 -{ -public: - using AbstractMesh_3_3::AbstractMesh; - void Scale(double const factor) override - { - PYBIND11_OVERRIDE_PURE( - void, - AbstractMesh_3_3, - Scale, - factor); - } -}; - -void register_AbstractMesh_3_3_class(py::module &m) -{ - py::class_>(m, "AbstractMesh_3_3") - .def(py::init<>()) - .def("GetIndex", - (unsigned int(AbstractMesh_3_3::*)() const) &AbstractMesh_3_3::GetIndex, - " ") - .def("SetIndex", - (void(AbstractMesh_3_3::*)(unsigned int)) &AbstractMesh_3_3::SetIndex, - " ", py::arg("index")) - .def("AddNode", - (void(AbstractMesh_3_3::*)(::Node<3>)) &AbstractMesh_3_3::AddNode, - " ", py::arg("node")) - .def("Scale", - (void(AbstractMesh_3_3::*)(double const)) &AbstractMesh_3_3::Scale, - " ", py::arg("factor")) - ; -} diff --git a/examples/cells/dynamic/wrappers/all/AbstractMesh_3_3.cppwg.hpp b/examples/cells/dynamic/wrappers/all/AbstractMesh_3_3.cppwg.hpp deleted file mode 100644 index 1b26ef7..0000000 --- a/examples/cells/dynamic/wrappers/all/AbstractMesh_3_3.cppwg.hpp +++ /dev/null @@ -1,9 +0,0 @@ -// This file is auto-generated by cppwg; manual changes will be overwritten. - -#ifndef AbstractMesh_3_3_hpp__cppwg_wrapper -#define AbstractMesh_3_3_hpp__cppwg_wrapper - -#include - -void register_AbstractMesh_3_3_class(pybind11::module &m); -#endif // AbstractMesh_3_3_hpp__cppwg_wrapper diff --git a/examples/cells/dynamic/wrappers/all/Cell.cppwg.cpp b/examples/cells/dynamic/wrappers/all/Cell.cppwg.cpp index f9ba7bb..d8513bf 100644 --- a/examples/cells/dynamic/wrappers/all/Cell.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/Cell.cppwg.cpp @@ -8,9 +8,10 @@ #include "Cell.cppwg.hpp" namespace py = pybind11; -typedef Cell Cell; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); +typedef Cell Cell; + void register_Cell_class(py::module &m) { py::class_>(m, "Cell") diff --git a/examples/cells/dynamic/wrappers/all/Corner_2.cppwg.cpp b/examples/cells/dynamic/wrappers/all/Corner.cppwg.cpp similarity index 97% rename from examples/cells/dynamic/wrappers/all/Corner_2.cppwg.cpp rename to examples/cells/dynamic/wrappers/all/Corner.cppwg.cpp index 6324ac6..5255ab2 100644 --- a/examples/cells/dynamic/wrappers/all/Corner_2.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/Corner.cppwg.cpp @@ -5,13 +5,14 @@ #include #include "Corner.hpp" -#include "Corner_2.cppwg.hpp" +#include "Corner.cppwg.hpp" namespace py = pybind11; -typedef Corner<2> Corner_2; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); typedef unsigned int unsignedint; +typedef Corner<2> Corner_2; + class Corner_2_Overrides : public Corner_2 { public: diff --git a/examples/cells/dynamic/wrappers/all/Corner_2.cppwg.hpp b/examples/cells/dynamic/wrappers/all/Corner.cppwg.hpp similarity index 59% rename from examples/cells/dynamic/wrappers/all/Corner_2.cppwg.hpp rename to examples/cells/dynamic/wrappers/all/Corner.cppwg.hpp index 169a082..63bd85c 100644 --- a/examples/cells/dynamic/wrappers/all/Corner_2.cppwg.hpp +++ b/examples/cells/dynamic/wrappers/all/Corner.cppwg.hpp @@ -1,9 +1,9 @@ // This file is auto-generated by cppwg; manual changes will be overwritten. -#ifndef Corner_2_hpp__cppwg_wrapper -#define Corner_2_hpp__cppwg_wrapper +#ifndef Corner_hpp__cppwg_wrapper +#define Corner_hpp__cppwg_wrapper #include void register_Corner_2_class(pybind11::module &m); -#endif // Corner_2_hpp__cppwg_wrapper +#endif // Corner_hpp__cppwg_wrapper diff --git a/examples/cells/dynamic/wrappers/all/Facet_2.cppwg.cpp b/examples/cells/dynamic/wrappers/all/Facet.cppwg.cpp similarity index 97% rename from examples/cells/dynamic/wrappers/all/Facet_2.cppwg.cpp rename to examples/cells/dynamic/wrappers/all/Facet.cppwg.cpp index d4bd58e..799ec02 100644 --- a/examples/cells/dynamic/wrappers/all/Facet_2.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/Facet.cppwg.cpp @@ -5,13 +5,14 @@ #include #include "Facet.hpp" -#include "Facet_2.cppwg.hpp" +#include "Facet.cppwg.hpp" namespace py = pybind11; -typedef Facet<2> Facet_2; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); typedef unsigned int unsignedint; +typedef Facet<2> Facet_2; + class Facet_2_Overrides : public Facet_2 { public: diff --git a/examples/cells/dynamic/wrappers/all/Facet_2.cppwg.hpp b/examples/cells/dynamic/wrappers/all/Facet.cppwg.hpp similarity index 60% rename from examples/cells/dynamic/wrappers/all/Facet_2.cppwg.hpp rename to examples/cells/dynamic/wrappers/all/Facet.cppwg.hpp index 111656e..1c6d72e 100644 --- a/examples/cells/dynamic/wrappers/all/Facet_2.cppwg.hpp +++ b/examples/cells/dynamic/wrappers/all/Facet.cppwg.hpp @@ -1,9 +1,9 @@ // This file is auto-generated by cppwg; manual changes will be overwritten. -#ifndef Facet_2_hpp__cppwg_wrapper -#define Facet_2_hpp__cppwg_wrapper +#ifndef Facet_hpp__cppwg_wrapper +#define Facet_hpp__cppwg_wrapper #include void register_Facet_2_class(pybind11::module &m); -#endif // Facet_2_hpp__cppwg_wrapper +#endif // Facet_hpp__cppwg_wrapper diff --git a/examples/cells/dynamic/wrappers/all/MacroMesh_2_2.cppwg.cpp b/examples/cells/dynamic/wrappers/all/MacroMesh.cppwg.cpp similarity index 61% rename from examples/cells/dynamic/wrappers/all/MacroMesh_2_2.cppwg.cpp rename to examples/cells/dynamic/wrappers/all/MacroMesh.cppwg.cpp index ab66d33..32b1d4e 100644 --- a/examples/cells/dynamic/wrappers/all/MacroMesh_2_2.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/MacroMesh.cppwg.cpp @@ -5,12 +5,13 @@ #include #include "MacroMesh.hpp" -#include "MacroMesh_2_2.cppwg.hpp" +#include "MacroMesh.cppwg.hpp" namespace py = pybind11; -typedef MacroMesh<2, 2> MacroMesh_2_2; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); +typedef MacroMesh<2, 2> MacroMesh_2_2; + void register_MacroMesh_2_2_class(py::module &m) { py::class_>(m, "MacroMesh_2_2") @@ -20,3 +21,15 @@ void register_MacroMesh_2_2_class(py::module &m) " ") ; } + +typedef MacroMesh<3, 3> MacroMesh_3_3; + +void register_MacroMesh_3_3_class(py::module &m) +{ + py::class_>(m, "MacroMesh_3_3") + .def(py::init<>()) + .def("GetDimension", + (unsigned int(MacroMesh_3_3::*)() const) &MacroMesh_3_3::GetDimension, + " ") + ; +} diff --git a/examples/cells/dynamic/wrappers/all/MacroMesh.cppwg.hpp b/examples/cells/dynamic/wrappers/all/MacroMesh.cppwg.hpp new file mode 100644 index 0000000..eae11f4 --- /dev/null +++ b/examples/cells/dynamic/wrappers/all/MacroMesh.cppwg.hpp @@ -0,0 +1,10 @@ +// This file is auto-generated by cppwg; manual changes will be overwritten. + +#ifndef MacroMesh_hpp__cppwg_wrapper +#define MacroMesh_hpp__cppwg_wrapper + +#include + +void register_MacroMesh_2_2_class(pybind11::module &m); +void register_MacroMesh_3_3_class(pybind11::module &m); +#endif // MacroMesh_hpp__cppwg_wrapper diff --git a/examples/cells/dynamic/wrappers/all/MacroMesh_2_2.cppwg.hpp b/examples/cells/dynamic/wrappers/all/MacroMesh_2_2.cppwg.hpp deleted file mode 100644 index 3e67411..0000000 --- a/examples/cells/dynamic/wrappers/all/MacroMesh_2_2.cppwg.hpp +++ /dev/null @@ -1,9 +0,0 @@ -// This file is auto-generated by cppwg; manual changes will be overwritten. - -#ifndef MacroMesh_2_2_hpp__cppwg_wrapper -#define MacroMesh_2_2_hpp__cppwg_wrapper - -#include - -void register_MacroMesh_2_2_class(pybind11::module &m); -#endif // MacroMesh_2_2_hpp__cppwg_wrapper diff --git a/examples/cells/dynamic/wrappers/all/MacroMesh_3_3.cppwg.cpp b/examples/cells/dynamic/wrappers/all/MacroMesh_3_3.cppwg.cpp deleted file mode 100644 index 3220bf6..0000000 --- a/examples/cells/dynamic/wrappers/all/MacroMesh_3_3.cppwg.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// This file is auto-generated by cppwg; manual changes will be overwritten. - -#include -#include -#include -#include "MacroMesh.hpp" - -#include "MacroMesh_3_3.cppwg.hpp" - -namespace py = pybind11; -typedef MacroMesh<3, 3> MacroMesh_3_3; -PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - -void register_MacroMesh_3_3_class(py::module &m) -{ - py::class_>(m, "MacroMesh_3_3") - .def(py::init<>()) - .def("GetDimension", - (unsigned int(MacroMesh_3_3::*)() const) &MacroMesh_3_3::GetDimension, - " ") - ; -} diff --git a/examples/cells/dynamic/wrappers/all/MacroMesh_3_3.cppwg.hpp b/examples/cells/dynamic/wrappers/all/MacroMesh_3_3.cppwg.hpp deleted file mode 100644 index bee7adc..0000000 --- a/examples/cells/dynamic/wrappers/all/MacroMesh_3_3.cppwg.hpp +++ /dev/null @@ -1,9 +0,0 @@ -// This file is auto-generated by cppwg; manual changes will be overwritten. - -#ifndef MacroMesh_3_3_hpp__cppwg_wrapper -#define MacroMesh_3_3_hpp__cppwg_wrapper - -#include - -void register_MacroMesh_3_3_class(pybind11::module &m); -#endif // MacroMesh_3_3_hpp__cppwg_wrapper diff --git a/examples/cells/dynamic/wrappers/all/MeshFactory_PottsMesh_2.cppwg.cpp b/examples/cells/dynamic/wrappers/all/MeshFactory.cppwg.cpp similarity index 60% rename from examples/cells/dynamic/wrappers/all/MeshFactory_PottsMesh_2.cppwg.cpp rename to examples/cells/dynamic/wrappers/all/MeshFactory.cppwg.cpp index 3147611..ff2eac6 100644 --- a/examples/cells/dynamic/wrappers/all/MeshFactory_PottsMesh_2.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/MeshFactory.cppwg.cpp @@ -6,12 +6,13 @@ #include #include "MeshFactory.hpp" -#include "MeshFactory_PottsMesh_2.cppwg.hpp" +#include "MeshFactory.cppwg.hpp" namespace py = pybind11; -typedef MeshFactory> MeshFactory_PottsMesh_2; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); +typedef MeshFactory> MeshFactory_PottsMesh_2; + void register_MeshFactory_PottsMesh_2_class(py::module &m) { py::class_>(m, "MeshFactory_PottsMesh_2") @@ -21,3 +22,15 @@ void register_MeshFactory_PottsMesh_2_class(py::module &m) " ") ; } + +typedef MeshFactory> MeshFactory_PottsMesh_3; + +void register_MeshFactory_PottsMesh_3_class(py::module &m) +{ + py::class_>(m, "MeshFactory_PottsMesh_3") + .def(py::init<>()) + .def("generateMesh", + (::std::shared_ptr>(MeshFactory_PottsMesh_3::*)()) &MeshFactory_PottsMesh_3::generateMesh, + " ") + ; +} diff --git a/examples/cells/dynamic/wrappers/all/MeshFactory.cppwg.hpp b/examples/cells/dynamic/wrappers/all/MeshFactory.cppwg.hpp new file mode 100644 index 0000000..1ec8792 --- /dev/null +++ b/examples/cells/dynamic/wrappers/all/MeshFactory.cppwg.hpp @@ -0,0 +1,10 @@ +// This file is auto-generated by cppwg; manual changes will be overwritten. + +#ifndef MeshFactory_hpp__cppwg_wrapper +#define MeshFactory_hpp__cppwg_wrapper + +#include + +void register_MeshFactory_PottsMesh_2_class(pybind11::module &m); +void register_MeshFactory_PottsMesh_3_class(pybind11::module &m); +#endif // MeshFactory_hpp__cppwg_wrapper diff --git a/examples/cells/dynamic/wrappers/all/MeshFactory_PottsMesh_2.cppwg.hpp b/examples/cells/dynamic/wrappers/all/MeshFactory_PottsMesh_2.cppwg.hpp deleted file mode 100644 index 28dc4ad..0000000 --- a/examples/cells/dynamic/wrappers/all/MeshFactory_PottsMesh_2.cppwg.hpp +++ /dev/null @@ -1,9 +0,0 @@ -// This file is auto-generated by cppwg; manual changes will be overwritten. - -#ifndef MeshFactory_PottsMesh_2_hpp__cppwg_wrapper -#define MeshFactory_PottsMesh_2_hpp__cppwg_wrapper - -#include - -void register_MeshFactory_PottsMesh_2_class(pybind11::module &m); -#endif // MeshFactory_PottsMesh_2_hpp__cppwg_wrapper diff --git a/examples/cells/dynamic/wrappers/all/MeshFactory_PottsMesh_3.cppwg.cpp b/examples/cells/dynamic/wrappers/all/MeshFactory_PottsMesh_3.cppwg.cpp deleted file mode 100644 index 2a8b1dc..0000000 --- a/examples/cells/dynamic/wrappers/all/MeshFactory_PottsMesh_3.cppwg.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// This file is auto-generated by cppwg; manual changes will be overwritten. - -#include -#include -#include "PottsMesh.hpp" -#include -#include "MeshFactory.hpp" - -#include "MeshFactory_PottsMesh_3.cppwg.hpp" - -namespace py = pybind11; -typedef MeshFactory> MeshFactory_PottsMesh_3; -PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - -void register_MeshFactory_PottsMesh_3_class(py::module &m) -{ - py::class_>(m, "MeshFactory_PottsMesh_3") - .def(py::init<>()) - .def("generateMesh", - (::std::shared_ptr>(MeshFactory_PottsMesh_3::*)()) &MeshFactory_PottsMesh_3::generateMesh, - " ") - ; -} diff --git a/examples/cells/dynamic/wrappers/all/MeshFactory_PottsMesh_3.cppwg.hpp b/examples/cells/dynamic/wrappers/all/MeshFactory_PottsMesh_3.cppwg.hpp deleted file mode 100644 index eb7de7c..0000000 --- a/examples/cells/dynamic/wrappers/all/MeshFactory_PottsMesh_3.cppwg.hpp +++ /dev/null @@ -1,9 +0,0 @@ -// This file is auto-generated by cppwg; manual changes will be overwritten. - -#ifndef MeshFactory_PottsMesh_3_hpp__cppwg_wrapper -#define MeshFactory_PottsMesh_3_hpp__cppwg_wrapper - -#include - -void register_MeshFactory_PottsMesh_3_class(pybind11::module &m); -#endif // MeshFactory_PottsMesh_3_hpp__cppwg_wrapper diff --git a/examples/cells/dynamic/wrappers/all/Node_2.cppwg.cpp b/examples/cells/dynamic/wrappers/all/Node.cppwg.cpp similarity index 57% rename from examples/cells/dynamic/wrappers/all/Node_2.cppwg.cpp rename to examples/cells/dynamic/wrappers/all/Node.cppwg.cpp index 3db7b00..7a8efc8 100644 --- a/examples/cells/dynamic/wrappers/all/Node_2.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/Node.cppwg.cpp @@ -6,12 +6,13 @@ #include #include "Node.hpp" -#include "Node_2.cppwg.hpp" +#include "Node.cppwg.hpp" namespace py = pybind11; -typedef Node<2> Node_2; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); +typedef Node<2> Node_2; + void register_Node_2_class(py::module &m) { py::class_>(m, "Node_2") @@ -29,3 +30,23 @@ void register_Node_2_class(py::module &m) " ", py::arg("rDisplacement")) ; } + +typedef Node<3> Node_3; + +void register_Node_3_class(py::module &m) +{ + py::class_>(m, "Node_3") + .def(py::init<>()) + .def(py::init<::std::vector>(), py::arg("coords")) + .def(py::init<::boost::numeric::ublas::c_vector>(), py::arg("coords")) + .def("GetIndex", + (unsigned int(Node_3::*)() const) &Node_3::GetIndex, + " ") + .def("GetLocation", + (::boost::numeric::ublas::c_vector(Node_3::*)()) &Node_3::GetLocation, + " ") + .def("Translate", + (void(Node_3::*)(::boost::numeric::ublas::c_vector const &)) &Node_3::Translate, + " ", py::arg("rDisplacement")) + ; +} diff --git a/examples/cells/dynamic/wrappers/all/Node_2.cppwg.hpp b/examples/cells/dynamic/wrappers/all/Node.cppwg.hpp similarity index 52% rename from examples/cells/dynamic/wrappers/all/Node_2.cppwg.hpp rename to examples/cells/dynamic/wrappers/all/Node.cppwg.hpp index 35b1aeb..15dfdfb 100644 --- a/examples/cells/dynamic/wrappers/all/Node_2.cppwg.hpp +++ b/examples/cells/dynamic/wrappers/all/Node.cppwg.hpp @@ -1,9 +1,10 @@ // This file is auto-generated by cppwg; manual changes will be overwritten. -#ifndef Node_2_hpp__cppwg_wrapper -#define Node_2_hpp__cppwg_wrapper +#ifndef Node_hpp__cppwg_wrapper +#define Node_hpp__cppwg_wrapper #include void register_Node_2_class(pybind11::module &m); -#endif // Node_2_hpp__cppwg_wrapper +void register_Node_3_class(pybind11::module &m); +#endif // Node_hpp__cppwg_wrapper diff --git a/examples/cells/dynamic/wrappers/all/Node_3.cppwg.cpp b/examples/cells/dynamic/wrappers/all/Node_3.cppwg.cpp deleted file mode 100644 index d6149b9..0000000 --- a/examples/cells/dynamic/wrappers/all/Node_3.cppwg.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// This file is auto-generated by cppwg; manual changes will be overwritten. - -#include -#include -#include "PybindUblasTypeCaster.hpp" -#include -#include "Node.hpp" - -#include "Node_3.cppwg.hpp" - -namespace py = pybind11; -typedef Node<3> Node_3; -PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - -void register_Node_3_class(py::module &m) -{ - py::class_>(m, "Node_3") - .def(py::init<>()) - .def(py::init<::std::vector>(), py::arg("coords")) - .def(py::init<::boost::numeric::ublas::c_vector>(), py::arg("coords")) - .def("GetIndex", - (unsigned int(Node_3::*)() const) &Node_3::GetIndex, - " ") - .def("GetLocation", - (::boost::numeric::ublas::c_vector(Node_3::*)()) &Node_3::GetLocation, - " ") - .def("Translate", - (void(Node_3::*)(::boost::numeric::ublas::c_vector const &)) &Node_3::Translate, - " ", py::arg("rDisplacement")) - ; -} diff --git a/examples/cells/dynamic/wrappers/all/Node_3.cppwg.hpp b/examples/cells/dynamic/wrappers/all/Node_3.cppwg.hpp deleted file mode 100644 index 0c2935d..0000000 --- a/examples/cells/dynamic/wrappers/all/Node_3.cppwg.hpp +++ /dev/null @@ -1,9 +0,0 @@ -// This file is auto-generated by cppwg; manual changes will be overwritten. - -#ifndef Node_3_hpp__cppwg_wrapper -#define Node_3_hpp__cppwg_wrapper - -#include - -void register_Node_3_class(pybind11::module &m); -#endif // Node_3_hpp__cppwg_wrapper diff --git a/examples/cells/dynamic/wrappers/all/PetscUtils.cppwg.cpp b/examples/cells/dynamic/wrappers/all/PetscUtils.cppwg.cpp index 5f0e9bf..de4ce46 100644 --- a/examples/cells/dynamic/wrappers/all/PetscUtils.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/PetscUtils.cppwg.cpp @@ -9,9 +9,10 @@ #include "PetscUtils.cppwg.hpp" namespace py = pybind11; -typedef PetscUtils PetscUtils; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); +typedef PetscUtils PetscUtils; + void register_PetscUtils_class(py::module &m) { py::class_>(m, "PetscUtils") diff --git a/examples/cells/dynamic/wrappers/all/PottsMesh_2.cppwg.cpp b/examples/cells/dynamic/wrappers/all/PottsMesh.cppwg.cpp similarity index 57% rename from examples/cells/dynamic/wrappers/all/PottsMesh_2.cppwg.cpp rename to examples/cells/dynamic/wrappers/all/PottsMesh.cppwg.cpp index 21a2ede..cebc85f 100644 --- a/examples/cells/dynamic/wrappers/all/PottsMesh_2.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/PottsMesh.cppwg.cpp @@ -5,12 +5,13 @@ #include #include "PottsMesh.hpp" -#include "PottsMesh_2.cppwg.hpp" +#include "PottsMesh.cppwg.hpp" namespace py = pybind11; -typedef PottsMesh<2> PottsMesh_2; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); +typedef PottsMesh<2> PottsMesh_2; + class PottsMesh_2_Overrides : public PottsMesh_2 { public: @@ -34,3 +35,29 @@ void register_PottsMesh_2_class(py::module &m) " ", py::arg("factor")) ; } + +typedef PottsMesh<3> PottsMesh_3; + +class PottsMesh_3_Overrides : public PottsMesh_3 +{ +public: + using PottsMesh_3::PottsMesh; + void Scale(double const factor) override + { + PYBIND11_OVERRIDE( + void, + PottsMesh_3, + Scale, + factor); + } +}; + +void register_PottsMesh_3_class(py::module &m) +{ + py::class_, AbstractMesh<3, 3>>(m, "PottsMesh_3") + .def(py::init<>()) + .def("Scale", + (void(PottsMesh_3::*)(double const)) &PottsMesh_3::Scale, + " ", py::arg("factor")) + ; +} diff --git a/examples/cells/dynamic/wrappers/all/PottsMesh.cppwg.hpp b/examples/cells/dynamic/wrappers/all/PottsMesh.cppwg.hpp new file mode 100644 index 0000000..4f1cc7f --- /dev/null +++ b/examples/cells/dynamic/wrappers/all/PottsMesh.cppwg.hpp @@ -0,0 +1,10 @@ +// This file is auto-generated by cppwg; manual changes will be overwritten. + +#ifndef PottsMesh_hpp__cppwg_wrapper +#define PottsMesh_hpp__cppwg_wrapper + +#include + +void register_PottsMesh_2_class(pybind11::module &m); +void register_PottsMesh_3_class(pybind11::module &m); +#endif // PottsMesh_hpp__cppwg_wrapper diff --git a/examples/cells/dynamic/wrappers/all/PottsMesh_2.cppwg.hpp b/examples/cells/dynamic/wrappers/all/PottsMesh_2.cppwg.hpp deleted file mode 100644 index 30615bd..0000000 --- a/examples/cells/dynamic/wrappers/all/PottsMesh_2.cppwg.hpp +++ /dev/null @@ -1,9 +0,0 @@ -// This file is auto-generated by cppwg; manual changes will be overwritten. - -#ifndef PottsMesh_2_hpp__cppwg_wrapper -#define PottsMesh_2_hpp__cppwg_wrapper - -#include - -void register_PottsMesh_2_class(pybind11::module &m); -#endif // PottsMesh_2_hpp__cppwg_wrapper diff --git a/examples/cells/dynamic/wrappers/all/PottsMesh_3.cppwg.cpp b/examples/cells/dynamic/wrappers/all/PottsMesh_3.cppwg.cpp deleted file mode 100644 index 408c8bb..0000000 --- a/examples/cells/dynamic/wrappers/all/PottsMesh_3.cppwg.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// This file is auto-generated by cppwg; manual changes will be overwritten. - -#include -#include -#include -#include "PottsMesh.hpp" - -#include "PottsMesh_3.cppwg.hpp" - -namespace py = pybind11; -typedef PottsMesh<3> PottsMesh_3; -PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - -class PottsMesh_3_Overrides : public PottsMesh_3 -{ -public: - using PottsMesh_3::PottsMesh; - void Scale(double const factor) override - { - PYBIND11_OVERRIDE( - void, - PottsMesh_3, - Scale, - factor); - } -}; - -void register_PottsMesh_3_class(py::module &m) -{ - py::class_, AbstractMesh<3, 3>>(m, "PottsMesh_3") - .def(py::init<>()) - .def("Scale", - (void(PottsMesh_3::*)(double const)) &PottsMesh_3::Scale, - " ", py::arg("factor")) - ; -} diff --git a/examples/cells/dynamic/wrappers/all/PottsMesh_3.cppwg.hpp b/examples/cells/dynamic/wrappers/all/PottsMesh_3.cppwg.hpp deleted file mode 100644 index 51ee08f..0000000 --- a/examples/cells/dynamic/wrappers/all/PottsMesh_3.cppwg.hpp +++ /dev/null @@ -1,9 +0,0 @@ -// This file is auto-generated by cppwg; manual changes will be overwritten. - -#ifndef PottsMesh_3_hpp__cppwg_wrapper -#define PottsMesh_3_hpp__cppwg_wrapper - -#include - -void register_PottsMesh_3_class(pybind11::module &m); -#endif // PottsMesh_3_hpp__cppwg_wrapper diff --git a/examples/cells/dynamic/wrappers/all/Scene_2.cppwg.cpp b/examples/cells/dynamic/wrappers/all/Scene.cppwg.cpp similarity index 61% rename from examples/cells/dynamic/wrappers/all/Scene_2.cppwg.cpp rename to examples/cells/dynamic/wrappers/all/Scene.cppwg.cpp index d03a68f..98ee02c 100644 --- a/examples/cells/dynamic/wrappers/all/Scene_2.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/Scene.cppwg.cpp @@ -6,12 +6,13 @@ #include #include "Scene.hpp" -#include "Scene_2.cppwg.hpp" +#include "Scene.cppwg.hpp" namespace py = pybind11; -typedef Scene<2> Scene_2; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); +typedef Scene<2> Scene_2; + void register_Scene_2_class(py::module &m) { py::class_>(m, "Scene_2") @@ -24,3 +25,18 @@ void register_Scene_2_class(py::module &m) " ") ; } + +typedef Scene<3> Scene_3; + +void register_Scene_3_class(py::module &m) +{ + py::class_>(m, "Scene_3") + .def(py::init<>()) + .def("GetRenderer", + (::vtkSmartPointer(Scene_3::*)()) &Scene_3::GetRenderer, + " ") + .def_static("ThrowException", + (void(*)()) &Scene_3::ThrowException, + " ") + ; +} diff --git a/examples/cells/dynamic/wrappers/all/Scene_2.cppwg.hpp b/examples/cells/dynamic/wrappers/all/Scene.cppwg.hpp similarity index 51% rename from examples/cells/dynamic/wrappers/all/Scene_2.cppwg.hpp rename to examples/cells/dynamic/wrappers/all/Scene.cppwg.hpp index bfca3fc..4051624 100644 --- a/examples/cells/dynamic/wrappers/all/Scene_2.cppwg.hpp +++ b/examples/cells/dynamic/wrappers/all/Scene.cppwg.hpp @@ -1,9 +1,10 @@ // This file is auto-generated by cppwg; manual changes will be overwritten. -#ifndef Scene_2_hpp__cppwg_wrapper -#define Scene_2_hpp__cppwg_wrapper +#ifndef Scene_hpp__cppwg_wrapper +#define Scene_hpp__cppwg_wrapper #include void register_Scene_2_class(pybind11::module &m); -#endif // Scene_2_hpp__cppwg_wrapper +void register_Scene_3_class(pybind11::module &m); +#endif // Scene_hpp__cppwg_wrapper diff --git a/examples/cells/dynamic/wrappers/all/Scene_3.cppwg.cpp b/examples/cells/dynamic/wrappers/all/Scene_3.cppwg.cpp deleted file mode 100644 index 6a8c433..0000000 --- a/examples/cells/dynamic/wrappers/all/Scene_3.cppwg.cpp +++ /dev/null @@ -1,26 +0,0 @@ -// This file is auto-generated by cppwg; manual changes will be overwritten. - -#include -#include -#include "PybindVTKTypeCaster.h" -#include -#include "Scene.hpp" - -#include "Scene_3.cppwg.hpp" - -namespace py = pybind11; -typedef Scene<3> Scene_3; -PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - -void register_Scene_3_class(py::module &m) -{ - py::class_>(m, "Scene_3") - .def(py::init<>()) - .def("GetRenderer", - (::vtkSmartPointer(Scene_3::*)()) &Scene_3::GetRenderer, - " ") - .def_static("ThrowException", - (void(*)()) &Scene_3::ThrowException, - " ") - ; -} diff --git a/examples/cells/dynamic/wrappers/all/Scene_3.cppwg.hpp b/examples/cells/dynamic/wrappers/all/Scene_3.cppwg.hpp deleted file mode 100644 index b9c477c..0000000 --- a/examples/cells/dynamic/wrappers/all/Scene_3.cppwg.hpp +++ /dev/null @@ -1,9 +0,0 @@ -// This file is auto-generated by cppwg; manual changes will be overwritten. - -#ifndef Scene_3_hpp__cppwg_wrapper -#define Scene_3_hpp__cppwg_wrapper - -#include - -void register_Scene_3_class(pybind11::module &m); -#endif // Scene_3_hpp__cppwg_wrapper diff --git a/examples/cells/dynamic/wrappers/all/_pycells_all.main.cppwg.cpp b/examples/cells/dynamic/wrappers/all/_pycells_all.main.cppwg.cpp index 5ae7d8f..b325c7a 100644 --- a/examples/cells/dynamic/wrappers/all/_pycells_all.main.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/_pycells_all.main.cppwg.cpp @@ -9,21 +9,15 @@ #pragma GCC visibility pop #endif #include "Cell.cppwg.hpp" -#include "Corner_2.cppwg.hpp" -#include "Facet_2.cppwg.hpp" -#include "MacroMesh_2_2.cppwg.hpp" -#include "MacroMesh_3_3.cppwg.hpp" -#include "Node_2.cppwg.hpp" -#include "Node_3.cppwg.hpp" -#include "AbstractMesh_2_2.cppwg.hpp" -#include "AbstractMesh_3_3.cppwg.hpp" +#include "Corner.cppwg.hpp" +#include "Facet.cppwg.hpp" +#include "MacroMesh.cppwg.hpp" +#include "Node.cppwg.hpp" +#include "AbstractMesh.cppwg.hpp" #include "PetscUtils.cppwg.hpp" -#include "PottsMesh_2.cppwg.hpp" -#include "PottsMesh_3.cppwg.hpp" -#include "MeshFactory_PottsMesh_2.cppwg.hpp" -#include "MeshFactory_PottsMesh_3.cppwg.hpp" -#include "Scene_2.cppwg.hpp" -#include "Scene_3.cppwg.hpp" +#include "PottsMesh.cppwg.hpp" +#include "MeshFactory.cppwg.hpp" +#include "Scene.cppwg.hpp" namespace py = pybind11; diff --git a/examples/shapes/wrapper/composites/Square.cppwg.cpp b/examples/shapes/wrapper/composites/Square.cppwg.cpp index cbdc630..b4b9636 100644 --- a/examples/shapes/wrapper/composites/Square.cppwg.cpp +++ b/examples/shapes/wrapper/composites/Square.cppwg.cpp @@ -8,9 +8,10 @@ #include "Square.cppwg.hpp" namespace py = pybind11; -typedef Square Square; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); +typedef Square Square; + void register_Square_class(py::module &m) { py::class_, ::Rectangle>(m, "Square") diff --git a/examples/shapes/wrapper/geometry/Point_2.cppwg.cpp b/examples/shapes/wrapper/geometry/Point.cppwg.cpp similarity index 55% rename from examples/shapes/wrapper/geometry/Point_2.cppwg.cpp rename to examples/shapes/wrapper/geometry/Point.cppwg.cpp index 190020d..89d57b4 100644 --- a/examples/shapes/wrapper/geometry/Point_2.cppwg.cpp +++ b/examples/shapes/wrapper/geometry/Point.cppwg.cpp @@ -5,12 +5,13 @@ #include #include "wrapper_header_collection.cppwg.hpp" -#include "Point_2.cppwg.hpp" +#include "Point.cppwg.hpp" namespace py = pybind11; -typedef Point<2> Point_2; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); +typedef Point<2> Point_2; + void register_Point_2_class(py::module &m) { py::class_>(m, "Point_2") @@ -33,3 +34,28 @@ void register_Point_2_class(py::module &m) " ", py::arg("rLocation")) ; } + +typedef Point<3> Point_3; + +void register_Point_3_class(py::module &m) +{ + py::class_>(m, "Point_3") + .def(py::init<>()) + .def(py::init(), py::arg("x"), py::arg("y"), py::arg("z") = (3 - 3)) + .def("GetLocation", + (::std::array(Point_3::*)() const) &Point_3::GetLocation, + " ") + .def("rGetLocation", + (::std::array const &(Point_3::*)() const) &Point_3::rGetLocation, + " ", py::return_value_policy::reference_internal) + .def("GetIndex", + (unsigned int(Point_3::*)() const) &Point_3::GetIndex, + " ") + .def("SetIndex", + (void(Point_3::*)(unsigned int)) &Point_3::SetIndex, + " ", py::arg("index")) + .def("SetLocation", + (void(Point_3::*)(::std::array const &)) &Point_3::SetLocation, + " ", py::arg("rLocation")) + ; +} diff --git a/examples/shapes/wrapper/geometry/Point_2.cppwg.hpp b/examples/shapes/wrapper/geometry/Point.cppwg.hpp similarity index 53% rename from examples/shapes/wrapper/geometry/Point_2.cppwg.hpp rename to examples/shapes/wrapper/geometry/Point.cppwg.hpp index 7b5e0ca..4bc0af5 100644 --- a/examples/shapes/wrapper/geometry/Point_2.cppwg.hpp +++ b/examples/shapes/wrapper/geometry/Point.cppwg.hpp @@ -1,10 +1,11 @@ // This file is automatically generated by cppwg. // Do not modify this file directly. -#ifndef Point_2_hpp__cppwg_wrapper -#define Point_2_hpp__cppwg_wrapper +#ifndef Point_hpp__cppwg_wrapper +#define Point_hpp__cppwg_wrapper #include void register_Point_2_class(pybind11::module &m); -#endif // Point_2_hpp__cppwg_wrapper +void register_Point_3_class(pybind11::module &m); +#endif // Point_hpp__cppwg_wrapper diff --git a/examples/shapes/wrapper/geometry/Point_3.cppwg.cpp b/examples/shapes/wrapper/geometry/Point_3.cppwg.cpp deleted file mode 100644 index 24c8bf7..0000000 --- a/examples/shapes/wrapper/geometry/Point_3.cppwg.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// This file is automatically generated by cppwg. -// Do not modify this file directly. - -#include -#include -#include "wrapper_header_collection.cppwg.hpp" - -#include "Point_3.cppwg.hpp" - -namespace py = pybind11; -typedef Point<3> Point_3; -PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - -void register_Point_3_class(py::module &m) -{ - py::class_>(m, "Point_3") - .def(py::init<>()) - .def(py::init(), py::arg("x"), py::arg("y"), py::arg("z") = (3 - 3)) - .def("GetLocation", - (::std::array(Point_3::*)() const) &Point_3::GetLocation, - " ") - .def("rGetLocation", - (::std::array const &(Point_3::*)() const) &Point_3::rGetLocation, - " ", py::return_value_policy::reference_internal) - .def("GetIndex", - (unsigned int(Point_3::*)() const) &Point_3::GetIndex, - " ") - .def("SetIndex", - (void(Point_3::*)(unsigned int)) &Point_3::SetIndex, - " ", py::arg("index")) - .def("SetLocation", - (void(Point_3::*)(::std::array const &)) &Point_3::SetLocation, - " ", py::arg("rLocation")) - ; -} diff --git a/examples/shapes/wrapper/geometry/Point_3.cppwg.hpp b/examples/shapes/wrapper/geometry/Point_3.cppwg.hpp deleted file mode 100644 index 4acc0ec..0000000 --- a/examples/shapes/wrapper/geometry/Point_3.cppwg.hpp +++ /dev/null @@ -1,10 +0,0 @@ -// This file is automatically generated by cppwg. -// Do not modify this file directly. - -#ifndef Point_3_hpp__cppwg_wrapper -#define Point_3_hpp__cppwg_wrapper - -#include - -void register_Point_3_class(pybind11::module &m); -#endif // Point_3_hpp__cppwg_wrapper diff --git a/examples/shapes/wrapper/geometry/_pyshapes_geometry.main.cppwg.cpp b/examples/shapes/wrapper/geometry/_pyshapes_geometry.main.cppwg.cpp index cc6aaba..487f31f 100644 --- a/examples/shapes/wrapper/geometry/_pyshapes_geometry.main.cppwg.cpp +++ b/examples/shapes/wrapper/geometry/_pyshapes_geometry.main.cppwg.cpp @@ -10,8 +10,7 @@ #pragma GCC visibility pop #endif #include "wrapper_header_collection.cppwg.hpp" -#include "Point_2.cppwg.hpp" -#include "Point_3.cppwg.hpp" +#include "Point.cppwg.hpp" namespace py = pybind11; diff --git a/examples/shapes/wrapper/primitives/Cuboid.cppwg.cpp b/examples/shapes/wrapper/primitives/Cuboid.cppwg.cpp index 8927818..c392d58 100644 --- a/examples/shapes/wrapper/primitives/Cuboid.cppwg.cpp +++ b/examples/shapes/wrapper/primitives/Cuboid.cppwg.cpp @@ -8,9 +8,10 @@ #include "Cuboid.cppwg.hpp" namespace py = pybind11; -typedef Cuboid Cuboid; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); +typedef Cuboid Cuboid; + void register_Cuboid_class(py::module &m) { py::class_, Shape<3>>(m, "Cuboid") diff --git a/examples/shapes/wrapper/primitives/Rectangle.cppwg.cpp b/examples/shapes/wrapper/primitives/Rectangle.cppwg.cpp index 2cc8a68..179e854 100644 --- a/examples/shapes/wrapper/primitives/Rectangle.cppwg.cpp +++ b/examples/shapes/wrapper/primitives/Rectangle.cppwg.cpp @@ -8,9 +8,10 @@ #include "Rectangle.cppwg.hpp" namespace py = pybind11; -typedef Rectangle Rectangle; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); +typedef Rectangle Rectangle; + void register_Rectangle_class(py::module &m) { py::class_, Shape<2>>(m, "Rectangle") diff --git a/examples/shapes/wrapper/primitives/Shape_2.cppwg.cpp b/examples/shapes/wrapper/primitives/Shape.cppwg.cpp similarity index 55% rename from examples/shapes/wrapper/primitives/Shape_2.cppwg.cpp rename to examples/shapes/wrapper/primitives/Shape.cppwg.cpp index 680e76d..7d38793 100644 --- a/examples/shapes/wrapper/primitives/Shape_2.cppwg.cpp +++ b/examples/shapes/wrapper/primitives/Shape.cppwg.cpp @@ -5,12 +5,13 @@ #include #include "wrapper_header_collection.cppwg.hpp" -#include "Shape_2.cppwg.hpp" +#include "Shape.cppwg.hpp" namespace py = pybind11; -typedef Shape<2> Shape_2; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); +typedef Shape<2> Shape_2; + void register_Shape_2_class(py::module &m) { py::class_>(m, "Shape_2") @@ -32,3 +33,27 @@ void register_Shape_2_class(py::module &m) " ", py::arg("point") = std::make_shared>()) ; } + +typedef Shape<3> Shape_3; + +void register_Shape_3_class(py::module &m) +{ + py::class_>(m, "Shape_3") + .def(py::init<>()) + .def("GetIndex", + (unsigned int(Shape_3::*)() const) &Shape_3::GetIndex, + " ") + .def("rGetVertices", + (::std::vector>> const &(Shape_3::*)() const) &Shape_3::rGetVertices, + " ", py::return_value_policy::reference_internal) + .def("SetIndex", + (void(Shape_3::*)(unsigned int)) &Shape_3::SetIndex, + " ", py::arg("index")) + .def("SetVertices", + (void(Shape_3::*)(::std::vector>> const &)) &Shape_3::SetVertices, + " ", py::arg("rVertices")) + .def("AddVertex", + (void(Shape_3::*)(::std::shared_ptr>)) &Shape_3::AddVertex, + " ", py::arg("point") = std::make_shared>()) + ; +} diff --git a/examples/shapes/wrapper/primitives/Shape_2.cppwg.hpp b/examples/shapes/wrapper/primitives/Shape.cppwg.hpp similarity index 53% rename from examples/shapes/wrapper/primitives/Shape_2.cppwg.hpp rename to examples/shapes/wrapper/primitives/Shape.cppwg.hpp index 0898be1..91ccece 100644 --- a/examples/shapes/wrapper/primitives/Shape_2.cppwg.hpp +++ b/examples/shapes/wrapper/primitives/Shape.cppwg.hpp @@ -1,10 +1,11 @@ // This file is automatically generated by cppwg. // Do not modify this file directly. -#ifndef Shape_2_hpp__cppwg_wrapper -#define Shape_2_hpp__cppwg_wrapper +#ifndef Shape_hpp__cppwg_wrapper +#define Shape_hpp__cppwg_wrapper #include void register_Shape_2_class(pybind11::module &m); -#endif // Shape_2_hpp__cppwg_wrapper +void register_Shape_3_class(pybind11::module &m); +#endif // Shape_hpp__cppwg_wrapper diff --git a/examples/shapes/wrapper/primitives/Shape_3.cppwg.cpp b/examples/shapes/wrapper/primitives/Shape_3.cppwg.cpp deleted file mode 100644 index b775a6e..0000000 --- a/examples/shapes/wrapper/primitives/Shape_3.cppwg.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// This file is automatically generated by cppwg. -// Do not modify this file directly. - -#include -#include -#include "wrapper_header_collection.cppwg.hpp" - -#include "Shape_3.cppwg.hpp" - -namespace py = pybind11; -typedef Shape<3> Shape_3; -PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - -void register_Shape_3_class(py::module &m) -{ - py::class_>(m, "Shape_3") - .def(py::init<>()) - .def("GetIndex", - (unsigned int(Shape_3::*)() const) &Shape_3::GetIndex, - " ") - .def("rGetVertices", - (::std::vector>> const &(Shape_3::*)() const) &Shape_3::rGetVertices, - " ", py::return_value_policy::reference_internal) - .def("SetIndex", - (void(Shape_3::*)(unsigned int)) &Shape_3::SetIndex, - " ", py::arg("index")) - .def("SetVertices", - (void(Shape_3::*)(::std::vector>> const &)) &Shape_3::SetVertices, - " ", py::arg("rVertices")) - .def("AddVertex", - (void(Shape_3::*)(::std::shared_ptr>)) &Shape_3::AddVertex, - " ", py::arg("point") = std::make_shared>()) - ; -} diff --git a/examples/shapes/wrapper/primitives/Shape_3.cppwg.hpp b/examples/shapes/wrapper/primitives/Shape_3.cppwg.hpp deleted file mode 100644 index a307241..0000000 --- a/examples/shapes/wrapper/primitives/Shape_3.cppwg.hpp +++ /dev/null @@ -1,10 +0,0 @@ -// This file is automatically generated by cppwg. -// Do not modify this file directly. - -#ifndef Shape_3_hpp__cppwg_wrapper -#define Shape_3_hpp__cppwg_wrapper - -#include - -void register_Shape_3_class(pybind11::module &m); -#endif // Shape_3_hpp__cppwg_wrapper diff --git a/examples/shapes/wrapper/primitives/_pyshapes_primitives.main.cppwg.cpp b/examples/shapes/wrapper/primitives/_pyshapes_primitives.main.cppwg.cpp index da42eb7..b77a7d7 100644 --- a/examples/shapes/wrapper/primitives/_pyshapes_primitives.main.cppwg.cpp +++ b/examples/shapes/wrapper/primitives/_pyshapes_primitives.main.cppwg.cpp @@ -10,8 +10,7 @@ #pragma GCC visibility pop #endif #include "wrapper_header_collection.cppwg.hpp" -#include "Shape_2.cppwg.hpp" -#include "Shape_3.cppwg.hpp" +#include "Shape.cppwg.hpp" #include "Cuboid.cppwg.hpp" #include "Rectangle.cppwg.hpp" From 8e4d73b2b1cd412d038dd6fb64023c944a93b513 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Thu, 9 Jul 2026 20:40:33 +0100 Subject: [PATCH 03/11] #30 Document that a templated class's instantiations share one wrapper file Add a README tip noting the combined-per-class output: Foo<2> and Foo<3> are wrapped in one Foo.cppwg.hpp/.cpp pair (with a register_Foo_2_class and register_Foo_3_class each) rather than a file pair per instantiation. Co-Authored-By: Claude Opus 4.8 --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 97fb220..3e68fdd 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,11 @@ r = Rectangle(4, 5) - By default, cppwg only rewrites wrapper files whose content has changed, leaving unchanged files untouched so build systems skip recompiling them. Pass `--overwrite` to force a full rewrite of all wrapper files. +- A templated class's instantiations share a single wrapper file: `Foo<2>` and + `Foo<3>` are wrapped in one `Foo.cppwg.hpp` / `Foo.cppwg.cpp` pair (declaring + and defining a `register_Foo_2_class` and `register_Foo_3_class` each) rather + than a separate file pair per instantiation. This reduces the number of + generated files, and the translation units the build has to compile. - To pass extra flags to the castxml clang frontend (e.g. to silence a diagnostic), use `--castxml_cflags`. Values starting with `-` must use `=`, e.g. `--castxml_cflags="-Wno-deprecated"`. From c3f72967eef1b4077e32e7b4ec054790e3e7f091 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Thu, 9 Jul 2026 20:41:08 +0100 Subject: [PATCH 04/11] #30 Remove a duplicate comment line above class_hpp Co-Authored-By: Claude Opus 4.8 --- cppwg/templates/pybind11_default.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cppwg/templates/pybind11_default.py b/cppwg/templates/pybind11_default.py index 375db50..ab78990 100644 --- a/cppwg/templates/pybind11_default.py +++ b/cppwg/templates/pybind11_default.py @@ -88,7 +88,6 @@ " PyErr_SetString(PyExc_RuntimeError, ${message_expr});\n" ) -# Skeleton for a class wrapper hpp file. # Skeleton for a class wrapper hpp file. One hpp is emitted per class (not per # template instantiation); it forward-declares the register function for every # instantiation via ${register_declarations}. From 0cead8005ff05432b43932f1d6870a4ddbd16efb Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Thu, 9 Jul 2026 20:44:40 +0100 Subject: [PATCH 05/11] #30 Validate decls/cpp_names/py_names are the same length before writing The combined-per-class write() loop iterates class_info.decls and indexes class_info.py_names by the same position, but the precondition only compared decls against cpp_names. A short py_names would then fail as an IndexError mid-loop (or silently mislabel a wrapper). Check all three parallel lists are equal length up-front and raise a message naming the class and the lengths, so a desync fails deterministically and is easy to diagnose. Co-Authored-By: Claude Opus 4.8 --- cppwg/writers/class_writer.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/cppwg/writers/class_writer.py b/cppwg/writers/class_writer.py index 74d9856..50453ca 100644 --- a/cppwg/writers/class_writer.py +++ b/cppwg/writers/class_writer.py @@ -503,9 +503,21 @@ def write(self, work_dir: str) -> None: """ logger = logging.getLogger() - if len(self.class_info.decls) != len(self.class_info.cpp_names): - logger.error("Not enough class decls added to do write.") - raise AssertionError() + # decls, cpp_names and py_names are parallel, one entry per instantiation, + # and the loop below indexes all three by the same position. Validate they + # are the same length up-front so a mismatch fails deterministically here + # rather than as an IndexError (or a mislabelled wrapper) mid-loop. + n_decls = len(self.class_info.decls) + n_cpp = len(self.class_info.cpp_names) + n_py = len(self.class_info.py_names) + if not n_decls == n_cpp == n_py: + message = ( + f"Class {self.class_info.name} has mismatched instantiation lists " + f"({n_decls} decls, {n_cpp} cpp_names, {n_py} py_names); they must " + "be kept in lockstep." + ) + logger.error(message) + raise AssertionError(message) register_blocks: list[str] = [] register_py_names: list[str] = [] From df5c041276967ddb3eeadd924e381550cc20b238 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Thu, 9 Jul 2026 20:50:13 +0100 Subject: [PATCH 06/11] #30 Fail fast when two classes share a wrapper file name Each class writes one wrapper file pair named after the class. If two classes in a module map to the same file stem, one overwrites the other's .hpp/.cpp and they collide on the register_..._class symbols, producing a broken output set that only surfaces later as a confusing compile/link error. There is no benign case, so raise a ValueError at generation time naming both classes and pointing at the fix, instead of logging a warning and continuing. Co-Authored-By: Claude Opus 4.8 --- cppwg/writers/module_writer.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/cppwg/writers/module_writer.py b/cppwg/writers/module_writer.py index 2df6c44..bb1c759 100644 --- a/cppwg/writers/module_writer.py +++ b/cppwg/writers/module_writer.py @@ -272,15 +272,19 @@ def write_class_wrappers(self) -> None: logger.info(f"Skipping class {class_info.name}") continue - # Each class writes one wrapper file pair named after the class; warn - # if two classes in the module would collide on that file name. + # Each class writes one wrapper file pair named after the class. Two + # classes sharing that name would overwrite each other's files (and + # collide on the register_..._class symbols), producing a broken + # output set that only fails later at compile/link. Fail fast instead. file_stem = class_info.py_name_base() if file_stem in seen_file_stems: - logger.warning( - f"Wrapper file '{file_stem}.{CPPWG_EXT}.*' for class " - f"{class_info.name} collides with class " - f"{seen_file_stems[file_stem]}; one will overwrite the other." + message = ( + f"Wrapper file name '{file_stem}.{CPPWG_EXT}.*' is used by both " + f"class {seen_file_stems[file_stem]} and class " + f"{class_info.name}. Give one of them a distinct name_override." ) + logger.error(message) + raise ValueError(message) seen_file_stems[file_stem] = class_info.name logger.info(f"Generating wrappers for class {class_info.name}") From 04005e0e0005616da080baf2177cfcd223be3c4b Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Fri, 10 Jul 2026 00:10:16 +0100 Subject: [PATCH 07/11] #30 Hoist all instantiation alias typedefs into the shared preamble When several instantiations share one wrapper file, each instantiation's alias typedef (typedef ;) previously sat inside its own registration block, after that block's generator pre-code. A custom generator whose pre-code refers to the class by its alias - e.g. pychaste's CellsGenerator, which emits an override class inheriting the alias - then used the alias before it was declared, so the combined wrapper failed to compile (the shapes/cells examples do not exercise that path, so it slipped through). Gather every instantiation's alias typedef and emit them once in the shared class_cpp_header preamble (${class_typedefs}) ahead of all registration code, so each block's generator pre-code, trampoline override class and registration can refer to any instantiation by its alias regardless of order. Drop the per-block typedef from class_cpp_register and struct_enum_register. Regenerate the shapes and cells example wrappers to the new layout. Verified: 157 unit tests pass; shapes 6/6 and cells 8/8 example tests pass; the full pychaste combined wrapper set (273 files, incl. CellsGenerator and the AbstractMesh trampoline) compiles, links and imports. Co-Authored-By: Claude Opus 4.8 --- cppwg/templates/pybind11_default.py | 29 ++++--- cppwg/writers/class_writer.py | 28 ++++++- .../wrappers/all/AbstractMesh.cppwg.cpp | 4 +- .../cells/dynamic/wrappers/all/Cell.cppwg.cpp | 2 +- .../dynamic/wrappers/all/Corner.cppwg.cpp | 2 +- .../dynamic/wrappers/all/Facet.cppwg.cpp | 2 +- .../dynamic/wrappers/all/MacroMesh.cppwg.cpp | 4 +- .../wrappers/all/MeshFactory.cppwg.cpp | 4 +- .../cells/dynamic/wrappers/all/Node.cppwg.cpp | 4 +- .../dynamic/wrappers/all/PetscUtils.cppwg.cpp | 2 +- .../dynamic/wrappers/all/PottsMesh.cppwg.cpp | 4 +- .../dynamic/wrappers/all/Scene.cppwg.cpp | 4 +- .../wrapper/composites/Square.cppwg.cpp | 2 +- .../shapes/wrapper/geometry/Point.cppwg.cpp | 4 +- .../wrapper/primitives/Cuboid.cppwg.cpp | 2 +- .../wrapper/primitives/Rectangle.cppwg.cpp | 2 +- .../shapes/wrapper/primitives/Shape.cppwg.cpp | 4 +- tests/test_class_writer.py | 81 ++++++++++++++++--- 18 files changed, 137 insertions(+), 47 deletions(-) diff --git a/cppwg/templates/pybind11_default.py b/cppwg/templates/pybind11_default.py index ab78990..d89fec1 100644 --- a/cppwg/templates/pybind11_default.py +++ b/cppwg/templates/pybind11_default.py @@ -8,7 +8,9 @@ # every entry in template_collection is filled the same way (via .substitute). class_virtual_override_header = Template( - "class ${class_py_name}" + CPPWG_CLASS_OVERRIDE_SUFFIX + " : public ${class_py_name}\n" + "class ${class_py_name}" + + CPPWG_CLASS_OVERRIDE_SUFFIX + + " : public ${class_py_name}\n" "{\n" "public:\n" " using ${class_py_name}::${class_base_name};\n" @@ -111,8 +113,12 @@ # Preamble for a class wrapper cpp file, emitted once per class. The file-scope # items that must appear only once when several instantiations share a file live # here: the includes, the smart-pointer holder declaration, class-level prefix -# code, and the (deduplicated) trampoline return typedefs. Each instantiation's -# registration follows via one class_cpp_register block. +# code, and the (deduplicated) trampoline return typedefs. It also carries the +# per-instantiation alias typedefs (typedef ;) for every instantiation +# in the file, gathered into ${class_typedefs} ahead of any registration code so +# each block's generator pre-code, trampoline and registration can refer to any +# instantiation by its alias. Each instantiation's registration follows via one +# class_cpp_register block. class_cpp_header = Template( "${prefix_text}" "#include \n" @@ -124,21 +130,24 @@ "namespace py = pybind11;\n" "${smart_ptr_handle};\n" "${prefix_code}" + "${class_typedefs}" "${return_typedefs}" ) # Registration block for one template instantiation, appended once per # instantiation after the class_cpp_header preamble. Refers to the class through -# the wrapper alias (class_py_name, typedef'd to the C++ type) so the trampoline, -# registration function name and Python-visible name all match. +# the wrapper alias (class_py_name) so the trampoline, registration function name +# and Python-visible name all match. The alias typedefs for every instantiation +# are emitted once in the class_cpp_header preamble (${class_typedefs}), so the +# generator pre-code, trampoline override class and registration below can all +# refer to the class by that alias without redefining it here. class_cpp_register = Template( "${generator_pre_code}" - "typedef ${class_cpp_name} ${class_py_name};\n" "\n" "${override_class}" "void register_${class_py_name}_class(py::module &m)\n" "{\n" - ' py::class_<${class_py_name}${overrides_string}${ptr_support}${bases}>' + " py::class_<${class_py_name}${overrides_string}${ptr_support}${bases}>" '(m, "${class_py_name}")\n' "${constructors}" "${methods}" @@ -151,11 +160,11 @@ # Skeleton for the struct-enum special case, e.g.: # struct Foo { enum Value { A, B, C }; }; # The registration body wraps the single nested enum. Like class_cpp_register it -# refers to the class through the wrapper alias (class_py_name), and follows the -# shared class_cpp_header preamble. +# refers to the class through the wrapper alias (class_py_name), whose typedef is +# emitted in the shared class_cpp_header preamble (${class_typedefs}), and follows +# that preamble. struct_enum_register = Template( "${generator_pre_code}" - "typedef ${class_cpp_name} ${class_py_name};\n" "void register_${class_py_name}_class(py::module &m){\n" ' py::class_<${class_py_name}> myclass(m, "${class_py_name}");\n' ' py::enum_<${class_py_name}::${enum_name}>(myclass, "${enum_name}")\n' diff --git a/cppwg/writers/class_writer.py b/cppwg/writers/class_writer.py index 50453ca..34d3efd 100644 --- a/cppwg/writers/class_writer.py +++ b/cppwg/writers/class_writer.py @@ -330,16 +330,22 @@ def build_hpp(self, register_py_names: list[str]) -> str: register_declarations=register_declarations, ) - def build_cpp_header(self, return_typedefs: str) -> str: + def build_cpp_header( + self, class_typedefs: str = "", return_typedefs: str = "" + ) -> str: """ Build the shared preamble of the class wrapper cpp file. Emitted once per class (not per instantiation): the includes, the smart - pointer holder declaration, class-level prefix code, and the - deduplicated trampoline return typedefs. + pointer holder declaration, class-level prefix code, the per-instantiation + alias typedefs, and the deduplicated trampoline return typedefs. Parameters ---------- + class_typedefs : str + The alias typedefs (typedef ;) for every instantiation in + the file, emitted ahead of the registration blocks so any block can + refer to any instantiation by its alias. return_typedefs : str The trampoline return typedefs, deduplicated across instantiations. @@ -354,6 +360,7 @@ def build_cpp_header(self, return_typedefs: str) -> str: class_hpp_name=self.class_info.py_name_base(), smart_ptr_handle=self.smart_ptr_handle(), prefix_code=self.prefix_code(), + class_typedefs=class_typedefs, return_typedefs=return_typedefs, ) @@ -521,12 +528,21 @@ def write(self, work_dir: str) -> None: register_blocks: list[str] = [] register_py_names: list[str] = [] + class_typedefs: list[str] = [] deduped_typedefs: list[str] = [] seen_typedefs: set[str] = set() for idx, class_decl in enumerate(self.class_info.decls): class_py_name = self.class_info.py_names[idx] + # The alias typedef (typedef ;) for this instantiation, kept + # for the preamble so every block can refer to it (and every other + # instantiation) by its alias regardless of block order. + alias_typedef = "typedef {cpp_name} {py_name};\n".format( + cpp_name=self.class_info.cpp_names[idx], + py_name=class_py_name, + ) + # Check for the struct-enum pattern, e.g.: # struct Foo { enum Value {A, B, C}; }; if type_traits_classes.is_struct(class_decl): @@ -534,11 +550,13 @@ def write(self, work_dir: str) -> None: if len(enums) == 1: register_blocks.append(self.build_struct_enum_register(idx)) register_py_names.append(class_py_name) + class_typedefs.append(alias_typedef) continue block, return_typedefs = self.build_class_register(idx) register_blocks.append(block) register_py_names.append(class_py_name) + class_typedefs.append(alias_typedef) # Hoist trampoline return typedefs into the shared preamble, # deduplicated so a type shared by several instantiations (e.g. @@ -554,7 +572,9 @@ def write(self, work_dir: str) -> None: return self.hpp_string = self.build_hpp(register_py_names) - self.cpp_string = self.build_cpp_header("".join(deduped_typedefs)) + self.cpp_string = self.build_cpp_header( + "".join(class_typedefs), "".join(deduped_typedefs) + ) self.cpp_string += "\n" + "\n".join(register_blocks) self.write_files(work_dir, self.class_info.py_name_base()) diff --git a/examples/cells/dynamic/wrappers/all/AbstractMesh.cppwg.cpp b/examples/cells/dynamic/wrappers/all/AbstractMesh.cppwg.cpp index bd250d5..e5b275e 100644 --- a/examples/cells/dynamic/wrappers/all/AbstractMesh.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/AbstractMesh.cppwg.cpp @@ -9,8 +9,9 @@ namespace py = pybind11; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - typedef AbstractMesh<2, 2> AbstractMesh_2_2; +typedef AbstractMesh<3, 3> AbstractMesh_3_3; + class AbstractMesh_2_2_Overrides : public AbstractMesh_2_2 { @@ -45,7 +46,6 @@ void register_AbstractMesh_2_2_class(py::module &m) ; } -typedef AbstractMesh<3, 3> AbstractMesh_3_3; class AbstractMesh_3_3_Overrides : public AbstractMesh_3_3 { diff --git a/examples/cells/dynamic/wrappers/all/Cell.cppwg.cpp b/examples/cells/dynamic/wrappers/all/Cell.cppwg.cpp index d8513bf..0a5b28d 100644 --- a/examples/cells/dynamic/wrappers/all/Cell.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/Cell.cppwg.cpp @@ -9,9 +9,9 @@ namespace py = pybind11; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - typedef Cell Cell; + void register_Cell_class(py::module &m) { py::class_>(m, "Cell") diff --git a/examples/cells/dynamic/wrappers/all/Corner.cppwg.cpp b/examples/cells/dynamic/wrappers/all/Corner.cppwg.cpp index 5255ab2..eb30826 100644 --- a/examples/cells/dynamic/wrappers/all/Corner.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/Corner.cppwg.cpp @@ -9,9 +9,9 @@ namespace py = pybind11; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); +typedef Corner<2> Corner_2; typedef unsigned int unsignedint; -typedef Corner<2> Corner_2; class Corner_2_Overrides : public Corner_2 { diff --git a/examples/cells/dynamic/wrappers/all/Facet.cppwg.cpp b/examples/cells/dynamic/wrappers/all/Facet.cppwg.cpp index 799ec02..b2d4257 100644 --- a/examples/cells/dynamic/wrappers/all/Facet.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/Facet.cppwg.cpp @@ -9,9 +9,9 @@ namespace py = pybind11; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); +typedef Facet<2> Facet_2; typedef unsigned int unsignedint; -typedef Facet<2> Facet_2; class Facet_2_Overrides : public Facet_2 { diff --git a/examples/cells/dynamic/wrappers/all/MacroMesh.cppwg.cpp b/examples/cells/dynamic/wrappers/all/MacroMesh.cppwg.cpp index 32b1d4e..5e23264 100644 --- a/examples/cells/dynamic/wrappers/all/MacroMesh.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/MacroMesh.cppwg.cpp @@ -9,8 +9,9 @@ namespace py = pybind11; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - typedef MacroMesh<2, 2> MacroMesh_2_2; +typedef MacroMesh<3, 3> MacroMesh_3_3; + void register_MacroMesh_2_2_class(py::module &m) { @@ -22,7 +23,6 @@ void register_MacroMesh_2_2_class(py::module &m) ; } -typedef MacroMesh<3, 3> MacroMesh_3_3; void register_MacroMesh_3_3_class(py::module &m) { diff --git a/examples/cells/dynamic/wrappers/all/MeshFactory.cppwg.cpp b/examples/cells/dynamic/wrappers/all/MeshFactory.cppwg.cpp index ff2eac6..5171e2d 100644 --- a/examples/cells/dynamic/wrappers/all/MeshFactory.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/MeshFactory.cppwg.cpp @@ -10,8 +10,9 @@ namespace py = pybind11; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - typedef MeshFactory> MeshFactory_PottsMesh_2; +typedef MeshFactory> MeshFactory_PottsMesh_3; + void register_MeshFactory_PottsMesh_2_class(py::module &m) { @@ -23,7 +24,6 @@ void register_MeshFactory_PottsMesh_2_class(py::module &m) ; } -typedef MeshFactory> MeshFactory_PottsMesh_3; void register_MeshFactory_PottsMesh_3_class(py::module &m) { diff --git a/examples/cells/dynamic/wrappers/all/Node.cppwg.cpp b/examples/cells/dynamic/wrappers/all/Node.cppwg.cpp index 7a8efc8..77b7268 100644 --- a/examples/cells/dynamic/wrappers/all/Node.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/Node.cppwg.cpp @@ -10,8 +10,9 @@ namespace py = pybind11; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - typedef Node<2> Node_2; +typedef Node<3> Node_3; + void register_Node_2_class(py::module &m) { @@ -31,7 +32,6 @@ void register_Node_2_class(py::module &m) ; } -typedef Node<3> Node_3; void register_Node_3_class(py::module &m) { diff --git a/examples/cells/dynamic/wrappers/all/PetscUtils.cppwg.cpp b/examples/cells/dynamic/wrappers/all/PetscUtils.cppwg.cpp index de4ce46..c6f7e94 100644 --- a/examples/cells/dynamic/wrappers/all/PetscUtils.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/PetscUtils.cppwg.cpp @@ -10,9 +10,9 @@ namespace py = pybind11; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - typedef PetscUtils PetscUtils; + void register_PetscUtils_class(py::module &m) { py::class_>(m, "PetscUtils") diff --git a/examples/cells/dynamic/wrappers/all/PottsMesh.cppwg.cpp b/examples/cells/dynamic/wrappers/all/PottsMesh.cppwg.cpp index cebc85f..43d7756 100644 --- a/examples/cells/dynamic/wrappers/all/PottsMesh.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/PottsMesh.cppwg.cpp @@ -9,8 +9,9 @@ namespace py = pybind11; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - typedef PottsMesh<2> PottsMesh_2; +typedef PottsMesh<3> PottsMesh_3; + class PottsMesh_2_Overrides : public PottsMesh_2 { @@ -36,7 +37,6 @@ void register_PottsMesh_2_class(py::module &m) ; } -typedef PottsMesh<3> PottsMesh_3; class PottsMesh_3_Overrides : public PottsMesh_3 { diff --git a/examples/cells/dynamic/wrappers/all/Scene.cppwg.cpp b/examples/cells/dynamic/wrappers/all/Scene.cppwg.cpp index 98ee02c..66cd5d8 100644 --- a/examples/cells/dynamic/wrappers/all/Scene.cppwg.cpp +++ b/examples/cells/dynamic/wrappers/all/Scene.cppwg.cpp @@ -10,8 +10,9 @@ namespace py = pybind11; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - typedef Scene<2> Scene_2; +typedef Scene<3> Scene_3; + void register_Scene_2_class(py::module &m) { @@ -26,7 +27,6 @@ void register_Scene_2_class(py::module &m) ; } -typedef Scene<3> Scene_3; void register_Scene_3_class(py::module &m) { diff --git a/examples/shapes/wrapper/composites/Square.cppwg.cpp b/examples/shapes/wrapper/composites/Square.cppwg.cpp index b4b9636..b657ca5 100644 --- a/examples/shapes/wrapper/composites/Square.cppwg.cpp +++ b/examples/shapes/wrapper/composites/Square.cppwg.cpp @@ -9,9 +9,9 @@ namespace py = pybind11; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - typedef Square Square; + void register_Square_class(py::module &m) { py::class_, ::Rectangle>(m, "Square") diff --git a/examples/shapes/wrapper/geometry/Point.cppwg.cpp b/examples/shapes/wrapper/geometry/Point.cppwg.cpp index 89d57b4..4a1f9a7 100644 --- a/examples/shapes/wrapper/geometry/Point.cppwg.cpp +++ b/examples/shapes/wrapper/geometry/Point.cppwg.cpp @@ -9,8 +9,9 @@ namespace py = pybind11; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - typedef Point<2> Point_2; +typedef Point<3> Point_3; + void register_Point_2_class(py::module &m) { @@ -35,7 +36,6 @@ void register_Point_2_class(py::module &m) ; } -typedef Point<3> Point_3; void register_Point_3_class(py::module &m) { diff --git a/examples/shapes/wrapper/primitives/Cuboid.cppwg.cpp b/examples/shapes/wrapper/primitives/Cuboid.cppwg.cpp index c392d58..a1b37ad 100644 --- a/examples/shapes/wrapper/primitives/Cuboid.cppwg.cpp +++ b/examples/shapes/wrapper/primitives/Cuboid.cppwg.cpp @@ -9,9 +9,9 @@ namespace py = pybind11; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - typedef Cuboid Cuboid; + void register_Cuboid_class(py::module &m) { py::class_, Shape<3>>(m, "Cuboid") diff --git a/examples/shapes/wrapper/primitives/Rectangle.cppwg.cpp b/examples/shapes/wrapper/primitives/Rectangle.cppwg.cpp index 179e854..e773936 100644 --- a/examples/shapes/wrapper/primitives/Rectangle.cppwg.cpp +++ b/examples/shapes/wrapper/primitives/Rectangle.cppwg.cpp @@ -9,9 +9,9 @@ namespace py = pybind11; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - typedef Rectangle Rectangle; + void register_Rectangle_class(py::module &m) { py::class_, Shape<2>>(m, "Rectangle") diff --git a/examples/shapes/wrapper/primitives/Shape.cppwg.cpp b/examples/shapes/wrapper/primitives/Shape.cppwg.cpp index 7d38793..be89755 100644 --- a/examples/shapes/wrapper/primitives/Shape.cppwg.cpp +++ b/examples/shapes/wrapper/primitives/Shape.cppwg.cpp @@ -9,8 +9,9 @@ namespace py = pybind11; PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); - typedef Shape<2> Shape_2; +typedef Shape<3> Shape_3; + void register_Shape_2_class(py::module &m) { @@ -34,7 +35,6 @@ void register_Shape_2_class(py::module &m) ; } -typedef Shape<3> Shape_3; void register_Shape_3_class(py::module &m) { diff --git a/tests/test_class_writer.py b/tests/test_class_writer.py index 7741538..664a45a 100644 --- a/tests/test_class_writer.py +++ b/tests/test_class_writer.py @@ -45,6 +45,7 @@ def __init__( py_names=None, name_base=None, decls=None, + generator=None, ): self.name = name self.cpp_names = cpp_names if cpp_names is not None else [name] @@ -53,7 +54,7 @@ def __init__( self.source_file = source_file self.prefix_code = [] self.suffix_code = [] - self.custom_generator_instance = None + self.custom_generator_instance = generator self._name_base = name_base or name self._attrs = attrs @@ -94,16 +95,18 @@ def test_struct_enum_wrapper_common_include(): ) writer = _make_writer(class_info) - header = writer.build_cpp_header("") + header = writer.build_cpp_header("typedef Color Color;\n") block = writer.build_struct_enum_register(0) - # The shared preamble carries the includes and the holder (exactly once). + # The shared preamble carries the includes, the holder (exactly once) and the + # instantiation's alias typedef. assert '#include "wrapper_header_collection.cppwg.hpp"\n' in header assert header.count("PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr);") == 1 + assert "typedef Color Color;\n" in header - # The per-instantiation registration block wraps the enum values. + # The per-instantiation registration block wraps the enum values; the alias + # typedef lives in the preamble, not the block. expected_block = ( - "typedef Color Color;\n" "void register_Color_class(py::module &m){\n" ' py::class_ myclass(m, "Color");\n' ' py::enum_(myclass, "Value")\n' @@ -139,15 +142,16 @@ def test_struct_enum_wrapper_uses_wrapper_alias_not_cpp_decl_name(): ) writer = _make_writer(class_info) - header = writer.build_cpp_header("") + header = writer.build_cpp_header("typedef Color MyColor;\n") block = writer.build_struct_enum_register(0) - # The wrapper file and its include are named after the Python alias. + # The wrapper file and its include are named after the Python alias, and the + # preamble defines the alias (C++ decl "Color" -> Python "MyColor"). assert '#include "MyColor.cppwg.hpp"\n' in header + assert "typedef Color MyColor;\n" in header # The registration refers to the class by the alias throughout. expected_block = ( - "typedef Color MyColor;\n" "void register_MyColor_class(py::module &m){\n" ' py::class_ myclass(m, "MyColor");\n' ' py::enum_(myclass, "Value")\n' @@ -230,9 +234,12 @@ def test_combined_wrapper_shares_one_preamble_for_all_instantiations(): assert "void register_Foo_3_class(" in hpp assert hpp.count("#define Foo_hpp__cppwg_wrapper") == 1 - # The combined cpp is one shared preamble + one register block per instantiation. + # The combined cpp is one shared preamble (carrying every instantiation's + # alias typedef) + one register block per instantiation. This mirrors how + # write() assembles the preamble from the gathered alias typedefs. + class_typedefs = "typedef Foo<2> Foo_2;\ntypedef Foo<3> Foo_3;\n" cpp = ( - writer.build_cpp_header("") + writer.build_cpp_header(class_typedefs) + "\n" + "\n".join(writer.build_struct_enum_register(i) for i in range(2)) ) @@ -244,6 +251,60 @@ def test_combined_wrapper_shares_one_preamble_for_all_instantiations(): assert "void register_Foo_2_class(" in cpp assert "void register_Foo_3_class(" in cpp + # Every alias typedef precedes all registration code, so any block can refer + # to any instantiation by its alias. + last_typedef = max( + cpp.index("typedef Foo<2> Foo_2;"), cpp.index("typedef Foo<3> Foo_3;") + ) + first_register = min( + cpp.index("void register_Foo_2_class("), + cpp.index("void register_Foo_3_class("), + ) + assert last_typedef < first_register + + +class _FakeGenerator: + """Custom generator whose pre-code refers to the class by its wrapper alias. + + Mirrors real generators (e.g. CellsGenerator's) that emit an override class + inheriting the alias in their pre-code. + """ + + def get_class_cpp_pre_code(self, class_py_name): + return f"class {class_py_name}_Overrides : public {class_py_name} {{}};\n" + + def get_class_cpp_def_code(self, class_py_name): + return "" + + +def test_generator_pre_code_follows_alias_typedef(): + """A custom generator's pre-code is emitted after the alias typedef. + + Regression test for issue #30: the generator's pre-code refers to the class + by its wrapper alias, so the `typedef ;` (emitted in the shared + preamble) must precede it. Emitting the pre-code first left the alias + undeclared and failed to compile (seen on pychaste's CellsGenerator). + """ + enum = _FakeEnum("Value", [("A", 0)]) + decl = _FakeStructDecl("Foo", "/src/Foo.hpp", enum) + class_info = _FakeClassInfo( + "Foo", + decl, + attrs={"common_include_file": True}, + source_file="Foo.hpp", + cpp_names=["Foo<2>"], + py_names=["Foo_2"], + generator=_FakeGenerator(), + ) + writer = _make_writer(class_info) + + # write() hoists the alias typedef into the preamble; the generator pre-code + # (which uses the alias) stays in the block that follows. + cpp = writer.build_cpp_header("typedef Foo<2> Foo_2;\n") + cpp += "\n" + writer.build_struct_enum_register(0) + + assert cpp.index("typedef Foo<2> Foo_2;") < cpp.index("class Foo_2_Overrides") + def test_includes_block_skips_non_string_source_include(): """A mis-typed (non-string) source_includes entry is skipped, not crashed on.""" From 5876ddcd14c74dc46fbc1d283fd5d38721bf5cc7 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Fri, 10 Jul 2026 17:38:36 +0100 Subject: [PATCH 08/11] #30 Add newline after generator pre-code in struct_enum_register The struct-enum registration template concatenated ${generator_pre_code} directly with the void register_... line. A custom generator's get_class_cpp_pre_code is not guaranteed to end with a newline, so a non-empty pre-code could emit invalid C++ (e.g. class X{};void register...). Insert an explicit newline, matching class_cpp_register. Co-Authored-By: Claude Opus 4.8 --- cppwg/templates/pybind11_default.py | 1 + tests/test_class_writer.py | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/cppwg/templates/pybind11_default.py b/cppwg/templates/pybind11_default.py index d89fec1..312862e 100644 --- a/cppwg/templates/pybind11_default.py +++ b/cppwg/templates/pybind11_default.py @@ -165,6 +165,7 @@ # that preamble. struct_enum_register = Template( "${generator_pre_code}" + "\n" "void register_${class_py_name}_class(py::module &m){\n" ' py::class_<${class_py_name}> myclass(m, "${class_py_name}");\n' ' py::enum_<${class_py_name}::${enum_name}>(myclass, "${enum_name}")\n' diff --git a/tests/test_class_writer.py b/tests/test_class_writer.py index 664a45a..b2706c5 100644 --- a/tests/test_class_writer.py +++ b/tests/test_class_writer.py @@ -105,8 +105,11 @@ def test_struct_enum_wrapper_common_include(): assert "typedef Color Color;\n" in header # The per-instantiation registration block wraps the enum values; the alias - # typedef lives in the preamble, not the block. + # typedef lives in the preamble, not the block. It opens with the (empty) + # generator pre-code slot followed by a blank line, mirroring the normal + # class_cpp_register block. expected_block = ( + "\n" "void register_Color_class(py::module &m){\n" ' py::class_ myclass(m, "Color");\n' ' py::enum_(myclass, "Value")\n' @@ -150,8 +153,10 @@ def test_struct_enum_wrapper_uses_wrapper_alias_not_cpp_decl_name(): assert '#include "MyColor.cppwg.hpp"\n' in header assert "typedef Color MyColor;\n" in header - # The registration refers to the class by the alias throughout. + # The registration refers to the class by the alias throughout, opening with + # the (empty) generator pre-code slot and a blank line. expected_block = ( + "\n" "void register_MyColor_class(py::module &m){\n" ' py::class_ myclass(m, "MyColor");\n' ' py::enum_(myclass, "Value")\n' From 6a2f436e244baeb8f53c401e6010fe952c26396c Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Fri, 10 Jul 2026 17:46:26 +0100 Subject: [PATCH 09/11] #30 Guarantee trailing newlines on custom-generator code snippets The custom-generator API (cppwg.templates.custom.Custom) returns raw C++ with no trailing-newline guarantee. Three template slots splice that output directly ahead of another line, so a missing newline can produce invalid C++: module_pre_code precedes the class #include lines (a directive that no longer starts a line), module_code precedes the module's closing brace, and generator_def_code precedes the .def() chain terminator (a trailing // comment would swallow it). Add ensure_trailing_newline() and apply it at the three consumption points. Normalising only non-empty output leaves generator-free files (all the examples) byte-identical, unlike a template-level newline. Also add get_module_pre_code() to the Custom base class: module_writer already calls it, so a generator subclassing Custom without it would raise AttributeError. Co-Authored-By: Claude Opus 4.8 --- cppwg/templates/custom.py | 8 ++++++++ cppwg/utils/utils.py | 27 +++++++++++++++++++++++++++ cppwg/writers/class_writer.py | 8 ++++++-- cppwg/writers/module_writer.py | 14 +++++++++++--- tests/test_utils.py | 17 +++++++++++++++++ 5 files changed, 69 insertions(+), 5 deletions(-) diff --git a/cppwg/templates/custom.py b/cppwg/templates/custom.py index 868421a..f2e5231 100644 --- a/cppwg/templates/custom.py +++ b/cppwg/templates/custom.py @@ -25,6 +25,14 @@ def get_class_cpp_def_code(self, *args, **kwargs) -> str: return "" + def get_module_pre_code(self) -> str: + """ + Return a string of C++ code to be inserted before the module + definition. + """ + + return "" + def get_module_code(self) -> str: """ Return a string of C++ code to be inserted in the module diff --git a/cppwg/utils/utils.py b/cppwg/utils/utils.py index 224d99d..08a77da 100644 --- a/cppwg/utils/utils.py +++ b/cppwg/utils/utils.py @@ -13,6 +13,33 @@ from pygccxml.declarations.class_declaration import class_t +def ensure_trailing_newline(code: str) -> str: + """ + Return `code` guaranteed to end with a newline (unless it is empty). + + Custom generators (subclasses of `cppwg.templates.custom.Custom`) return raw + C++ snippets with no trailing-newline guarantee. When such a snippet is + substituted into a wrapper template immediately ahead of another line, a + missing newline glues the two together and can produce invalid C++ (e.g. a + `#include` directive that no longer starts a line, or a closing `}` swallowed + by a trailing `//` comment). Normalise the snippet here so callers can splice + it safely regardless of how the generator formatted it. + + Parameters + ---------- + code : str + The generator-produced code snippet, possibly empty. + + Returns + ------- + str + The snippet with a single trailing newline, or "" unchanged. + """ + if code and not code.endswith("\n"): + return code + "\n" + return code + + def write_file_if_changed(filepath: str, content: str, overwrite: bool = False) -> bool: """ Write content to filepath unless an identical file already exists. diff --git a/cppwg/writers/class_writer.py b/cppwg/writers/class_writer.py index 34d3efd..3506abf 100644 --- a/cppwg/writers/class_writer.py +++ b/cppwg/writers/class_writer.py @@ -12,7 +12,7 @@ CPPWG_EXT, CPPWG_HEADER_COLLECTION_FILENAME, ) -from cppwg.utils.utils import write_file_if_changed +from cppwg.utils.utils import ensure_trailing_newline, write_file_if_changed from cppwg.writers.base_writer import CppBaseWrapperWriter from cppwg.writers.constructor_writer import CppConstructorWrapperWriter from cppwg.writers.method_writer import CppMethodWrapperWriter @@ -439,7 +439,11 @@ def build_class_register(self, template_idx: int) -> tuple[str, str]: bases=self.bases_block(class_decl), constructors=constructors, methods=methods, - generator_def_code=( + # Normalise the generator snippet to end with a newline: it is spliced + # into the .def() chain ahead of suffix_code and the closing `;`, so a + # missing newline (or a trailing // comment) could swallow the + # statement terminator. See ensure_trailing_newline. + generator_def_code=ensure_trailing_newline( generator.get_class_cpp_def_code(class_py_name) if generator else "" ), suffix_code=self.suffix_code(), diff --git a/cppwg/writers/module_writer.py b/cppwg/writers/module_writer.py index bb1c759..013aa2d 100644 --- a/cppwg/writers/module_writer.py +++ b/cppwg/writers/module_writer.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING from cppwg.utils.constants import CPPWG_EXT, CPPWG_HEADER_COLLECTION_FILENAME -from cppwg.utils.utils import write_file_if_changed +from cppwg.utils.utils import ensure_trailing_newline, write_file_if_changed from cppwg.writers.class_writer import CppClassWrapperWriter from cppwg.writers.free_function_writer import CppFreeFunctionWrapperWriter @@ -212,7 +212,13 @@ def build_module_context(self) -> dict[str, str]: return { "prefix_text": prefix_block, "includes": includes, - "module_pre_code": (generator.get_module_pre_code() if generator else ""), + # Generator snippets carry no trailing-newline guarantee. module_pre_code + # is followed by the class #include lines and module_code by the module's + # closing brace, so normalise both to end with a newline (see + # ensure_trailing_newline) to avoid producing invalid C++. + "module_pre_code": ensure_trailing_newline( + generator.get_module_pre_code() if generator else "" + ), "class_includes": class_includes, "full_module_name": self.full_module_name, "imports": imports, @@ -221,7 +227,9 @@ def build_module_context(self) -> dict[str, str]: "exception_translator": self.generate_exception_translator(), "free_functions": free_functions, "register_calls": register_calls, - "module_code": generator.get_module_code() if generator else "", + "module_code": ensure_trailing_newline( + generator.get_module_code() if generator else "" + ), } def write_module_wrapper(self) -> None: diff --git a/tests/test_utils.py b/tests/test_utils.py index 2c5c3ee..38f3982 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -5,6 +5,7 @@ import pytest from cppwg.utils.utils import ( + ensure_trailing_newline, find_template_instantiations_in_source, find_template_instantiations_in_source_file, find_template_params_in_source, @@ -17,6 +18,22 @@ ) +@pytest.mark.parametrize( + "code, expected", + [ + ("", ""), # empty is left untouched (no spurious blank line) + ("class X{};", "class X{};\n"), # unterminated snippet gets a newline + ('#include "foo.h"', '#include "foo.h"\n'), + ("already\n", "already\n"), # already terminated is unchanged + ("two\nlines\n", "two\nlines\n"), + ("// trailing comment", "// trailing comment\n"), + ], +) +def test_ensure_trailing_newline(code, expected): + """A non-empty snippet is guaranteed a single trailing newline.""" + assert ensure_trailing_newline(code) == expected + + @pytest.mark.parametrize( "arg, expected", [ From a6c7aba5e4fb945d258f65f6de4d8374526124cb Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Fri, 10 Jul 2026 18:51:44 +0100 Subject: [PATCH 10/11] Make ensure_trailing_newline description more precise Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- cppwg/utils/utils.py | 3 +-- tests/test_utils.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cppwg/utils/utils.py b/cppwg/utils/utils.py index 08a77da..c4fe911 100644 --- a/cppwg/utils/utils.py +++ b/cppwg/utils/utils.py @@ -33,8 +33,7 @@ def ensure_trailing_newline(code: str) -> str: Returns ------- str - The snippet with a single trailing newline, or "" unchanged. - """ + The snippet guaranteed to end with a trailing newline, or "" unchanged. if code and not code.endswith("\n"): return code + "\n" return code diff --git a/tests/test_utils.py b/tests/test_utils.py index 38f3982..1a2a875 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -30,7 +30,7 @@ ], ) def test_ensure_trailing_newline(code, expected): - """A non-empty snippet is guaranteed a single trailing newline.""" + """A non-empty snippet is guaranteed to end with a trailing newline.""" assert ensure_trailing_newline(code) == expected From decca49f8d0224ffac4feb750bd03bfe3f945de6 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Fri, 10 Jul 2026 19:04:57 +0100 Subject: [PATCH 11/11] #30 Fix ensure_trailing_newline docstring and pin black to py310 Restore the closing triple-quote on ensure_trailing_newline's docstring; a stray edit had dropped it, turning the rest of utils.py into one unterminated string literal (E999 / black parse failure). Pin black's target-version to the project's minimum supported Python (py310, matching requires-python) instead of listing every supported version; black gates formatting on the oldest target, so this is equivalent output with less to maintain. Reformat two tuple-unpack assignments accordingly. Co-Authored-By: Claude Opus 4.8 --- cppwg/generators.py | 7 ++++--- cppwg/utils/utils.py | 1 + cppwg/writers/class_writer.py | 8 +++++--- pyproject.toml | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/cppwg/generators.py b/cppwg/generators.py index c33a285..74a7252 100644 --- a/cppwg/generators.py +++ b/cppwg/generators.py @@ -323,9 +323,10 @@ def discover_template_instantiations(self) -> None: instantiation_map: dict[str, list[list[str]]] = {} macro_only_files: list[str] = [] for filepath in self.package_info.source_cpp_files: - has_instantiations, file_map = ( - utils.find_template_instantiations_in_source_file(filepath) - ) + ( + has_instantiations, + file_map, + ) = utils.find_template_instantiations_in_source_file(filepath) if not has_instantiations: continue diff --git a/cppwg/utils/utils.py b/cppwg/utils/utils.py index c4fe911..3bf17d1 100644 --- a/cppwg/utils/utils.py +++ b/cppwg/utils/utils.py @@ -34,6 +34,7 @@ def ensure_trailing_newline(code: str) -> str: ------- str The snippet guaranteed to end with a trailing newline, or "" unchanged. + """ if code and not code.endswith("\n"): return code + "\n" return code diff --git a/cppwg/writers/class_writer.py b/cppwg/writers/class_writer.py index 3506abf..2328052 100644 --- a/cppwg/writers/class_writer.py +++ b/cppwg/writers/class_writer.py @@ -385,9 +385,11 @@ def build_class_register(self, template_idx: int) -> tuple[str, str]: generator = self.class_info.custom_generator_instance # Find and define virtual function "trampoline" overrides - return_typedefs, override_class, methods_needing_override = ( - self.virtual_overrides(template_idx) - ) + ( + return_typedefs, + override_class, + methods_needing_override, + ) = self.virtual_overrides(template_idx) # Add the trampoline override class to the class definition if needed # e.g. py::class_(m, "Foo") diff --git a/pyproject.toml b/pyproject.toml index 8869f93..0835fa5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ zip-safe = false include = ["cppwg*"] [tool.black] -target-version = ["py310", "py311", "py312", "py313"] +target-version = ["py310"] extend-exclude = """ ( ^/cppwg/templates/