diff --git a/src/c2py/converters/stl/any.hpp b/src/c2py/converters/stl/any.hpp new file mode 100644 index 0000000..4c87609 --- /dev/null +++ b/src/c2py/converters/stl/any.hpp @@ -0,0 +1,52 @@ +#pragma once +#include +#include +#include "./common.hpp" + +namespace c2py { + + template <> struct py_converter { + + static void capsule_destructor(PyObject *capsule) { + void *p = PyCapsule_GetPointer(capsule, "std::any"); + assert(p); + auto *a_ptr = static_cast(p); + delete a_ptr; //NOLINT + } + + template static PyObject *c2py(U &&u) { + auto *p = new std::any{std::forward(u)}; //NOLINT + return PyCapsule_New(p, "std::any", capsule_destructor); + } + + // -------------------------------------- + + static bool is_convertible(PyObject *ob, bool raise_exception) { + bool ok = (PyCapsule_CheckExact(ob) and (std::string_view{PyCapsule_GetName(ob)} == "std::any")); + if (!ok and raise_exception) { PyErr_SetString(PyExc_TypeError, ("Cannot convert "s + to_string(ob) + " to std::any"s).c_str()); } + return ok; + } + + // -------------------------------------- + + static std::any &py2c(PyObject *ob) { + void *p = PyCapsule_GetPointer(ob, "std::any"); + assert(p); + return *static_cast(p); + } + }; + + // ------------------------------------------------------- + + template struct py_converter_as_any : py_converter { + + using base_t = py_converter; + + static bool is_convertible(PyObject *ob, bool raise_exception) { + if (!base_t::is_convertible(ob, raise_exception)) return false; + return base_t::py2c(ob).type() == std::type_index(typeid(T)); + } + + static T &py2c(PyObject *ob) { return std::any_cast(base_t::py2c(ob)); } + }; +} // namespace c2py \ No newline at end of file diff --git a/src/c2py/user_api.hpp b/src/c2py/user_api.hpp index 9d3cf25..0aeae2e 100644 --- a/src/c2py/user_api.hpp +++ b/src/c2py/user_api.hpp @@ -7,7 +7,7 @@ // ------------- Annotations for function --------------- #define C2PY_IGNORE __attribute__((annotate("c2py_ignore"))) -#define C2PY_WRAP __attribute__((annotate("c2py_wrap"))) +#define C2PY_OPAQUE __attribute__((annotate("c2py_wrap_as_opaque"))) #define C2PY_NOGIL __attribute__((annotate("c2py_nogil"))) //#define C2PY_METHODS_AS_PROPERTY __attribute__((annotate("c2py_methods_as_property"))) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bc7d649..b1e7699 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -38,13 +38,15 @@ add_custom_command( # ---------------------------- set(c2py_all_low_level_tests cls CACHE INTERNAL "") set(c2py_all_full_tests - annote basicfun + any + basicfun cls_basic cls_der comparison issue9 callables itertool + ignore generator enumcxx synth_init diff --git a/test/annote.cpp b/test/annote.cpp deleted file mode 100644 index f142f87..0000000 --- a/test/annote.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#ifdef __GNUC__ -#pragma GCC diagnostic ignored "-Wunused-parameter" -#endif - -#include -#include -#include - -// Test the C2PY_WRAP annotation - -int f(int i) { return 1; } - -C2PY_WRAP int g(int i) { return 2; } - -struct C2PY_WRAP A { - int k = 12; -}; - -namespace c2py_module { - // Filter all by default. - constexpr auto reject_names = ".*"; -} // namespace c2py_module diff --git a/test/annote_test.py b/test/annote_test.py deleted file mode 100644 index 2806d10..0000000 --- a/test/annote_test.py +++ /dev/null @@ -1,18 +0,0 @@ -import unittest -import numpy as np -import annote as M - -class TestIterable(unittest.TestCase): - - def test_f(self): - self.assertEqual(M.g(2), 2) - self.assertFalse(hasattr(M, "f")) - - def test_A(self): - self.assertEqual(M.A().k, 12) - -if __name__ == '__main__': - unittest.main() - - - diff --git a/test/any.cpp b/test/any.cpp new file mode 100644 index 0000000..01fbdb7 --- /dev/null +++ b/test/any.cpp @@ -0,0 +1,46 @@ +#include +#include +#include + +struct C2PY_OPAQUE opaque { + int c = 17; // not exposed + int *non_convertible = 0; + int f(int x) { return x + 1; }; // not exposed +}; + +// maker +opaque make_opaque() { return {}; } + +// passing the object back from Python +int take_opaque(opaque const &x) { return x.c; } + +// we can modify the object passed from Python +int inc_opaque(opaque &x) { return ++(x.c); } + +// ------------------------------------- +/// Same test again, with the regex + +struct opaque2 { + int c = 17; // not exposed + int *non_convertible = 0; + int f(int x) { return x + 1; }; // not exposed +}; + +namespace NN { + // maker + opaque2 make_opaque2() { return {}; } + + // passing the object back from Python + int take_opaque2(opaque2 const &x) { return x.c; } + + // we can modify the object passed from Python + int inc_opaque2(opaque2 &x) { return ++(x.c); } +} // namespace NN +// ------------------------------------------------- + +namespace c2py_module { +#pragma clang diagnostic ignored "-Wunused-const-variable" + + constexpr auto opaque_match_names = ".*opaque2"; + +} // namespace c2py_module diff --git a/test/any_test.py b/test/any_test.py new file mode 100644 index 0000000..60da8b3 --- /dev/null +++ b/test/any_test.py @@ -0,0 +1,33 @@ +import unittest +import numpy as np +import any as A + +def is_capsule(o): + t = type(o) + return t.__module__ == 'builtins' and t.__name__ == 'PyCapsule' + +class TestIterable(unittest.TestCase): + + def test_opaque(self): + a = A.make_opaque() + self.assertTrue(is_capsule(a)) + self.assertEqual(A.take_opaque(a), 17) + self.assertEqual(A.inc_opaque(a), 18) + self.assertEqual(A.inc_opaque(a), 19) + self.assertEqual(A.take_opaque(a), 19) + + + def test_opaque2(self): + a = A.make_opaque2() + self.assertTrue(is_capsule(a)) + self.assertEqual(A.take_opaque2(a), 17) + self.assertEqual(A.inc_opaque2(a), 18) + self.assertEqual(A.inc_opaque2(a), 19) + self.assertEqual(A.take_opaque2(a), 19) + + +if __name__ == '__main__': + unittest.main() + + + diff --git a/test/ignore.cpp b/test/ignore.cpp new file mode 100644 index 0000000..dd8aad3 --- /dev/null +++ b/test/ignore.cpp @@ -0,0 +1,23 @@ +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wunused-parameter" +#endif + +#include +#include +#include + +struct a_struct { + int a; + C2PY_IGNORE int b = 1; + double x_ignore_me = 0; // should be ignored by the regex + + C2PY_IGNORE int bad_method() { return 0; } + int method_ignore_me() { return 1; } +}; +// =============== Declare module =========================== + +namespace c2py_module { + + constexpr auto reject_names = ".*ignore_me"; + +} // namespace c2py_module diff --git a/test/ignore_test.py b/test/ignore_test.py new file mode 100644 index 0000000..e31f63d --- /dev/null +++ b/test/ignore_test.py @@ -0,0 +1,38 @@ +import unittest +import numpy as np +import pickle + +import ignore as M +A = M.AStruct + +class TestIterable(unittest.TestCase): + + def test_ignore(self): + a = A(a = 10) + print(a.a) # ok + + def f(): + A(a= 10, b =2) + self.assertRaises(RuntimeError, f) + + def f(x): + print(x.b) + self.assertRaises(AttributeError, f, a) + + def f(x): + print(x.x_ignore_me) + self.assertRaises(AttributeError, f, a) + + def f(x): + x.bad_method(1) + self.assertRaises(AttributeError, f, a) + + def f(x): + x.method_ignore_me(1) + self.assertRaises(AttributeError, f, a) + + +if __name__ == '__main__': + unittest.main() + +