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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ nanobind_tests:
-Dnanobind_DIR=$$(python3 -m nanobind --cmake_dir)
cmake --build "$(NB_TESTS_BUILD_DIR)"
NB_SO_DIR="$(NB_TESTS_BUILD_DIR)" \
pytest tests/nanobind/
pytest tests/nanobind/pytest

release_test:
uv sync --no-default-groups --no-install-project
Expand Down
2 changes: 1 addition & 1 deletion nanobind/include/bind_vector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,4 @@ void bind_array(nb::module_& m, char const* name) {

} // namespace coconext_nb

#endif // NB_BIND_ARRAY_HPP
#endif // NB_BIND_VECTOR_HPP
139 changes: 139 additions & 0 deletions nanobind/include/type_cast_array.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#ifndef NB_TYPE_CAST_ARRAY_HPP
#define NB_TYPE_CAST_ARRAY_HPP

#include <nanobind/nanobind.h>
#include <nanobind/stl/string.h>
#include <optional>
#include <stdexcept>
#include <vector>

#include <coconext/types/array.hpp>
#include <coconext/types/direction.hpp>
#include <coconext/types/range.hpp>

namespace nanobind::detail {

using namespace coconext::types;
using coconext::types::detail::Array;

template <typename T, Range R>
struct type_caster<Array<T, R>> {
private:
using Value = Array<T, R>;
using index_t = typename Value::index_type;
std::optional<Value> value;

public:
static constexpr auto Name =
const_name("cocotb.types.Array[") + make_caster<T>::Name + const_name("]");

template <typename T_>
using Cast = movable_cast_t<T_>;

// Python -> C++ (Dynamic Python Array -> Static C++ Array)
bool from_python(handle src, uint8_t flags, cleanup_list* cleanup) noexcept {
try {
if (!hasattr(src, "range")) {
return false;
}

object py_range = src.attr("range");
index_t left = cast<index_t>(py_range.attr("left"));
index_t right = cast<index_t>(py_range.attr("right"));
std::string dir_str = cast<std::string>(py_range.attr("direction"));
auto direction = to_direction(dir_str);

Range py_c_range{left, direction, right};

if (py_c_range.length() != R.length()) {
return false;
}

if (!isinstance<iterable>(src)) {
return false;
}

constexpr size_t N = R.length();
value.emplace();

make_caster<T> item_caster;
auto it = value->begin();
size_t count = 0;

for (handle item : borrow<iterable>(src)) {
if (count >= N) {
value.reset();
return false;
}

if (!item_caster.from_python(
item, flags & ~nanobind::detail::cast_flags::convert, cleanup
))
{
value.reset();
return false;
}

*it = std::move(item_caster.operator Cast<T>());
++it;
count++;
}

if (count != N) {
value.reset();
return false;
}

return true;
} catch (std::exception const& e) {
fprintf(stderr, "C++ Exception in from_python: %s\n", e.what());
return false;
} catch (...) {
return false;
}
}

// C++ -> Python (Static C++ Array -> Dynamic Python Array)
static handle from_cpp(
Value const& src, rv_policy policy, cleanup_list* cleanup
) noexcept {
try {
module_ cocotb_types = module_::import_("cocotb.types");
object py_Array = cocotb_types.attr("Array");
object py_Range = cocotb_types.attr("Range");

list py_list;
for (auto const& item : src) {
py_list.append(nanobind::cast(item, policy));
}

object cpp_range = nanobind::cast(src.range(), policy);

index_t left = nanobind::cast<index_t>(cpp_range.attr("left"));
index_t right = nanobind::cast<index_t>(cpp_range.attr("right"));
std::string_view py_dir_str = to_string(src.range().direction);

object pure_py_range = py_Range(left, std::string{py_dir_str}, right);
object result = py_Array(py_list, nanobind::arg("range") = pure_py_range);
return result.release();

} catch (python_error& e) {
e.restore();
return handle();
} catch (std::exception const& e) {
PyErr_SetString(PyExc_RuntimeError, e.what());
return handle();
} catch (...) {
PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception inside from_cpp");
return handle();
}
}

explicit operator Value*() { return &(*value); }
explicit operator Value&() { return *value; }
explicit operator Value&&() { return std::move(*value); }
};

} // namespace nanobind::detail

#endif // NB_TYPE_CAST_ARRAY_HPP
137 changes: 137 additions & 0 deletions nanobind/include/type_cast_vector.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#ifndef NB_TYPE_CAST_VECTOR_HPP
#define NB_TYPE_CAST_VECTOR_HPP

#include <nanobind/nanobind.h>
#include <nanobind/stl/string.h>
#include <optional>
#include <stdexcept>
#include <vector>

#include <coconext/types/direction.hpp>
#include <coconext/types/range.hpp>
#include <coconext/types/vector.hpp>

namespace nanobind::detail {

using namespace coconext::types;

template <typename T>
struct type_caster<Vector<T>> {
private:
using Value = Vector<T>;
using index_t = typename Value::index_type;
std::optional<Value> value;

public:
static constexpr auto Name =
const_name("cocotb.types.Array[") + make_caster<T>::Name + const_name("]");

template <typename T_>
using Cast = movable_cast_t<T_>;

// Python -> C++ (Array to Vector)
bool from_python(handle src, uint8_t flags, cleanup_list* cleanup) noexcept {
try {
if (!hasattr(src, "range")) {
return false;
}

object py_range = src.attr("range");
index_t left = cast<index_t>(py_range.attr("left"));
index_t right = cast<index_t>(py_range.attr("right"));
std::string dir_str = cast<std::string>(py_range.attr("direction"));
auto direction = to_direction(dir_str);

Range c_range{left, direction, right};

if (!isinstance<iterable>(src)) {
return false;
}

value.emplace(c_range);
make_caster<T> item_caster;
auto it = value->begin();
size_t count = 0;
size_t expected_len = c_range.length();

for (handle item : borrow<iterable>(src)) {
if (count >= expected_len) {
value.reset();
return false;
}

if (!item_caster.from_python(
item, flags & ~nanobind::detail::cast_flags::convert, cleanup
))
{
value.reset();
return false;
}

*it = std::move(item_caster.operator Cast<T>());
++it;
++count;
}

if (count != expected_len) {
value.reset();
return false;
}

return true;
} catch (std::exception const& e) {
fprintf(stderr, "C++ Exception caught: %s\n", e.what());
return false;
} catch (...) {
return false;
}
}

// C++ -> Python (Vector to Array)
static handle from_cpp(
Value const& src, rv_policy policy, cleanup_list* cleanup
) noexcept {
try {
module_ cocotb_types = module_::import_("cocotb.types");
object py_Array = cocotb_types.attr("Array");
object py_Range = cocotb_types.attr("Range");

list py_list;
for (auto const& item : src) {
py_list.append(nanobind::cast(item, policy));
}

object cpp_range = nanobind::cast(src.range(), policy);

index_t left = nanobind::cast<index_t>(cpp_range.attr("left"));
index_t right = nanobind::cast<index_t>(cpp_range.attr("right"));
std::string_view py_dir_str = to_string(src.range().direction);

object pure_py_range = py_Range(left, std::string{py_dir_str}, right);

object result = py_Array(py_list, nanobind::arg("range") = pure_py_range);
return result.release();

} catch (python_error& e) {
// If a Python exception occurred, restore it so pytest shows the exact
// traceback
e.restore();
return handle();
} catch (std::exception const& e) {
// Surface C++ exceptions (like nanobind::cast_error) directly to Python
PyErr_SetString(PyExc_RuntimeError, e.what());
return handle();
} catch (...) {
PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception inside from_cpp");
return handle();
}
}

explicit operator Value*() { return &(*value); }
explicit operator Value&() { return *value; }
explicit operator Value&&() { return std::move(*value); }
};

} // namespace nanobind::detail

#endif // NB_TYPE_CAST_VECTOR_HPP
4 changes: 3 additions & 1 deletion tests/nanobind/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ nanobind_add_module(
nanobind_tests
STABLE_ABI
NB_STATIC
bind_vector/bind_typed_vector.cpp
../../nanobind/src/types/bind_range.cpp
bind.cpp
type_cast_vector.cpp
type_cast_array.cpp
)

target_include_directories(nanobind_tests PRIVATE ../../cpp/include)
22 changes: 22 additions & 0 deletions tests/nanobind/bind.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include "../../nanobind/include/bind_vector.hpp"
#include <coconext/types/vector.hpp>
#include <nanobind/nanobind.h>
#include <string>

namespace nb = nanobind;
using namespace coconext::types;

void register_range(nb::module_& m);
void init_test_vector_caster(nb::module_& m);
void init_test_array_caster(nb::module_& m);

NB_MODULE(nanobind_tests, m) {

register_range(m);

coconext_nb::bind_array<Vector<int>>(m, "IntVector");
coconext_nb::bind_array<Vector<std::string>>(m, "StringVector");

init_test_vector_caster(m);
init_test_array_caster(m);
}
16 changes: 0 additions & 16 deletions tests/nanobind/bind_vector/bind_typed_vector.cpp

This file was deleted.

File renamed without changes.
Loading
Loading