From e6449d0a79aad30670fe6054700520dbf40df2d7 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 28 Dec 2023 12:16:54 -0500 Subject: [PATCH] Ensure template classes are instantiated in correct order - If it depends on a base class, the base needs to be initialized first - Not all template classes exist in the header (in particular, CRTP) so anything not found in the current header gets initialized first --- robotpy_build/autowrap/cxxparser.py | 34 ++++++++++++------- robotpy_build/autowrap/header.cpp.j2 | 24 +++++++++---- robotpy_build/autowrap/j2_context.py | 6 ++++ tests/cpp/gen/ft/tdependent_base.yml | 11 ++++++ tests/cpp/pyproject.toml.tmpl | 1 + .../ft/include/templates/dependent_base.h | 10 ++++++ 6 files changed, 68 insertions(+), 18 deletions(-) create mode 100644 tests/cpp/gen/ft/tdependent_base.yml create mode 100644 tests/cpp/rpytest/ft/include/templates/dependent_base.h diff --git a/robotpy_build/autowrap/cxxparser.py b/robotpy_build/autowrap/cxxparser.py index abe50fad..5ed06e4b 100644 --- a/robotpy_build/autowrap/cxxparser.py +++ b/robotpy_build/autowrap/cxxparser.py @@ -1987,19 +1987,29 @@ def parse_header( if doc_add: doc_add = f"\n{doc_add}" - hctx.template_instances.append( - TemplateInstanceContext( - scope_var=visitor._get_module_var(tmpl_data), - var_name=f"tmplCls{i}", - py_name=k, - full_cpp_name_identifier=qualname, - binder_typename=f"bind_{qualname}_{i}", - params=tmpl_data.params, - header_name=f"{qualname}.hpp", - doc_set=visitor._quote_doc(tmpl_data.doc), - doc_add=visitor._quote_doc(doc_add), - ) + tctx = TemplateInstanceContext( + scope_var=visitor._get_module_var(tmpl_data), + var_name=f"tmplCls{i}", + py_name=k, + full_cpp_name_identifier=qualname, + binder_typename=f"bind_{qualname}_{i}", + params=tmpl_data.params, + header_name=f"{qualname}.hpp", + doc_set=visitor._quote_doc(tmpl_data.doc), + doc_add=visitor._quote_doc(doc_add), ) + hctx.template_instances.append(tctx) + + # Ensure that template instances are created in class order if the + # template class is in this header file + # - not matching here is not an error + qualname_match = tmpl_data.qualname.lstrip(":") + for cctx in hctx.classes: + if cctx.dep_cpp_name.lstrip(":") == qualname_match: + assert cctx.template + tctx.matched = True + cctx.template.instances.append(tctx) + break for param in tmpl_data.params: visitor._add_user_type_caster(param) diff --git a/robotpy_build/autowrap/header.cpp.j2 b/robotpy_build/autowrap/header.cpp.j2 index 215c2617..dcb5e63f 100644 --- a/robotpy_build/autowrap/header.cpp.j2 +++ b/robotpy_build/autowrap/header.cpp.j2 @@ -64,13 +64,19 @@ struct rpybuild_{{ hname }}_initializer { {% endfor %} {# template decls #} -{% for tmpl_data in template_instances %} +{% for tmpl_data in template_instances if not tmpl_data.matched %} rpygen::{{ tmpl_data.binder_typename }} {{ tmpl_data.var_name }}; {% endfor %} {# class decls #} -{%- for cls in classes if not cls.template %} - {{ pybind11.cls_decl(cls) }} +{%- for cls in classes %} + {% if not cls.template -%} + {{ pybind11.cls_decl(cls) }} + {%- else -%} + {%- for tmpl_data in cls.template.instances %} + rpygen::{{ tmpl_data.binder_typename }} {{ tmpl_data.var_name }}; + {% endfor -%} + {%- endif -%} {% endfor %} py::module &m; @@ -86,12 +92,18 @@ struct rpybuild_{{ hname }}_initializer { enum{{ loop.index }}{{ pybind11.enum_init(enum.scope_var, enum) }}, {% endfor %} - {% for tmpl_data in template_instances %} + {% for tmpl_data in template_instances if not tmpl_data.matched %} {{ tmpl_data.var_name }}({{ tmpl_data.scope_var }}, "{{ tmpl_data.py_name }}"), {% endfor %} - {% for cls in classes if not cls.template %} - {{ pybind11.cls_init(cls, '"' + cls.py_name + '"') }} + {% for cls in classes %} + {% if not cls.template -%} + {{ pybind11.cls_init(cls, '"' + cls.py_name + '"') }} + {%- else -%} + {%- for tmpl_data in cls.template.instances %} + {{ tmpl_data.var_name }}({{ tmpl_data.scope_var }}, "{{ tmpl_data.py_name }}"), + {% endfor -%} + {%- endif %} {% endfor %} m(m) diff --git a/robotpy_build/autowrap/j2_context.py b/robotpy_build/autowrap/j2_context.py index 12f8969b..fcd0aca6 100644 --- a/robotpy_build/autowrap/j2_context.py +++ b/robotpy_build/autowrap/j2_context.py @@ -353,6 +353,9 @@ class ClassTemplateData: #: the specified C++ code is inserted into the template definition inline_code: str + #: Instances of this class + instances: typing.List["TemplateInstanceContext"] = field(default_factory=list) + @dataclass class ClassContext: @@ -493,6 +496,9 @@ class TemplateInstanceContext: doc_set: Documentation doc_add: Documentation + #: If true, instantiated in class order + matched: bool = False + @dataclass class HeaderContext: diff --git a/tests/cpp/gen/ft/tdependent_base.yml b/tests/cpp/gen/ft/tdependent_base.yml new file mode 100644 index 00000000..851ef481 --- /dev/null +++ b/tests/cpp/gen/ft/tdependent_base.yml @@ -0,0 +1,11 @@ +classes: + TDBase: + TDChild: + template_params: + - T + +templates: + TDChild: + qualname: TDChild + params: + - int diff --git a/tests/cpp/pyproject.toml.tmpl b/tests/cpp/pyproject.toml.tmpl index ee517e11..5d911165 100644 --- a/tests/cpp/pyproject.toml.tmpl +++ b/tests/cpp/pyproject.toml.tmpl @@ -101,6 +101,7 @@ generate = [ {tvchild = "templates/tvchild.h"}, {tbasic = "templates/basic.h"}, + {tdependent_base = "templates/dependent_base.h"}, {tdependent_param = "templates/dependent_param.h"}, {tdependent_using = "templates/dependent_using.h"}, {tdependent_using2 = "templates/dependent_using2.h"}, diff --git a/tests/cpp/rpytest/ft/include/templates/dependent_base.h b/tests/cpp/rpytest/ft/include/templates/dependent_base.h new file mode 100644 index 00000000..aad3ba2e --- /dev/null +++ b/tests/cpp/rpytest/ft/include/templates/dependent_base.h @@ -0,0 +1,10 @@ +#pragma once + +// Template class that has a non-template base class in the same file + +struct TDBase { + virtual ~TDBase() {} +}; + +template +struct TDChild : TDBase {};