From 5ccbe415a35b4605e2a75b1b59abf8324c2ab356 Mon Sep 17 00:00:00 2001 From: ZiaCheemaGit Date: Wed, 17 Jun 2026 20:03:01 +0500 Subject: [PATCH 1/6] Re-structure nanobind tests all test bindings .cpp files are separated per binding and all python tests are now in `tests/nanobind/pytest` --- nanobind/include/bind_vector.hpp | 2 +- tests/nanobind/CMakeLists.txt | 2 +- tests/nanobind/bind_vector.cpp | 17 +++++++++++++++++ .../nanobind/bind_vector/bind_typed_vector.cpp | 16 ---------------- tests/nanobind/{ => pytest}/conftest.py | 0 .../test_typed_vector.py | 0 6 files changed, 19 insertions(+), 18 deletions(-) create mode 100644 tests/nanobind/bind_vector.cpp delete mode 100644 tests/nanobind/bind_vector/bind_typed_vector.cpp rename tests/nanobind/{ => pytest}/conftest.py (100%) rename tests/nanobind/{bind_vector => pytest}/test_typed_vector.py (100%) diff --git a/nanobind/include/bind_vector.hpp b/nanobind/include/bind_vector.hpp index 7646521..22572e4 100644 --- a/nanobind/include/bind_vector.hpp +++ b/nanobind/include/bind_vector.hpp @@ -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 diff --git a/tests/nanobind/CMakeLists.txt b/tests/nanobind/CMakeLists.txt index f06306a..3faf3de 100644 --- a/tests/nanobind/CMakeLists.txt +++ b/tests/nanobind/CMakeLists.txt @@ -12,8 +12,8 @@ nanobind_add_module( nanobind_tests STABLE_ABI NB_STATIC - bind_vector/bind_typed_vector.cpp ../../nanobind/src/types/bind_range.cpp + bind_vector.cpp ) target_include_directories(nanobind_tests PRIVATE ../../cpp/include) diff --git a/tests/nanobind/bind_vector.cpp b/tests/nanobind/bind_vector.cpp new file mode 100644 index 0000000..d3bbf70 --- /dev/null +++ b/tests/nanobind/bind_vector.cpp @@ -0,0 +1,17 @@ +#include "../../nanobind/include/bind_vector.hpp" +#include +#include +#include + +namespace nb = nanobind; +using namespace coconext::types; + +void register_range(nb::module_& m); + +NB_MODULE(nanobind_tests, m) { + + register_range(m); + + coconext_nb::bind_array>(m, "IntVector"); + coconext_nb::bind_array>(m, "StringVector"); +} diff --git a/tests/nanobind/bind_vector/bind_typed_vector.cpp b/tests/nanobind/bind_vector/bind_typed_vector.cpp deleted file mode 100644 index 52f16e1..0000000 --- a/tests/nanobind/bind_vector/bind_typed_vector.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "../../../nanobind/include/bind_vector.hpp" -#include -#include -#include - -namespace nb = nanobind; - -void register_range(nb::module_& m); - -NB_MODULE(nanobind_tests, m) { - - register_range(m); - - coconext_nb::bind_array>(m, "IntVector"); - coconext_nb::bind_array>(m, "StringVector"); -} diff --git a/tests/nanobind/conftest.py b/tests/nanobind/pytest/conftest.py similarity index 100% rename from tests/nanobind/conftest.py rename to tests/nanobind/pytest/conftest.py diff --git a/tests/nanobind/bind_vector/test_typed_vector.py b/tests/nanobind/pytest/test_typed_vector.py similarity index 100% rename from tests/nanobind/bind_vector/test_typed_vector.py rename to tests/nanobind/pytest/test_typed_vector.py From 00b9b4654178009375be914910840c2a42f87646 Mon Sep 17 00:00:00 2001 From: ZiaCheemaGit Date: Thu, 18 Jun 2026 14:17:09 +0500 Subject: [PATCH 2/6] final restructuring touchups --- Makefile | 2 +- tests/nanobind/CMakeLists.txt | 2 +- tests/nanobind/{bind_vector.cpp => bind.cpp} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename tests/nanobind/{bind_vector.cpp => bind.cpp} (100%) diff --git a/Makefile b/Makefile index 67d8c27..af5dcec 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/tests/nanobind/CMakeLists.txt b/tests/nanobind/CMakeLists.txt index 3faf3de..42d7547 100644 --- a/tests/nanobind/CMakeLists.txt +++ b/tests/nanobind/CMakeLists.txt @@ -13,7 +13,7 @@ nanobind_add_module( STABLE_ABI NB_STATIC ../../nanobind/src/types/bind_range.cpp - bind_vector.cpp + bind.cpp ) target_include_directories(nanobind_tests PRIVATE ../../cpp/include) diff --git a/tests/nanobind/bind_vector.cpp b/tests/nanobind/bind.cpp similarity index 100% rename from tests/nanobind/bind_vector.cpp rename to tests/nanobind/bind.cpp From 68c8ff8366b547208910129343f1e7c7b7210691 Mon Sep 17 00:00:00 2001 From: ZiaCheemaGit Date: Thu, 18 Jun 2026 14:23:25 +0500 Subject: [PATCH 3/6] Add cocotb-Array/cpp-Vector-Array type caster(issue #267) --- nanobind/include/type_cast_array.hpp | 119 ++++++++++++++++++++++++++ nanobind/include/type_cast_vector.hpp | 107 +++++++++++++++++++++++ tests/nanobind/CMakeLists.txt | 2 + tests/nanobind/bind.cpp | 5 ++ tests/nanobind/type_cast_array.cpp | 34 ++++++++ tests/nanobind/type_cast_vector.cpp | 35 ++++++++ 6 files changed, 302 insertions(+) create mode 100644 nanobind/include/type_cast_array.hpp create mode 100644 nanobind/include/type_cast_vector.hpp create mode 100644 tests/nanobind/type_cast_array.cpp create mode 100644 tests/nanobind/type_cast_vector.cpp diff --git a/nanobind/include/type_cast_array.hpp b/nanobind/include/type_cast_array.hpp new file mode 100644 index 0000000..e79d434 --- /dev/null +++ b/nanobind/include/type_cast_array.hpp @@ -0,0 +1,119 @@ +#ifndef NB_TYPE_CAST_ARRAY_HPP +#define NB_TYPE_CAST_ARRAY_HPP + +#include +#include +#include +#include + +#include +#include + +namespace nanobind::detail { + +template +struct type_caster> { + private: + using Value = coconext::types::detail::Array; + std::optional value; + + public: + static constexpr auto Name = + const_name("cocotb.types.Array[") + make_caster::Name + const_name("]"); + + template + using Cast = movable_cast_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"); + int left = cast(py_range.attr("left")); + int right = cast(py_range.attr("right")); + + coconext::types::Range py_c_range{left, right}; + + if (py_c_range != R) { + return false; + } + + if (!isinstance(src)) { + return false; + } + + constexpr size_t N = R.length(); + std::array temp; + + make_caster item_caster; + size_t count = 0; + + for (handle item : borrow(src)) { + if (count >= N) { + return false; + } + + if (!item_caster.from_python(item, flags, cleanup)) { + return false; + } + + temp[count] = std::move(item_caster.operator Cast()); + count++; + } + + if (count != N) { + return false; + } + + value.emplace(temp); + return true; + } 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); + int left = nanobind::cast(cpp_range.attr("left")); + int right = nanobind::cast(cpp_range.attr("right")); + + object pure_py_range = py_Range(left, 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 diff --git a/nanobind/include/type_cast_vector.hpp b/nanobind/include/type_cast_vector.hpp new file mode 100644 index 0000000..7900223 --- /dev/null +++ b/nanobind/include/type_cast_vector.hpp @@ -0,0 +1,107 @@ +#ifndef NB_TYPE_CAST_VECTOR_HPP +#define NB_TYPE_CAST_VECTOR_HPP + +#include +#include +#include +#include + +#include +#include + +namespace nanobind::detail { + +template +struct type_caster> { + private: + using Value = coconext::types::Vector; + std::optional value; + + public: + static constexpr auto Name = + const_name("cocotb.types.Array[") + make_caster::Name + const_name("]"); + + template + using Cast = movable_cast_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"); + int left = cast(py_range.attr("left")); + int right = cast(py_range.attr("right")); + + coconext::types::Range c_range{left, right}; + + if (!isinstance(src)) { + return false; + } + + std::vector temp; + make_caster item_caster; + + for (handle item : borrow(src)) { + if (!item_caster.from_python(item, flags, cleanup)) { + return false; + } + temp.push_back(item_caster.operator Cast()); + } + + value.emplace(temp, c_range); + return true; + } 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); + + int left = nanobind::cast(cpp_range.attr("left")); + int right = nanobind::cast(cpp_range.attr("right")); + + object pure_py_range = py_Range(left, 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 diff --git a/tests/nanobind/CMakeLists.txt b/tests/nanobind/CMakeLists.txt index 42d7547..3fbee03 100644 --- a/tests/nanobind/CMakeLists.txt +++ b/tests/nanobind/CMakeLists.txt @@ -14,6 +14,8 @@ nanobind_add_module( NB_STATIC ../../nanobind/src/types/bind_range.cpp bind.cpp + type_cast_vector.cpp + type_cast_array.cpp ) target_include_directories(nanobind_tests PRIVATE ../../cpp/include) diff --git a/tests/nanobind/bind.cpp b/tests/nanobind/bind.cpp index d3bbf70..f3abcff 100644 --- a/tests/nanobind/bind.cpp +++ b/tests/nanobind/bind.cpp @@ -7,6 +7,8 @@ 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) { @@ -14,4 +16,7 @@ NB_MODULE(nanobind_tests, m) { coconext_nb::bind_array>(m, "IntVector"); coconext_nb::bind_array>(m, "StringVector"); + + init_test_vector_caster(m); + init_test_array_caster(m); } diff --git a/tests/nanobind/type_cast_array.cpp b/tests/nanobind/type_cast_array.cpp new file mode 100644 index 0000000..7dbf0d6 --- /dev/null +++ b/tests/nanobind/type_cast_array.cpp @@ -0,0 +1,34 @@ +#include "../../nanobind/include/type_cast_array.hpp" +#include +#include + +namespace nb = nanobind; +using namespace coconext::types; + +using TestArray4 = Array; + +TestArray4 element_wise_add_array(TestArray4 const& a, TestArray4 const& b) { + TestArray4 res; + + auto a_it = a.begin(); + auto b_it = b.begin(); + auto res_it = res.begin(); + + for (; a_it != a.end(); ++a_it, ++b_it, ++res_it) { + *res_it = *a_it + *b_it; + } + + return res; +} + +void init_test_array_caster(nb::module_& m) { + nb::module_ sm = m.def_submodule("test_array_caster_ext", "Array caster tests"); + + sm.def( + "element_wise_add_array", + &element_wise_add_array, + "Add two fixed-size Arrays element-wise", + nb::arg("a"), + nb::arg("b") + ); +} diff --git a/tests/nanobind/type_cast_vector.cpp b/tests/nanobind/type_cast_vector.cpp new file mode 100644 index 0000000..48655d1 --- /dev/null +++ b/tests/nanobind/type_cast_vector.cpp @@ -0,0 +1,35 @@ +#include "../../nanobind/include/type_cast_vector.hpp" +#include +#include + +namespace nb = nanobind; +using namespace coconext::types; + +Vector element_wise_add(Vector const& a, Vector const& b) { + if (a.range() != b.range()) { + throw std::invalid_argument("Lengths/Ranges must match"); + } + + Vector res(a.range()); + auto a_it = a.begin(); + auto b_it = b.begin(); + auto res_it = res.begin(); + + for (; a_it != a.end(); ++a_it, ++b_it, ++res_it) { + *res_it = *a_it + *b_it; + } + + return res; +} + +void init_test_vector_caster(nb::module_& m) { + nb::module_ sm = m.def_submodule("test_vector_caster_ext", "Vector caster tests"); + + sm.def( + "element_wise_add", + &element_wise_add, + "Add two Vectors element-wise", + nb::arg("a"), + nb::arg("b") + ); +} From 734fed8b9b5bdfc618bf01626a32f7c8d2e41772 Mon Sep 17 00:00:00 2001 From: ZiaCheemaGit Date: Thu, 18 Jun 2026 14:24:30 +0500 Subject: [PATCH 4/6] Add tests for type caster --- tests/nanobind/pytest/test_array_caster.py | 54 +++++++++++++++++++ tests/nanobind/pytest/test_vector_caster.py | 58 +++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 tests/nanobind/pytest/test_array_caster.py create mode 100644 tests/nanobind/pytest/test_vector_caster.py diff --git a/tests/nanobind/pytest/test_array_caster.py b/tests/nanobind/pytest/test_array_caster.py new file mode 100644 index 0000000..f0fba24 --- /dev/null +++ b/tests/nanobind/pytest/test_array_caster.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +import nanobind_tests +import pytest +from cocotb.types import Array, Range + +ext = nanobind_tests.test_array_caster_ext + + +def test_array_element_wise_add(): + """Test successful translation Python -> C++ -> Python.""" + + r = Range(3, "downto", 0) + + a: Array[int] = Array([1, 2, 3, 4], range=r) + b: Array[int] = Array([100, 95, 89, 67], range=r) + + # call test cpp function (Python -> C++ -> Python) + c = ext.element_wise_add_array(a, b) + + assert isinstance(c, Array) + assert c == [101, 97, 92, 71] + assert c.range == r + + +def test_array_bounds_mismatch_throws(): + """Passing an Array with the wrong static size fails nanobind type casting.""" + + r_wrong = Range(2, "downto", 0) + + a = Array([1, 2, 3], range=r_wrong) + b = Array([1, 2, 3], range=r_wrong) + + with pytest.raises(TypeError): + ext.element_wise_add_array(a, b) + + +def test_array_no_range_throws(): + """Unspecified Range for an Array fails nanobind type casting.""" + + a = Array([1, 2, 3, 4]) + b = Array([1, 2, 3, 4]) + + with pytest.raises(TypeError): + ext.element_wise_add_array(a, b) + + +def test_array_invalid_type_throws(): + """Only cocotb.types.Array is compatible with our test cpp function""" + a = [1, 2, 3, 4] + b = [1, 2, 3, 4] + + with pytest.raises(TypeError): + ext.element_wise_add_array(a, b) diff --git a/tests/nanobind/pytest/test_vector_caster.py b/tests/nanobind/pytest/test_vector_caster.py new file mode 100644 index 0000000..2e269f2 --- /dev/null +++ b/tests/nanobind/pytest/test_vector_caster.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import nanobind_tests +import pytest +from cocotb.types import Array, Range + +ext = nanobind_tests.test_vector_caster_ext + + +def test_vector_element_wise_add(): + """Test successful translation from Python -> C++ -> Python.""" + + r = Range(3, "downto", 0) + + a: Array[int] = Array([1, 2, 3, 4], range=r) + b: Array[int] = Array([100, 95, 89, 67], range=r) + + c = ext.element_wise_add(a, b) + + assert isinstance(c, Array), ( + "Return type should automatically cast to cocotb.types.Array" + ) + + assert c == [101, 97, 92, 71] + assert c.range == r + + +def test_vector_element_wise_add_without_range(): + + c: Array[int] = Array([1, 2, 3, 4]) + d: Array[int] = Array([100, 95, 89, 67]) + + c = ext.element_wise_add(c, d) + + assert isinstance(c, Array), ( + "Return type should automatically cast to cocotb.types.Array" + ) + + assert c == [101, 97, 92, 71] + + +def test_vector_mismatched_ranges_throws(): + """Verify standard C++ exceptions map correctly to Python exceptions.""" + + a = Array([1, 2, 3]) # Length 3 + b = Array([1, 2, 3, 4]) # Length 4 + + with pytest.raises(ValueError): + ext.element_wise_add(a, b) + + +def test_array_invalid_type_throws(): + """Only cocotb.types.Array is compatible with our test cpp function""" + a = [1, 2, 3, 4] + b = [1, 2, 3, 4] + + with pytest.raises(TypeError): + ext.element_wise_add(a, b) From a8635c89b91cf8d355970d1919b975d14d6263c3 Mon Sep 17 00:00:00 2001 From: ZiaCheemaGit Date: Thu, 18 Jun 2026 15:16:51 +0500 Subject: [PATCH 5/6] use move instead of copying --- nanobind/include/type_cast_array.hpp | 2 +- nanobind/include/type_cast_vector.hpp | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/nanobind/include/type_cast_array.hpp b/nanobind/include/type_cast_array.hpp index e79d434..09cdbe1 100644 --- a/nanobind/include/type_cast_array.hpp +++ b/nanobind/include/type_cast_array.hpp @@ -68,7 +68,7 @@ struct type_caster> { return false; } - value.emplace(temp); + value.emplace(std::move(temp)); return true; } catch (...) { return false; diff --git a/nanobind/include/type_cast_vector.hpp b/nanobind/include/type_cast_vector.hpp index 7900223..7a1b61c 100644 --- a/nanobind/include/type_cast_vector.hpp +++ b/nanobind/include/type_cast_vector.hpp @@ -42,16 +42,17 @@ struct type_caster> { } std::vector temp; + temp.reserve(c_range.length()); make_caster item_caster; for (handle item : borrow(src)) { if (!item_caster.from_python(item, flags, cleanup)) { return false; } - temp.push_back(item_caster.operator Cast()); + temp.push_back(std::move(item_caster.operator Cast())); } - value.emplace(temp, c_range); + value.emplace(std::move(temp), c_range); return true; } catch (...) { return false; From 96f361a0c1d65f74ee91b9a1fb4bdef7746748c1 Mon Sep 17 00:00:00 2001 From: ZiaCheemaGit Date: Sun, 21 Jun 2026 07:37:17 +0500 Subject: [PATCH 6/6] Address review comments #288 --- nanobind/include/type_cast_array.hpp | 48 ++++++++++++------ nanobind/include/type_cast_vector.hpp | 55 ++++++++++++++++----- tests/nanobind/pytest/test_array_caster.py | 29 +++++++---- tests/nanobind/pytest/test_vector_caster.py | 10 ++++ 4 files changed, 106 insertions(+), 36 deletions(-) diff --git a/nanobind/include/type_cast_array.hpp b/nanobind/include/type_cast_array.hpp index 09cdbe1..c66d850 100644 --- a/nanobind/include/type_cast_array.hpp +++ b/nanobind/include/type_cast_array.hpp @@ -2,19 +2,25 @@ #define NB_TYPE_CAST_ARRAY_HPP #include +#include #include #include #include #include +#include #include namespace nanobind::detail { -template -struct type_caster> { +using namespace coconext::types; +using coconext::types::detail::Array; + +template +struct type_caster> { private: - using Value = coconext::types::detail::Array; + using Value = Array; + using index_t = typename Value::index_type; std::optional value; public: @@ -32,12 +38,14 @@ struct type_caster> { } object py_range = src.attr("range"); - int left = cast(py_range.attr("left")); - int right = cast(py_range.attr("right")); + index_t left = cast(py_range.attr("left")); + index_t right = cast(py_range.attr("right")); + std::string dir_str = cast(py_range.attr("direction")); + auto direction = to_direction(dir_str); - coconext::types::Range py_c_range{left, right}; + Range py_c_range{left, direction, right}; - if (py_c_range != R) { + if (py_c_range.length() != R.length()) { return false; } @@ -46,30 +54,40 @@ struct type_caster> { } constexpr size_t N = R.length(); - std::array temp; + value.emplace(); make_caster item_caster; + auto it = value->begin(); size_t count = 0; for (handle item : borrow(src)) { if (count >= N) { + value.reset(); return false; } - if (!item_caster.from_python(item, flags, cleanup)) { + if (!item_caster.from_python( + item, flags & ~nanobind::detail::cast_flags::convert, cleanup + )) + { + value.reset(); return false; } - temp[count] = std::move(item_caster.operator Cast()); + *it = std::move(item_caster.operator Cast()); + ++it; count++; } if (count != N) { + value.reset(); return false; } - value.emplace(std::move(temp)); return true; + } catch (std::exception const& e) { + fprintf(stderr, "C++ Exception in from_python: %s\n", e.what()); + return false; } catch (...) { return false; } @@ -90,10 +108,12 @@ struct type_caster> { } object cpp_range = nanobind::cast(src.range(), policy); - int left = nanobind::cast(cpp_range.attr("left")); - int right = nanobind::cast(cpp_range.attr("right")); - object pure_py_range = py_Range(left, right); + index_t left = nanobind::cast(cpp_range.attr("left")); + index_t right = nanobind::cast(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(); diff --git a/nanobind/include/type_cast_vector.hpp b/nanobind/include/type_cast_vector.hpp index 7a1b61c..72bb56d 100644 --- a/nanobind/include/type_cast_vector.hpp +++ b/nanobind/include/type_cast_vector.hpp @@ -2,19 +2,24 @@ #define NB_TYPE_CAST_VECTOR_HPP #include +#include #include #include #include +#include #include #include namespace nanobind::detail { +using namespace coconext::types; + template -struct type_caster> { +struct type_caster> { private: - using Value = coconext::types::Vector; + using Value = Vector; + using index_t = typename Value::index_type; std::optional value; public: @@ -32,28 +37,51 @@ struct type_caster> { } object py_range = src.attr("range"); - int left = cast(py_range.attr("left")); - int right = cast(py_range.attr("right")); + index_t left = cast(py_range.attr("left")); + index_t right = cast(py_range.attr("right")); + std::string dir_str = cast(py_range.attr("direction")); + auto direction = to_direction(dir_str); - coconext::types::Range c_range{left, right}; + Range c_range{left, direction, right}; if (!isinstance(src)) { return false; } - std::vector temp; - temp.reserve(c_range.length()); + value.emplace(c_range); make_caster item_caster; + auto it = value->begin(); + size_t count = 0; + size_t expected_len = c_range.length(); for (handle item : borrow(src)) { - if (!item_caster.from_python(item, flags, cleanup)) { + if (count >= expected_len) { + value.reset(); return false; } - temp.push_back(std::move(item_caster.operator Cast())); + + if (!item_caster.from_python( + item, flags & ~nanobind::detail::cast_flags::convert, cleanup + )) + { + value.reset(); + return false; + } + + *it = std::move(item_caster.operator Cast()); + ++it; + ++count; + } + + if (count != expected_len) { + value.reset(); + return false; } - value.emplace(std::move(temp), c_range); return true; + } catch (std::exception const& e) { + fprintf(stderr, "C++ Exception caught: %s\n", e.what()); + return false; } catch (...) { return false; } @@ -75,10 +103,11 @@ struct type_caster> { object cpp_range = nanobind::cast(src.range(), policy); - int left = nanobind::cast(cpp_range.attr("left")); - int right = nanobind::cast(cpp_range.attr("right")); + index_t left = nanobind::cast(cpp_range.attr("left")); + index_t right = nanobind::cast(cpp_range.attr("right")); + std::string_view py_dir_str = to_string(src.range().direction); - object pure_py_range = py_Range(left, right); + 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(); diff --git a/tests/nanobind/pytest/test_array_caster.py b/tests/nanobind/pytest/test_array_caster.py index f0fba24..8740f83 100644 --- a/tests/nanobind/pytest/test_array_caster.py +++ b/tests/nanobind/pytest/test_array_caster.py @@ -23,6 +23,18 @@ def test_array_element_wise_add(): assert c.range == r +def test_array_element_wise_add_without_range(): + """Test successful translation from Python -> C++ -> Python(Unspecified Range).""" + a: Array[int] = Array([1, 2, 3, 4]) + b: Array[int] = Array([100, 95, 89, 67]) + + # call test cpp function (Python -> C++ -> Python) + c = ext.element_wise_add_array(a, b) + + assert isinstance(c, Array) + assert c == [101, 97, 92, 71] + + def test_array_bounds_mismatch_throws(): """Passing an Array with the wrong static size fails nanobind type casting.""" @@ -35,20 +47,19 @@ def test_array_bounds_mismatch_throws(): ext.element_wise_add_array(a, b) -def test_array_no_range_throws(): - """Unspecified Range for an Array fails nanobind type casting.""" - - a = Array([1, 2, 3, 4]) - b = Array([1, 2, 3, 4]) +def test_array_invalid_type_throws(): + """Only cocotb.types.Array is compatible with our test cpp function""" + a = [1, 2, 3, 4] + b = [1, 2, 3, 4] with pytest.raises(TypeError): ext.element_wise_add_array(a, b) -def test_array_invalid_type_throws(): - """Only cocotb.types.Array is compatible with our test cpp function""" - a = [1, 2, 3, 4] - b = [1, 2, 3, 4] +def test_array_element_invalid_type_throws(): + """Incompatible array element type must raise a type error""" + a = Array([1, 2, 3, "4"]) + b = Array([1, 2, 3, 4]) with pytest.raises(TypeError): ext.element_wise_add_array(a, b) diff --git a/tests/nanobind/pytest/test_vector_caster.py b/tests/nanobind/pytest/test_vector_caster.py index 2e269f2..460ef10 100644 --- a/tests/nanobind/pytest/test_vector_caster.py +++ b/tests/nanobind/pytest/test_vector_caster.py @@ -26,6 +26,7 @@ def test_vector_element_wise_add(): def test_vector_element_wise_add_without_range(): + """Test successful translation from Python -> C++ -> Python(Unspecified Range).""" c: Array[int] = Array([1, 2, 3, 4]) d: Array[int] = Array([100, 95, 89, 67]) @@ -56,3 +57,12 @@ def test_array_invalid_type_throws(): with pytest.raises(TypeError): ext.element_wise_add(a, b) + + +def test_array_element_invalid_type_throws(): + """Incompatible array element type must raise a type error""" + a = Array([1, 2, 3, "4"]) + b = Array([1, 2, 3, 4]) + + with pytest.raises(TypeError): + ext.element_wise_add(a, b)