From 7e99584ddc68ec20c32b53088926d1e605d6d6f7 Mon Sep 17 00:00:00 2001 From: ykiko Date: Fri, 23 Aug 2024 09:55:27 +0800 Subject: [PATCH] pybind11 for pkpy 2.0 (#299) * update pkbind. * some fix. * some fix. * update job name. * fix CI. * remove iostream. --- .github/workflows/pybind11.yml | 76 +++ include/pybind11/embed.h | 3 + include/pybind11/internal/accessor.h | 164 ++++++ include/pybind11/internal/builtins.h | 229 +++++--- include/pybind11/internal/cast.h | 206 ++++--- include/pybind11/internal/class.h | 165 ++++-- include/pybind11/internal/cpp_function.h | 358 ------------ include/pybind11/internal/error.h | 115 ++++ include/pybind11/internal/function.h | 686 +++++++++++++++++++++++ include/pybind11/internal/instance.h | 143 ++--- include/pybind11/internal/kernel.h | 225 +++++--- include/pybind11/internal/module.h | 47 ++ include/pybind11/internal/object.h | 413 ++++++++------ include/pybind11/internal/type_traits.h | 183 ++++-- include/pybind11/internal/types.h | 468 ++++++++++------ include/pybind11/operators.h | 204 +++++++ include/pybind11/pkbind.h | 3 + include/pybind11/pybind11.h | 62 ++ include/pybind11/stl.h | 170 ++++++ include/pybind11/tests/CMakeLists.txt | 26 + include/pybind11/tests/builtins.cpp | 121 ++++ include/pybind11/tests/class.cpp | 225 ++++++++ include/pybind11/tests/error.cpp | 50 ++ include/pybind11/tests/function.cpp | 402 +++++++++++++ include/pybind11/tests/module.cpp | 31 + include/pybind11/tests/object.cpp | 98 ++++ include/pybind11/tests/operators.cpp | 150 +++++ include/pybind11/tests/stl.cpp | 125 +++++ include/pybind11/tests/test.h | 16 + include/pybind11/tests/types.cpp | 169 ++++++ 30 files changed, 4194 insertions(+), 1139 deletions(-) create mode 100644 .github/workflows/pybind11.yml create mode 100644 include/pybind11/embed.h create mode 100644 include/pybind11/internal/accessor.h delete mode 100644 include/pybind11/internal/cpp_function.h create mode 100644 include/pybind11/internal/error.h create mode 100644 include/pybind11/internal/function.h create mode 100644 include/pybind11/internal/module.h create mode 100644 include/pybind11/operators.h create mode 100644 include/pybind11/pkbind.h create mode 100644 include/pybind11/stl.h create mode 100644 include/pybind11/tests/CMakeLists.txt create mode 100644 include/pybind11/tests/builtins.cpp create mode 100644 include/pybind11/tests/class.cpp create mode 100644 include/pybind11/tests/error.cpp create mode 100644 include/pybind11/tests/function.cpp create mode 100644 include/pybind11/tests/module.cpp create mode 100644 include/pybind11/tests/object.cpp create mode 100644 include/pybind11/tests/operators.cpp create mode 100644 include/pybind11/tests/stl.cpp create mode 100644 include/pybind11/tests/test.h create mode 100644 include/pybind11/tests/types.cpp diff --git a/.github/workflows/pybind11.yml b/.github/workflows/pybind11.yml new file mode 100644 index 00000000..7578b030 --- /dev/null +++ b/.github/workflows/pybind11.yml @@ -0,0 +1,76 @@ +name: PKBIND Build and Test + +on: + push: + paths-ignore: + - "docs/**" + - "web/**" + - "**.md" + pull_request: + paths-ignore: + - "docs/**" + - "web/**" + - "**.md" + +jobs: + build_linux: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up GCC + run: | + sudo apt-get update + sudo apt-get install -y gcc g++ + + - name: Set up CMake + uses: jwlawson/actions-setup-cmake@v1.10 + + - name: Test + run: | + cd include/pybind11/tests + cmake -B build + cmake --build build --config Release --parallel + ./build/PKBIND_TEST + + build_win: + runs-on: windows-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up MSVC + uses: ilammy/msvc-dev-cmd@v1 + + - name: Set up CMake + uses: jwlawson/actions-setup-cmake@v1.10 + + - name: Test + run: | + cd include\pybind11\tests + cmake -B build + cmake --build build --config Release --parallel + build\Release\PKBIND_TEST.exe + + build_mac: + runs-on: macos-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Clang + run: | + brew install llvm + echo 'export PATH="/usr/local/opt/llvm/bin:$PATH"' >> ~/.zshrc + source ~/.zshrc + + - name: Set up CMake + uses: jwlawson/actions-setup-cmake@v1.10 + + - name: Test + run: | + cd include/pybind11/tests + cmake -B build -DENABLE_TEST=ON + cmake --build build --config Release --parallel + ./build/PKBIND_TEST diff --git a/include/pybind11/embed.h b/include/pybind11/embed.h new file mode 100644 index 00000000..687757a4 --- /dev/null +++ b/include/pybind11/embed.h @@ -0,0 +1,3 @@ +#pragma once + +#include "pybind11.h" \ No newline at end of file diff --git a/include/pybind11/internal/accessor.h b/include/pybind11/internal/accessor.h new file mode 100644 index 00000000..549ebf0f --- /dev/null +++ b/include/pybind11/internal/accessor.h @@ -0,0 +1,164 @@ +#pragma once + +#include "builtins.h" + +namespace pkbind { + +template +class accessor : public interface> { + using key_type = typename policy::key_type; + + friend interface; + friend interface>; + friend tuple; + friend list; + friend dict; + + accessor(handle obj, key_type key) : m_obj(obj), m_value(), m_key(key) {} + +public: + auto ptr() const { + if(m_value.empty()) { + m_value = borrow(policy::get(m_obj, m_key)); + } + return m_value.ptr(); + } + + template + accessor& operator= (Value&& value) && { + policy::set(m_obj, m_key, pkbind::cast(std::forward(value))); + return *this; + } + + template + accessor& operator= (Value&& value) & { + m_value = std::forward(value); + return *this; + } + + operator handle () const { return ptr(); } + +private: + handle m_obj; + mutable object m_value; + key_type m_key; +}; + +namespace policy { + +struct attr { + using key_type = name; + + static handle get(handle obj, name key) { + raise_call(obj.ptr(), key.index()); + return py_retval(); + } + + static void set(handle obj, name key, handle value) { raise_call(obj.ptr(), key.index(), value.ptr()); } +}; + +template +struct item { + using key_type = Key; + + static handle get(handle obj, int key) { return get(obj, int_(key)); } + + static handle get(handle obj, name key) { return get(obj, str(key)); } + + static handle get(handle obj, handle key) { + raise_call(obj.ptr(), key.ptr()); + return py_retval(); + } + + static void set(handle obj, int key, handle value) { set(obj, int_(key), value); } + + static void set(handle obj, name key, handle value) { set(obj, str(key), value); } + + static void set(handle obj, handle key, handle value) { raise_call(obj.ptr(), key.ptr(), value.ptr()); } +}; + +struct tuple { + using key_type = int; + + static handle get(handle obj, int key) { return py_tuple_getitem(obj.ptr(), key); } + + static void set(handle obj, int key, handle value) { py_tuple_setitem(obj.ptr(), key, value.ptr()); } +}; + +struct list { + using key_type = int; + + static handle get(handle obj, int key) { return py_list_getitem(obj.ptr(), key); } + + static void set(handle obj, int key, handle value) { py_list_setitem(obj.ptr(), key, value.ptr()); } +}; + +template +struct dict { + using key_type = Key; + + static handle get(handle obj, int key) { return get(obj, int_(key)); } + + static handle get(handle obj, name key) { return get(obj, str(key)); } + + static handle get(handle obj, handle key) { + raise_call(obj.ptr(), key.ptr()); + return py_retval(); + } + + static void set(handle obj, int key, handle value) { set(obj, int_(key), value); } + + static void set(handle obj, name key, handle value) { set(obj, str(key), value); } + + static void set(handle obj, handle key, handle value) { + raise_call(obj.ptr(), key.ptr(), value.ptr()); + } +}; + +} // namespace policy + +// implement other methods of interface + +template +inline attr_accessor interface::attr(name key) const { + return {ptr(), key}; +} + +template +inline item_accessor interface::operator[] (int key) const { + return {ptr(), key}; +} + +template +inline item_accessor interface::operator[] (name key) const { + return {ptr(), key}; +} + +template +inline item_accessor interface::operator[] (handle key) const { + return {ptr(), key}; +} + +template +object str::format(Args&&... args) { + return attr("format")(std::forward(args)...); +} + +inline tuple_accessor tuple::operator[] (int index) const { return {m_ptr, index}; } + +inline list_accessor list::operator[] (int index) const { return {m_ptr, index}; }; + +inline dict_accessor dict::operator[] (int key) const { return {m_ptr, key}; } + +inline dict_accessor dict::operator[] (name key) const { return {m_ptr, key}; } + +inline dict_accessor dict::operator[] (handle key) const { return {m_ptr, key}; } + +inline dict::iterator::iterator(handle h) : items(h.attr("items")()), iter(items.begin()) {} + +inline std::pair dict::iterator::operator* () const { + tuple pair = *iter; + return {borrow(pair[0]), borrow(pair[1])}; +} + +} // namespace pkbind diff --git a/include/pybind11/internal/builtins.h b/include/pybind11/internal/builtins.h index a48f5e63..34b8c462 100644 --- a/include/pybind11/internal/builtins.h +++ b/include/pybind11/internal/builtins.h @@ -2,107 +2,192 @@ #include "types.h" -namespace pybind11 { -inline void exec(const char* code, handle global = {}, handle local = {}) { - vm->py_exec(code, global.ptr(), local.ptr()); +namespace pkbind { + +template +inline void print(Args&&... args) { + handle print = py_getbuiltin(py_name("print")); + print(std::forward(args)...); } -// wrapper for builtin functions in Python -inline bool hasattr(const handle& obj, const handle& name) { - auto& key = _builtin_cast(name); - return vm->getattr(obj.ptr(), key, false) != nullptr; +inline object eval(std::string_view code, handle globals = none(), handle locals = none()) { + if(globals.is_none() && locals.is_none()) { + std::string src{code}; + raise_call(src.c_str(), nullptr); + return object::from_ret(); + } else { + handle eval = py_getbuiltin(py_name("eval")); + return eval(str(code), globals.is_none() ? dict() : globals, locals.is_none() ? dict() : locals); + } } -inline bool hasattr(const handle& obj, const char* name) { return vm->getattr(obj.ptr(), name, false) != nullptr; } - -inline void delattr(const handle& obj, const handle& name) { - auto& key = _builtin_cast(name); - vm->delattr(obj.ptr(), key); +inline object exec(std::string_view code, handle globals = none(), handle locals = none()) { + if(globals.is_none() && locals.is_none()) { + std::string src{code}; + raise_call(src.c_str(), "exec", EXEC_MODE, nullptr); + return object::from_ret(); + } else { + handle exec = py_getbuiltin(py_name("exec")); + return exec(str(code), globals, locals); + } } -inline void delattr(const handle& obj, const char* name) { vm->delattr(obj.ptr(), name); } - -inline object getattr(const handle& obj, const handle& name) { - auto& key = _builtin_cast(name); - return reinterpret_borrow(vm->getattr(obj.ptr(), key)); +inline object locals() { + handle locals = py_getbuiltin(py_name("locals")); + return locals(); } -inline object getattr(const handle& obj, const char* name) { - return reinterpret_borrow(vm->getattr(obj.ptr(), name)); +inline object globals() { + handle globals = py_getbuiltin(py_name("globals")); + return globals(); } -inline object getattr(const handle& obj, const handle& name, const handle& default_) { - if(!hasattr(obj, name)) { return reinterpret_borrow(default_); } - return getattr(obj, name); +inline bool hasattr(handle obj, name name) { + auto pc = py_peek(0); + auto result = py_getattr(obj.ptr(), name.index()); + if(result) { + return true; + } else { + py_clearexc(pc); + return false; + } } -inline object getattr(const handle& obj, const char* name, const handle& default_) { - if(!hasattr(obj, name)) { return reinterpret_borrow(default_); } - return getattr(obj, name); +inline object getattr(handle obj, name name) { + raise_call(obj.ptr(), name.index()); + return object::from_ret(); } -inline void setattr(const handle& obj, const handle& name, const handle& value) { - auto& key = _builtin_cast(name); - vm->setattr(obj.ptr(), key, value.ptr()); +inline void setattr(handle obj, name name, handle value) { + raise_call(obj.ptr(), name.index(), value.ptr()); } -inline void setattr(const handle& obj, const char* name, const handle& value) { - vm->setattr(obj.ptr(), name, value.ptr()); +inline void delattr(handle obj, name name) { raise_call(obj.ptr(), name.index()); } + +inline py_i64 hash(handle obj) { + py_i64 result = 0; + raise_call(obj.ptr(), &result); + return result; +} + +inline bool isinstance(handle obj, type type) { return py_isinstance(obj.ptr(), type.index()); } + +inline bool python_error::match(type type) const { return isinstance(m_exception.ptr(), type); } + +template +constexpr inline bool is_pyobject_v = std::is_base_of_v> || std::is_same_v; + +template +inline type type::of() { + if constexpr(is_pyobject_v) { + if constexpr(is_check_v) { + return type(tp_object); + } else { + return type(T::type_or_check()); + } + } else { + auto it = m_type_map->find(typeid(T)); + if(it != m_type_map->end()) { + return type(it->second); + } else { + // if not found, raise error + std::string msg = "can not c++ instance cast to object, type: {"; + msg += type_name(); + msg += "} is not registered."; + throw std::runtime_error(msg); + } + } } template -inline bool isinstance(const handle& obj) { - pkpy::Type cls = _builtin_cast(type::handle_of().ptr()); - return vm->isinstance(obj.ptr(), cls); +inline bool type::isinstance(handle obj) { + if constexpr(is_pyobject_v) { + // for every python object wrapper type, there must be a `type_or_check` method. + // for some types, it returns the underlying type in pkpy, e.g., `int_` -> `tp_int`. + // for other types that may not have a corresponding type in pkpy, it returns a check function. + // e.g., `iterable` -> `[](handle h){ return hasattr(h, "iter"); }`. + auto type_or_check = T::type_or_check(); + if constexpr(is_check_v) { + return type_or_check(obj); + } else { + return pkbind::isinstance(obj, type(type_or_check)); + } + } else { + return pkbind::isinstance(obj, of()); + } +} + +inline bool issubclass(type derived, type base) { return py_issubclass(derived.index(), base.index()); } + +template +inline bool isinstance(handle obj) { + return type::isinstance(obj); } template <> -inline bool isinstance(const handle&) = delete; - -template <> -inline bool isinstance(const handle& obj) { - return hasattr(obj, "__iter__"); -} - -template <> -inline bool isinstance(const handle& obj) { - return hasattr(obj, "__iter__") && hasattr(obj, "__next__"); -} - -inline bool isinstance(const handle& obj, const handle& type) { - return vm->isinstance(obj.ptr(), _builtin_cast(type)); -} - -inline int64_t hash(const handle& obj) { return vm->py_hash(obj.ptr()); } +inline bool isinstance(handle) = delete; template struct type_caster; template -handle - _cast(T&& value, return_value_policy policy = return_value_policy::automatic_reference, handle parent = handle()) { - using U = std::remove_pointer_t>>; - return type_caster::cast(std::forward(value), policy, parent); -} +object cast(T&& value, return_value_policy policy = return_value_policy::automatic_reference, handle parent = {}) { + // decay_t can resolve c-array type, but remove_cv_ref_t can't. + using underlying_type = std::decay_t; -template -object - cast(T&& value, return_value_policy policy = return_value_policy::automatic_reference, handle parent = handle()) { - return reinterpret_borrow(_cast(std::forward(value), policy, parent)); -} + if constexpr(std::is_convertible_v && !is_pyobject_v) { + return object(std::forward(value), object::realloc_t{}); + } else if constexpr(is_unique_pointer_v) { + using pointer = typename underlying_type::pointer; + return type_caster::cast(value.release(), return_value_policy::take_ownership, parent); + } else { + static_assert(!is_multiple_pointer_v, "multiple pointer is not supported."); + static_assert(!std::is_void_v>, + "void* is not supported, consider using py::capsule."); -template -T cast(handle obj, bool convert = false) { - using Caster = type_caster>>>; - Caster caster; - - if(caster.load(obj, convert)) { - if constexpr(std::is_rvalue_reference_v) { - return std::move(caster.value); - } else { - return caster.value; + // resolve for automatic policy. + if(policy == return_value_policy::automatic) { + policy = std::is_pointer_v ? return_value_policy::take_ownership + : std::is_lvalue_reference_v ? return_value_policy::copy + : return_value_policy::move; + } else if(policy == return_value_policy::automatic_reference) { + policy = std::is_pointer_v ? return_value_policy::reference + : std::is_lvalue_reference_v ? return_value_policy::copy + : return_value_policy::move; } + + return type_caster::cast(std::forward(value), policy, parent); } - throw std::runtime_error("Unable to cast Python instance to C++ type"); } -} // namespace pybind11 + +template +T cast(handle obj, bool convert = true) { + using caster_t = type_caster; + constexpr auto is_dangling_v = (std::is_reference_v || is_pointer_v) && caster_t::is_temporary_v; + static_assert(!is_dangling_v, "dangling reference or pointer is not allowed."); + assert(obj.ptr() != nullptr); + + caster_t caster; + if(caster.load(obj, convert)) { + if constexpr(std::is_reference_v) { + return caster.value(); + } else { + return std::move(caster.value()); + } + } else { + std::string msg = "cast python instance to c++ failed, obj type is: {"; + msg += type::of(obj).name(); + msg += "}, target type is: {"; + msg += type_name(); + msg += "}."; + throw cast_error(msg); + } +} + +template +template +T interface::cast() const { + return pkbind::cast(handle(this->ptr()), true); +} + +} // namespace pkbind diff --git a/include/pybind11/internal/cast.h b/include/pybind11/internal/cast.h index 385230b5..b092050c 100644 --- a/include/pybind11/internal/cast.h +++ b/include/pybind11/internal/cast.h @@ -1,166 +1,190 @@ #pragma once #include "instance.h" -#include "builtins.h" -#include "type_traits.h" -namespace pybind11 { - -using pkpy::is_floating_point_v; -using pkpy::is_integral_v; +namespace pkbind { template -constexpr inline bool is_string_v = std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v; +constexpr inline bool is_string_v = + std::is_same_v || std::is_same_v || std::is_same_v; -template -constexpr bool is_pyobject_v = std::is_base_of_v; +/// The `type_caster` class is responsible for converting between Python objects and C++ objects. +/// +/// The static method `type_caster::cast(...)` is used to convert a C++ object to a Python object. +/// If the conversion fails, an exception is thrown. +/// +/// The method `type_caster::load(...)` is used to try to convert a Python object to a C++ object. +/// If the conversion is successful, it returns true, and you can then call `type_caster::value()` +/// to access the resulting C++ object. If the conversion fails, it returns false. +/// +/// NOTE: The type T could be a reference type or a pointer type. What is the lifetime of the reference or pointer? +/// It depends on the referenced type. For some types, such as bool, int, float, etc., the loaded value is stored +/// in the type_caster itself, so the lifetime of the reference is no longer than the lifetime of the type_caster +/// object. For other user-registered types, the lifetime of the reference is the same as the corresponding Python +/// object. A static variable `is_temporary_v` is used to indicate whether the loaded value is temporary or not. -template -struct type_caster; +template +struct type_caster { + T* data; + + static_assert(!std::is_pointer_v, "type caster for pointer type must be specialized."); + static_assert(!std::is_reference_v, "type caster for reference type must be specialized."); + + template + static object cast(U&& value, return_value_policy policy, handle parent) { + // TODO: support implicit cast + return instance::create(type::of(), std::forward(value), parent, policy); + } + + bool load(handle src, bool convert) { + if(isinstance(src)) { + auto& i = *static_cast(py_touserdata(src.ptr())); + data = &i.as(); + return true; + } + + return false; + } + + T& value() { return *data; } + + constexpr inline static bool is_temporary_v = false; +}; template <> struct type_caster { - bool value; + bool data; - bool load(const handle& src, bool) { - if(isinstance(src)) { - value = pkpy::_py_cast(vm, src.ptr()); + static object cast(bool src, return_value_policy, handle) { return bool_(src); } + + bool load(handle src, bool) { + if(isinstance(src)) { + data = py_tobool(src.ptr()); return true; } return false; } - static handle cast(bool src, return_value_policy, handle) { return src ? vm->True : vm->False; } + bool& value() { return data; } + + constexpr inline static bool is_temporary_v = true; }; template -struct type_caster>> { - T value; +struct type_caster>> { + T data; - bool load(const handle& src, bool convert) { - if(isinstance(src)) { - value = pkpy::_py_cast(vm, src.ptr()); + static object cast(T src, return_value_policy, handle) { return int_(src); } + + bool load(handle src, bool) { + if(isinstance(src)) { + data = static_cast(py_toint(src.ptr())); return true; } return false; } - static handle cast(T src, return_value_policy, handle) { return pkpy::py_var(vm, src); } + T& value() { return data; } + + constexpr inline static bool is_temporary_v = true; }; template struct type_caster>> { - T value; + T data; - bool load(const handle& src, bool convert) { - if(isinstance(src)) { - value = pkpy::_py_cast(vm, src.ptr()); + static object cast(T src, return_value_policy, handle) { return float_(src); } + + bool load(handle src, bool convert) { + if(isinstance(src)) { + data = py_tofloat(src.ptr()); return true; } - if(convert && isinstance(src)) { - value = pkpy::_py_cast(vm, src.ptr()); + if(convert && isinstance(src)) { + data = py_toint(src.ptr()); return true; } return false; } - static handle cast(T src, return_value_policy, handle) { return pkpy::py_var(vm, src); } + T& value() { return data; } + + constexpr inline static bool is_temporary_v = true; }; template struct type_caster>> { - T value; + T data; - bool load(const handle& src, bool) { - if(isinstance(src)) { - // FIXME: support other kinds of string - value = pkpy::_py_cast(vm, src.ptr()); + template + static object cast(U&& src, return_value_policy, handle) { + return str(std::forward(src)); + } + + bool load(handle src, bool) { + if(isinstance(src)) { + data = py_tostr(src.ptr()); return true; } return false; } - static handle cast(const std::string& src, return_value_policy, handle) { return pkpy::py_var(vm, src); } + T& value() { return data; } + + constexpr inline static bool is_temporary_v = true; }; template struct type_caster>> { - T value; + T data; - bool load(const handle& src, bool) { + template + static object cast(U&& src, return_value_policy, handle) { + return object(std::forward(src)); + } + + bool load(handle src, bool) { if(isinstance(src)) { - value = reinterpret_borrow(src); + data = T(src.ptr(), object::realloc_t{}); return true; } return false; } - template - static handle cast(U&& src, return_value_policy, handle) { - return std::forward(src); - } -}; + T& value() { return data; } -template -struct type_caster { - value_wrapper value; - - using underlying_type = std::remove_pointer_t; - - bool load(handle src, bool convert) { - if(isinstance(src)) { - auto& i = _builtin_cast(src); - value.pointer = &i.cast(); - return true; - } - - return false; - } - - template - static handle cast(U&& value, return_value_policy policy, const handle& parent = handle()) { - // TODO: support implicit cast - const auto& info = typeid(underlying_type); - bool existed = vm->_cxx_typeid_map.find(info) != vm->_cxx_typeid_map.end(); - if(existed) { - auto type = vm->_cxx_typeid_map[info]; - return instance::create(std::forward(value), type, policy, parent.ptr()); - } - vm->TypeError("type not registered"); - } + constexpr inline static bool is_temporary_v = true; }; template -struct type_caster || std::is_reference_v>> { - using underlying = std::conditional_t, std::remove_pointer_t, std::remove_reference_t>; +struct type_caster || std::is_reference_v>> { + using underlying = + std::remove_cv_t, std::remove_pointer_t, std::remove_reference_t>>; - struct wrapper { - type_caster caster; - - operator T () { - if constexpr(std::is_pointer_v) { - return caster.value.pointer; - } else { - return caster.value; - } - } - }; - - wrapper value; - - bool load(const handle& src, bool convert) { return value.caster.load(src, convert); } + type_caster caster; template - static handle cast(U&& value, return_value_policy policy, const handle& parent) { + static object cast(U&& value, return_value_policy policy, handle parent) { return type_caster::cast(std::forward(value), policy, parent); } -}; -} // namespace pybind11 + bool load(handle src, bool convert) { return caster.load(src, convert); } + + T value() { + if constexpr(std::is_pointer_v) { + return &caster.value(); + } else { + return caster.value(); + } + } + + constexpr inline static bool is_temporary_v = type_caster::is_temporary_v; +}; + +} // namespace pkbind diff --git a/include/pybind11/internal/class.h b/include/pybind11/internal/class.h index 94a2e650..5cf5ea52 100644 --- a/include/pybind11/internal/class.h +++ b/include/pybind11/internal/class.h @@ -1,75 +1,93 @@ #pragma once -#include "cpp_function.h" +#include "module.h" +#include "type_traits.h" -namespace pybind11 { +namespace pkbind { -class module : public object { +struct dynamic_attr {}; -public: - using object::object; - - static module import(const char* name) { - if(name == std::string_view{"__main__"}) { - return module{vm->_main, true}; - } else { - return module{vm->py_import(name, false), true}; - } - } -}; - -// TODO: -// 1. inheritance -// 2. virtual function -// 3. factory function - -template +template class class_ : public type { +protected: + handle m_scope; + public: using type::type; + using underlying_type = T; template - class_(const handle& scope, const char* name, Args&&... args) : - type(vm->new_type_object(scope.ptr(), name, vm->tp_object, false, pkpy::PyTypeInfo::Vt::get()), - true) { - pkpy::PyVar mod = scope.ptr(); - mod->attr().set(name, m_ptr); - vm->_cxx_typeid_map[typeid(T)] = _builtin_cast(m_ptr); - vm->bind_func(m_ptr, "__new__", -1, [](pkpy::VM* vm, pkpy::ArgsView args) { - auto cls = _builtin_cast(args[0]); - return instance::create(cls); - }); + class_(handle scope, const char* name, const Args&... args) : + type(py_newtype(name, + std::is_same_v ? tp_object : type::of().index(), + scope.ptr(), + [](void* data) { + static_cast(data)->~instance(); + })), + m_scope(scope) { + m_type_map->try_emplace(typeid(T), this->index()); + + auto& info = type_info::of(); + info.name = name; + + py_newfunction( + py_tpgetmagic(this->index(), py_MagicNames::__new__), + "__new__(type, *args, **kwargs)", + [](int, py_Ref stack) { + auto cls = py_offset(stack, 0); + [[maybe_unused]] auto args = py_offset(stack, 1); + [[maybe_unused]] auto kwargs = py_offset(stack, 2); + + auto info = &type_info::of(); + int slot = ((std::is_same_v || ...) ? -1 : 0); + void* data = py_newobject(retv, steal(cls).index(), slot, sizeof(instance)); + new (data) instance{instance::Flag::Own, operator new (info->size), info}; + return true; + }, + nullptr, + 0); } /// bind constructor template - class_& def(init, const Extra&... extra) { + class_& def(impl::constructor, const Extra&... extra) { if constexpr(!std::is_constructible_v) { static_assert(std::is_constructible_v, "Invalid constructor arguments"); } else { - bind_function( + impl::bind_function( *this, "__init__", [](T* self, Args... args) { new (self) T(args...); }, - pkpy::BindType::DEFAULT, extra...); return *this; } } + template + class_& def(impl::factory factory, const Extra&... extra) { + using ret = callable_return_t; + if constexpr(!std::is_same_v) { + static_assert(std::is_same_v, "Factory function must return the class type"); + } else { + impl::bind_function(*this, "__init__", factory.make(), extra...); + return *this; + } + } + /// bind member function template class_& def(const char* name, Fn&& f, const Extra&... extra) { - using first = std::tuple_element_t<0, callable_args_t>>; - constexpr bool is_first_base_of_v = std::is_reference_v && std::is_base_of_v>; + using first = remove_cvref_t>>>; + constexpr bool is_first_base_of_v = std::is_base_of_v || std::is_same_v; if constexpr(!is_first_base_of_v) { - static_assert(is_first_base_of_v, - "If you want to bind member function, the first argument must be the base class"); + static_assert( + is_first_base_of_v, + "If you want to bind member function, the first argument must be the base class"); } else { - bind_function(*this, name, std::forward(f), pkpy::BindType::DEFAULT, extra...); + impl::bind_function(*this, name, std::forward(f), extra...); } return *this; @@ -82,21 +100,29 @@ public: return *this; } - // TODO: factory function - /// bind static function template class_& def_static(const char* name, Fn&& f, const Extra&... extra) { - bind_function(*this, name, std::forward(f), pkpy::BindType::STATICMETHOD, extra...); + impl::bind_function(*this, name, std::forward(f), extra...); return *this; } template class_& def_readwrite(const char* name, MP mp, const Extras&... extras) { if constexpr(!std::is_member_object_pointer_v) { - static_assert(std::is_member_object_pointer_v, "def_readwrite only supports pointer to data member"); + static_assert(std::is_member_object_pointer_v, + "def_readwrite only supports pointer to data member"); } else { - bind_property(*this, name, mp, mp, extras...); + impl::bind_property( + *this, + name, + [mp](class_type_t& self) -> auto& { + return self.*mp; + }, + [mp](class_type_t& self, const member_type_t& value) { + self.*mp = value; + }, + extras...); } return *this; } @@ -104,22 +130,34 @@ public: template class_& def_readonly(const char* name, MP mp, const Extras&... extras) { if constexpr(!std::is_member_object_pointer_v) { - static_assert(std::is_member_object_pointer_v, "def_readonly only supports pointer to data member"); + static_assert(std::is_member_object_pointer_v, + "def_readonly only supports pointer to data member"); } else { - bind_property(*this, name, mp, nullptr, extras...); + impl::bind_property( + *this, + name, + [mp](class_type_t& self) -> auto& { + return self.*mp; + }, + nullptr, + extras...); } return *this; } template class_& def_property(const char* name, Getter&& g, Setter&& s, const Extras&... extras) { - bind_property(*this, name, std::forward(g), std::forward(s), extras...); + impl::bind_property(*this, + name, + std::forward(g), + std::forward(s), + extras...); return *this; } template class_& def_property_readonly(const char* name, Getter&& mp, const Extras&... extras) { - bind_property(*this, name, std::forward(mp), nullptr, extras...); + impl::bind_property(*this, name, std::forward(mp), nullptr, extras...); return *this; } @@ -150,28 +188,41 @@ public: template class enum_ : public class_ { - std::map m_values; + std::vector> m_values; public: - using class_::class_; + using Base = class_; template enum_(const handle& scope, const char* name, Args&&... args) : - class_(scope, name, std::forward(args)...) {} + class_(scope, name, std::forward(args)...) { + + Base::def(init([](int value) { + return static_cast(value); + })); + + Base::def("__eq__", [](T& self, T& other) { + return self == other; + }); + + Base::def_property_readonly("value", [](T& self) { + return int_(static_cast>(self)); + }); + } enum_& value(const char* name, T value) { - handle var = type_caster::cast(value, return_value_policy::copy); - this->m_ptr->attr().set(name, var.ptr()); - m_values[name] = var.ptr(); + auto var = pkbind::cast(value, return_value_policy::copy); + setattr(*this, name, var); + m_values.emplace_back(name, std::move(var)); return *this; } enum_& export_values() { - pkpy::PyVar mod = this->m_ptr->attr("__module__"); for(auto& [name, value]: m_values) { - mod->attr().set(name, value); + setattr(Base::m_scope, name, value); } return *this; } }; -} // namespace pybind11 + +} // namespace pkbind diff --git a/include/pybind11/internal/cpp_function.h b/include/pybind11/internal/cpp_function.h deleted file mode 100644 index 3eb31d56..00000000 --- a/include/pybind11/internal/cpp_function.h +++ /dev/null @@ -1,358 +0,0 @@ -#pragma once - -#include "cast.h" -#include - -namespace pybind11 { - -template -struct keep_alive {}; - -template -struct call_guard { - static_assert(std::is_default_constructible_v, "call_guard must be default constructible"); -}; - -// append the overload to the beginning of the overload list -struct prepend {}; - -template -struct init {}; - -// TODO: support more customized tags -// struct kw_only {}; -// -// struct pos_only {}; -// -// struct default_arg {}; -// -// struct arg { -// const char* name; -// const char* description; -// }; -// -// struct default_arg { -// const char* name; -// const char* description; -// const char* value; -// }; - -template >, - typename IndexSequence = std::make_index_sequence>> -struct generator; - -class function_record { - union { - void* data; - char buffer[16]; - }; - - // TODO: optimize the function_record size to reduce memory usage - const char* name; - function_record* next; - void (*destructor)(function_record*); - return_value_policy policy = return_value_policy::automatic; - handle (*wrapper)(function_record&, pkpy::ArgsView, bool convert, handle parent); - - template - friend struct generator; - -public: - template - function_record(Fn&& f, const char* name, const Extras&... extras) : name(name), next(nullptr) { - - if constexpr(sizeof(f) <= sizeof(buffer)) { - new (buffer) auto(std::forward(f)); - destructor = [](function_record* self) { - reinterpret_cast(self->buffer)->~Fn(); - }; - } else { - data = new auto(std::forward(f)); - destructor = [](function_record* self) { - delete static_cast(self->data); - }; - } - - using Generator = generator, std::tuple>; - Generator::initialize(*this, extras...); - wrapper = Generator::generate(); - } - - ~function_record() { destructor(this); } - - template - auto& cast() { - if constexpr(sizeof(Fn) <= sizeof(buffer)) { - return *reinterpret_cast(buffer); - } else { - return *static_cast(data); - } - } - - void append(function_record* record) { - function_record* p = this; - while(p->next != nullptr) { - p = p->next; - } - p->next = record; - } - - handle operator() (pkpy::ArgsView view) { - function_record* p = this; - // foreach function record and call the function with not convert - while(p != nullptr) { - handle result = p->wrapper(*this, view, false, {}); - if(result) { return result; } - p = p->next; - } - - p = this; - // foreach function record and call the function with convert - while(p != nullptr) { - handle result = p->wrapper(*this, view, true, {}); - if(result) { return result; } - p = p->next; - } - - vm->TypeError("no matching function found"); - } -}; - -template -handle invoke(Fn&& fn, - std::index_sequence, - std::tuple...>& casters, - return_value_policy policy, - handle parent) { - using underlying_type = std::decay_t; - using ret = callable_return_t; - - // if the return type is void, return None - if constexpr(std::is_void_v) { - // resolve the member function pointer - if constexpr(std::is_member_function_pointer_v) { - [&](class_type_t& self, auto&... args) { - (self.*fn)(args...); - }(std::get(casters).value...); - } else { - fn(std::get(casters).value...); - } - return vm->None; - } else { - // resolve the member function pointer - if constexpr(std::is_member_function_pointer_v>) { - return type_caster::cast( - [&](class_type_t& self, auto&... args) { - return (self.*fn)(args...); - }(std::get(casters).value...), - policy, - parent); - } else { - return type_caster::cast(fn(std::get(casters).value...), policy, parent); - } - } -} - -template -struct generator, std::tuple, std::index_sequence> { - static void initialize(function_record& record, const Extras&... extras) {} - - static auto generate() { - return +[](function_record& self, pkpy::ArgsView view, bool convert, handle parent) { - // FIXME: - // Temporarily, args and kwargs must be at the end of the arguments list - // Named arguments are not supported yet - constexpr bool has_args = types_count_v...> != 0; - constexpr bool has_kwargs = types_count_v...> != 0; - constexpr std::size_t count = sizeof...(Args) - has_args - has_kwargs; - - handle stack[sizeof...(Args)] = {}; - - // initialize the stack - - if(!has_args && (view.size() != count)) { return handle(); } - - if(has_args && (view.size() < count)) { return handle(); } - - for(std::size_t i = 0; i < count; ++i) { - stack[i] = view[i]; - } - - // pack the args and kwargs - if constexpr(has_args) { - const auto n = view.size() - count; - pkpy::PyVar var = vm->new_object(vm->tp_tuple, n); - auto& tuple = var.obj_get(); - for(std::size_t i = 0; i < n; ++i) { - tuple[i] = view[count + i]; - } - stack[count] = var; - } - - if constexpr(has_kwargs) { - const auto n = vm->s_data._sp - view.end(); - pkpy::PyVar var = vm->new_object(vm->tp_dict); - auto& dict = var.obj_get(); - - for(std::size_t i = 0; i < n; i += 2) { - pkpy::i64 index = pkpy::_py_cast(vm, view[count + i]); - pkpy::PyVar str = vm->new_object(vm->tp_str, pkpy::StrName(index).sv()); - dict.set(vm, str, view[count + i + 1]); - } - - stack[count + 1] = var; - } - - // check if all the arguments are not valid - for(std::size_t i = 0; i < sizeof...(Args); ++i) { - if(!stack[i]) { return handle(); } - } - - // ok, all the arguments are valid, call the function - std::tuple...> casters; - - // check type compatibility - if(((std::get(casters).load(stack[Is], convert)) && ...)) { - return invoke(self.cast(), std::index_sequence{}, casters, self.policy, parent); - } - - return handle(); - }; - } -}; - -constexpr inline static auto _wrapper = +[](pkpy::VM*, pkpy::ArgsView view) { - auto& record = pkpy::lambda_get_userdata(view.begin()); - return record(view).ptr(); -}; - -class cpp_function : public function { -public: - template - cpp_function(Fn&& f, const Extras&... extras) { - pkpy::any userdata = function_record(std::forward(f), "anonymous", extras...); - m_ptr = vm->bind_func(nullptr, "", -1, _wrapper, std::move(userdata)); - inc_ref(); - } -}; - -template -handle bind_function(const handle& obj, const char* name, Fn&& fn, pkpy::BindType type, const Extras&... extras) { - // do not use cpp_function directly to avoid unnecessary reference count change - pkpy::PyVar var = obj.ptr(); - pkpy::PyVar callable = var->attr().try_get(name); - - // if the function is not bound yet, bind it - if(!callable) { - pkpy::any userdata = function_record(std::forward(fn), name, extras...); - callable = vm->bind_func(var, name, -1, _wrapper, std::move(userdata)); - } else { - auto& userdata = callable.obj_get()._userdata; - function_record* record = new function_record(std::forward(fn), name, extras...); - - constexpr bool is_prepend = (types_count_v != 0); - if constexpr(is_prepend) { - // if prepend is specified, append the new record to the beginning of the list - function_record* last = (function_record*)userdata.data; - userdata.data = record; - record->append(last); - } else { - // otherwise, append the new record to the end of the list - function_record* last = (function_record*)userdata.data; - last->append(record); - } - } - return callable; -} - -template -handle - bind_property(const handle& obj, const char* name, Getter_&& getter_, Setter_&& setter_, const Extras&... extras) { - pkpy::PyVar var = obj.ptr(); - pkpy::PyVar getter = vm->None; - pkpy::PyVar setter = vm->None; - using Getter = std::decay_t; - using Setter = std::decay_t; - - getter = vm->new_object( - vm->tp_native_func, - [](pkpy::VM* vm, pkpy::ArgsView view) -> pkpy::PyVar { - auto& getter = pkpy::lambda_get_userdata(view.begin()); - - if constexpr(std::is_member_pointer_v) { - using Self = class_type_t; - auto& self = _builtin_cast(view[0]).cast(); - - if constexpr(std::is_member_object_pointer_v) { - return type_caster>::cast(self.*getter, - return_value_policy::reference_internal, - view[0]) - .ptr(); - } else { - return type_caster>::cast((self.*getter)(), - return_value_policy::reference_internal, - view[0]) - .ptr(); - } - - } else { - using Self = std::tuple_element_t<0, callable_args_t>; - auto& self = _builtin_cast(view[0]).cast(); - - return type_caster>::cast(getter(self), - return_value_policy::reference_internal, - view[0]) - .ptr(); - } - }, - 1, - std::forward(getter_)); - - if constexpr(!std::is_same_v) { - setter = vm->new_object( - vm->tp_native_func, - [](pkpy::VM* vm, pkpy::ArgsView view) -> pkpy::PyVar { - auto& setter = pkpy::lambda_get_userdata(view.begin()); - - if constexpr(std::is_member_pointer_v) { - using Self = class_type_t; - auto& self = _builtin_cast(view[0]).cast(); - - if constexpr(std::is_member_object_pointer_v) { - type_caster> caster; - if(caster.load(view[1], true)) { - self.*setter = caster.value; - return vm->None; - } - } else { - type_caster>> caster; - if(caster.load(view[1], true)) { - (self.*setter)(caster.value); - return vm->None; - } - } - } else { - using Self = std::tuple_element_t<0, callable_args_t>; - auto& self = _builtin_cast(view[0]).cast(); - - type_caster>> caster; - if(caster.load(view[1], true)) { - setter(self, caster.value); - return vm->None; - } - } - - vm->TypeError("invalid argument"); - }, - 2, - std::forward(setter_)); - } - - pkpy::PyVar property = vm->new_object(vm->tp_property, getter, setter); - var->attr().set(name, property); - return property; -} - -} // namespace pybind11 diff --git a/include/pybind11/internal/error.h b/include/pybind11/internal/error.h new file mode 100644 index 00000000..763d29f0 --- /dev/null +++ b/include/pybind11/internal/error.h @@ -0,0 +1,115 @@ +#pragma once + +#include "object.h" + +namespace pkbind { + +/// represent a all exception raised by python. +class python_error : public std::exception { +public: + python_error(char* what, object exception) : m_what(what), m_exception(std::move(exception)) {} + + const char* what() const noexcept override { return m_what; } + + // get the python exception object + object& exception() { return m_exception; } + + ~python_error() { std::free(m_what); } + + bool match(py_Type type) const { return py_isinstance(m_exception.ptr(), type); } + + bool match(class type) const; + +private: + char* m_what; + object m_exception; +}; + +using error_already_set = python_error; + +template +inline auto raise_call(Args&&... args) { + auto pc = py_peek(0); + auto result = Fn(std::forward(args)...); + + using type = decltype(result); + if constexpr(std::is_same_v) { + if(result != false) { + return result; + } + } else if constexpr(std::is_same_v) { + if(result != -1) { + return result; + } + } else { + static_assert(dependent_false, "invalid return type"); + } + + bool o = py_matchexc(tp_Exception); + object e = object::from_ret(); + auto what = py_formatexc(); + py_clearexc(pc); + throw python_error(what, std::move(e)); +} + +class stop_iteration {}; + +class cast_error : public std::runtime_error { + using std::runtime_error::runtime_error; +}; + +class index_error : public std::runtime_error { + using std::runtime_error::runtime_error; +}; + +class key_error : public std::runtime_error { + using std::runtime_error::runtime_error; +}; + +class value_error : public std::runtime_error { + using std::runtime_error::runtime_error; +}; + +class type_error : public std::runtime_error { + using std::runtime_error::runtime_error; +}; + +class import_error : public std::runtime_error { + using std::runtime_error::runtime_error; +}; + +class attribute_error : public std::runtime_error { + using std::runtime_error::runtime_error; +}; + +inline object::operator bool () const { return raise_call(m_ptr); } + +#define PKBIND_BINARY_OPERATOR(name, lop, rop) \ + inline object operator name (handle lhs, handle rhs) { \ + raise_call(lhs.ptr(), rhs.ptr(), lop, rop); \ + return object(retv, object::realloc_t{}); \ + } + +PKBIND_BINARY_OPERATOR(==, __eq__, __eq__) +PKBIND_BINARY_OPERATOR(!=, __ne__, __ne__) +PKBIND_BINARY_OPERATOR(<, __lt__, __gt__) +PKBIND_BINARY_OPERATOR(<=, __le__, __ge__) +PKBIND_BINARY_OPERATOR(>, __gt__, __lt__) +PKBIND_BINARY_OPERATOR(>=, __ge__, __le__) + +PKBIND_BINARY_OPERATOR(+, __add__, __radd__) +PKBIND_BINARY_OPERATOR(-, __sub__, __rsub__) +PKBIND_BINARY_OPERATOR(*, __mul__, __rmul__) +PKBIND_BINARY_OPERATOR(/, __truediv__, __rtruediv__) +PKBIND_BINARY_OPERATOR(%, __mod__, __rmod__) + +// FIXME: support __rand__ ... +PKBIND_BINARY_OPERATOR(&, __and__, 0) +PKBIND_BINARY_OPERATOR(|, __or__, 0) +PKBIND_BINARY_OPERATOR(^, __xor__, 0) +PKBIND_BINARY_OPERATOR(<<, __lshift__, 0) +PKBIND_BINARY_OPERATOR(>>, __rshift__, 0) + +#undef PKBIND_BINARY_OPERATOR + +} // namespace pkbind diff --git a/include/pybind11/internal/function.h b/include/pybind11/internal/function.h new file mode 100644 index 00000000..934ea7a1 --- /dev/null +++ b/include/pybind11/internal/function.h @@ -0,0 +1,686 @@ +#pragma once + +#include "cast.h" + +namespace pkbind { + +namespace impl { + +template +struct constructor {}; + +template > +struct factory; + +template +struct factory> { + Fn fn; + + auto make() { + using Self = callable_return_t; + return [fn = std::move(fn)](Self* self, Args... args) { + new (self) Self(fn(args...)); + }; + } +}; + +} // namespace impl + +template +impl::constructor init() { + return {}; +} + +template +impl::factory init(Fn&& fn) { + return {std::forward(fn)}; +} + +struct arg_with_default { + const char* name; + object value; +}; + +struct arg { + const char* name; + + arg(const char* name) : name(name) {} + + template + arg_with_default operator= (T&& value) { + return arg_with_default{name, cast(std::forward(value))}; + } +}; + +struct kwargs_proxy { + handle value; +}; + +struct args_proxy { + handle value; + + kwargs_proxy operator* () { return kwargs_proxy{value}; } +}; + +template +args_proxy interface::operator* () const { + return args_proxy{handle(this->ptr())}; +} + +template +template +object interface::operator() (Args&&... args) const { + py_push(ptr()); + py_pushnil(); + + int argc = 0; + int kwargsc = 0; + + auto foreach = [&](auto&& argument) { + using type = std::decay_t; + if constexpr(std::is_constructible_v) { + argc += 1; + py_push(handle(argument).ptr()); + } else if constexpr(std::is_same_v) { + kwargsc += 1; + arg_with_default& default_ = argument; + py_pushname(name(default_.name).index()); + py_push(default_.value.ptr()); + } else if constexpr(std::is_same_v) { + tuple args = argument.value.template cast(); + for(auto arg: args) { + argc += 1; + py_push(arg.ptr()); + } + } else if constexpr(std::is_same_v) { + dict kwargs = argument.value.template cast(); + kwargs.apply([&](handle key, handle value) { + kwargsc += 1; + name name = key.cast(); + py_pushname(name.index()); + py_push(value.ptr()); + }); + } else { + argc += 1; + py_push(pkbind::cast(std::forward(argument), policy).ptr()); + } + }; + + (foreach(std::forward(args)), ...); + + raise_call(argc, kwargsc); + + return object::from_ret(); +} + +class function : public object { + PKBIND_TYPE_IMPL(object, function, tp_function); +}; + +namespace impl { + +template , + typename IndexSequence = std::make_index_sequence>> +struct template_parser; + +class function_record { + template + friend struct template_parser; + + using destructor_t = void (*)(function_record*); + using wrapper_t = bool (*)(function_record&, + std::vector&, + std::vector>&, + bool convert, + handle parent); + + struct arguments_t { + std::vector names; + std::vector defaults; + }; + +public: + template + function_record(Fn&& f, const Extras&... extras) { + using Callable = std::decay_t; + + if constexpr(std::is_trivially_copyable_v && sizeof(Callable) <= sizeof(buffer)) { + // if the callable object is trivially copyable and the size is less than 16 bytes, + // store it in the buffer + new (buffer) auto(std::forward(f)); + destructor = [](function_record* self) { + reinterpret_cast(self->buffer)->~Callable(); + }; + } else { + // otherwise, store it in the heap + data = new auto(std::forward(f)); + destructor = [](function_record* self) { + delete static_cast(self->data); + }; + } + + using Parser = template_parser>; + Parser::initialize(*this, extras...); + wrapper = Parser::call; + } + + function_record(const function_record&) = delete; + + function_record& operator= (const function_record&) = delete; + + function_record(function_record&& other) noexcept { + std::memcpy(this, &other, sizeof(function_record)); + std::memset(&other, 0, sizeof(function_record)); + } + + function_record& operator= (function_record&&) = delete; + + ~function_record() { + if(destructor) { destructor(this); } + if(arguments) { delete arguments; } + if(next) { delete next; } + if(signature) { delete[] signature; } + } + + void append(function_record* record) { + function_record* p = this; + while(p->next) { + p = p->next; + } + p->next = record; + } + + template + T& as() { + if constexpr(std::is_trivially_copyable_v && sizeof(T) <= sizeof(buffer)) { + return *reinterpret_cast(buffer); + } else { + return *static_cast(data); + } + } + + static function_record& from(handle h) { + auto slot = py_getslot(h.ptr(), 0); + return *static_cast(py_touserdata(slot)); + } + + void operator() (int argc, handle stack) { + function_record* p = this; + + bool has_self = argc == 3; + std::vector args; + handle self = py_offset(stack.ptr(), 0); + if(has_self) { args.push_back(self); } + + auto tuple = py_offset(stack.ptr(), 0 + has_self); + for(int i = 0; i < py_tuple_len(tuple); ++i) { + args.push_back(py_tuple_getitem(tuple, i)); + } + + auto dict = steal(py_offset(stack.ptr(), 1 + has_self)); + + std::vector> kwargs; + dict.apply([&](handle key, handle value) { + kwargs.emplace_back(key, value); + }); + + // foreach function record and call the function with not convert + while(p != nullptr) { + auto result = p->wrapper(*p, args, kwargs, false, self); + if(result) { return; } + p = p->next; + } + + p = this; + // foreach function record and call the function with convert + while(p != nullptr) { + auto result = p->wrapper(*p, args, kwargs, true, self); + if(result) { return; } + p = p->next; + } + + std::string msg = "no matching function found, function signature:\n"; + p = this; + while(p != nullptr) { + msg += " "; + msg += p->signature; + msg += "\n"; + p = p->next; + } + throw std::runtime_error(msg); + } + +private: + union { + void* data; + char buffer[16]; + }; + + wrapper_t wrapper = nullptr; + function_record* next = nullptr; + arguments_t* arguments = nullptr; + destructor_t destructor = nullptr; + const char* signature = nullptr; + return_value_policy policy = return_value_policy::automatic; +}; + +template +void invoke(Fn&& fn, + std::index_sequence, + std::tuple...>& casters, + return_value_policy policy, + handle parent) { + using underlying_type = std::decay_t; + using return_type = callable_return_t; + + constexpr bool is_void = std::is_void_v; + constexpr bool is_member_function_pointer = std::is_member_function_pointer_v; + + if constexpr(is_member_function_pointer) { + // helper function to unpack the arguments to call the member pointer + auto unpack = [&](class_type_t& self, auto&... args) { + return (self.*fn)(args...); + }; + + if constexpr(!is_void) { + py_assign(py_retval(), + pkbind::cast(unpack(std::get(casters).value()...), policy, parent).ptr()); + } else { + unpack(std::get(casters).value()...); + py_newnone(py_retval()); + } + } else { + if constexpr(!is_void) { + py_assign(py_retval(), + pkbind::cast(fn(std::get(casters).value()...), policy, parent).ptr()); + } else { + fn(std::get(casters).value()...); + py_newnone(py_retval()); + } + } +} + +template +struct template_parser, + std::tuple, + std::index_sequence> { + using types = type_list; + + /// count of the Callable parameters. + constexpr inline static auto argc = types::size; + + // count the number of py::args and py::kwargs + constexpr inline static auto args_count = types::template count; + constexpr inline static auto kwargs_count = types::template count; + static_assert(args_count <= 1, "py::args can occur at most once"); + static_assert(kwargs_count <= 1, "py::kwargs can occur at most once"); + + /// find the position of py::args and py::kwargs + constexpr inline static auto args_pos = types::template find; + constexpr inline static auto kwargs_pos = types::template find; + + // FIXME: temporarily, args and kwargs must be at the end of the arguments list + /// if have py::kwargs, it must be at the end of the arguments list. + static_assert(kwargs_count == 0 || kwargs_pos == argc - 1, + "py::kwargs must be the last parameter"); + /// if have py::args, it must be before py::kwargs or at the end of the arguments list. + static_assert(args_count == 0 || args_pos == kwargs_pos - 1 || args_pos == argc - 1, + "py::args must be before py::kwargs or at the end of the parameter list"); + + using extras = type_list; + + // count the number of py::doc and py::return_value_policy + constexpr inline static auto doc_count = extras::template count; + constexpr inline static auto policy_count = extras::template count; + static_assert(doc_count <= 1, "doc can occur at most once"); + static_assert(policy_count <= 1, "return_value_policy can occur at most once"); + + constexpr inline static auto policy_pos = extras::template find; + + constexpr inline static auto last_arg_without_default_pos = + types::template find_last; + constexpr inline static auto first_arg_with_default_pos = + types::template find; + static_assert(last_arg_without_default_pos < first_arg_with_default_pos || + first_arg_with_default_pos == -1, + "parameter with default value must be after parameter without default value"); + + /// count of named parameters(explicit with name). + constexpr inline static auto named_only_argc = extras::template count; + constexpr inline static auto named_default_argc = + extras::template count; + constexpr inline static auto named_argc = named_only_argc + named_default_argc; + + /// count of normal parameters(which are not py::args or py::kwargs). + constexpr inline static auto normal_argc = argc - (args_pos != -1) - (kwargs_pos != -1); + + /// all parameters must either have no names or all must have names. + static_assert(named_argc == 0 || named_argc == normal_argc, + "all parameters must either have no names or all must have names."); + + static void initialize(function_record& record, const Extras&... extras) { + auto extras_tuple = std::make_tuple(extras...); + constexpr static bool has_named_args = (named_argc > 0); + if constexpr(policy_pos != -1) { record.policy = std::get(extras_tuple); } + + // TODO: set others + + // set default arguments + if constexpr(has_named_args) { + record.arguments = new function_record::arguments_t(); + + auto add_arguments = [&](const auto& default_) { + using type = remove_cvref_t; + if constexpr(std::is_same_v) { + auto& arguments = *record.arguments; + arguments.names.emplace_back(default_.name); + arguments.defaults.emplace_back(); + } else if constexpr(std::is_same_v) { + auto& arguments = *record.arguments; + arguments.names.emplace_back(default_.name); + arguments.defaults.emplace_back(std::move(default_.value)); + } + }; + + (add_arguments(extras), ...); + } + + // set signature + { + std::string sig = "("; + std::size_t index = 0; + auto append = [&](auto _t) { + using T = remove_cvref_t; + if constexpr(std::is_same_v) { + sig += "*args"; + } else if constexpr(std::is_same_v) { + sig += "**kwargs"; + } else if constexpr(has_named_args) { + sig += record.arguments->names[index].c_str(); + sig += ": "; + sig += type_info::of().name; + if(!record.arguments->defaults[index].empty()) { + sig += " = "; + sig += record.arguments->defaults[index] + .attr("__repr__")() + .cast(); + } + } else { + sig += "_: "; + sig += type_info::of().name; + } + if(index + 1 < argc) { sig += ", "; } + index++; + }; + (append(type_identity{}), ...); + sig += ")"; + char* buffer = new char[sig.size() + 1]; + std::memcpy(buffer, sig.data(), sig.size()); + buffer[sig.size()] = '\0'; + record.signature = buffer; + } + } + + /// try to call a C++ function(store in function_record) with the arguments which are from + /// Python. if success, return true, otherwise return false. + static bool call(function_record& record, + std::vector& args, + std::vector>& kwargs, + bool convert, + handle parent) { + // first, we try to load arguments into the stack. + // use argc + 1 to avoid compile error when argc is 0. + handle stack[argc + 1] = {}; + + // if have default arguments, load them + if constexpr(named_default_argc > 0) { + auto& defaults = record.arguments->defaults; + for(std::size_t i = named_only_argc; i < named_argc; ++i) { + stack[i] = defaults[i]; + } + } + + // load arguments from call arguments + if(args.size() > normal_argc) { + if constexpr(args_pos == -1) { return false; } + } + + for(std::size_t i = 0; i < std::min(normal_argc, (int)args.size()); ++i) { + stack[i] = args[i]; + } + + object repack_args; + // pack the args + if constexpr(args_pos != -1) { + const auto n = + static_cast(args.size() > normal_argc ? args.size() - normal_argc : 0); + auto pack = tuple(n); + for(int i = 0; i < n; ++i) { + pack[i] = args[normal_argc + i]; + } + repack_args = std::move(pack); + stack[args_pos] = repack_args; + } + + // pack the kwargs + int index = 0; + if constexpr(named_argc != 0) { + int arg_index = 0; + while(arg_index < named_argc && index < kwargs.size()) { + const auto name = kwargs[index].first; + const auto value = kwargs[index].second; + if(name.cast() == record.arguments->names[arg_index]) { + stack[arg_index] = value; + index += 1; + } + arg_index += 1; + } + } + + object repacked_kwargs; + if constexpr(kwargs_pos != -1) { + auto pack = dict(); + while(index < kwargs.size()) { + pack[kwargs[index].first] = kwargs[index].second; + index += 1; + } + repacked_kwargs = std::move(pack); + stack[kwargs_pos] = repacked_kwargs; + } + + // check if all the arguments are valid + for(std::size_t i = 0; i < argc; ++i) { + if(!stack[i]) { return false; } + } + + // ok, all the arguments are valid, call the function + std::tuple...> casters; + + if(((std::get(casters).load(stack[Is], convert)) && ...)) { + invoke(record.as(), + std::index_sequence{}, + casters, + record.policy, + parent); + return true; + } + + return false; + } +}; + +} // namespace impl + +class cpp_function : public function { + inline static py_Type m_type = 0; + + PKBIND_TYPE_IMPL(function, cpp_function, tp_function); + + static void register_() { + m_type = py_newtype("function_record", tp_object, nullptr, [](void* data) { + static_cast(data)->~function_record(); + }); + } + + static bool is_function_record(handle h) { + if(isinstance(h)) { + auto slot = py_getslot(h.ptr(), 0); + if(slot) { return py_typeof(slot) == m_type; } + } + return false; + } + + template + cpp_function(bool is_method, const char* name, Fn&& fn, const Extras&... extras) : + function(alloc_t{}) { + // bind the function + std::string sig = name; + sig += is_method ? "(self, *args, **kwargs)" : "(*args, **kwargs)"; + auto call = [](int argc, py_Ref stack) { + handle func = py_inspect_currentfunction(); + auto data = py_touserdata(py_getslot(func.ptr(), 0)); + auto& record = *static_cast(data); + try { + record(argc, stack); + return true; + } catch(std::domain_error& e) { + py_exception(tp_ValueError, e.what()); + } catch(std::invalid_argument& e) { + py_exception(tp_ValueError, e.what()); + } catch(std::length_error& e) { + py_exception(tp_ValueError, e.what()); + } catch(std::out_of_range& e) { + py_exception(tp_IndexError, e.what()); + } catch(std::range_error& e) { + py_exception(tp_ValueError, e.what()); + } catch(stop_iteration&) { StopIteration(); } catch(index_error& e) { + py_exception(tp_IndexError, e.what()); + } catch(key_error& e) { py_exception(tp_KeyError, e.what()); } catch(value_error& e) { + py_exception(tp_ValueError, e.what()); + } catch(type_error& e) { + py_exception(tp_TypeError, e.what()); + } catch(import_error& e) { + py_exception(tp_ImportError, e.what()); + } catch(attribute_error& e) { + py_exception(tp_AttributeError, e.what()); + } catch(std::exception& e) { py_exception(tp_RuntimeError, e.what()); } + return false; + }; + py_newfunction(m_ptr, sig.c_str(), call, nullptr, 1); + + assert(m_type != 0 && "function record type not registered"); + auto slot = py_getslot(m_ptr, 0); + void* data = py_newobject(slot, m_type, 0, sizeof(impl::function_record)); + new (data) impl::function_record(std::forward(fn), extras...); + } + + template + cpp_function(Fn&& fn, const Extras&... extras) : + cpp_function("lambda", std::forward(fn), extras...) {} +}; + +class property : public object { + PKBIND_TYPE_IMPL(object, property, tp_property); + + property(handle getter, handle setter = none()) : object() { + auto start = py_peek(0); + py_push(getter.ptr()); + py_push(setter.ptr()); + raise_call(type::of().index(), 2, start); + *this = object::from_ret(); + } +}; + +class staticmethod : public object { + PKBIND_TYPE_IMPL(object, staticmethod, tp_staticmethod); + + staticmethod(handle method) : object() { + auto start = py_peek(0); + py_push(method.ptr()); + raise_call(type::of().index(), 1, start); + *this = object::from_ret(); + } +}; + +namespace impl { + +template +void bind_function(handle obj, const char* name_, Fn&& fn, const Extras&... extras) { + constexpr bool has_named_args = + ((std::is_same_v || std::is_same_v) || ...); + auto name = py_name(name_); + auto func = py_getdict(obj.ptr(), name); + + if(func && cpp_function::is_function_record(func)) { + auto slot = py_getslot(func, 0); + auto& record = *static_cast(py_touserdata(slot)); + if constexpr(has_named_args && is_method) { + record.append(new function_record(std::forward(fn), arg("self"), extras...)); + } else { + record.append(new function_record(std::forward(fn), extras...)); + } + } else { + if constexpr(is_static) { + py_setdict( + obj.ptr(), + name, + staticmethod(cpp_function(is_method, name_, std::forward(fn), extras...).ptr()) + .ptr()); + } else { + if constexpr(has_named_args && is_method) { + py_setdict( + obj.ptr(), + name, + cpp_function(is_method, name_, std::forward(fn), arg("self"), extras...) + .ptr()); + } else { + py_setdict(obj.ptr(), + name, + cpp_function(is_method, name_, std::forward(fn), extras...).ptr()); + } + } + } +} + +template +void bind_property(handle obj, + const char* name, + Getter&& getter_, + Setter&& setter_, + const Extras&... extras) { + if constexpr(std::is_same_v, std::nullptr_t>) { + cpp_function getter(true, + name, + std::forward(getter_), + return_value_policy::reference_internal, + extras...); + property prop(getter.ptr()); + setattr(obj, name, prop); + } else { + cpp_function getter(true, + name, + std::forward(getter_), + return_value_policy::reference_internal, + extras...); + cpp_function setter(true, + name, + std::forward(setter_), + return_value_policy::reference_internal, + extras...); + property prop(getter.ptr(), setter.ptr()); + setattr(obj, name, prop); + } +} + +} // namespace impl + +inline dict::dict(std::initializer_list args) : dict() { + for(auto& arg: args) { + this->operator[] (arg.name) = arg.value; + } +} + +} // namespace pkbind diff --git a/include/pybind11/internal/instance.h b/include/pybind11/internal/instance.h index d80e36bf..80c50ba6 100644 --- a/include/pybind11/internal/instance.h +++ b/include/pybind11/internal/instance.h @@ -1,15 +1,14 @@ #pragma once -#include "kernel.h" +#include "accessor.h" + +namespace pkbind { -namespace pybind11 { struct type_info { - const char* name; + std::string_view name; std::size_t size; std::size_t alignment; void (*destructor)(void*); - void (*copy)(void*, const void*); - void (*move)(void*, void*); const std::type_info* type; template @@ -17,18 +16,11 @@ struct type_info { static_assert(!std::is_reference_v && !std::is_const_v>, "T must not be a reference type or const type."); static type_info info = { - typeid(T).name(), + type_name(), sizeof(T), alignof(T), [](void* ptr) { - ((T*)ptr)->~T(); - operator delete (ptr); - }, - [](void* dst, const void* src) { - new (dst) T(*(const T*)src); - }, - [](void* dst, void* src) { - new (dst) T(std::move(*(T*)src)); + delete static_cast(ptr); }, &typeid(T), }; @@ -37,11 +29,8 @@ struct type_info { }; // all registered C++ class will be ensured as instance type. -class instance { -public: +struct instance { // use to record the type information of C++ class. - -private: enum Flag { None = 0, Own = 1 << 0, // if the instance is owned by C++ side. @@ -50,96 +39,78 @@ private: Flag flag; void* data; - const type_info* type; - pkpy::PyVar parent; - // pkpy::PyVar + const type_info* info; + object parent; public: - instance() noexcept : flag(Flag::None), data(nullptr), type(nullptr), parent(nullptr) {} + template + static object create(type type, Value&& value_, handle parent_, return_value_policy policy) { + using underlying_type = remove_cvref_t; - instance(const instance&) = delete; - - instance(instance&& other) noexcept : flag(other.flag), data(other.data), type(other.type), parent(other.parent) { - other.flag = Flag::None; - other.data = nullptr; - other.type = nullptr; - other.parent = nullptr; - } - - template - static pkpy::PyVar create(pkpy::Type type) { - instance instance; - instance.type = &type_info::of(); - instance.data = operator new (sizeof(T)); - instance.flag = Flag::Own; - return vm->new_object(type, std::move(instance)); - } - - template - static pkpy::PyVar create(T&& value, - pkpy::Type type, - return_value_policy policy = return_value_policy::automatic_reference, - pkpy::PyVar parent = nullptr) noexcept { - using underlying_type = std::remove_cv_t>; - - // resolve for automatic policy. - if(policy == return_value_policy::automatic) { - policy = std::is_pointer_v ? return_value_policy::take_ownership - : std::is_lvalue_reference_v ? return_value_policy::copy - : return_value_policy::move; - } else if(policy == return_value_policy::automatic_reference) { - policy = std::is_pointer_v ? return_value_policy::reference - : std::is_lvalue_reference_v ? return_value_policy::copy - : return_value_policy::move; - } - - auto& _value = [&]() -> auto& { - /** - * note that, pybind11 will ignore the const qualifier. - * in fact, try to modify a const value will result in undefined behavior. - */ + auto& value = [&]() -> auto& { + // note that, pybind11 will ignore the const qualifier. + // in fact, try to modify a const value will result in undefined behavior. if constexpr(std::is_pointer_v) { - return *reinterpret_cast(value); + return *reinterpret_cast(value_); } else { - return const_cast(value); + return const_cast(value_); } }(); - instance instance; - instance.type = &type_info::of(); + using primary = std::remove_pointer_t; + + auto info = &type_info::of(); + + void* data = nullptr; + Flag flag = Flag::None; + object parent; if(policy == return_value_policy::take_ownership) { - instance.data = &_value; - instance.flag = Flag::Own; + data = &value; + flag = Flag::Own; } else if(policy == return_value_policy::copy) { - instance.data = ::new auto(_value); - instance.flag = Flag::Own; + if constexpr(std::is_copy_constructible_v) { + data = new auto(value); + flag = Flag::Own; + } else { + std::string msg = "cannot use copy policy on non-copyable type: "; + msg += type_name(); + throw std::runtime_error(msg); + } } else if(policy == return_value_policy::move) { - instance.data = ::new auto(std::move(_value)); - instance.flag = Flag::Own; + if constexpr(std::is_move_constructible_v) { + data = new auto(std::move(value)); + flag = Flag::Own; + } else { + std::string msg = "cannot use move policy on non-moveable type: "; + msg += type_name(); + throw std::runtime_error(msg); + } } else if(policy == return_value_policy::reference) { - instance.data = &_value; - instance.flag = Flag::None; + data = &value; + flag = Flag::None; } else if(policy == return_value_policy::reference_internal) { - instance.data = &_value; - instance.flag = Flag::Ref; - instance.parent = parent; + data = &value; + flag = Flag::Ref; + parent = borrow(parent_); } - return vm->new_object(type, std::move(instance)); + object result(object::alloc_t{}); + void* temp = py_newobject(result.ptr(), type.index(), 1, sizeof(instance)); + new (temp) instance{flag, data, info, std::move(parent)}; + return result; } ~instance() { - if(flag & Flag::Own) { type->destructor(data); } - } - - void _gc_mark(pkpy::VM* vm) const noexcept { - if(parent && (flag & Flag::Ref)) { PK_OBJ_MARK(parent); } + if(flag & Flag::Own) { + info->destructor(data); + } } template - T& cast() noexcept { + T& as() noexcept { return *static_cast(data); } }; -} // namespace pybind11 + +} // namespace pkbind diff --git a/include/pybind11/internal/kernel.h b/include/pybind11/internal/kernel.h index 1331fdff..340da4e0 100644 --- a/include/pybind11/internal/kernel.h +++ b/include/pybind11/internal/kernel.h @@ -1,99 +1,150 @@ #pragma once -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -namespace pybind11 { -inline pkpy::VM* vm = nullptr; -inline std::map* _ref_counts_map = nullptr; +#include "pocketpy.h" +#include "type_traits.h" -inline void initialize(bool enable_os = true) { - vm = new pkpy::VM(enable_os); - _ref_counts_map = new std::map(); +namespace pkbind { - // use to keep alive PyObject, when the object is hold by C++ side. - vm->heap._gc_marker_ex = [](pkpy::VM* vm) { - for(auto iter = _ref_counts_map->begin(); iter != _ref_counts_map->end();) { - auto ref_count = iter->second; - if(*ref_count != 0) { - // if ref count is not zero, then mark it. - PK_OBJ_MARK(iter->first); - ++iter; - } else { - // if ref count is zero, then delete it. - iter = _ref_counts_map->erase(iter); - delete ref_count; +class handle; + +/// hold the object temporarily +template +struct reg_t { + py_Ref value; + + void operator= (py_Ref ref) & { py_setreg(N, ref); } + + operator py_Ref () & { + assert(value && "register is not initialized"); + return value; + } + + void operator= (handle value) &; + + operator handle () &; + + // pkpy provide user 8 registers. + // 8th register is used for object pool, so N is limited to [0, 7). + static_assert(N >= 0 && N <= 6, "N must be in [0, 7)"); +}; + +struct retv_t { + py_Ref value; + + void operator= (py_Ref ref) & { py_assign(value, ref); } + + operator py_Ref () & { + assert(value && "return value is not initialized"); + return value; + } + + void operator= (handle value) &; + + operator handle () &; +}; + +/// hold the object long time. +struct object_pool { + inline static int cache = -1; + inline static py_Ref pool = nullptr; + inline static std::vector* indices_ = nullptr; + + struct object_ref { + py_Ref data; + int index; + }; + + static void initialize(int size) noexcept { + // use 8th register. + pool = py_getreg(7); + py_newtuple(pool, size); + indices_ = new std::vector(size, 0); + } + + static void finalize() noexcept { + delete indices_; + indices_ = nullptr; + } + + /// alloc an object from pool, note that the object is uninitialized. + static object_ref alloc() { + auto& indices = *indices_; + if(cache != -1) { + auto index = cache; + cache = -1; + indices[index] = 1; + return {py_tuple_getitem(pool, index), index}; + } + + for(int i = 0; i < indices.size(); ++i) { + if(indices[i] == 0) { + indices[i] = 1; + return {py_tuple_getitem(pool, i), i}; } } - }; -} -inline void finalize() { - delete _ref_counts_map; - delete vm; -} + throw std::runtime_error("object pool is full"); + } -enum class return_value_policy : uint8_t { - /** - * This is the default return value policy, which falls back to the policy - * return_value_policy::take_ownership when the return value is a pointer. - * Otherwise, it uses return_value::move or return_value::copy for rvalue - * and lvalue references, respectively. See below for a description of what - * all of these different policies do. - */ - automatic = 0, + /// alloc an object from pool, the object is initialized with ref. + static object_ref realloc(py_Ref ref) { + auto result = alloc(); + py_assign(result.data, ref); + return result; + } - /** - * As above, but use policy return_value_policy::reference when the return - * value is a pointer. This is the default conversion policy for function - * arguments when calling Python functions manually from C++ code (i.e. via - * handle::operator()). You probably won't need to use this. - */ - automatic_reference, + static void inc_ref(object_ref ref) { + if(!indices_) { return; } + if(ref.data == py_tuple_getitem(pool, ref.index)) { + auto& indices = *indices_; + indices[ref.index] += 1; + } else { + throw std::runtime_error("object_ref is invalid"); + } + } - /** - * Reference an existing object (i.e. do not create a new copy) and take - * ownership. Python will call the destructor and delete operator when the - * object's reference count reaches zero. Undefined behavior ensues when - * the C++ side does the same.. - */ - take_ownership, - - /** - * Create a new copy of the returned object, which will be owned by - * Python. This policy is comparably safe because the lifetimes of the two - * instances are decoupled. - */ - copy, - - /** - * Use std::move to move the return value contents into a new instance - * that will be owned by Python. This policy is comparably safe because the - * lifetimes of the two instances (move source and destination) are - * decoupled. - */ - move, - - /** - * Reference an existing object, but do not take ownership. The C++ side - * is responsible for managing the object's lifetime and deallocating it - * when it is no longer used. Warning: undefined behavior will ensue when - * the C++ side deletes an object that is still referenced and used by - * Python. - */ - reference, - - /** - * This policy only applies to methods and properties. It references the - * object without taking ownership similar to the above - * return_value_policy::reference policy. In contrast to that policy, the - * function or property's implicit this argument (called the parent) is - * considered to be the the owner of the return value (the child). - * pybind11 then couples the lifetime of the parent to the child via a - * reference relationship that ensures that the parent cannot be garbage - * collected while Python is still using the child. More advanced - * variations of this scheme are also possible using combinations of - * return_value_policy::reference and the keep_alive call policy - */ - reference_internal + static void dec_ref(object_ref ref) { + if(!indices_) { return; } + if(ref.data == py_tuple_getitem(pool, ref.index)) { + auto& indices = *indices_; + indices[ref.index] -= 1; + assert(indices[ref.index] >= 0 && "ref count is negative"); + if(indices[ref.index] == 0) { cache = ref.index; } + } else { + throw std::runtime_error("object_ref is invalid"); + } + } }; -} // namespace pybind11 + +struct action { + using function = void (*)(); + inline static std::vector starts; + + static void initialize() noexcept { + for(auto func: starts) { + func(); + } + } + + // register a function to be called at the start of the vm. + static void register_start(function func) { starts.push_back(func); } +}; + +template +inline reg_t reg; + +inline retv_t retv; + +inline std::unordered_map* m_type_map = nullptr; + +} // namespace pkbind diff --git a/include/pybind11/internal/module.h b/include/pybind11/internal/module.h new file mode 100644 index 00000000..859b6039 --- /dev/null +++ b/include/pybind11/internal/module.h @@ -0,0 +1,47 @@ +#pragma once + +#include "function.h" + +namespace pkbind { + +class module : public object { + PKBIND_TYPE_IMPL(object, module, tp_module) + + static module __main__() { return module(py_getmodule("__main__"), object::ref_t{}); } + + static module import(const char* name) { + raise_call(name); + return module(py_retval(), object::realloc_t{}); + } + + module def_submodule(const char* name, const char* doc = nullptr) { + // auto package = (attr("__package__").cast() += ".") += attr("__name__").cast(); + auto fname = (attr("__name__").cast() += ".") += name; + auto m = py_newmodule(fname.c_str()); + setattr(*this, name, m); + return module(m, object::ref_t{}); + } + + template + module& def(const char* name, Fn&& fn, const Extras... extras) { + impl::bind_function(*this, name, std::forward(fn), extras...); + return *this; + } +}; + +using module_ = module; + +#define PYBIND11_EMBEDDED_MODULE(name, variable) \ + static void _pkbind_register_##name(::pkbind::module& variable); \ + namespace pkbind::impl { \ + auto _module_##name = [] { \ + ::pkbind::action::register_start([] { \ + auto m = ::pkbind::module(py_newmodule(#name), ::pkbind::object::ref_t{}); \ + _pkbind_register_##name(m); \ + }); \ + return 1; \ + }(); \ + } \ + static void _pkbind_register_##name(::pkbind::module& variable) + +} // namespace pkbind diff --git a/include/pybind11/internal/object.h b/include/pybind11/internal/object.h index b6b1514e..db90fcd9 100644 --- a/include/pybind11/internal/object.h +++ b/include/pybind11/internal/object.h @@ -2,251 +2,304 @@ #include "kernel.h" -namespace pybind11 { +namespace pkbind { + class handle; class object; -class attr_accessor; -class item_accessor; class iterator; class str; -class bytes; -class iterable; -class tuple; -class dict; -class list; -class set; -class function; -class module; -class type; -class bool_; -class int_; -class float_; -class str; -class bytes; -template -T& _builtin_cast(const handle& obj); +struct arg; +struct arg_with_default; +struct args_proxy; -template -T reinterpret_borrow(const handle& h); +enum class return_value_policy : uint8_t { + /** + * This is the default return value policy, which falls back to the policy + * return_value_policy::take_ownership when the return value is a pointer. + * Otherwise, it uses return_value::move or return_value::copy for rvalue + * and lvalue references, respectively. See below for a description of what + * all of these different policies do. + */ + automatic = 0, -template -T reinterpret_steal(const handle& h); + /** + * As above, but use policy return_value_policy::reference when the return + * value is a pointer. This is the default conversion policy for function + * arguments when calling Python functions manually from C++ code (i.e. via + * handle::operator()). You probably won't need to use this. + */ + automatic_reference, -class handle { -protected: - pkpy::PyVar m_ptr = nullptr; - mutable int* ref_count = nullptr; + /** + * Reference an existing object (i.e. do not create a new copy) and take + * ownership. Python will call the destructor and delete operator when the + * object's reference count reaches zero. Undefined behavior ensues when + * the C++ side does the same.. + */ + take_ownership, + /** + * Create a new copy of the returned object, which will be owned by + * Python. This policy is comparably safe because the lifetimes of the two + * instances are decoupled. + */ + copy, + + /** + * Use std::move to move the return value contents into a new instance + * that will be owned by Python. This policy is comparably safe because the + * lifetimes of the two instances (move source and destination) are + * decoupled. + */ + move, + + /** + * Reference an existing object, but do not take ownership. The C++ side + * is responsible for managing the object's lifetime and deallocating it + * when it is no longer used. Warning: undefined behavior will ensue when + * the C++ side deletes an object that is still referenced and used by + * Python. + */ + reference, + + /** + * This policy only applies to methods and properties. It references the + * object without taking ownership similar to the above + * return_value_policy::reference policy. In contrast to that policy, the + * function or property's implicit this argument (called the parent) is + * considered to be the the owner of the return value (the child). + * pybind11 then couples the lifetime of the parent to the child via a + * reference relationship that ensures that the parent cannot be garbage + * collected while Python is still using the child. More advanced + * variations of this scheme are also possible using combinations of + * return_value_policy::reference and the keep_alive call policy + */ + reference_internal +}; + +template +class accessor; + +namespace policy { + +struct attr; +template +struct item; +struct tuple; +struct list; +template +struct dict; + +} // namespace policy + +using attr_accessor = accessor; +template +using item_accessor = accessor>; +using tuple_accessor = accessor; +using list_accessor = accessor; +template +using dict_accessor = accessor>; + +/// call a pkpy function which may raise a python exception. +template +auto raise_call(Args&&... args); + +/// an effective representation of a python small string. +class name { public: - handle() = default; - handle(const handle& h) = default; - handle& operator= (const handle& other) = default; + name() = default; - handle(pkpy::PyVar ptr) : m_ptr(ptr) {} + name(const name&) = default; - pkpy::PyVar ptr() const { return m_ptr; } + name& operator= (const name&) = default; - int reference_count() const { return ref_count == nullptr ? 0 : *ref_count; } + explicit name(py_Name data) : data(data) {} - const handle& inc_ref() const { - assert(m_ptr != nullptr); - if(ref_count == nullptr) { - auto iter = _ref_counts_map->find(m_ptr); - if(iter == _ref_counts_map->end()) { - ref_count = ::new int(1); - _ref_counts_map->insert({m_ptr, ref_count}); - } else { - ref_count = iter->second; - *ref_count += 1; - } - } else { - *ref_count += 1; - } - return *this; + name(const char* str) : data(py_name(str)) {} + + name(const char* data, int size) : data(py_namev({data, size})) {} + + name(std::string_view str) : name(str.data(), static_cast(str.size())) {} + + name(handle h); + + py_Name index() const { return data; } + + const char* c_str() const { return py_name2str(data); } + + operator std::string_view () const { + auto temp = py_name2sv(data); + return std::string_view(temp.data, temp.size); } - const handle& dec_ref() const { - assert(m_ptr != nullptr); - assert(ref_count != nullptr); - - *ref_count -= 1; - try { - if(*ref_count == 0) { - _ref_counts_map->erase(m_ptr); - ::delete ref_count; - ref_count = nullptr; - } - } catch(std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; } - - return *this; - } +private: + py_Name data; +}; +template +class interface { public: - template - T cast() const; + /// equal to `self is None` in python. + bool is_none() const { return py_isnone(ptr()); } - explicit operator bool () const { return m_ptr.operator bool (); } + /// equal to `self is other` in python. + bool is(const interface& other) const { return py_isidentical(ptr(), other.ptr()); } - bool is(const handle& other) const { return m_ptr == other.m_ptr; } - - bool is_none() const { return m_ptr == vm->None; } - - bool in(const handle& other) const { - return pkpy::py_cast(vm, vm->call(vm->py_op("contains"), other.m_ptr, m_ptr)); - } - - bool contains(const handle& other) const { - return pkpy::py_cast(vm, vm->call(vm->py_op("contains"), m_ptr, other.m_ptr)); - } + void assign(const interface& other) { py_assign(ptr(), other.ptr()); } iterator begin() const; iterator end() const; - str doc() const; - - attr_accessor attr(const char* name) const; - attr_accessor attr(const handle& name) const; - attr_accessor attr(object&& name) const; - - item_accessor operator[] (int64_t key) const; - item_accessor operator[] (const char* key) const; - item_accessor operator[] (const handle& key) const; - item_accessor operator[] (object&& key) const; + attr_accessor attr(name key) const; object operator- () const; object operator~() const; + args_proxy operator* () const; + + item_accessor operator[] (int index) const; + item_accessor operator[] (name key) const; + item_accessor operator[] (handle key) const; template object operator() (Args&&... args) const; -private: - friend object operator+ (const handle& lhs, const handle& rhs); - friend object operator- (const handle& lhs, const handle& rhs); - friend object operator* (const handle& lhs, const handle& rhs); - friend object operator% (const handle& lhs, const handle& rhs); - friend object operator/ (const handle& lhs, const handle& rhs); - friend object operator| (const handle& lhs, const handle& rhs); - friend object operator& (const handle& lhs, const handle& rhs); - friend object operator^ (const handle& lhs, const handle& rhs); - friend object operator<< (const handle& lhs, const handle& rhs); - friend object operator>> (const handle& lhs, const handle& rhs); - - friend object operator+= (const handle& lhs, const handle& rhs); - friend object operator-= (const handle& lhs, const handle& rhs); - friend object operator*= (const handle& lhs, const handle& rhs); - friend object operator/= (const handle& lhs, const handle& rhs); - friend object operator%= (const handle& lhs, const handle& rhs); - friend object operator|= (const handle& lhs, const handle& rhs); - friend object operator&= (const handle& lhs, const handle& rhs); - friend object operator^= (const handle& lhs, const handle& rhs); - friend object operator<<= (const handle& lhs, const handle& rhs); - friend object operator>>= (const handle& lhs, const handle& rhs); - - friend object operator== (const handle& lhs, const handle& rhs); - friend object operator!= (const handle& lhs, const handle& rhs); - friend object operator< (const handle& lhs, const handle& rhs); - friend object operator> (const handle& lhs, const handle& rhs); - friend object operator<= (const handle& lhs, const handle& rhs); - friend object operator>= (const handle& lhs, const handle& rhs); + auto doc() const { return attr("__doc__"); } template - friend T& _builtin_cast(const handle& obj) { - // FIXME: 2.0 does not use Py_ anymore - static_assert(!std::is_reference_v, "T must not be a reference type."); - return obj.ptr().obj_get(); - } + T cast() const; + +private: + py_Ref ptr() const { return static_cast(this)->ptr(); } }; -static_assert(std::is_trivially_copyable_v); +/// a simple wrapper to py_Ref. +/// Note that it does not manage the lifetime of the object. +class handle : public interface { +public: + handle() = default; + + handle(const handle&) = default; + + handle& operator= (const handle&) = default; + + handle(py_Ref ptr) : m_ptr(ptr) {} + + auto ptr() const { return m_ptr; } + + explicit operator bool () const { return m_ptr != nullptr; } + +protected: + py_Ref m_ptr = nullptr; +}; class object : public handle { public: - object(const object& other) : handle(other) { inc_ref(); } + object() = default; - object(object&& other) noexcept : handle(other) { + object(const object& other) : handle(other), m_index(other.m_index) { + if(other.in_pool()) { object_pool::inc_ref(other); } + } + + object(object&& other) : handle(other), m_index(other.m_index) { other.m_ptr = nullptr; - other.ref_count = nullptr; + other.m_index = -1; } object& operator= (const object& other) { if(this != &other) { - dec_ref(); + if(in_pool()) { object_pool::dec_ref(*this); } + if(other.in_pool()) { object_pool::inc_ref(other); } m_ptr = other.m_ptr; - ref_count = other.ref_count; - inc_ref(); + m_index = other.m_index; } return *this; } - object& operator= (object&& other) noexcept { + object& operator= (object&& other) { if(this != &other) { - dec_ref(); + if(in_pool()) { object_pool::dec_ref(*this); } m_ptr = other.m_ptr; - ref_count = other.ref_count; + m_index = other.m_index; other.m_ptr = nullptr; - other.ref_count = nullptr; + other.m_index = -1; } return *this; } ~object() { - if(m_ptr != nullptr) { dec_ref(); } + if(in_pool()) { object_pool::dec_ref(*this); } } + bool is_singleton() const { return m_ptr && m_index == -1; } + + bool empty() const { return m_ptr == nullptr && m_index == -1; } + + /// return whether the object is in the object pool. + bool in_pool() const { return m_ptr && m_index != -1; } + +public: + static auto type_or_check() { return tp_object; } + + struct alloc_t {}; + + struct realloc_t {}; + + struct ref_t {}; + + object(alloc_t) { + auto ref = object_pool::alloc(); + m_ptr = ref.data; + m_index = ref.index; + } + + object(handle h, realloc_t) { + auto ref = object_pool::realloc(h.ptr()); + m_ptr = ref.data; + m_index = ref.index; + } + + object(handle h, ref_t) : handle(h) {} + + static object from_ret() { return object(retv, realloc_t{}); } + + operator object_pool::object_ref () const { return {m_ptr, m_index}; } + + explicit operator bool () const; + protected: - object(const handle& h, bool borrow) : handle(h) { - if(borrow) { inc_ref(); } - } - - template - friend T reinterpret_borrow(const handle& h) { - return {h, true}; - } - - template - friend T reinterpret_steal(const handle& h) { - return {h, false}; - } + int m_index = -1; }; -inline void setattr(const handle& obj, const handle& name, const handle& value); -inline void setitem(const handle& obj, const handle& key, const handle& value); +template +T steal(handle h) { + return T(h, object::ref_t{}); +} -#define PYBIND11_BINARY_OPERATOR(OP, NAME) \ - inline object operator OP (const handle& lhs, const handle& rhs) { \ - return reinterpret_borrow(vm->call(vm->py_op(NAME), lhs.m_ptr, rhs.m_ptr)); \ - } +template +T borrow(handle h) { + return T(h, object::realloc_t{}); +} -PYBIND11_BINARY_OPERATOR(+, "add"); -PYBIND11_BINARY_OPERATOR(-, "sub"); -PYBIND11_BINARY_OPERATOR(*, "mul"); -PYBIND11_BINARY_OPERATOR(/, "truediv"); -PYBIND11_BINARY_OPERATOR(%, "mod"); -PYBIND11_BINARY_OPERATOR(|, "or_"); -PYBIND11_BINARY_OPERATOR(&, "and_"); -PYBIND11_BINARY_OPERATOR(^, "xor"); -PYBIND11_BINARY_OPERATOR(<<, "lshift"); -PYBIND11_BINARY_OPERATOR(>>, "rshift"); +template +void reg_t::operator= (handle h) & { + py_setreg(N, h.ptr()); +} -PYBIND11_BINARY_OPERATOR(+=, "iadd"); -PYBIND11_BINARY_OPERATOR(-=, "isub"); -PYBIND11_BINARY_OPERATOR(*=, "imul"); -PYBIND11_BINARY_OPERATOR(/=, "itruediv"); -PYBIND11_BINARY_OPERATOR(%=, "imod"); -PYBIND11_BINARY_OPERATOR(|=, "ior"); -PYBIND11_BINARY_OPERATOR(&=, "iand"); -PYBIND11_BINARY_OPERATOR(^=, "ixor"); -PYBIND11_BINARY_OPERATOR(<<=, "ilshift"); -PYBIND11_BINARY_OPERATOR(>>=, "irshift"); +template +reg_t::operator handle () & { + assert(value && "register is not initialized"); + return value; +} -PYBIND11_BINARY_OPERATOR(==, "eq"); -PYBIND11_BINARY_OPERATOR(!=, "ne"); -PYBIND11_BINARY_OPERATOR(<, "lt"); -PYBIND11_BINARY_OPERATOR(>, "gt"); -PYBIND11_BINARY_OPERATOR(<=, "le"); -PYBIND11_BINARY_OPERATOR(>=, "ge"); +inline void retv_t::operator= (handle h) & { py_assign(value, h.ptr()); } -#undef PYBIND11_BINARY_OPERATOR +inline retv_t::operator handle () & { + assert(value && "return value is not initialized"); + return value; +} -} // namespace pybind11 +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_copyable_v); + +} // namespace pkbind diff --git a/include/pybind11/internal/type_traits.h b/include/pybind11/internal/type_traits.h index ea9dee70..ba4b8906 100644 --- a/include/pybind11/internal/type_traits.h +++ b/include/pybind11/internal/type_traits.h @@ -1,12 +1,86 @@ #pragma once #include +#include +#include #include -namespace pybind11 { +namespace pkbind { + +template +struct type_identity { + using type = T; +}; + template constexpr bool dependent_false = false; +template +using remove_cvref_t = std::remove_cv_t>; + +/// check if T is one of Ts... +template +constexpr inline bool is_one_of = (std::is_same_v || ...); + +using std::is_member_function_pointer_v; +using std::is_member_object_pointer_v; + +/// check if T is a function pointer type +template +constexpr inline bool is_function_pointer_v = std::is_function_v>; + +/// check if T is a functor type(has a unique operator()) +template +constexpr bool is_functor_v = false; + +template +constexpr inline bool is_functor_v> = true; + +template +constexpr inline bool is_integer_v = std::is_integral_v && !is_one_of; + +template +constexpr inline bool is_floating_point_v = std::is_floating_point_v; + +template +constexpr inline bool is_unique_pointer_v = false; + +template +constexpr inline bool is_unique_pointer_v> = true; + +template +constexpr bool is_pointer_v = std::is_pointer_v && !std::is_same_v; + +template +constexpr inline bool is_multiple_pointer_v = std::is_pointer_v && is_multiple_pointer_v>; + +template +constexpr auto type_name() { +#if __GNUC__ || __clang__ + std::string_view name = __PRETTY_FUNCTION__; + // format is "auto type_name() [T = int]" + std::size_t start = name.find("= ") + 2; + std::size_t end = name.rfind(']'); + return name.substr(start, end - start); +#elif _MSC_VER + std::string_view name = __FUNCSIG__; + // format is possible one of three following: + // - "auto __cdecl type_name(void)" + // - "auto __cdecl type_name(void)" + // - "auto __cdecl type_name(void)" + std::size_t start = name.find('<') + 1; + std::size_t end = name.rfind(">("); + name = name.substr(start, end - start); + auto space = name.find(' '); + return space == std::string_view::npos ? name : name.substr(space + 1); +#else + static_assert(false, "current compiler is not supported"); +#endif +} + +static_assert(type_name() == "int" && type_name() == "double", + "type_name() test failed, please report this issue"); + template struct tuple_push_front; @@ -24,9 +98,9 @@ struct function_traits { static_assert(dependent_false, "unsupported function type"); }; -#define PYBIND11_FUNCTION_TRAITS_SPECIALIZE(qualifiers) \ +#define PYBIND11_FUNCTION_TRAITS_SPECIALIZE(...) \ template \ - struct function_traits { \ + struct function_traits { \ using return_type = R; \ using args_type = std::tuple; \ constexpr static std::size_t args_count = sizeof...(Args); \ @@ -70,17 +144,6 @@ using class_type_t = typename member_traits::class_type; // some traits for distinguishing between function pointers, member function pointers and // functors -using std::is_member_function_pointer_v; -using std::is_member_object_pointer_v; - -template -constexpr inline bool is_function_pointer_v = std::is_function_v>; - -template -constexpr bool is_functor_v = false; - -template -constexpr inline bool is_functor_v> = true; template struct callable_traits; @@ -93,8 +156,8 @@ struct callable_traits>> { template struct callable_traits>> { - using args_type = function_args_t>; - using return_type = function_return_t>; + using args_type = function_args_t>; + using return_type = function_return_t>; }; template @@ -112,46 +175,64 @@ using callable_return_t = typename callable_traits::return_type; template constexpr std::size_t callable_args_count_v = std::tuple_size_v>; -template -struct type_identity { - using type = T; +template +struct type_list { + constexpr inline static int size = sizeof...(Ts); + + template + constexpr inline static int count = [] { + int count = 0; + ((count += std::is_same_v), ...); + return count; + }(); + + /// find first index of T in type_list. + template + constexpr inline static int find = [] { + bool arr[size + 1] = {std::is_same_v...}; + for(int i = 0; i < size; ++i) { + if(arr[i]) return i; + } + return -1; + }(); + + template + constexpr inline static int find_last = [] { + bool arr[size + 1] = {std::is_same_v...}; + for(int i = size - 1; i >= 0; --i) { + if(arr[i]) return i; + } + return -1; + }(); }; -template -using remove_cvref_t = std::remove_cv_t>; +template +struct overload_cast_t { + template + constexpr auto operator() (Return (*pf)(Args...)) const noexcept -> decltype(pf) { + return pf; + } -template -constexpr inline std::size_t types_count_v = (std::is_same_v + ...); + template + constexpr auto operator() (Return (Class::* pmf)(Args...), std::false_type = {}) const noexcept -> decltype(pmf) { + return pmf; + } -template -constexpr inline std::size_t types_count_v = 0; - -template -struct value_wrapper { - T* pointer; - - operator T& () { return *pointer; } + template + constexpr auto operator() (Return (Class::* pmf)(Args...) const, std::true_type) const noexcept -> decltype(pmf) { + return pmf; + } }; -template -struct value_wrapper { - T* pointer; +/// Syntax sugar for resolving overloaded function pointers: +/// - regular: static_cast(&Class::func) +/// - sweet: overload_cast(&Class::func) +template +constexpr inline overload_cast_t overload_cast; - operator T* () { return pointer; } -}; +/// Const member function selector for overload_cast +/// - regular: static_cast(&Class::func) +/// - sweet: overload_cast(&Class::func, const_) +constexpr inline auto const_ = std::true_type{}; -template -struct value_wrapper { - T* pointer; - - operator T& () { return *pointer; } -}; - -template -struct value_wrapper { - T* pointer; - - operator T&& () { return std::move(*pointer); } -}; - -} // namespace pybind11 +} // namespace pkbind diff --git a/include/pybind11/internal/types.h b/include/pybind11/internal/types.h index e1cd1512..70896877 100644 --- a/include/pybind11/internal/types.h +++ b/include/pybind11/internal/types.h @@ -1,208 +1,362 @@ #pragma once -#include "object.h" +#include "error.h" + +namespace pkbind { + +#define PKBIND_TYPE_IMPL(parent, child, expr) \ + \ +private: \ + friend class type; \ + static auto type_or_check() { return expr; } \ + \ +public: \ + using parent::parent; \ + using parent::operator=; \ + child(const object& o) : parent(type::isinstance(o) ? o : type::of()(o)) {} \ + child(object&& o) : \ + parent(type::isinstance(o) ? std::move(o) : type::of()(std::move(o))) {} -namespace pybind11 { class type : public object { -public: - using object::object; +protected: template - static handle handle_of(); -}; + constexpr inline static bool is_check_v = + std::is_invocable_r_v; -class iterable : public object { -public: - using object::object; - iterable() = delete; -}; - -class iterator : public object { -public: - using object::object; - iterator() = delete; -}; - -class list : public object { -public: - using object::object; - - list() : object(vm->new_object(pkpy::VM::tp_list), true) {} -}; - -class tuple : public object { -public: - using object::object; - - tuple(int n) : object(vm->new_object(pkpy::VM::tp_tuple, n), true) {} - - //& operator[](int i){ return _args[i]; } - // PyVar operator[](int i) const { return _args[i]; } -}; - -class set : public object { -public: - using object::object; - // set() : object(vm->new_object(pkpy::VM::tp_set), true) {} -}; - -class dict : public object { -public: - using object::object; - - dict() : object(vm->new_object(pkpy::VM::tp_dict), true) {} -}; - -class str : public object { + static auto type_or_check() { return tp_type; } public: - using object::object; - str(const char* c, int len) : - object(vm->new_object(pkpy::VM::tp_str, c, len), true) { + using object ::object; + using object ::operator=; - }; + // note: type is global instance, so we use ref_t. + explicit type(py_Type type) : object(py_tpobject(type), ref_t{}) {} - str(const char* c = "") : str(c, strlen(c)) {} + py_Type index() const { return py_totype(ptr()); } - str(const std::string& s) : str(s.data(), s.size()) {} + const char* name() const { return py_tpname(index()); } - str(std::string_view sv) : str(sv.data(), sv.size()) {} + static type of(handle h) { return type(py_typeof(h.ptr())); } - explicit str(const bytes& b); - explicit str(handle h); - operator std::string () const; + template + static type of(); - template - str format(Args&&... args) const; + template + static bool isinstance(handle obj); }; -class int_ : public object { -public: - using object::object; +class none : public object { + PKBIND_TYPE_IMPL(object, none, tp_NoneType); - int_(int64_t value) : object(pkpy::py_var(vm, value), true) {} -}; - -class float_ : public object { -public: - using object::object; - - float_(double value) : object(pkpy::py_var(vm, value), true) {} + // note: none is global instance, so we use ref_t. + none() : object(py_None, ref_t{}) {} }; class bool_ : public object { -public: - using object::object; + PKBIND_TYPE_IMPL(object, bool_, tp_bool); - bool_(bool value) : object(pkpy::py_var(vm, value), true) {} + // same as none, bool is a singleton. + bool_(bool value) : object(value ? py_True : py_False, ref_t{}) {} + + explicit operator bool () { return py_tobool(ptr()); } }; -class function : public object { -public: - using object::object; +class int_ : public object { + PKBIND_TYPE_IMPL(object, int_, tp_int); + + int_(py_i64 value) : object(alloc_t{}) { py_newint(m_ptr, value); } + + explicit operator py_i64 () { return py_toint(ptr()); } }; -class attr_accessor : public object { +class float_ : public object { + PKBIND_TYPE_IMPL(object, float_, tp_float); + + float_(py_f64 value) : object(alloc_t{}) { py_newfloat(m_ptr, value); } + + explicit operator py_f64 () { return py_tofloat(ptr()); } +}; + +bool hasattr(handle obj, name name); + +class iterable : public object { + PKBIND_TYPE_IMPL(object, iterable, [](handle h) { + return hasattr(h, "__iter__"); + }); +}; + +class iterator : public object { + template + friend class interface; + + iterator() : object(), m_value() {} + + iterator(handle h) : object(h, realloc_t{}), m_value() { operator++ (); } + +public: + PKBIND_TYPE_IMPL(object, iterator, [](handle h) { + return hasattr(h, "__iter__") && hasattr(h, "__next__"); + }); + + iterator& operator++ () { + int result = raise_call(m_ptr); + if(result == 1) { + m_value = object(retv, realloc_t{}); + } else if(result == 0) { + m_value = object(); + } + return *this; + } + + object operator* () const { return m_value; } + + friend bool operator== (const iterator& lhs, const iterator& rhs) { + return lhs.m_value.ptr() == rhs.m_value.ptr(); + } + + friend bool operator!= (const iterator& lhs, const iterator& rhs) { return !(lhs == rhs); } + + static iterator sentinel() { return iterator(); } + private: - object key; - -public: - template - attr_accessor(const object& obj, T&& key) : object(obj), key(std::forward(key)){}; - - template - attr_accessor& operator= (T&& value) & { - static_assert(std::is_base_of_v>, "T must be derived from object"); - m_ptr = std::forward(value); - return *this; - } - - template - attr_accessor& operator= (T&& value) && { - static_assert(std::is_base_of_v>, "T must be derived from object"); - setattr(*this, key, std::forward(value)); - return *this; - } + object m_value; }; -inline attr_accessor handle::attr(const char* name) const { - return attr_accessor(reinterpret_borrow(*this), str(name)); +template +iterator interface::begin() const { + raise_call(ptr()); + return iterator(retv); } -inline attr_accessor handle::attr(const handle& name) const { - return attr_accessor(reinterpret_borrow(*this), reinterpret_borrow(name)); +template +iterator interface::end() const { + return iterator::sentinel(); } -inline attr_accessor handle::attr(object&& name) const { - return attr_accessor(reinterpret_borrow(*this), std::move(name)); -} +class str : public object { + PKBIND_TYPE_IMPL(object, str, tp_str); -class item_accessor : public object { -public: - object key; + str(const char* data, int size) : object(alloc_t{}) { py_newstrn(m_ptr, data, size); } -public: - template - item_accessor(const object& obj, T&& key) : object(obj), key(std::forward(key)){}; + str(const char* data) : str(data, static_cast(strlen(data))) {} - template - item_accessor& operator= (T&& value) & { - static_assert(std::is_base_of_v>, "T must be derived from object"); - m_ptr = std::forward(value); - } + str(std::string_view s) : str(s.data(), static_cast(s.size())) {} - template - item_accessor& operator= (object&& value) && { - static_assert(std::is_base_of_v>, "T must be derived from object"); - setitem(*this, key, std::forward(value)); - } + template + object format(Args&&... args); }; -inline item_accessor handle::operator[] (int64_t key) const { - return item_accessor(reinterpret_borrow(*this), int_(key)); -} +class tuple : public object { + PKBIND_TYPE_IMPL(object, tuple, tp_tuple); -inline item_accessor handle::operator[] (const char* key) const { - return item_accessor(reinterpret_borrow(*this), str(key)); -} + tuple(int size) : object(alloc_t{}) { py_newtuple(m_ptr, size); } -inline item_accessor handle::operator[] (const handle& key) const { - return item_accessor(reinterpret_borrow(*this), reinterpret_borrow(key)); -} + tuple(std::initializer_list args) : tuple(static_cast(args.size())) { + int index = 0; + for(auto& arg: args) { + py_tuple_setitem(m_ptr, index++, arg.ptr()); + } + } -inline item_accessor handle::operator[] (object&& key) const { - return item_accessor(reinterpret_borrow(*this), std::move(key)); -} + tuple_accessor operator[] (int index) const; + + int size() const { return py_tuple_len(m_ptr); } + + bool empty() const { return size() == 0; } + + class iterator { + friend tuple; + + iterator(py_Ref ptr, int index) : ptr(ptr), index(index) {} + + public: + bool operator== (const iterator& other) const { return index == other.index; } + + bool operator!= (const iterator& other) const { return index != other.index; } + + iterator& operator++ () { + index++; + return *this; + } + + object operator* () const { return borrow(py_tuple_getitem(ptr, index)); } + + private: + py_Ref ptr; + int index; + }; + + iterator begin() const { return iterator(m_ptr, 0); } + + iterator end() const { return iterator(m_ptr, size()); } +}; + +class list : public object { + PKBIND_TYPE_IMPL(object, list, tp_list); + + list() : object(alloc_t{}) { py_newlist(m_ptr); } + + list(int size) : object(alloc_t{}) { py_newlistn(m_ptr, size); } + + list(std::initializer_list args) : list(static_cast(args.size())) { + int index = 0; + for(auto& arg: args) { + py_list_setitem(m_ptr, index++, arg.ptr()); + } + } + + list_accessor operator[] (int index) const; + + int size() const { return py_list_len(m_ptr); } + + bool empty() const { return size() == 0; } + + void swap(int i, int j) { py_list_swap(m_ptr, i, j); } + + void append(handle item) { py_list_append(m_ptr, item.ptr()); } + + void insert(int index, handle item) { py_list_insert(m_ptr, index, item.ptr()); } + + void remove(int index) { py_list_delitem(m_ptr, index); } + + void clear() { py_list_clear(m_ptr); } + + class iterator { + friend list; + + iterator(py_Ref ptr, int index) : ptr(ptr), index(index) {} + + public: + iterator(const iterator&) = default; + + bool operator== (const iterator& other) const { return index == other.index; } + + bool operator!= (const iterator& other) const { return index != other.index; } + + iterator operator++ () { + index++; + return *this; + } + + object operator* () const { return borrow(py_list_getitem(ptr, index)); } + + private: + py_Ref ptr; + int index; + }; + + iterator begin() const { return iterator(m_ptr, 0); } + + iterator end() const { return iterator(m_ptr, size()); } +}; + +class dict : public object { + PKBIND_TYPE_IMPL(object, dict, tp_dict); + + dict() : object(alloc_t{}) { py_newdict(m_ptr); } + + dict(std::initializer_list args); + + dict_accessor operator[] (int key) const; + + dict_accessor operator[] (name key) const; + + dict_accessor operator[] (handle key) const; + + int size() const { return py_dict_len(m_ptr); } + + bool empty() const { return size() == 0; } + + template + void apply(Fn&& fn) { + static_assert(std::is_invocable_v); + using ret = std::invoke_result_t; + auto callback = +[](py_Ref key, py_Ref value, void* data) -> bool { + auto fn = reinterpret_cast(data); + if constexpr(std::is_same_v) { + (*fn)(key, value); + return true; + } else { + return (*fn)(key, value); + } + }; + raise_call(m_ptr, callback, &fn); + } + + class iterator { + friend dict; + + iterator() : iter(pkbind::iterator::sentinel()) {} + + iterator(handle ptr); + + public: + iterator(const iterator&) = default; + + bool operator== (const iterator& other) const { return iter == other.iter; } + + bool operator!= (const iterator& other) const { return !(*this == other); } + + iterator operator++ () { + ++iter; + return *this; + } + + std::pair operator* () const; + + private: + object items; + pkbind::iterator iter; + }; + + iterator begin() const { return iterator(m_ptr); } + + iterator end() const { return iterator(); } +}; + +class slice : public object { + PKBIND_TYPE_IMPL(object, slice, tp_slice); +}; + +class set : public object {}; class args : public tuple { - using tuple::tuple; + PKBIND_TYPE_IMPL(tuple, args, tp_tuple); }; class kwargs : public dict { - using dict::dict; + PKBIND_TYPE_IMPL(dict, kwargs, tp_dict); }; -template -handle type::handle_of() { - if constexpr(std::is_same_v) { return vm->_t(vm->tp_object); } -#define PYBIND11_TYPE_MAPPER(type, tp) \ - else if constexpr(std::is_same_v) { return vm->_t(vm->tp); } - PYBIND11_TYPE_MAPPER(type, tp_type) - PYBIND11_TYPE_MAPPER(str, tp_str) - PYBIND11_TYPE_MAPPER(int_, tp_int) - PYBIND11_TYPE_MAPPER(float_, tp_float) - PYBIND11_TYPE_MAPPER(bool_, tp_bool) - PYBIND11_TYPE_MAPPER(list, tp_list) - PYBIND11_TYPE_MAPPER(tuple, tp_tuple) - PYBIND11_TYPE_MAPPER(args, tp_tuple) - PYBIND11_TYPE_MAPPER(dict, tp_dict) - PYBIND11_TYPE_MAPPER(kwargs, tp_dict) -#undef PYBIND11_TYPE_MAPPER - else { - auto result = vm->_cxx_typeid_map.find(typeid(T)); - if(result != vm->_cxx_typeid_map.end()) { return vm->_t(result->second); } +// TODO: +class capsule : public object { + struct capsule_impl { + void* data; + void (*destructor)(void*); + }; - vm->TypeError("Type not registered"); + inline static py_Type m_type = 0; + + PKBIND_TYPE_IMPL(object, capsule, m_type); + + static void register_() { + m_type = py_newtype("capsule", tp_object, nullptr, [](void* data) { + auto impl = static_cast(data); + if(impl->data && impl->destructor) { impl->destructor(impl->data); } + }); } -} -} // namespace pybind11 + capsule(void* data, void (*destructor)(void*) = nullptr) : object(alloc_t{}) { + void* impl = py_newobject(m_ptr, m_type, 0, sizeof(capsule_impl)); + new (impl) capsule_impl{data, destructor}; + } + + void* data() { return static_cast(py_touserdata(m_ptr))->data; } + + template + T& cast() { + return *static_cast(data()); + } +}; + +} // namespace pkbind diff --git a/include/pybind11/operators.h b/include/pybind11/operators.h new file mode 100644 index 00000000..80bcdcf0 --- /dev/null +++ b/include/pybind11/operators.h @@ -0,0 +1,204 @@ +#pragma once + +#include "pybind11.h" + +namespace pkbind::impl { + +enum op_id : int { + op_add, + op_sub, + op_mul, + op_div, + op_mod, + op_divmod, + op_pow, + op_lshift, + op_rshift, + op_and, + op_xor, + op_or, + op_neg, + op_pos, + op_abs, + op_invert, + op_int, + op_long, + op_float, + op_str, + op_cmp, + op_gt, + op_ge, + op_lt, + op_le, + op_eq, + op_ne, + op_iadd, + op_isub, + op_imul, + op_idiv, + op_imod, + op_ilshift, + op_irshift, + op_iand, + op_ixor, + op_ior, + op_complex, + op_bool, + op_nonzero, + op_repr, + op_truediv, + op_itruediv, + op_hash +}; + +enum op_type : int { + op_l, /* base type on left */ + op_r, /* base type on right */ + op_u /* unary operator */ +}; + +struct self_t {}; + +const static self_t self = self_t(); + +/// Type for an unused type slot +struct undefined_t {}; + +/// Don't warn about an unused variable +inline self_t __self() { return self; } + +/// base template of operator implementations +template +struct op_impl {}; + +/// Operator implementation generator +template +struct op_ { + constexpr static bool op_enable_if_hook = true; + + template + void execute(Class& cl, const Extra&... extra) const { + using Base = typename Class::underlying_type; + using L_type = std::conditional_t::value, Base, L>; + using R_type = std::conditional_t::value, Base, R>; + using op = op_impl; + cl.def(op::name(), &op::execute, extra...); + } + + template + void execute_cast(Class& cl, const Extra&... extra) const { + using Base = typename Class::type; + using L_type = std::conditional_t::value, Base, L>; + using R_type = std::conditional_t::value, Base, R>; + using op = op_impl; + cl.def(op::name(), &op::execute_cast, extra...); + } +}; + +#define PKBIND_BINARY_OPERATOR(id, rid, op, expr) \ + template \ + struct op_impl { \ + static char const* name() { return "__" #id "__"; } \ + static auto execute(const L& l, const R& r) -> decltype(expr) { return (expr); } \ + static B execute_cast(const L& l, const R& r) { return B(expr); } \ + }; \ + \ + template \ + struct op_impl { \ + static char const* name() { return "__" #rid "__"; } \ + static auto execute(const R& r, const L& l) -> decltype(expr) { return (expr); } \ + static B execute_cast(const R& r, const L& l) { return B(expr); } \ + }; \ + \ + inline op_ op(const self_t&, const self_t&) { \ + return op_(); \ + } \ + \ + template \ + op_ op(const self_t&, const T&) { \ + return op_(); \ + } \ + \ + template \ + op_ op(const T&, const self_t&) { \ + return op_(); \ + } + +#define PKBIND_INPLACE_OPERATOR(id, op, expr) \ + template \ + struct op_impl { \ + static char const* name() { return "__" #id "__"; } \ + static auto execute(L& l, const R& r) -> decltype(expr) { return expr; } \ + static B execute_cast(L& l, const R& r) { return B(expr); } \ + }; \ + \ + template \ + op_ op(const self_t&, const T&) { \ + return op_(); \ + } + +#define PKBIND_UNARY_OPERATOR(id, op, expr) \ + template \ + struct op_impl { \ + static char const* name() { return "__" #id "__"; } \ + static auto execute(const L& l) -> decltype(expr) { return expr; } \ + static B execute_cast(const L& l) { return B(expr); } \ + }; \ + \ + inline op_ op(const self_t&) { \ + return op_(); \ + } + +PKBIND_BINARY_OPERATOR(sub, rsub, operator-, l - r) +PKBIND_BINARY_OPERATOR(add, radd, operator+, l + r) +PKBIND_BINARY_OPERATOR(mul, rmul, operator*, l* r) +PKBIND_BINARY_OPERATOR(truediv, rtruediv, operator/, l / r) +PKBIND_BINARY_OPERATOR(mod, rmod, operator%, l % r) +PKBIND_BINARY_OPERATOR(lshift, rlshift, operator<<, l << r) +PKBIND_BINARY_OPERATOR(rshift, rrshift, operator>>, l >> r) +PKBIND_BINARY_OPERATOR(and, rand, operator&, l& r) +PKBIND_BINARY_OPERATOR(xor, rxor, operator^, l ^ r) +PKBIND_BINARY_OPERATOR(eq, eq, operator==, l == r) +PKBIND_BINARY_OPERATOR(ne, ne, operator!=, l != r) +PKBIND_BINARY_OPERATOR(or, ror, operator|, l | r) +PKBIND_BINARY_OPERATOR(gt, lt, operator>, l > r) +PKBIND_BINARY_OPERATOR(ge, le, operator>=, l >= r) +PKBIND_BINARY_OPERATOR(lt, gt, operator<, l < r) +PKBIND_BINARY_OPERATOR(le, ge, operator<=, l <= r) +// PKBIND_BINARY_OPERATOR(pow, rpow, pow, std::pow(l, r)) +PKBIND_INPLACE_OPERATOR(iadd, operator+=, l += r) +PKBIND_INPLACE_OPERATOR(isub, operator-=, l -= r) +PKBIND_INPLACE_OPERATOR(imul, operator*=, l *= r) +PKBIND_INPLACE_OPERATOR(itruediv, operator/=, l /= r) +PKBIND_INPLACE_OPERATOR(imod, operator%=, l %= r) +PKBIND_INPLACE_OPERATOR(ilshift, operator<<=, l <<= r) +PKBIND_INPLACE_OPERATOR(irshift, operator>>=, l >>= r) +PKBIND_INPLACE_OPERATOR(iand, operator&=, l &= r) +PKBIND_INPLACE_OPERATOR(ixor, operator^=, l ^= r) +PKBIND_INPLACE_OPERATOR(ior, operator|=, l |= r) + +PKBIND_UNARY_OPERATOR(neg, operator-, -l) +PKBIND_UNARY_OPERATOR(pos, operator+, +l) + +// WARNING: This usage of `abs` should only be done for existing STL overloads. +// Adding overloads directly in to the `std::` namespace is advised against: +// https://en.cppreference.com/w/cpp/language/extending_std + +// PKBIND_UNARY_OPERATOR(abs, abs, std::abs(l)) +// PKBIND_UNARY_OPERATOR(hash, hash, std::hash()(l)) +// PKBIND_UNARY_OPERATOR(invert, operator~, (~l)) +// PKBIND_UNARY_OPERATOR(bool, operator!, !!l) +// PKBIND_UNARY_OPERATOR(int, int_, (int)l) +// PKBIND_UNARY_OPERATOR(float, float_, (double)l) + +#undef PKBIND_BINARY_OPERATOR +#undef PKBIND_INPLACE_OPERATOR +#undef PKBIND_UNARY_OPERATOR + +} // namespace pkbind::impl + +namespace pkbind { + +using impl::self; + +} // namespace pkbind diff --git a/include/pybind11/pkbind.h b/include/pybind11/pkbind.h new file mode 100644 index 00000000..687757a4 --- /dev/null +++ b/include/pybind11/pkbind.h @@ -0,0 +1,3 @@ +#pragma once + +#include "pybind11.h" \ No newline at end of file diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index e33f169b..1593c2c7 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1,3 +1,65 @@ #pragma once #include "internal/class.h" + +namespace pkbind { + +namespace literals { +inline arg operator""_a (const char* c, size_t) { return arg(c); } +} // namespace literals + +inline bool initialized = false; + +/// initialize the vm. +inline void initialize(int object_pool_size = 1024) { + if(!initialized) { py_initialize(); } + + // initialize all registers. + reg<0>.value = py_getreg(0); + reg<1>.value = py_getreg(1); + reg<2>.value = py_getreg(2); + reg<3>.value = py_getreg(3); + reg<4>.value = py_getreg(4); + reg<5>.value = py_getreg(5); + reg<6>.value = py_getreg(6); + + // initialize ret. + retv.value = py_retval(); + + // initialize object pool. + object_pool::initialize(object_pool_size); + + m_type_map = new std::unordered_map(); + + // register types. + capsule::register_(); + cpp_function::register_(); + + action::initialize(); + initialized = true; +} + +/// finalize the vm. +inline void finalize(bool test = false) { + if(!initialized) { return; } + delete m_type_map; + m_type_map = nullptr; + object_pool::finalize(); + if(test) { + py_resetvm(); + } else { + py_finalize(); + } +} + +/// a RAII class to initialize and finalize python interpreter +class scoped_interpreter { +public: + scoped_interpreter(int object_pool_size = 1024) { initialize(object_pool_size); } + + ~scoped_interpreter() { finalize(); } +}; + +} // namespace pkbind + +namespace pybind11 = pkbind; \ No newline at end of file diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h new file mode 100644 index 00000000..d8bb7481 --- /dev/null +++ b/include/pybind11/stl.h @@ -0,0 +1,170 @@ +#include "pybind11.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace pkbind { + +template +struct type_caster> { + std::array data; + + template + static handle cast(U&& src, return_value_policy policy, handle parent) { + auto list = pkbind::list(); + for(auto&& item: src) { + list.append(pkbind::cast(std::move(item), policy, parent)); + } + return list; + } + + bool load(handle src, bool convert) { + if(!isinstance(src)) { return false; } + auto list = src.cast(); + + if(list.size() != N) { return false; } + + for(int i = 0; i < N; ++i) { + type_caster caster; + if(!caster.load(list[i], convert)) { return false; } + data[i] = std::move(caster.value()); + } + + return true; + } + + std::array& value() { return data; } + + constexpr inline static bool is_temporary_v = true; +}; + +template +constexpr bool is_py_list_like_v = false; + +template +constexpr bool is_py_list_like_v> = true; + +template +constexpr bool is_py_list_like_v> = true; + +template +constexpr bool is_py_list_like_v> = true; + +template <> +struct type_caster> { + std::vector data; + + template + static object cast(U&& src, return_value_policy policy, handle parent) { + auto list = pkbind::list(); + for(auto&& item: src) { + list.append(pkbind::cast(bool(item), policy, parent)); + } + return list; + } + + bool load(handle src, bool convert) { + if(!isinstance(src)) { return false; } + + auto list = src.cast(); + data.clear(); + data.reserve(list.size()); + + for(auto item: list) { + type_caster caster; + if(!caster.load(item, convert)) { return false; } + data.push_back(caster.value()); + } + + return true; + } + + std::vector& value() { return data; } + + constexpr inline static bool is_temporary_v = true; +}; + +template +struct type_caster>> { + T data; + + template + static object cast(U&& src, return_value_policy policy, handle parent) { + auto list = pkbind::list(); + for(auto&& item: src) { + list.append(pkbind::cast(std::move(item), policy, parent)); + } + return list; + } + + bool load(handle src, bool convert) { + if(!isinstance(src)) { return false; } + + auto list = src.cast(); + + for(auto item: list) { + type_caster caster; + if(!caster.load(item, convert)) { return false; } + data.push_back(std::move(caster.value())); + } + + return true; + } + + T& value() { return data; } + + constexpr inline static bool is_temporary_v = true; +}; + +template +constexpr bool is_py_map_like_v = false; + +template +constexpr bool is_py_map_like_v> = true; + +template +constexpr bool is_py_map_like_v> = true; + +template +struct type_caster>> { + T data; + + template + static object cast(U&& src, return_value_policy policy, handle parent) { + auto dict = pkbind::dict(); + for(auto&& [key, value]: src) { + dict[pkbind::cast(std::move(key), policy, parent)] = + pkbind::cast(std::move(value), policy, parent); + } + return dict; + } + + bool load(handle src, bool convert) { + if(!isinstance(src)) { return false; } + auto dict = src.cast(); + + for(auto item: dict) { + type_caster key_caster; + if(!key_caster.load(item.first, convert)) { return false; } + + type_caster value_caster; + if(!value_caster.load(item.second, convert)) { return false; } + + data.try_emplace(std::move(key_caster.value()), std::move(value_caster.value())); + } + + return true; + } + + T& value() { return data; } + + constexpr inline static bool is_temporary_v = true; +}; + +} // namespace pkbind diff --git a/include/pybind11/tests/CMakeLists.txt b/include/pybind11/tests/CMakeLists.txt new file mode 100644 index 00000000..3331ec3f --- /dev/null +++ b/include/pybind11/tests/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.10) +project(PKBIND_TEST) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../../.." "${CMAKE_CURRENT_BINARY_DIR}/pocketpy") + +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip + DOWNLOAD_EXTRACT_TIMESTAMP true +) + +if(WIN32) + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +endif() + +FetchContent_MakeAvailable(googletest) + +file(GLOB CPP_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") +add_executable(PKBIND_TEST ${CPP_SOURCES}) + +target_include_directories(PKBIND_TEST PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../..") +target_link_libraries(PKBIND_TEST PRIVATE pocketpy gtest_main) \ No newline at end of file diff --git a/include/pybind11/tests/builtins.cpp b/include/pybind11/tests/builtins.cpp new file mode 100644 index 00000000..663c3fb8 --- /dev/null +++ b/include/pybind11/tests/builtins.cpp @@ -0,0 +1,121 @@ +#include "test.h" + +namespace { + +int copy_constructor_calls = 0; +int move_constructor_calls = 0; +int destructor_calls = 0; + +struct Point { + int x; + int y; + + Point(int x, int y) : x(x), y(y) {} + + Point(const Point& other) : x(other.x), y(other.y) { ++copy_constructor_calls; } + + Point(Point&& other) : x(other.x), y(other.y) { ++move_constructor_calls; } + + ~Point() { ++destructor_calls; } + + bool operator== (const Point& p) const { return x == p.x && y == p.y; } +}; + +TEST_F(PYBIND11_TEST, exec_and_eval) { + auto m = py::module::__main__(); + + py::dict locals = {py::arg("x") = 1, py::arg("y") = 2}; + py::object obj = py::eval("x + y", py::none{}, locals); + EXPECT_EQ(obj.cast(), 3); + + py::exec("x = 1 + 2"); + EXPECT_EQ(py::eval("x").cast(), 3); + + py::exec("y = 1 + 2", m.attr("__dict__")); + EXPECT_EQ(py::eval("y", m.attr("__dict__")).cast(), 3); + + EXPECT_EQ(locals["x"].cast(), 1); + EXPECT_EQ(locals["y"].cast(), 2); +} + +TEST_F(PYBIND11_TEST, locals_and_globals) { + py::exec("x = 1"); + + auto globals = py::globals(); + EXPECT_EQ(globals["x"].cast(), 1); + + globals["y"] = 2; + EXPECT_EQ(py::eval("y").cast(), 2); +} + +TEST_F(PYBIND11_TEST, cast) { + auto m = py::module::__main__(); + + py::class_(m, "Point") + .def(py::init()) + .def_readwrite("x", &Point::x) + .def_readwrite("y", &Point::y) + .def("__eq__", &Point::operator==); + + Point p(1, 2); + + // for py::cast's default policy + + // if argument is lvalue, policy is copy + py::object o = py::cast(p); + EXPECT_EQ(py::cast(o), p); + EXPECT_EQ(copy_constructor_calls, 1); + EXPECT_EQ(move_constructor_calls, 0); + + // if argument is rvalue, policy is move + py::object o2 = py::cast(std::move(p)); + EXPECT_EQ(py::cast(o2), p); + EXPECT_EQ(copy_constructor_calls, 1); + EXPECT_EQ(move_constructor_calls, 1); + + // if argument is pointer, policy is reference(no taking ownership) + py::object o3 = py::cast(&p); + EXPECT_EQ(py::cast(o3), p); + EXPECT_EQ(copy_constructor_calls, 1); + EXPECT_EQ(move_constructor_calls, 1); + + py::finalize(true); + + EXPECT_EQ(destructor_calls, 2); +} + +TEST_F(PYBIND11_TEST, cpp_call_py) { + auto m = py::module::__main__(); + + py::exec(R"( +class Test: + def __init__(self, x, y): + self.x = x + self.y = y + + def sum1(self, z): + return self.x + self.y + z + + def sum2(self, z, *args): + return self.x + self.y + z + sum(args) + + def sum3(self, z, n=0): + return self.x + self.y + z + n + + def sum4(self, z, *args, **kwargs): + return self.x + self.y + z + sum(args) + kwargs['a'] + kwargs['b'] +)"); + + auto obj = py::eval("Test(1, 2)"); + + EXPECT_EQ(obj.attr("sum1")(3).cast(), 6); + EXPECT_EQ(obj.attr("sum2")(3, 4, 5).cast(), 15); + + using namespace py::literals; + + EXPECT_EQ(obj.attr("sum3")(3, "n"_a = 4).cast(), 10); + EXPECT_EQ(obj.attr("sum4")(3, 4, 5, "a"_a = 6, "b"_a = 7).cast(), 28); +} + +} // namespace + diff --git a/include/pybind11/tests/class.cpp b/include/pybind11/tests/class.cpp new file mode 100644 index 00000000..e5a13c0a --- /dev/null +++ b/include/pybind11/tests/class.cpp @@ -0,0 +1,225 @@ +#include "test.h" + +namespace { + +struct Point { + int x; + int y; + +private: + int z; + +public: + inline static int constructor_calls = 0; + inline static int copy_constructor_calls = 0; + inline static int move_constructor_calls = 0; + inline static int destructor_calls = 0; + + Point() : x(0), y(0), z(0) { constructor_calls++; } + + Point(int x, int y, int z) : x(x), y(y), z(z) { constructor_calls++; } + + Point(const Point& p) : x(p.x), y(p.y), z(p.z) { + copy_constructor_calls++; + constructor_calls++; + } + + Point(Point&& p) noexcept : x(p.x), y(p.y), z(p.z) { + move_constructor_calls++; + constructor_calls++; + } + + Point& operator= (const Point& p) { + x = p.x; + y = p.y; + z = p.z; + return *this; + } + + ~Point() { destructor_calls++; } + + int& get_z() { return z; } + + void set_z(int z) { this->z = z; } + + std::string stringfy() const { + return "(" + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(z) + ")"; + } +}; + +struct Line { + Point start; + Point end; +}; + +TEST_F(PYBIND11_TEST, class) { + py::module_ m = py::module_::import("__main__"); + py::class_(m, "Point") + .def(py::init<>()) + .def(py::init()) + .def_readwrite("x", &Point::x) + .def_readwrite("y", &Point::y) + .def_property("z", &Point::get_z, &Point::set_z) + .def("stringfy", &Point::stringfy); + + py::exec(R"( +p = Point() +assert p.stringfy() == '(0, 0, 0)' +p = Point(1, 2, 3) +assert p.x == 1 +assert p.y == 2 +assert p.z == 3 +assert p.stringfy() == '(1, 2, 3)' +p.x = 10 +p.y = 20 +p.z = 30 +assert p.stringfy() == '(10, 20, 30)' +)"); + + py::class_ line(m, "Line"); + + line // not bind constructor + .def_readwrite("start", &Line::start) + .def_readwrite("end", &Line::end); + + // bind constructor + line.def(py::init<>()); + + py::exec(R"( +l = Line() +l.start = Point(1, 2, 3) +l.end = Point(4, 5, 6) +p = l.start +assert l.start.stringfy() == '(1, 2, 3)' +assert l.end.stringfy() == '(4, 5, 6)' +)"); + + py::finalize(true); + + EXPECT_EQ(Point::constructor_calls, Point::destructor_calls); + EXPECT_EQ(Point::copy_constructor_calls, 0); + EXPECT_EQ(Point::move_constructor_calls, 0); +} + +TEST_F(PYBIND11_TEST, inheritance) { + static int constructor_calls = 0; + + struct Point { + int x; + int y; + + Point() : x(0), y(0) { constructor_calls++; } + + Point(int x, int y) : x(x), y(y) { constructor_calls++; } + }; + + struct Point3D : Point { + int z; + + Point3D() : Point(), z(0) { constructor_calls++; } + + Point3D(int x, int y, int z) : Point(x, y), z(z) { constructor_calls++; } + }; + + py::module_ m = py::module_::import("__main__"); + + py::class_(m, "Point") + .def(py::init<>()) + .def(py::init()) + .def_readwrite("x", &Point::x) + .def_readwrite("y", &Point::y); + + py::class_(m, "Point3D") + .def(py::init<>()) + .def(py::init()) + .def_readwrite("z", &Point3D::z); + + py::exec(R"( +p = Point3D() +assert type(p) == Point3D +assert p.x == 0 +assert p.y == 0 +assert p.z == 0 + +p = Point3D(1, 2, 3) +assert p.x == 1 +assert p.y == 2 +assert p.z == 3 + +p.x = 10 +p.y = 20 +p.z = 30 +assert p.x == 10 +assert p.y == 20 +assert p.z == 30 +)"); + + py::finalize(true); + + EXPECT_EQ(constructor_calls, 4); +} + +TEST_F(PYBIND11_TEST, dynamic_attr) { + py::module_ m = py::module_::import("__main__"); + + struct Point { + int x; + int y; + + Point(int x, int y) : x(x), y(y) {} + }; + + py::class_(m, "Point", py::dynamic_attr()) + .def(py::init()) + .def_readwrite("x", &Point::x) + .def_readwrite("y", &Point::y); + + py::object p = py::eval("Point(1, 2)"); + EXPECT_EQ(p.attr("x").cast(), 1); + EXPECT_EQ(p.attr("y").cast(), 2); + + p.attr("z") = py::int_(3); + EXPECT_EQ(p.attr("z").cast(), 3); +} + +TEST_F(PYBIND11_TEST, enum) { + enum class Color { RED, Yellow, GREEN, BLUE }; + + py::module_ m = py::module_::import("__main__"); + + py::enum_ color(m, "Color"); + + color.value("RED", Color::RED) + .value("Yellow", Color::Yellow) + .value("GREEN", Color::GREEN) + .value("BLUE", Color::BLUE); + + EXPECT_EVAL_EQ("Color.RED", Color::RED); + EXPECT_EVAL_EQ("Color.Yellow", Color::Yellow); + EXPECT_EVAL_EQ("Color.GREEN", Color::GREEN); + EXPECT_EVAL_EQ("Color.BLUE", Color::BLUE); + + EXPECT_EVAL_EQ("Color(0)", Color::RED); + EXPECT_EVAL_EQ("Color(1)", Color::Yellow); + EXPECT_EVAL_EQ("Color(2)", Color::GREEN); + EXPECT_EVAL_EQ("Color(3)", Color::BLUE); + + EXPECT_EXEC_EQ("Color(0)", "Color.RED"); + EXPECT_EXEC_EQ("Color(1)", "Color.Yellow"); + EXPECT_EXEC_EQ("Color(2)", "Color.GREEN"); + EXPECT_EXEC_EQ("Color(3)", "Color.BLUE"); + + EXPECT_EVAL_EQ("Color.RED.value", static_cast(Color::RED)); + EXPECT_EVAL_EQ("Color.Yellow.value", static_cast(Color::Yellow)); + EXPECT_EVAL_EQ("Color.GREEN.value", static_cast(Color::GREEN)); + EXPECT_EVAL_EQ("Color.BLUE.value", static_cast(Color::BLUE)); + + color.export_values(); + EXPECT_EVAL_EQ("RED", Color::RED); + EXPECT_EVAL_EQ("Yellow", Color::Yellow); + EXPECT_EVAL_EQ("GREEN", Color::GREEN); + EXPECT_EVAL_EQ("BLUE", Color::BLUE); +} + +} // namespace + diff --git a/include/pybind11/tests/error.cpp b/include/pybind11/tests/error.cpp new file mode 100644 index 00000000..605773d7 --- /dev/null +++ b/include/pybind11/tests/error.cpp @@ -0,0 +1,50 @@ +#include "test.h" + +TEST_F(PYBIND11_TEST, exception_python_to_cpp) { + auto test = [](const char* code, py_Type type) { + try { + py::exec(code); + } catch(py::python_error& e) { + EXPECT_TRUE(e.match(type)); + } + }; + + test("raise ValueError()", tp_ValueError); + test("raise KeyError()", tp_KeyError); + test("raise IndexError()", tp_IndexError); + test("raise StopIteration()", tp_StopIteration); + test("raise Exception()", tp_Exception); +} + +TEST_F(PYBIND11_TEST, exception_cpp_to_python) { + auto m = py::module_::__main__(); + +#define TEST_EXCEPTION(cppe, pye) \ + m.def("test_" #cppe, []() { \ + throw cppe(#cppe); \ + }); \ + py::exec("try:\n test_" #cppe "()\nexcept Exception as e:\n assert isinstance(e, " #pye \ + ")\n assert str(e) == '" #cppe "'") + + using namespace std; + TEST_EXCEPTION(runtime_error, RuntimeError); + TEST_EXCEPTION(domain_error, ValueError); + TEST_EXCEPTION(invalid_argument, ValueError); + TEST_EXCEPTION(length_error, ValueError); + TEST_EXCEPTION(out_of_range, IndexError); + TEST_EXCEPTION(range_error, ValueError); + + using namespace py; + + m.def("test_stop_iteration", []() { + throw py::stop_iteration(); + }); + py::exec("try:\n test_stop_iteration()\nexcept StopIteration as e:\n pass"); + TEST_EXCEPTION(index_error, IndexError); + // FIXME: TEST_EXCEPTION(key_error, KeyError); + TEST_EXCEPTION(value_error, ValueError); + TEST_EXCEPTION(type_error, TypeError); + TEST_EXCEPTION(import_error, ImportError); + TEST_EXCEPTION(attribute_error, AttributeError); + TEST_EXCEPTION(runtime_error, RuntimeError); +} diff --git a/include/pybind11/tests/function.cpp b/include/pybind11/tests/function.cpp new file mode 100644 index 00000000..59f1c023 --- /dev/null +++ b/include/pybind11/tests/function.cpp @@ -0,0 +1,402 @@ +#include "test.h" + +namespace { + +TEST_F(PYBIND11_TEST, vectorcall) { + auto m = py::module_::__main__(); + + py::exec(R"( +def add(a, b): + return a + b +)"); + // position only + EXPECT_CAST_EQ(m.attr("add")(1, 2), 3); + // FIXME: pkpy does not support such calling. + // keyword only + // EXPECT_CAST_EQ(m.attr("add")(py::arg("a") = 1, py::arg("b") = 2), 3); + // mix + // EXPECT_CAST_EQ(m.attr("add")(1, py::arg("b") = 2), 3); + + py::exec(R"( +def add2(a, *args): + return a + sum(args) +)"); + EXPECT_CAST_EQ(m.attr("add2")(1, 2, 3, 4), 10); + + EXPECT_EQ(py::type::of()(py::eval("[1, 2, 3]")), py::eval("(1, 2, 3)")); + EXPECT_EQ(py::type::of()(py::eval("(1, 2, 3)")), py::eval("[1, 2, 3]")); +} + +TEST_F(PYBIND11_TEST, constructor) { + auto m = py::module_::__main__(); + + struct Point { + int x, y; + + Point(int x, int y) : x(x), y(y) {} + + bool operator== (const Point& p) const { return x == p.x && y == p.y; } + }; + + py::class_(m, "Point") + .def(py::init(), py::arg("x") = 1, py::arg("y") = 2) + .def(py::init([](py::tuple tuple) { + return Point(tuple[0].cast(), tuple[1].cast()); + })) + .def("__eq__", &Point::operator==); + + EXPECT_EVAL_EQ("Point()", Point(1, 2)); + EXPECT_EVAL_EQ("Point(3)", Point(3, 2)); + EXPECT_EVAL_EQ("Point(3, 4)", Point(3, 4)); + EXPECT_EVAL_EQ("Point((3, 4))", Point(3, 4)); +} + +TEST_F(PYBIND11_TEST, args) { + auto m = py::module_::__main__(); + + // test for binding function with args + m.def("sum", [](py::args args) { + int sum = 0; + for(auto arg: args) { + sum += arg.cast(); + } + return sum; + }); + + EXPECT_EVAL_EQ("sum(1, 2, 3)", 6); +} + +TEST_F(PYBIND11_TEST, kwargs) { + auto m = py::module_::__main__(); + + // test for binding function with kwargs + m.def("cal", [](py::kwargs kwargs) { + int sum = kwargs["a"].cast() + kwargs["b"].cast() * kwargs["c"].cast(); + return sum; + }); + + EXPECT_EVAL_EQ("cal(a=1, b=2, c=3)", 7); +} + +TEST_F(PYBIND11_TEST, defaults) { + auto m = py::module_::__main__(); + + // test for binding function with defaults + m.def( + "cal", + [](int a, int b = 2, int c = 3) { + return a + b * c; + }, + py::arg("a"), + py::arg("b") = 2, + py::arg("c") = 3); + + EXPECT_EVAL_EQ("cal(1)", 7); // a = 1, b = 2, c = 3 + EXPECT_EVAL_EQ("cal(1, 4)", 13); // a = 1, b = 4, c = 3 + EXPECT_EVAL_EQ("cal(1, 4, 5)", 21); // a = 1, b = 4, c = 5 + EXPECT_EVAL_EQ("cal(2, c=6)", 14); // a = 2, b = 2, c = 6 + EXPECT_EVAL_EQ("cal(2, b=4, c=6)", 26); // a = 2, b = 4, c = 6 +} + +TEST_F(PYBIND11_TEST, defaults_with_args) { + auto m = py::module_::__main__(); + + // test for binding function with defaults + m.def( + "cal", + [](int a, int b, int c, py::args args) { + int sum = a + b + c; + for(auto arg: args) { + sum += arg.cast(); + } + return sum; + }, + py::arg("a"), + py::arg("b") = 2, + py::arg("c") = 3); + + EXPECT_EVAL_EQ("cal(1)", 6); // a = 1, b = 2, c = 3 + EXPECT_EVAL_EQ("cal(1, 4)", 8); // a = 1, b = 4, c = 3 + EXPECT_EVAL_EQ("cal(1, 4, 5)", 10); // a = 1, b = 4, c = 5 + EXPECT_EVAL_EQ("cal(1, 4, 5, 6)", 16); // a = 1, b = 4, c = 5, args = (6) + EXPECT_EVAL_EQ("cal(1, 4, 5, 6, 7)", 23); // a = 1, b = 4, c = 5, args = (6, 7) +} + +TEST_F(PYBIND11_TEST, default_with_args_and_kwargs) { + auto m = py::module_::__main__(); + + // test for binding function with defaults + m.def( + "cal", + [](int a, int b, int c, py::args args, py::kwargs kwargs) { + int sum = a + b + c; + for(auto arg: args) { + sum += arg.cast(); + } + for(auto item: kwargs) { + sum += item.second.cast(); + } + return sum; + }, + py::arg("a"), + py::arg("b") = 2, + py::arg("c") = 3); + + EXPECT_EVAL_EQ("cal(1)", 6); // a = 1, b = 2, c = 3 + EXPECT_EVAL_EQ("cal(1, 4)", 8); // a = 1, b = 4, c = 3 + EXPECT_EVAL_EQ("cal(1, 4, 5)", 10); // a = 1, b = 4, c = 5 + EXPECT_EVAL_EQ("cal(1, 4, 5, 6)", 16); // a = 1, b = 4, c = 5, args = (6) + EXPECT_EVAL_EQ("cal(1, 4, 5, 6, 7)", 23); // a = 1, b = 4, c = 5, args = (6, 7) + + EXPECT_EVAL_EQ("cal(1, 4, 5, d=6, e=7)", 23); // a = 1, b = 4, c = 5, kwargs = {d=6, e=7} + EXPECT_EVAL_EQ("cal(1, d=6, e=7)", 19); // a = 1, b = 2, c = 3, kwargs = {d=6, e=7} + + EXPECT_EVAL_EQ("cal(1)", 6); // a = 1, b = 2, c = 3 + EXPECT_EVAL_EQ("cal(1, b=4)", 8); // a = 1, b = 4, c = 3 + EXPECT_EVAL_EQ("cal(1, c=5)", 8); // a = 1, b = 2, c = 5 + + EXPECT_EVAL_EQ("cal(1, 4, d=6)", 14); // a = 1, b = 4, c = 3, kwargs = {d=6} + EXPECT_EVAL_EQ("cal(1, c=5, d=6)", 14); // a = 1, b = 2, c = 5, kwargs = {d=6} + EXPECT_EVAL_EQ("cal(1, 4, 5, d=6)", 16); // a = 1, b = 4, c = 5, kwargs = {d=6} + + EXPECT_EVAL_EQ("cal(1, 4, 5)", 10); // a = 1, b = 4, c = 5, args = (), kwargs = {} + EXPECT_EVAL_EQ("cal(1, 4, 5, *[], **{})", 10); // a = 1, b = 4, c = 5, args = (), kwargs = {} + + EXPECT_EVAL_EQ("cal(1, 4, 5, 6, 7, d=8, e=9)", 40); // a = 1, b = 4, c = 5, args = (6, 7), kwargs = {d=8, e=9} + + struct Point { + int x, y; + + Point(int x, int y) : x(x), y(y) {} + + int sum(int x = 1, int y = 2) { return this->x + this->y + x + y; } + }; + + py::class_(m, "Point") + .def(py::init()) // + .def("sum", &Point::sum, py::arg("x") = 1, py::arg("y") = 2); + + EXPECT_EVAL_EQ("Point(1, 2).sum()", 6); // x = 1, y = 2 + EXPECT_EVAL_EQ("Point(1, 2).sum(3)", 8); // x = 3, y = 2 + EXPECT_EVAL_EQ("Point(1, 2).sum(3, 4)", 10); // x = 3, y = 4 +} + +TEST_F(PYBIND11_TEST, overload) { + auto m = py::module_::__main__(); + + // test for binding function with overloads + m.def("cal", [](int a, int b) { + return a + b; + }); + + m.def("cal", [](int a, int b, int c) { + return a + b + c; + }); + + EXPECT_EVAL_EQ("cal(1, 2)", 3); + EXPECT_EVAL_EQ("cal(1, 2, 3)", 6); +} + +TEST_F(PYBIND11_TEST, return_value_policy) { + static int copy_constructor_calls = 0; + static int move_constructor_calls = 0; + static int destructor_calls = 0; + + struct Point { + int x, y; + + Point(int x, int y) : x(x), y(y) {} + + Point(const Point& p) : x(p.x), y(p.y) { copy_constructor_calls++; } + + Point(Point&& p) : x(p.x), y(p.y) { move_constructor_calls++; } + + ~Point() { destructor_calls++; } + + static Point& make_point() { + static Point p(1, 2); + return p; + } + + static Point& new_point() { return *new Point(1, 2); } + + bool operator== (const Point& p) const { return x == p.x && y == p.y; } + }; + + py::finalize(true); + + auto test = [](py::return_value_policy policy, auto bound_fn, auto fn) { + py::initialize(); + copy_constructor_calls = 0; + move_constructor_calls = 0; + destructor_calls = 0; + + auto m = py::module_::__main__(); + + py::class_(m, "Point") + .def(py::init()) + .def_readwrite("x", &Point::x) + .def_readwrite("y", &Point::y) + .def("__eq__", &Point::operator==); + + m.def("make_point", bound_fn, policy); + + EXPECT_EVAL_EQ("make_point()", Point::make_point()); + + py::finalize(true); + + fn(); + }; + + test(py::return_value_policy::reference, &Point::make_point, []() { + EXPECT_EQ(copy_constructor_calls, 0); + EXPECT_EQ(move_constructor_calls, 0); + EXPECT_EQ(destructor_calls, 0); + }); + + test(py::return_value_policy::copy, &Point::make_point, []() { + EXPECT_EQ(copy_constructor_calls, 1); + EXPECT_EQ(move_constructor_calls, 0); + EXPECT_EQ(destructor_calls, 1); + }); + + test(py::return_value_policy::move, &Point::make_point, []() { + EXPECT_EQ(copy_constructor_calls, 0); + EXPECT_EQ(move_constructor_calls, 1); + EXPECT_EQ(destructor_calls, 1); + }); + + test(py::return_value_policy::take_ownership, &Point::new_point, []() { + EXPECT_EQ(copy_constructor_calls, 0); + EXPECT_EQ(move_constructor_calls, 0); + EXPECT_EQ(destructor_calls, 1); + }); +} + +TEST_F(PYBIND11_TEST, default_return_value_policy) { + static int copy_constructor_calls = 0; + static int move_constructor_calls = 0; + static int destructor_calls = 0; + + struct Point { + int x, y; + + Point(int x, int y) : x(x), y(y) {} + + Point(const Point& p) : x(p.x), y(p.y) { copy_constructor_calls++; } + + Point(Point&& p) : x(p.x), y(p.y) { move_constructor_calls++; } + + ~Point() { destructor_calls++; } + + bool operator== (const Point& p) const { return x == p.x && y == p.y; } + }; + + auto m = py::module_::__main__(); + + py::class_(m, "Point") + .def(py::init()) + .def_readwrite("x", &Point::x) + .def_readwrite("y", &Point::y) + .def("__eq__", &Point::operator==); + + // for function return value policy + + // if return type is lvalue reference, policy is copy + m.def("make_point2", []() -> Point& { + static Point p(1, 2); + return p; + }); + py::exec("p2 = make_point2()"); + EXPECT_EQ(copy_constructor_calls, 1); + EXPECT_EQ(move_constructor_calls, 0); + + // if return type is rvalue reference, policy is move + m.def("make_point3", []() -> Point&& { + static Point p(1, 2); + return std::move(p); + }); + py::exec("p3 = make_point3()"); + EXPECT_EQ(copy_constructor_calls, 1); + EXPECT_EQ(move_constructor_calls, 1); + + // if return type is pointer, policy is take_ownership + m.def("make_point4", []() -> Point* { + return new Point(1, 2); + }); + py::exec("p4 = make_point4()"); + EXPECT_EQ(copy_constructor_calls, 1); + EXPECT_EQ(move_constructor_calls, 1); + + py::finalize(true); + EXPECT_EQ(destructor_calls, 3); +} + +TEST_F(PYBIND11_TEST, lambda) { + auto m = py::module_::__main__(); + + static int destructor_calls = 0; + + struct NotTrivial { + int data; + + int operator() (int x, int y) { return x + y + data; } + + ~NotTrivial() { destructor_calls++; } + }; + + struct NotSmall { + size_t a; + size_t b; + size_t c; + size_t d; + + size_t operator() (size_t x, size_t y) { return x + y + a + b + c + d; } + + ~NotSmall() { destructor_calls++; } + }; + + // test for binding lambda + m.def("cal", NotTrivial{3}); + m.def("cal2", NotSmall{3, 4, 5, 6}); + + EXPECT_EVAL_EQ("cal(1, 2)", 6); + EXPECT_EVAL_EQ("cal2(1, 2)", 21); + + py::finalize(true); + + EXPECT_EQ(destructor_calls, 4); +} + +int add(int a, int b) { return a + b; } + +int add(int a, int b, int c) { return a + b + c; } + +TEST_F(PYBIND11_TEST, overload_cast) { + auto m = py::module_::__main__(); + + m.def("add", py::overload_cast(add)); + m.def("add", py::overload_cast(add)); + + EXPECT_EVAL_EQ("add(1, 2)", 3); + EXPECT_EVAL_EQ("add(1, 2, 3)", 6); + + struct X { + X() {} + + int add(int a, int b) { return a + b; } + + int add(int a, int b, int c) { return a + b + c; } + }; + + py::class_(m, "X") + .def(py::init<>()) + .def("add", py::overload_cast(&X::add)) + .def("add", py::overload_cast(&X::add)); + + EXPECT_EVAL_EQ("X().add(1, 2)", 3); + EXPECT_EVAL_EQ("X().add(1, 2, 3)", 6); +} + +} // namespace + diff --git a/include/pybind11/tests/module.cpp b/include/pybind11/tests/module.cpp new file mode 100644 index 00000000..9de9538a --- /dev/null +++ b/include/pybind11/tests/module.cpp @@ -0,0 +1,31 @@ +#include "test.h" + +PYBIND11_EMBEDDED_MODULE(example, m) { + m.def("add", [](int a, int b) { + return a + b; + }); + + auto math = m.def_submodule("math"); + math.def("sub", [](int a, int b) { + return a - b; + }); +} + +namespace { + +TEST_F(PYBIND11_TEST, module) { + py::exec("import example"); + EXPECT_EVAL_EQ("example.add(1, 2)", 3); + + py::exec("from example import math"); + EXPECT_EVAL_EQ("math.sub(1, 2)", -1); + + py::exec("from example.math import sub"); + EXPECT_EVAL_EQ("sub(1, 2)", -1); + + auto math = py::module_::import("example.math"); + EXPECT_EQ(math.attr("sub")(4, 3).cast(), 1); +} + +} // namespace + diff --git a/include/pybind11/tests/object.cpp b/include/pybind11/tests/object.cpp new file mode 100644 index 00000000..083c9c41 --- /dev/null +++ b/include/pybind11/tests/object.cpp @@ -0,0 +1,98 @@ +#include "test.h" + +namespace { + +const char* source = R"( +class Point: + def __init__(self, x, y): + self.x = x + self.y = y + + def __add__(self, other): + return Point(self.x + other.x, self.y + other.y) + + def __sub__(self, other): + return Point(self.x - other.x, self.y - other.y) + + def __mul__(self, other): + return Point(self.x * other.x, self.y * other.y) + + def __truediv__(self, other): + return Point(self.x / other.x, self.y / other.y) + + def __floordiv__(self, other): + return Point(self.x // other.x, self.y // other.y) + + def __mod__(self, other): + return Point(self.x % other.x, self.y % other.y) + + def __pow__(self, other): + return Point(self.x ** other.x, self.y ** other.y) + + def __lshift__(self, other): + return Point(self.x << other.x, self.y << other.y) + + def __rshift__(self, other): + return Point(self.x >> other.x, self.y >> other.y) + + def __eq__(self, other): + return self.x == other.x and self.y == other.y + + def __ne__(self, other) -> bool: + return not self.__eq__(other) + + def __lt__(self, other) -> bool: + return self.x < other.x and self.y < other.y + + def __le__(self, other) -> bool: + return self.x <= other.x and self.y <= other.y + + def __gt__(self, other) -> bool: + return self.x > other.x and self.y > other.y + + def __ge__(self, other) -> bool: + return self.x >= other.x and self.y >= other.y + + def __repr__(self): + return f'Point({self.x}, {self.y})' +)"; + +TEST_F(PYBIND11_TEST, object) { + py::module_ m = py::module_::import("__main__"); + py::exec(source); + py::exec("p = Point(3, 4)"); + py::object p = py::eval("p"); + + // is + EXPECT_FALSE(p.is_none()); + EXPECT_TRUE(p.is(p)); + + // attrs + EXPECT_EQ(p.attr("x").cast(), 3); + EXPECT_EQ(p.attr("y").cast(), 4); + + p.attr("x") = py::int_(5); + p.attr("y") = py::int_(6); + + EXPECT_EQ(p.attr("x").cast(), 5); + EXPECT_EQ(p.attr("y").cast(), 6); + EXPECT_EXEC_EQ("p", "Point(5, 6)"); + + // operators + EXPECT_EVAL_EQ("Point(10, 12)", p + p); + EXPECT_EVAL_EQ("Point(0, 0)", p - p); + EXPECT_EVAL_EQ("Point(25, 36)", p * p); + EXPECT_EVAL_EQ("Point(1, 1)", p / p); + // EXPECT_EVAL_EQ("Point(0, 0)", p // p); + EXPECT_EVAL_EQ("Point(0, 0)", p % p); + + // iterators + py::object l = py::eval("[1, 2, 3]"); + int index = 0; + for(auto item: l) { + EXPECT_EQ(item.cast(), index + 1); + index++; + } +} + +} // namespace diff --git a/include/pybind11/tests/operators.cpp b/include/pybind11/tests/operators.cpp new file mode 100644 index 00000000..378b0460 --- /dev/null +++ b/include/pybind11/tests/operators.cpp @@ -0,0 +1,150 @@ +#include "test.h" +#include + +namespace { + +struct Int { + int x; + + Int(int x) : x(x) {} + +#define OPERATOR_IMPL(op) \ + template \ + friend int operator op (const LHS& lhs, const RHS& rhs) { \ + int l, r; \ + if constexpr(std::is_same_v) \ + l = lhs.x; \ + else \ + l = lhs; \ + if constexpr(std::is_same_v) \ + r = rhs.x; \ + else \ + r = rhs; \ + return l op r; \ + } + + OPERATOR_IMPL(+) + OPERATOR_IMPL(-) + OPERATOR_IMPL(*) + OPERATOR_IMPL(/) + OPERATOR_IMPL(%) + OPERATOR_IMPL(&) + OPERATOR_IMPL(|) + OPERATOR_IMPL(^) + OPERATOR_IMPL(<<) + OPERATOR_IMPL(>>) + +#undef OPERATOR_IMPL + + bool operator== (const Int& other) const { return x == other.x; } + + bool operator!= (const Int& other) const { return x != other.x; } + + bool operator< (const Int& other) const { return x < other.x; } + + bool operator<= (const Int& other) const { return x <= other.x; } + + bool operator> (const Int& other) const { return x > other.x; } + + bool operator>= (const Int& other) const { return x >= other.x; } + + bool operator!() const { return !x; } +}; + +} // namespace + +TEST_F(PYBIND11_TEST, arithmetic_operators) { + py::module_ m = py::module_::import("__main__"); + py::class_(m, "Int") + .def(py::init()) + .def(py::self + py::self) + .def(py::self + int()) + .def(int() + py::self) + .def(py::self - py::self) + .def(py::self - int()) + .def(int() - py::self) + .def(py::self * py::self) + .def(py::self * int()) + .def(int() * py::self) + .def(py::self / py::self) + .def(py::self / int()) + .def(int() / py::self) + .def(py::self % py::self) + .def(py::self % int()) + .def(int() % py::self) + .def(py::self & py::self) + .def(py::self & int()) + .def(int() & py::self) + .def(py::self | py::self) + .def(py::self | int()) + .def(int() | py::self) + .def(py::self ^ py::self) + .def(py::self ^ int()) + .def(int() ^ py::self) + .def(py::self << py::self) + .def(py::self << int()) + .def(int() << py::self) + .def(py::self >> py::self) + .def(py::self >> int()) + .def(int() >> py::self); + + auto a = py::cast(Int(1)); + auto ai = py::cast(1); + auto b = py::cast(Int(2)); + auto bi = py::cast(2); + + EXPECT_CAST_EQ(a + b, 3); + EXPECT_CAST_EQ(a + bi, 3); + EXPECT_CAST_EQ(ai + b, 3); + EXPECT_CAST_EQ(a - b, -1); + EXPECT_CAST_EQ(a - bi, -1); + EXPECT_CAST_EQ(ai - b, -1); + EXPECT_CAST_EQ(a * b, 2); + EXPECT_CAST_EQ(a * bi, 2); + EXPECT_CAST_EQ(ai * b, 2); + EXPECT_CAST_EQ(a / b, 0); + EXPECT_CAST_EQ(a / bi, 0); + // EXPECT_CAST_EQ(ai / b, 0); + EXPECT_CAST_EQ(a % b, 1); + EXPECT_CAST_EQ(a % bi, 1); + // EXPECT_CAST_EQ(ai % b, 1); + EXPECT_CAST_EQ(a & b, 0); + EXPECT_CAST_EQ(a & bi, 0); + // EXPECT_CAST_EQ(ai & b, 0); + EXPECT_CAST_EQ(a | b, 3); + EXPECT_CAST_EQ(a | bi, 3); + // EXPECT_CAST_EQ(ai | b, 3); + EXPECT_CAST_EQ(a ^ b, 3); + EXPECT_CAST_EQ(a ^ bi, 3); + // EXPECT_CAST_EQ(ai ^ b, 3); + EXPECT_CAST_EQ(a << b, 4); + EXPECT_CAST_EQ(a << bi, 4); + // EXPECT_CAST_EQ(ai << b, 4); + EXPECT_CAST_EQ(a >> b, 0); + EXPECT_CAST_EQ(a >> bi, 0); + // EXPECT_CAST_EQ(ai >> b, 0); +} + +TEST_F(PYBIND11_TEST, logic_operators) { + py::module_ m = py::module_::import("__main__"); + py::class_(m, "Int") + .def(py::init()) + .def_readwrite("x", &Int::x) + .def(py::self == py::self) + .def(py::self != py::self) + .def(py::self < py::self) + .def(py::self <= py::self) + .def(py::self > py::self) + .def(py::self >= py::self); + + auto a = py::cast(Int(1)); + auto b = py::cast(Int(2)); + + EXPECT_FALSE(a == b); + EXPECT_TRUE(a != b); + EXPECT_TRUE(a < b); + EXPECT_TRUE(a <= b); + EXPECT_FALSE(a > b); + EXPECT_FALSE(a >= b); +} + diff --git a/include/pybind11/tests/stl.cpp b/include/pybind11/tests/stl.cpp new file mode 100644 index 00000000..33d8a886 --- /dev/null +++ b/include/pybind11/tests/stl.cpp @@ -0,0 +1,125 @@ +#include "test.h" +#include + +namespace { + +int constructor_calls = 0; +int destructor_calls = 0; + +struct Point { + int x; + int y; + + Point() : x(0), y(0) { constructor_calls++; } + + Point(int x, int y) : x(x), y(y) { constructor_calls++; } + + Point(const Point& p) : x(p.x), y(p.y) { constructor_calls++; } + + Point(Point&& p) noexcept : x(p.x), y(p.y) { constructor_calls++; } + + Point& operator= (const Point& p) { + x = p.x; + y = p.y; + return *this; + } + + ~Point() { destructor_calls++; } + + bool operator== (const Point& p) const { return x == p.x && y == p.y; } +}; + +} // namespace + +TEST_F(PYBIND11_TEST, vector_bool) { + std::vector v = {true, false, true}; + py::object obj = py::cast(v); + EXPECT_EVAL_EQ("[True, False, True]", obj); + + std::vector v2 = obj.cast>(); + EXPECT_EQ(v, v2); +} + +TEST_F(PYBIND11_TEST, list_like) { + py::class_(py::module_::__main__(), "Point") + .def(py::init()) + .def_readwrite("x", &Point::x) + .def_readwrite("y", &Point::y) + .def("__eq__", &Point::operator==); + + // array + { + std::array a = {Point(1, 2), Point(3, 4)}; + py::object obj = py::eval("[Point(1, 2), Point(3, 4)]"); + EXPECT_EVAL_EQ("[Point(1, 2), Point(3, 4)]", obj); + + std::array a2 = obj.cast>(); + EXPECT_EQ(a, a2); + } + + // vector + { + std::vector v = {Point(1, 2), Point(3, 4)}; + py::object obj = py::cast(v); + EXPECT_EVAL_EQ("[Point(1, 2), Point(3, 4)]", obj); + + std::vector v2 = obj.cast>(); + EXPECT_EQ(v, v2); + } + + // list + { + std::list l = {Point(1, 2), Point(3, 4)}; + py::object obj = py::cast(l); + EXPECT_EVAL_EQ("[Point(1, 2), Point(3, 4)]", obj); + + std::list l2 = obj.cast>(); + EXPECT_EQ(l, l2); + } + + // deque + { + std::deque d = {Point(1, 2), Point(3, 4)}; + py::object obj = py::cast(d); + EXPECT_EVAL_EQ("[Point(1, 2), Point(3, 4)]", obj); + + std::deque d2 = obj.cast>(); + EXPECT_EQ(d, d2); + } +} + +TEST_F(PYBIND11_TEST, dict_like) { + py::class_(py::module_::__main__(), "Point") + .def(py::init()) + .def_readwrite("x", &Point::x) + .def_readwrite("y", &Point::y) + .def("__eq__", &Point::operator==); + + // map + { + std::map m = { + {"a", Point(1, 2)}, + {"b", Point(3, 4)} + }; + + py::object obj = py::cast(m); + EXPECT_EVAL_EQ("{'a': Point(1, 2), 'b': Point(3, 4)}", obj); + + std::map m2 = obj.cast>(); + EXPECT_EQ(m, m2); + } + + // unordered_map + { + std::unordered_map m = { + {"a", Point(1, 2)}, + {"b", Point(3, 4)} + }; + + py::object obj = py::cast(m); + EXPECT_EVAL_EQ("{'a': Point(1, 2), 'b': Point(3, 4)}", obj); + + std::unordered_map m2 = obj.cast>(); + EXPECT_EQ(m, m2); + } +} diff --git a/include/pybind11/tests/test.h b/include/pybind11/tests/test.h new file mode 100644 index 00000000..765417d8 --- /dev/null +++ b/include/pybind11/tests/test.h @@ -0,0 +1,16 @@ +#include +#include + +namespace py = pkbind; + +class PYBIND11_TEST : public ::testing::Test { +protected: + void SetUp() override { py::initialize(); } + + void TearDown() override { py::finalize(true); } +}; + +#define EXPECT_CAST_EQ(expr, expected) EXPECT_EQ(py::cast(expr), py::cast(expected)) +#define EXPECT_EVAL_EQ(expr, expected) EXPECT_EQ(py::eval(expr).cast(), expected) +#define EXPECT_EXEC_EQ(expr, expected) EXPECT_EQ(py::eval(expr), py::eval(expected)) + diff --git a/include/pybind11/tests/types.cpp b/include/pybind11/tests/types.cpp new file mode 100644 index 00000000..4b3818e3 --- /dev/null +++ b/include/pybind11/tests/types.cpp @@ -0,0 +1,169 @@ +#include "test.h" + +TEST_F(PYBIND11_TEST, int) { + py::object obj = py::int_(123); + py::object obj2 = py::eval("123"); + + EXPECT_EQ(obj, obj2); + + EXPECT_EQ(obj.cast(), 123); + EXPECT_EQ(obj.cast(), 123); + EXPECT_EQ(obj.cast(), 123); +} + +TEST_F(PYBIND11_TEST, float) { + py::object obj = py::float_(123.0); + py::object obj2 = py::eval("123.0"); + + EXPECT_EQ(obj, obj2); + + EXPECT_EQ(obj.cast(), 123.0); + EXPECT_EQ(obj.cast(), 123.0); +} + +TEST_F(PYBIND11_TEST, str) { + py::object obj = py::str("123"); + py::object obj2 = py::eval("'123'"); + + EXPECT_EQ(obj, obj2); + + EXPECT_STREQ(obj.cast(), "123"); + EXPECT_EQ(obj.cast(), "123"); + EXPECT_EQ(obj.cast(), "123"); + + auto s = py::str("Hello, {}"); + EXPECT_EQ(s.format("world").cast(), "Hello, world"); +} + +TEST_F(PYBIND11_TEST, tuple) { + py::tuple tuple = py::tuple{ + py::int_(1), + py::str("123"), + py::int_(3), + }; + EXPECT_EQ(tuple, py::eval("(1, '123', 3)")); + EXPECT_EQ(tuple.size(), 3); + EXPECT_FALSE(tuple.empty()); + + tuple[0] = py::int_(3); + tuple[2] = py::int_(1); + EXPECT_EQ(tuple, py::eval("(3, '123', 1)")); + + // iterators. + int index = 0; + for(auto item: tuple) { + if(index == 0) { + EXPECT_EQ(item, py::int_(3)); + } else if(index == 1) { + EXPECT_EQ(item, py::str("123")); + } else if(index == 2) { + EXPECT_EQ(item, py::int_(1)); + } + index++; + } +} + +TEST_F(PYBIND11_TEST, list) { + // constructors + py::list list = py::list(); + EXPECT_EQ(list, py::eval("[]")); + EXPECT_EQ(list.size(), 0); + EXPECT_TRUE(list.empty()); + + list = py::list{ + py::int_(1), + py::int_(2), + py::int_(3), + }; + EXPECT_EQ(list, py::eval("[1, 2, 3]")); + EXPECT_EQ(list.size(), 3); + EXPECT_FALSE(list.empty()); + + // accessor + list[0] = py::int_(3); + list[2] = py::int_(1); + EXPECT_EQ(list, py::eval("[3, 2, 1]")); + + // iterators + int index = 0; + for(auto item: list) { + if(index == 0) { + EXPECT_EQ(item, py::int_(3)); + } else if(index == 1) { + EXPECT_EQ(item, py::int_(2)); + } else if(index == 2) { + EXPECT_EQ(item, py::int_(1)); + } + index++; + } + + // others + list.append(py::int_(4)); + EXPECT_EQ(list, py::eval("[3, 2, 1, 4]")); + + list.insert(0, py::int_(7)); + EXPECT_EQ(list, py::eval("[7, 3, 2, 1, 4]")); +} + +TEST_F(PYBIND11_TEST, dict) { + // constructors + py::dict dict = py::dict(); + EXPECT_EQ(dict, py::eval("{}")); + EXPECT_EQ(dict.size(), 0); + EXPECT_TRUE(dict.empty()); + + // accessor + dict["a"] = py::int_(1); + dict["b"] = py::int_(2); + dict["c"] = py::int_(3); + EXPECT_EQ(dict, py::eval("{'a': 1, 'b': 2, 'c': 3}")); + EXPECT_EQ(dict, + py::dict({ + {"a", py::int_(1)}, + {"b", py::int_(2)}, + {"c", py::int_(3)}, + })); + + // iterators + int index = 0; + for(auto item: dict) { + if(index == 0) { + EXPECT_EQ(item.first.cast(), "a"); + EXPECT_EQ(item.second, py::int_(1)); + } else if(index == 1) { + EXPECT_EQ(item.first.cast(), "b"); + EXPECT_EQ(item.second, py::int_(2)); + } else if(index == 2) { + EXPECT_EQ(item.first.cast(), "c"); + EXPECT_EQ(item.second, py::int_(3)); + } + index++; + } +} + +TEST_F(PYBIND11_TEST, capsule) { + static int times = 0; + + struct NotTrivial { + ~NotTrivial() { times++; } + }; + + py::handle x = py::capsule(new NotTrivial(), [](void* ptr) { + delete static_cast(ptr); + }); + + auto m = py::module_::__main__(); + + m.def("foo", [](int x) { + return py::capsule(new int(x)); + }); + + m.def("bar", [](py::capsule x, int y) { + EXPECT_EQ(x.cast(), y); + delete (int*)x.data(); + }); + + py::exec("bar(foo(123), 123)"); + py::finalize(true); + EXPECT_EQ(times, 1); +}