diff --git a/.github/workflows/pybind11.yml b/.github/workflows/pybind11.yml new file mode 100644 index 00000000..eb413216 --- /dev/null +++ b/.github/workflows/pybind11.yml @@ -0,0 +1,93 @@ +name: CMake Build and Test + +on: + push: + branches: + - pkpy-v2 + pull_request: + branches: + - pkpy-v2 + +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: Install dependencies + run: | + git submodule update --init --recursive + git clone https://github.com/google/googletest.git + + - name: Build + run: | + cmake -B build -DENABLE_TEST=ON + cmake --build build --config Release --parallel + + - name: Test + run: | + build/pybind11_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: Install dependencies + run: | + git submodule update --init --recursive + git clone https://github.com/google/googletest.git + + - name: Build + run: | + cmake -B build -DENABLE_TEST=ON + cmake --build build --config Release --parallel + + - name: Test + run: | + build\Release\pybind11_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: Install dependencies + run: | + git submodule update --init --recursive + git clone https://github.com/google/googletest.git + + - name: Build + run: | + cmake -B build -DENABLE_TEST=ON + cmake --build build --config Release --parallel + + - name: Test + run: | + build/pybind11_test diff --git a/include/pybind11/internal/accessor.h b/include/pybind11/internal/accessor.h index 2aba6fa3..549ebf0f 100644 --- a/include/pybind11/internal/accessor.h +++ b/include/pybind11/internal/accessor.h @@ -18,7 +18,9 @@ class accessor : public interface> { public: auto ptr() const { - if(!m_value) { m_value = policy::get(m_obj, m_key); } + if(m_value.empty()) { + m_value = borrow(policy::get(m_obj, m_key)); + } return m_value.ptr(); } @@ -38,11 +40,12 @@ public: private: handle m_obj; - mutable handle m_value; + mutable object m_value; key_type m_key; }; namespace policy { + struct attr { using key_type = name; @@ -136,6 +139,11 @@ 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}; }; @@ -148,9 +156,9 @@ inline dict_accessor dict::operator[] (handle key) const { return {m_ptr inline dict::iterator::iterator(handle h) : items(h.attr("items")()), iter(items.begin()) {} -inline std::pair dict::iterator::operator* () const { - tuple pair = tuple(*iter, object::ref_t{}); - return {pair[0], pair[1]}; +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 c2d46780..34b8c462 100644 --- a/include/pybind11/internal/builtins.h +++ b/include/pybind11/internal/builtins.h @@ -72,6 +72,8 @@ inline py_i64 hash(handle obj) { 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; @@ -173,13 +175,12 @@ T cast(handle obj, bool convert = true) { return std::move(caster.value()); } } else { - std::string msg = "cast python instance to c++ failed, "; - msg += "obj type is: {"; + 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 std::runtime_error(msg); + throw cast_error(msg); } } diff --git a/include/pybind11/internal/cast.h b/include/pybind11/internal/cast.h index 075aa465..2e847d95 100644 --- a/include/pybind11/internal/cast.h +++ b/include/pybind11/internal/cast.h @@ -31,9 +31,9 @@ struct type_caster { 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 = none()) { + static object cast(U&& value, return_value_policy policy, handle parent) { // TODO: support implicit cast - return instance::create(std::forward(value), type::of(), policy, parent.ptr()); + return instance::create(type::of(), std::forward(value), parent, policy); } bool load(handle src, bool convert) { diff --git a/include/pybind11/internal/class.h b/include/pybind11/internal/class.h index b491ef21..50f30b6b 100644 --- a/include/pybind11/internal/class.h +++ b/include/pybind11/internal/class.h @@ -37,16 +37,10 @@ public: [[maybe_unused]] auto args = py_offset(stack, 1); [[maybe_unused]] auto kwargs = py_offset(stack, 2); - // if constexpr(types_count_v != 0) { - // // FIXME: - // // bind dynamic attributes - // } - - auto type = pkbind::type(cls, object::ref_t{}); auto info = &type_info::of(); - void* data = py_newobject(py_retval(), type.index(), 1, sizeof(instance)); + int slot = (type_list::template count ? -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, diff --git a/include/pybind11/internal/error.h b/include/pybind11/internal/error.h index 64560820..763d29f0 100644 --- a/include/pybind11/internal/error.h +++ b/include/pybind11/internal/error.h @@ -16,6 +16,10 @@ public: ~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; @@ -30,19 +34,54 @@ inline auto raise_call(Args&&... args) { using type = decltype(result); if constexpr(std::is_same_v) { - if(result != false) { return result; } + if(result != false) { + return result; + } } else if constexpr(std::is_same_v) { - if(result != -1) { return result; } + 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_matchexc(tp_Exception); py_clearexc(pc); - throw python_error(what, object(retv, object::realloc_t{})); + 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) \ diff --git a/include/pybind11/internal/function.h b/include/pybind11/internal/function.h index fb197ff4..8be22c8c 100644 --- a/include/pybind11/internal/function.h +++ b/include/pybind11/internal/function.h @@ -130,10 +130,14 @@ class function_record { friend struct template_parser; using destructor_t = void (*)(function_record*); - using wrapper_t = bool (*)(function_record&, std::vector&, dict&, bool convert, handle parent); + using wrapper_t = bool (*)(function_record&, + std::vector&, + std::vector>&, + bool convert, + handle parent); struct arguments_t { - std::vector names; + std::vector names; std::vector defaults; }; @@ -174,10 +178,18 @@ public: function_record& operator= (function_record&&) = delete; ~function_record() { - if(destructor) { destructor(this); } - if(arguments) { delete arguments; } - if(next) { delete next; } - if(signature) { delete[] signature; } + if(destructor) { + destructor(this); + } + if(arguments) { + delete arguments; + } + if(next) { + delete next; + } + if(signature) { + delete[] signature; + } } void append(function_record* record) { @@ -202,32 +214,44 @@ public: return *static_cast(py_touserdata(slot)); } - bool operator() (int argc, handle stack) { + void operator() (int argc, handle stack) { function_record* p = this; bool has_self = argc == 3; - std::vector args2; - if(has_self) { args2.push_back(py_offset(stack.ptr(), 0)); } - - tuple args(py_offset(stack.ptr(), 0 + has_self), object::ref_t{}); - for(int i = 0; i < args.size(); ++i) { - args2.push_back(args[i]); + std::vector args; + handle self = py_offset(stack.ptr(), 0); + if(has_self) { + args.push_back(self); } - dict kwargs(py_offset(stack.ptr(), 1 + has_self), object::ref_t{}); + 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, args2, kwargs, false, {}); - if(result) { return result; } + 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, args2, kwargs, true, {}); - if(result) { return result; } + auto result = p->wrapper(*p, args, kwargs, true, self); + if(result) { + return; + } p = p->next; } @@ -344,7 +368,9 @@ struct template_parser, std::tuple, std 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); } + if constexpr(policy_pos != -1) { + record.policy = std::get(extras_tuple); + } // TODO: set others @@ -382,17 +408,17 @@ struct template_parser, std::tuple, std sig += record.arguments->names[index].c_str(); sig += ": "; sig += type_info::of().name; - if(!record.arguments->defaults[index].is_empty()) { + if(!record.arguments->defaults[index].empty()) { sig += " = "; - // FIXME: - // sig += record.arguments->defaults[index].repr(); + sig += record.arguments->defaults[index].attr("__repr__")().cast(); } } else { sig += "_: "; sig += type_info::of().name; } - - if(index + 1 < argc) { sig += ", "; } + if(index + 1 < argc) { + sig += ", "; + } index++; }; (append(type_identity{}), ...); @@ -406,7 +432,11 @@ struct template_parser, std::tuple, std /// 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, dict& kwargs, bool convert, handle parent) { + 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] = {}; @@ -421,48 +451,58 @@ struct template_parser, std::tuple, std // load arguments from call arguments if(args.size() > normal_argc) { - if constexpr(args_pos == -1) { return false; } + 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 = args.size() > normal_argc ? args.size() - normal_argc : 0; - reg<0> = tuple(n); - stack[args_pos] = reg<0>; - auto pack = tuple(reg<0>, object::ref_t{}); + auto pack = tuple(n); for(std::size_t 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; - bool init_dict = false; - dict pack2; - - kwargs.apply([&](handle key, handle value) { - if constexpr(named_argc != 0) { - for(int i = index; i < named_argc; ++i) { - if(key.cast() == record.arguments->names[i]) { - stack[i] = value; - index = i + 1; - return; - } + 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; } + } - if constexpr(kwargs_pos != -1) { pack2[key] = value; } - }); - - if constexpr(kwargs_pos != -1) { stack[kwargs_pos] = pack2; } + 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; } + if(!stack[i]) { + return false; + } } // ok, all the arguments are valid, call the function @@ -493,7 +533,9 @@ class cpp_function : public function { 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; } + if(slot) { + return py_typeof(slot) == m_type; + } } return false; } @@ -505,8 +547,39 @@ class cpp_function : public function { sig += is_method ? "(self, *args, **kwargs)" : "(*args, **kwargs)"; auto call = [](int argc, py_Ref stack) { handle func = py_inspect_currentfunction(); - auto& record = *static_cast(py_touserdata(py_getslot(func.ptr(), 0))); - return record(argc, stack); + 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& e) { + 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); diff --git a/include/pybind11/internal/instance.h b/include/pybind11/internal/instance.h index 7dc4563c..80c50ba6 100644 --- a/include/pybind11/internal/instance.h +++ b/include/pybind11/internal/instance.h @@ -29,8 +29,7 @@ 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. enum Flag { None = 0, @@ -41,13 +40,11 @@ public: Flag flag; void* data; const type_info* info; + object parent; public: template - static object create(Value&& value_, - type type, - return_value_policy policy = return_value_policy::automatic_reference, - handle parent_ = {}) { + static object create(type type, Value&& value_, handle parent_, return_value_policy policy) { using underlying_type = remove_cvref_t; auto& value = [&]() -> auto& { @@ -66,7 +63,7 @@ public: void* data = nullptr; Flag flag = Flag::None; - handle parent = parent_; + object parent; if(policy == return_value_policy::take_ownership) { data = &value; @@ -95,16 +92,19 @@ public: } else if(policy == return_value_policy::reference_internal) { data = &value; flag = Flag::Ref; + parent = borrow(parent_); } object result(object::alloc_t{}); void* temp = py_newobject(result.ptr(), type.index(), 1, sizeof(instance)); - new (temp) instance{flag, data, info}; + new (temp) instance{flag, data, info, std::move(parent)}; return result; } ~instance() { - if(flag & Flag::Own) { info->destructor(data); } + if(flag & Flag::Own) { + info->destructor(data); + } } template diff --git a/include/pybind11/internal/object.h b/include/pybind11/internal/object.h index 43613df5..86da0103 100644 --- a/include/pybind11/internal/object.h +++ b/include/pybind11/internal/object.h @@ -198,7 +198,9 @@ public: object() = default; object(const object& other) : handle(other), m_index(other.m_index) { - if(other.in_pool()) { object_pool::inc_ref(other); } + if(other.in_pool()) { + object_pool::inc_ref(other); + } } object(object&& other) : handle(other), m_index(other.m_index) { @@ -208,8 +210,12 @@ public: object& operator= (const object& other) { if(this != &other) { - if(in_pool()) { object_pool::dec_ref(*this); } - if(other.in_pool()) { object_pool::inc_ref(other); } + if(in_pool()) { + object_pool::dec_ref(*this); + } + if(other.in_pool()) { + object_pool::inc_ref(other); + } m_ptr = other.m_ptr; m_index = other.m_index; } @@ -218,7 +224,9 @@ public: object& operator= (object&& other) { if(this != &other) { - if(in_pool()) { object_pool::dec_ref(*this); } + if(in_pool()) { + object_pool::dec_ref(*this); + } m_ptr = other.m_ptr; m_index = other.m_index; other.m_ptr = nullptr; @@ -228,12 +236,14 @@ public: } ~object() { - if(in_pool()) { object_pool::dec_ref(*this); } + if(in_pool()) { + object_pool::dec_ref(*this); + } } bool is_singleton() const { return m_ptr && m_index == -1; } - bool is_empty() const { return m_ptr == nullptr && 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; } @@ -271,6 +281,16 @@ protected: int m_index = -1; }; +template +T steal(handle h) { + return T(h, object::ref_t{}); +} + +template +T borrow(handle h) { + return T(h, object::realloc_t{}); +} + template void reg_t::operator= (handle h) & { py_setreg(N, h.ptr()); diff --git a/include/pybind11/internal/types.h b/include/pybind11/internal/types.h index 8992aca6..ad6cbec2 100644 --- a/include/pybind11/internal/types.h +++ b/include/pybind11/internal/types.h @@ -13,8 +13,8 @@ private: public: \ using parent::parent; \ using parent::operator=; \ - child(const object& o) : parent(o) {} \ - child(object&& o) : parent(std::move(o)) {} + 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))) {} class type : public object { protected: @@ -106,7 +106,7 @@ public: return *this; } - handle operator* () const { return m_value; } + object operator* () const { return m_value; } friend bool operator== (const iterator& lhs, const iterator& rhs) { return lhs.m_value.ptr() == rhs.m_value.ptr(); } @@ -137,6 +137,9 @@ class str : public object { str(const char* data) : str(data, strlen(data)) {} str(std::string_view s) : str(s.data(), static_cast(s.size())) {} + + template + object format(Args&&... args); }; class tuple : public object { @@ -172,7 +175,7 @@ class tuple : public object { return *this; } - handle operator* () const { return py_tuple_getitem(ptr, index); } + object operator* () const { return borrow(py_tuple_getitem(ptr, index)); } private: py_Ref ptr; @@ -231,7 +234,7 @@ class list : public object { return *this; } - handle operator* () const { return py_list_getitem(ptr, index); } + object operator* () const { return borrow(py_list_getitem(ptr, index)); } private: py_Ref ptr; @@ -295,7 +298,7 @@ class dict : public object { return *this; } - std::pair operator* () const; + std::pair operator* () const; private: object items; @@ -335,7 +338,9 @@ class capsule : public object { 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); } + if(impl->data && impl->destructor) { + impl->destructor(impl->data); + } }); } 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/class.cpp b/include/pybind11/tests/class.cpp index 106f29b0..e5a13c0a 100644 --- a/include/pybind11/tests/class.cpp +++ b/include/pybind11/tests/class.cpp @@ -158,7 +158,7 @@ assert p.z == 30 EXPECT_EQ(constructor_calls, 4); } -// FIXME: + TEST_F(PYBIND11_TEST, dynamic_attr) { py::module_ m = py::module_::import("__main__"); @@ -178,8 +178,8 @@ TEST_F(PYBIND11_TEST, dynamic_attr) { 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); + p.attr("z") = py::int_(3); + EXPECT_EQ(p.attr("z").cast(), 3); } TEST_F(PYBIND11_TEST, enum) { 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 index 942e02b0..7e2e0146 100644 --- a/include/pybind11/tests/function.cpp +++ b/include/pybind11/tests/function.cpp @@ -2,6 +2,31 @@ 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__(); diff --git a/include/pybind11/tests/object.cpp b/include/pybind11/tests/object.cpp index 15b58c3a..083c9c41 100644 --- a/include/pybind11/tests/object.cpp +++ b/include/pybind11/tests/object.cpp @@ -63,11 +63,11 @@ TEST_F(PYBIND11_TEST, object) { py::exec("p = Point(3, 4)"); py::object p = py::eval("p"); - // test is + // is EXPECT_FALSE(p.is_none()); EXPECT_TRUE(p.is(p)); - // test attrs + // attrs EXPECT_EQ(p.attr("x").cast(), 3); EXPECT_EQ(p.attr("y").cast(), 4); @@ -78,13 +78,21 @@ TEST_F(PYBIND11_TEST, object) { EXPECT_EQ(p.attr("y").cast(), 6); EXPECT_EXEC_EQ("p", "Point(5, 6)"); - // test operators + // 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/types.cpp b/include/pybind11/tests/types.cpp index 4f3056d5..4b3818e3 100644 --- a/include/pybind11/tests/types.cpp +++ b/include/pybind11/tests/types.cpp @@ -2,7 +2,7 @@ TEST_F(PYBIND11_TEST, int) { py::object obj = py::int_(123); - py::handle obj2 = py::eval("123"); + py::object obj2 = py::eval("123"); EXPECT_EQ(obj, obj2); @@ -13,7 +13,7 @@ TEST_F(PYBIND11_TEST, int) { TEST_F(PYBIND11_TEST, float) { py::object obj = py::float_(123.0); - py::handle obj2 = py::eval("123.0"); + py::object obj2 = py::eval("123.0"); EXPECT_EQ(obj, obj2); @@ -23,17 +23,24 @@ TEST_F(PYBIND11_TEST, float) { TEST_F(PYBIND11_TEST, str) { py::object obj = py::str("123"); - py::handle obj2 = py::eval("'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)}; + 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()); @@ -41,26 +48,56 @@ TEST_F(PYBIND11_TEST, tuple) { 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) { - // test constructors + // 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)}; + 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()); - // test accessor + // accessor list[0] = py::int_(3); list[2] = py::int_(1); EXPECT_EQ(list, py::eval("[3, 2, 1]")); - // test other apis + // 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]")); @@ -69,22 +106,39 @@ TEST_F(PYBIND11_TEST, list) { } TEST_F(PYBIND11_TEST, dict) { - // test constructors + // constructors py::dict dict = py::dict(); EXPECT_EQ(dict, py::eval("{}")); EXPECT_EQ(dict.size(), 0); EXPECT_TRUE(dict.empty()); - // test accessor + // 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)}, + })); - // FIXME: - // test other apis - // dict.clear(); - // EXPECT_EQ(dict, py::eval("{}")); + // 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) {