Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,9 @@ r = Rectangle(4, 5)
auto_includes: True # resolves PottsMesh.hpp from the signature
```

cppwg then scans each such class's wrapped method/constructor signatures, and
cppwg then scans each such class's wrapped method/constructor signatures, plus
the template arguments of its instantiations (e.g. a `Foo<Bar, DIM>` needs
`Bar.hpp` even if `Bar` never appears in a method/constructor signature), and
for every **project type** it finds (a class defined under the module
`source_locations`) adds that class's header to the wrapper. Only project types
are resolved — library types (`std::`, boost, PETSc, VTK, …) are left alone —
Expand Down
23 changes: 18 additions & 5 deletions cppwg/info/package_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -819,11 +819,12 @@ def resolve_auto_includes(self) -> None:

For each wrapped class that opts into ``auto_includes``, inspect the types
its wrapped methods/constructors actually expose (via
_iter_wrapped_arg_return_types, so exclusions are honoured), resolve any
that name a project class to that class's header, and record the headers
on ``class_info.auto_include_headers`` for the writer to emit. The class's
own header is dropped (the writer always includes it), as is anything that
does not resolve to a project header (library types are left alone).
_iter_wrapped_arg_return_types, so exclusions are honoured) plus the
project types named as its template arguments (e.g. ``Foo<Bar, DIM>``),
resolve any that name a project class to that class's header, and record
the headers on ``class_info.auto_include_headers`` for the writer to emit.
The class's own header is dropped (the writer always includes it), as is
anything that does not resolve to a project header (library types are left alone).

A class using ``common_include_file`` is skipped: the common header
already includes every project header, so per-type includes are moot.
Expand Down Expand Up @@ -854,6 +855,18 @@ def resolve_auto_includes(self) -> None:
if header:
headers.add(header)

# Project types named as template arguments of this class's
# instantiations (e.g. Foo<Bar, DIM>) are dependencies the generated
# code needs even if they never appear in a wrapped signature, so
# resolve their headers here too. Numeric args (dimensions like 2
# or 3) and library types resolve to nothing.
for arg_list in class_info.template_arg_lists:
for arg in arg_list:
for name in _IDENTIFIER_RE.findall(str(arg)):
header = type_header_map.get(name)
if header:
headers.add(header)

# The writer always includes the class's own header; drop it here.
own_header = class_info.source_file
if not own_header and class_info.decls:
Expand Down
10 changes: 10 additions & 0 deletions examples/cells/dynamic/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ modules:
# cell
- name: Cell

# CellFactory<CELL_TYPE, DIM> is header-only and uses CELL_TYPE only
# inside its method bodies, never in the wrapped interface. cppwg sees
# CELL_TYPE only as a template argument, so the package-level
# auto_includes resolves Cell.hpp from the instantiation arguments below.
# Without it the generated wrapper would not compile.
- name: CellFactory
template_substitutions:
- signature: <class CELL_TYPE, unsigned DIM>
replacement: [[Cell, 2], [Cell, 3]]

# mesh
- name: AbstractMesh

Expand Down
42 changes: 42 additions & 0 deletions examples/cells/dynamic/wrappers/all/CellFactory.cppwg.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// This file is auto-generated by cppwg; manual changes will be overwritten.

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "Cell.hpp"
#include <memory>
#include "CellFactory.hpp"

#include "CellFactory.cppwg.hpp"

namespace py = pybind11;
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>);
typedef CellFactory<Cell, 2> CellFactory_Cell_2;
typedef CellFactory<Cell, 3> CellFactory_Cell_3;


void register_CellFactory_Cell_2_class(py::module &m)
{
py::class_<CellFactory_Cell_2, std::shared_ptr<CellFactory_Cell_2>>(m, "CellFactory_Cell_2")
.def(py::init<>())
.def("GetDimension",
(unsigned int(CellFactory_Cell_2::*)() const) &CellFactory_Cell_2::GetDimension,
" ")
.def("CreateCell",
(unsigned int(CellFactory_Cell_2::*)()) &CellFactory_Cell_2::CreateCell,
" ")
;
}


void register_CellFactory_Cell_3_class(py::module &m)
{
py::class_<CellFactory_Cell_3, std::shared_ptr<CellFactory_Cell_3>>(m, "CellFactory_Cell_3")
.def(py::init<>())
.def("GetDimension",
(unsigned int(CellFactory_Cell_3::*)() const) &CellFactory_Cell_3::GetDimension,
" ")
.def("CreateCell",
(unsigned int(CellFactory_Cell_3::*)()) &CellFactory_Cell_3::CreateCell,
" ")
;
}
10 changes: 10 additions & 0 deletions examples/cells/dynamic/wrappers/all/CellFactory.cppwg.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// This file is auto-generated by cppwg; manual changes will be overwritten.

#ifndef CellFactory_hpp__cppwg_wrapper
#define CellFactory_hpp__cppwg_wrapper

#include <pybind11/pybind11.h>

void register_CellFactory_Cell_2_class(pybind11::module &m);
void register_CellFactory_Cell_3_class(pybind11::module &m);
#endif // CellFactory_hpp__cppwg_wrapper
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#pragma GCC visibility pop
#endif
#include "Cell.cppwg.hpp"
#include "CellFactory.cppwg.hpp"
#include "Corner.cppwg.hpp"
#include "Facet.cppwg.hpp"
#include "MacroMesh.cppwg.hpp"
Expand All @@ -32,6 +33,8 @@ PYBIND11_MODULE(_pycells_all, m)
});

register_Cell_class(m);
register_CellFactory_Cell_2_class(m);
register_CellFactory_Cell_3_class(m);
register_Corner_2_class(m);
register_Facet_2_class(m);
register_MacroMesh_2_2_class(m);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// Includes
#include "AbstractMesh.hpp"
#include "Cell.hpp"
#include "CellFactory.hpp"
#include "Corner.hpp"
#include "Facet.hpp"
#include "MacroMesh.hpp"
Expand All @@ -19,6 +20,8 @@
// Instantiate Template Classes
template class AbstractMesh<2, 2>;
template class AbstractMesh<3, 3>;
template class CellFactory<Cell, 2>;
template class CellFactory<Cell, 3>;
template class Corner<2>;
template class Facet<2>;
template class MacroMesh<2, 2>;
Expand All @@ -37,6 +40,8 @@ namespace cppwg
{
typedef AbstractMesh<2, 2> AbstractMesh_2_2;
typedef AbstractMesh<3, 3> AbstractMesh_3_3;
typedef CellFactory<Cell, 2> CellFactory_Cell_2;
typedef CellFactory<Cell, 3> CellFactory_Cell_3;
typedef Corner<2> Corner_2;
typedef Facet<2> Facet_2;
typedef MacroMesh<2, 2> MacroMesh_2_2;
Expand Down
48 changes: 48 additions & 0 deletions examples/cells/src/cpp/cell/CellFactory.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#ifndef CELLFACTORY_HPP_
#define CELLFACTORY_HPP_

/**
* A minimal, header-only factory templated on a cell type. CELL_TYPE is used
* only in the inline method bodies and never appears in the wrapped interface,
* so cppwg sees it only as a template argument of the CellFactory<Cell, DIM>
* instantiations - not in any signature. With auto_includes enabled, cppwg
* resolves CELL_TYPE's header (Cell.hpp) from those template arguments, so the
* generated wrapper - which odr-uses CreateCell and therefore needs the complete
* CELL_TYPE - compiles.
*/
template <class CELL_TYPE, unsigned DIM>
class CellFactory
{
public:
/**
* Default Constructor
*/
CellFactory() : mNumCells(0u)
{
}

/**
* Return the spatial dimension this factory builds in.
*/
unsigned GetDimension() const
{
return DIM;
}

/**
* Build one cell of CELL_TYPE and return the running total. Constructing a
* CELL_TYPE here is what makes the wrapper need CELL_TYPE's header.
*/
unsigned CreateCell()
{
CELL_TYPE cell;
static_cast<void>(cell);
return ++mNumCells;
}

private:
/** Number of cells created so far. */
unsigned mNumCells;
};

#endif // CELLFACTORY_HPP_
13 changes: 13 additions & 0 deletions examples/cells/src/py/pycells/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from ._pycells_all import (
Cell,
CellFactory_Cell_2,
CellFactory_Cell_3,
Corner_2,
Facet_2,
MacroMesh_2_2,
Expand All @@ -18,6 +20,16 @@
)
from ._syntax import TemplateClassDict

# CellFactory<CELL_TYPE, DIM> names CELL_TYPE only as a template argument, so
# cppwg's auto_includes resolved Cell.hpp from the instantiation arguments (not
# a signature) - see CellFactory.hpp and examples/cells/dynamic/config.yaml.
CellFactory = TemplateClassDict(
{
("Cell", "2"): CellFactory_Cell_2,
("Cell", "3"): CellFactory_Cell_3,
}
)

# Only Facet<2> is wrapped (curated). Facet<1> is deliberately left unwrapped;
# wrapping it would reference the never-instantiated Facet<0> and fail to import
# (see Facet.hpp and examples/cells/dynamic/config.yaml).
Expand Down Expand Up @@ -73,6 +85,7 @@

__all__ = [
"Cell",
"CellFactory",
"Corner",
"Facet",
"MacroMesh",
Expand Down
23 changes: 22 additions & 1 deletion examples/cells/tests/test_cells.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@

import petsc4py
import vtk
from pycells import Corner, Facet, MacroMesh, Node, PetscUtils, Scene
from pycells import (
Cell,
CellFactory,
Corner,
Facet,
MacroMesh,
Node,
PetscUtils,
Scene,
)


class TestCells(unittest.TestCase):
Expand All @@ -25,6 +34,18 @@ def testNonCuratedBoundaryElement(self):
with self.assertRaises(KeyError):
_ = Corner[1]

def testAutoIncludeTemplateArg(self):
# CellFactory<CELL_TYPE, DIM> names CELL_TYPE (Cell) only as a template
# argument - never in a wrapped signature - mirroring pychaste's
# CellsGenerator<CELL_CYCLE_MODEL, DIM>. auto_includes resolved Cell.hpp
# from the instantiation arguments so the wrapper compiled at all; this
# confirms the instantiations import and work.
factory = CellFactory[Cell, 2]()
self.assertEqual(factory.GetDimension(), 2)
self.assertEqual(factory.CreateCell(), 1)
self.assertEqual(factory.CreateCell(), 2)
self.assertEqual(CellFactory[Cell, 3]().GetDimension(), 3)

def testMacroInstantiationFallback(self):
# MacroMesh's explicit template instantiations are declared via a macro
# (see MacroMesh.cpp), which cppwg's source-text scan cannot see. This
Expand Down
42 changes: 42 additions & 0 deletions tests/test_package_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,48 @@ def test_resolve_auto_includes_resolves_project_type_headers(tmp_path):
assert cls.auto_include_headers == ["PottsMesh.hpp"]


def test_resolve_auto_includes_resolves_template_arg_headers(tmp_path):
"""Project types named as template arguments resolve to their headers.

CellsGenerator<NoCellCycleModel, DIM> needs NoCellCycleModel.hpp even though
the model type never appears in a wrapped signature. Numeric args (the
dimension) resolve to nothing, and the class's own header is dropped.
"""
(tmp_path / "CellsGenerator.hpp").write_text(
"template<class MODEL, unsigned DIM> class CellsGenerator {};\n"
)
(tmp_path / "NoCellCycleModel.hpp").write_text("class NoCellCycleModel {};\n")
(tmp_path / "UniformCellCycleModel.hpp").write_text(
"class UniformCellCycleModel {};\n"
)

package = PackageInfo("pkg", {"source_root": str(tmp_path)})
package.source_hpp_files = [
str(tmp_path / "CellsGenerator.hpp"),
str(tmp_path / "NoCellCycleModel.hpp"),
str(tmp_path / "UniformCellCycleModel.hpp"),
]
module = ModuleInfo("mod")
package.add_module(module)

cls = CppClassInfo("CellsGenerator", {"auto_includes": True})
cls.source_file = "CellsGenerator.hpp"
# The model types appear only as template arguments, never in a signature.
cls.template_arg_lists = [
["NoCellCycleModel", "2"],
["UniformCellCycleModel", "3"],
]
cls.decls = [_FakeDecl(), _FakeDecl()]
module.add_class(cls)

package.resolve_auto_includes()

assert cls.auto_include_headers == [
"NoCellCycleModel.hpp",
"UniformCellCycleModel.hpp",
]


def test_resolve_auto_includes_noop_without_opt_in(tmp_path):
"""Without auto_includes enabled, no headers are resolved."""
(tmp_path / "PottsMesh.hpp").write_text("class PottsMesh {};\n")
Expand Down
Loading