From 6d19036cb26cf0675d8b35285c83b1e43bd75ad8 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Thu, 16 Nov 2017 22:24:36 +0100 Subject: [PATCH 1/8] support docstrings in enum::value() (#1160) --- docs/changelog.rst | 3 +++ include/pybind11/pybind11.h | 27 +++++++++++++++++++++------ tests/test_enum.cpp | 6 +++--- tests/test_enum.py | 16 ++++++++++++++++ 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index b0d958d4910..ec8dc720393 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -18,6 +18,9 @@ v2.3.0 (Not yet released) * Added support for write only properties. `#1144 `_. +* The ``value()`` method of ``py::enum_`` now accepts an optional docstring + that will be shown in the documentation of the associated enumeration. + v2.2.1 (September 14, 2017) ----------------------------------------------------- diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index b489bb2493d..5bd2cccc4dd 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1375,15 +1375,30 @@ template class enum_ : public class_ { auto m_entries_ptr = m_entries.inc_ref().ptr(); def("__repr__", [name, m_entries_ptr](Type value) -> pybind11::str { for (const auto &kv : reinterpret_borrow(m_entries_ptr)) { - if (pybind11::cast(kv.second) == value) + if (pybind11::cast(kv.second[int_(0)]) == value) return pybind11::str("{}.{}").format(name, kv.first); } return pybind11::str("{}.???").format(name); }); - def_property_readonly_static("__members__", [m_entries_ptr](object /* self */) { + def_property_readonly_static("__doc__", [m_entries_ptr](handle self) { + std::string docstring; + const char *tp_doc = ((PyTypeObject *) self.ptr())->tp_doc; + if (tp_doc) + docstring += std::string(tp_doc) + "\n\n"; + docstring += "Members:"; + for (const auto &kv : reinterpret_borrow(m_entries_ptr)) { + auto key = std::string(pybind11::str(kv.first)); + auto comment = kv.second[int_(1)]; + docstring += "\n\n " + key; + if (!comment.is_none()) + docstring += " : " + (std::string) pybind11::str(comment); + } + return docstring; + }); + def_property_readonly_static("__members__", [m_entries_ptr](handle /* self */) { dict m; for (const auto &kv : reinterpret_borrow(m_entries_ptr)) - m[kv.first] = kv.second; + m[kv.first] = kv.second[int_(0)]; return m; }, return_value_policy::copy); def(init([](Scalar i) { return static_cast(i); })); @@ -1431,15 +1446,15 @@ template class enum_ : public class_ { /// Export enumeration entries into the parent scope enum_& export_values() { for (const auto &kv : m_entries) - m_parent.attr(kv.first) = kv.second; + m_parent.attr(kv.first) = kv.second[int_(0)]; return *this; } /// Add an enumeration entry - enum_& value(char const* name, Type value) { + enum_& value(char const* name, Type value, const char *doc = nullptr) { auto v = pybind11::cast(value, return_value_policy::copy); this->attr(name) = v; - m_entries[pybind11::str(name)] = v; + m_entries[pybind11::str(name)] = std::make_pair(v, doc); return *this; } diff --git a/tests/test_enum.cpp b/tests/test_enum.cpp index 49f31ba1f0d..4cd14a96a68 100644 --- a/tests/test_enum.cpp +++ b/tests/test_enum.cpp @@ -15,9 +15,9 @@ TEST_SUBMODULE(enums, m) { EOne = 1, ETwo }; - py::enum_(m, "UnscopedEnum", py::arithmetic()) - .value("EOne", EOne) - .value("ETwo", ETwo) + py::enum_(m, "UnscopedEnum", py::arithmetic(), "An unscoped enumeration") + .value("EOne", EOne, "Docstring for EOne") + .value("ETwo", ETwo, "Docstring for ETwo") .export_values(); // test_scoped_enum diff --git a/tests/test_enum.py b/tests/test_enum.py index d8eff5278c5..d3f5b4d1bfe 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -18,6 +18,22 @@ def test_unscoped_enum(): assert m.UnscopedEnum.__members__ == \ {"EOne": m.UnscopedEnum.EOne, "ETwo": m.UnscopedEnum.ETwo} + assert m.UnscopedEnum.__doc__ == \ + '''An unscoped enumeration + +Members: + + EOne : Docstring for EOne + + ETwo : Docstring for ETwo''' or m.UnscopedEnum.__doc__ == \ + '''An unscoped enumeration + +Members: + + ETwo : Docstring for ETwo + + EOne : Docstring for EOne''' + # no TypeError exception for unscoped enum ==/!= int comparisons y = m.UnscopedEnum.ETwo assert y == 2 From e7d304fbc6f0aa22b6105e2b1a4807c7a294eafa Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Fri, 17 Nov 2017 18:44:20 +0100 Subject: [PATCH 2/8] added citation reference (fixes #767) (#1189) --- docs/faq.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/faq.rst b/docs/faq.rst index d44a272bb4b..bfe8303661e 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -241,3 +241,19 @@ Common gotchas to watch out for involve not ``free()``-ing memory region that that were ``malloc()``-ed in another shared library, using data structures with incompatible ABIs, and so on. pybind11 is very careful not to make these types of mistakes. + +How to cite this project? +========================= + +We suggest the following BibTeX template to cite pybind11 in scientific +discourse: + +.. code-block:: bash + + @misc{pybind11, + author = {Wenzel Jakob and Jason Rhinelander and Dean Moldovan}, + year = {2017}, + note = {https://github.com/pybind/pybind11}, + title = {pybind11 -- Seamless operability between C++11 and Python} + } + From 15e0e445499be526d8e2d6bc0b1f04b262c2e86d Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Fri, 17 Nov 2017 18:44:50 +0100 Subject: [PATCH 3/8] Moved section on licensing of contributions (fixes #1109) (#1188) --- CONTRIBUTING.md | 12 +++++++++++- LICENSE | 11 ++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2beaf8d4d0c..375735f6ce2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,8 +30,18 @@ adhere to the following rules to make the process as smooth as possible: * This project has a strong focus on providing general solutions using a minimal amount of code, thus small pull requests are greatly preferred. -### License +### Licensing of contributions pybind11 is provided under a BSD-style license that can be found in the ``LICENSE`` file. By using, distributing, or contributing to this project, you agree to the terms and conditions of this license. + +You are under no obligation whatsoever to provide any bug fixes, patches, or +upgrades to the features, functionality or performance of the source code +("Enhancements") to anyone; however, if you choose to make your Enhancements +available either publicly, or directly to the author of this software, without +imposing a separate written license agreement for such Enhancements, then you +hereby grant the following license: a non-exclusive, royalty-free perpetual +license to install, use, modify, prepare derivative works, incorporate into +other computer software, distribute, and sublicense such enhancements or +derivative works thereof, in binary and source code form. diff --git a/LICENSE b/LICENSE index ccf4e978788..6f15578cc40 100644 --- a/LICENSE +++ b/LICENSE @@ -25,12 +25,5 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -You are under no obligation whatsoever to provide any bug fixes, patches, or -upgrades to the features, functionality or performance of the source code -("Enhancements") to anyone; however, if you choose to make your Enhancements -available either publicly, or directly to the author of this software, without -imposing a separate written license agreement for such Enhancements, then you -hereby grant the following license: a non-exclusive, royalty-free perpetual -license to install, use, modify, prepare derivative works, incorporate into -other computer software, distribute, and sublicense such enhancements or -derivative works thereof, in binary and source code form. +Please also refer to the file CONTRIBUTING.md, which clarifies licensing of +external contributions to this project including patches, pull requests, etc. From ba33b2fc798418c8c9dfe801c5b9023d3703f417 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Mon, 20 Nov 2017 14:19:53 +0100 Subject: [PATCH 4/8] Add -Wdeprecated to test suite and fix associated warnings (#1191) This commit turns on `-Wdeprecated` in the test suite and fixes several associated deprecation warnings that show up as a result: - in C++17 `static constexpr` members are implicitly inline; our redeclaration (needed for C++11/14) is deprecated in C++17. - various test suite classes have destructors and rely on implicit copy constructors, but implicit copy constructor definitions when a user-declared destructor is present was deprecated in C++11. - Eigen also has various implicit copy constructors, so just disable `-Wdeprecated` in `eigen.h`. --- include/pybind11/complex.h | 4 ++++ include/pybind11/detail/common.h | 4 ++++ include/pybind11/eigen.h | 5 +++++ include/pybind11/pytypes.h | 3 +++ tests/CMakeLists.txt | 2 +- tests/test_call_policies.cpp | 2 ++ tests/test_class.cpp | 10 +++++++++- tests/test_constants_and_functions.cpp | 14 ++++++++++++++ tests/test_factory_constructors.cpp | 1 + tests/test_multiple_inheritance.cpp | 6 +++--- tests/test_smart_ptr.cpp | 4 ++++ 11 files changed, 50 insertions(+), 5 deletions(-) diff --git a/include/pybind11/complex.h b/include/pybind11/complex.h index 5dac27cc4ef..3f896385717 100644 --- a/include/pybind11/complex.h +++ b/include/pybind11/complex.h @@ -25,9 +25,13 @@ template struct format_descriptor, detail::enable_i static std::string format() { return std::string(value); } }; +#ifndef PYBIND11_CPP17 + template constexpr const char format_descriptor< std::complex, detail::enable_if_t::value>>::value[3]; +#endif + NAMESPACE_BEGIN(detail) template struct is_fmt_numeric, detail::enable_if_t::value>> { diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 1da36bf321c..7692063abe3 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -704,9 +704,13 @@ template struct format_descriptor constexpr const char format_descriptor< T, detail::enable_if_t::value>>::value[2]; +#endif + /// RAII wrapper that temporarily clears any Python error state struct error_scope { PyObject *type, *value, *trace; diff --git a/include/pybind11/eigen.h b/include/pybind11/eigen.h index 2a234cef7f5..e8b4249ae25 100644 --- a/include/pybind11/eigen.h +++ b/include/pybind11/eigen.h @@ -17,6 +17,11 @@ # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wconversion" # pragma GCC diagnostic ignored "-Wdeprecated-declarations" +# ifdef __clang__ +// Eigen generates a bunch of implicit-copy-constructor-is-deprecated warnings with -Wdeprecated +// under Clang, so disable that warning here: +# pragma GCC diagnostic ignored "-Wdeprecated" +# endif # if __GNUC__ >= 7 # pragma GCC diagnostic ignored "-Wint-in-bool-context" # endif diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index d7fa17775c3..bcee8b5b81c 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -295,6 +295,9 @@ class error_already_set : public std::runtime_error { PyErr_Fetch(&type.ptr(), &value.ptr(), &trace.ptr()); } + error_already_set(const error_already_set &) = default; + error_already_set(error_already_set &&) = default; + inline ~error_already_set(); /// Give the currently-held error back to Python, if any. If there is currently a Python error diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5953e0baacc..8f2f300ef74 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -124,7 +124,7 @@ function(pybind11_enable_warnings target_name) if(MSVC) target_compile_options(${target_name} PRIVATE /W4) else() - target_compile_options(${target_name} PRIVATE -Wall -Wextra -Wconversion -Wcast-qual) + target_compile_options(${target_name} PRIVATE -Wall -Wextra -Wconversion -Wcast-qual -Wdeprecated) endif() if(PYBIND11_WERROR) diff --git a/tests/test_call_policies.cpp b/tests/test_call_policies.cpp index 8642188f9ea..fd24557834b 100644 --- a/tests/test_call_policies.cpp +++ b/tests/test_call_policies.cpp @@ -36,6 +36,8 @@ TEST_SUBMODULE(call_policies, m) { class Child { public: Child() { py::print("Allocating child."); } + Child(const Child &) = default; + Child(Child &&) = default; ~Child() { py::print("Releasing child."); } }; py::class_(m, "Child") diff --git a/tests/test_class.cpp b/tests/test_class.cpp index 927adf3f953..57db3ab701a 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -14,6 +14,9 @@ TEST_SUBMODULE(class_, m) { // test_instance struct NoConstructor { + NoConstructor() = default; + NoConstructor(const NoConstructor &) = default; + NoConstructor(NoConstructor &&) = default; static NoConstructor *new_instance() { auto *ptr = new NoConstructor(); print_created(ptr, "via new_instance"); @@ -82,7 +85,12 @@ TEST_SUBMODULE(class_, m) { m.def("dog_bark", [](const Dog &dog) { return dog.bark(); }); // test_automatic_upcasting - struct BaseClass { virtual ~BaseClass() {} }; + struct BaseClass { + BaseClass() = default; + BaseClass(const BaseClass &) = default; + BaseClass(BaseClass &&) = default; + virtual ~BaseClass() {} + }; struct DerivedClass1 : BaseClass { }; struct DerivedClass2 : BaseClass { }; diff --git a/tests/test_constants_and_functions.cpp b/tests/test_constants_and_functions.cpp index 8c9ef7f67bc..e8ec74b7bc7 100644 --- a/tests/test_constants_and_functions.cpp +++ b/tests/test_constants_and_functions.cpp @@ -49,7 +49,14 @@ namespace test_exc_sp { int f1(int x) noexcept { return x+1; } int f2(int x) noexcept(true) { return x+2; } int f3(int x) noexcept(false) { return x+3; } +#if defined(__GNUG__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated" +#endif int f4(int x) throw() { return x+4; } // Deprecated equivalent to noexcept(true) +#if defined(__GNUG__) +# pragma GCC diagnostic pop +#endif struct C { int m1(int x) noexcept { return x-1; } int m2(int x) const noexcept { return x-2; } @@ -57,8 +64,15 @@ struct C { int m4(int x) const noexcept(true) { return x-4; } int m5(int x) noexcept(false) { return x-5; } int m6(int x) const noexcept(false) { return x-6; } +#if defined(__GNUG__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated" +#endif int m7(int x) throw() { return x-7; } int m8(int x) const throw() { return x-8; } +#if defined(__GNUG__) +# pragma GCC diagnostic pop +#endif }; } diff --git a/tests/test_factory_constructors.cpp b/tests/test_factory_constructors.cpp index 687a5bf43f6..5cfbfdc3f8f 100644 --- a/tests/test_factory_constructors.cpp +++ b/tests/test_factory_constructors.cpp @@ -285,6 +285,7 @@ TEST_SUBMODULE(factory_constructors, m) { // test_reallocations // Class that has verbose operator_new/operator_delete calls struct NoisyAlloc { + NoisyAlloc(const NoisyAlloc &) = default; NoisyAlloc(int i) { py::print(py::str("NoisyAlloc(int {})").format(i)); } NoisyAlloc(double d) { py::print(py::str("NoisyAlloc(double {})").format(d)); } ~NoisyAlloc() { py::print("~NoisyAlloc()"); } diff --git a/tests/test_multiple_inheritance.cpp b/tests/test_multiple_inheritance.cpp index 35f9d9c4e3e..ba1674fb2d0 100644 --- a/tests/test_multiple_inheritance.cpp +++ b/tests/test_multiple_inheritance.cpp @@ -130,8 +130,8 @@ TEST_SUBMODULE(multiple_inheritance, m) { // test_mi_unaligned_base // test_mi_base_return // Issue #801: invalid casting to derived type with MI bases - struct I801B1 { int a = 1; virtual ~I801B1() = default; }; - struct I801B2 { int b = 2; virtual ~I801B2() = default; }; + struct I801B1 { int a = 1; I801B1() = default; I801B1(const I801B1 &) = default; virtual ~I801B1() = default; }; + struct I801B2 { int b = 2; I801B2() = default; I801B2(const I801B2 &) = default; virtual ~I801B2() = default; }; struct I801C : I801B1, I801B2 {}; struct I801D : I801C {}; // Indirect MI // Unregistered classes: @@ -205,7 +205,7 @@ TEST_SUBMODULE(multiple_inheritance, m) { // test_diamond_inheritance // Issue #959: segfault when constructing diamond inheritance instance // All of these have int members so that there will be various unequal pointers involved. - struct B { int b; virtual ~B() = default; }; + struct B { int b; B() = default; B(const B&) = default; virtual ~B() = default; }; struct C0 : public virtual B { int c0; }; struct C1 : public virtual B { int c1; }; struct D : public C0, public C1 { int d; }; diff --git a/tests/test_smart_ptr.cpp b/tests/test_smart_ptr.cpp index dccb1e9be51..5f298506499 100644 --- a/tests/test_smart_ptr.cpp +++ b/tests/test_smart_ptr.cpp @@ -98,6 +98,7 @@ TEST_SUBMODULE(smart_ptr, m) { // Object managed by a std::shared_ptr<> class MyObject2 { public: + MyObject2(const MyObject2 &) = default; MyObject2(int value) : value(value) { print_created(this, toString()); } std::string toString() const { return "MyObject2[" + std::to_string(value) + "]"; } virtual ~MyObject2() { print_destroyed(this); } @@ -116,6 +117,7 @@ TEST_SUBMODULE(smart_ptr, m) { // Object managed by a std::shared_ptr<>, additionally derives from std::enable_shared_from_this<> class MyObject3 : public std::enable_shared_from_this { public: + MyObject3(const MyObject3 &) = default; MyObject3(int value) : value(value) { print_created(this, toString()); } std::string toString() const { return "MyObject3[" + std::to_string(value) + "]"; } virtual ~MyObject3() { print_destroyed(this); } @@ -219,6 +221,8 @@ TEST_SUBMODULE(smart_ptr, m) { // Issue #865: shared_from_this doesn't work with virtual inheritance struct SharedFromThisVBase : std::enable_shared_from_this { + SharedFromThisVBase() = default; + SharedFromThisVBase(const SharedFromThisVBase &) = default; virtual ~SharedFromThisVBase() = default; }; struct SharedFromThisVirt : virtual SharedFromThisVBase {}; From 086d53e8c66a84d0ec723d5435918c76edd878e8 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Tue, 21 Nov 2017 17:05:43 -0400 Subject: [PATCH 5/8] Clean up eigen download code (and bump to 3.3.4) This changes the travis-ci eigen download code to extract the tar on the fly (rather than saving to a file first), and extracts into an `eigen` directory rather than using upstream's `eigen-eigen-xxxxx` directory. This also bumps the travis-ci eigen release to 3.3.4, in an attempt to see if it fixed the -Wdeprecated warnings (it did not); the build setup cleanup seems worth committing anyway. --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c9ea008a26b..d1ebdb0b2b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -193,9 +193,10 @@ install: ${PYPY:+--extra-index-url https://imaginary.ca/trusty-pypi} echo "done." - wget -q -O eigen.tar.gz https://bitbucket.org/eigen/eigen/get/3.3.3.tar.gz - tar xzf eigen.tar.gz - export CMAKE_INCLUDE_PATH="${CMAKE_INCLUDE_PATH:+$CMAKE_INCLUDE_PATH:}$PWD/eigen-eigen-67e894c6cd8f" + mkdir eigen + curl -fsSL https://bitbucket.org/eigen/eigen/get/3.3.4.tar.bz2 | \ + tar --extract -j --directory=eigen --strip-components=1 + export CMAKE_INCLUDE_PATH="${CMAKE_INCLUDE_PATH:+$CMAKE_INCLUDE_PATH:}$PWD/eigen" fi set +e script: From 3b265787f253b84598a0453fc2ff3fa25f3d5acf Mon Sep 17 00:00:00 2001 From: Bruce Merry Date: Fri, 24 Nov 2017 16:19:45 +0200 Subject: [PATCH 6/8] Document using atexit for module destructors on PyPy (#1169) None of the three currently recommended approaches works on PyPy, due to it not garbage collecting things when you want it to. Added a note with example showing how to get interpreter shutdown callbacks using the Python atexit module. --- docs/advanced/misc.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/advanced/misc.rst b/docs/advanced/misc.rst index 87481ba32fd..5faf11f0d2f 100644 --- a/docs/advanced/misc.rst +++ b/docs/advanced/misc.rst @@ -216,6 +216,21 @@ avoids this issue involves weak reference with a cleanup callback: // Create a weak reference with a cleanup callback and initially leak it (void) py::weakref(m.attr("BaseClass"), cleanup_callback).release(); +.. note:: + + PyPy (at least version 5.9) does not garbage collect objects when the + interpreter exits. An alternative approach (which also works on CPython) is to use + the :py:mod:`atexit` module [#f7]_, for example: + + .. code-block:: cpp + + auto atexit = py::module::import("atexit"); + atexit.attr("register")(py::cpp_function([]() { + // perform cleanup here -- this function is called with the GIL held + })); + + .. [#f7] https://docs.python.org/3/library/atexit.html + Generating documentation using Sphinx ===================================== From cf0d0f9d5ad79349e313694ddf3efeef7de21890 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 30 Nov 2017 11:33:24 -0600 Subject: [PATCH 7/8] Matching Python 2 int behavior on Python 2 (#1186) Pybind11's default conversion to int always produces a long on Python 2 (`int`s and `long`s were unified in Python 3). This patch fixes `int` handling to match Python 2 on Python 2; for short types (`size_t` or smaller), the number will be returned as an `int` if possible, otherwise `long`. Requires Python 2.5+. This is needed for things like `sys.exit`, which refuse to accept a `long`. --- include/pybind11/cast.h | 7 ++++--- include/pybind11/detail/common.h | 4 ++++ tests/test_builtin_casters.cpp | 5 +++++ tests/test_builtin_casters.py | 13 +++++++++++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 715ec932d95..b3ab7f23601 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -980,11 +980,12 @@ struct type_caster::value && !is_std_char_t static handle cast(T src, return_value_policy /* policy */, handle /* parent */) { if (std::is_floating_point::value) { return PyFloat_FromDouble((double) src); - } else if (sizeof(T) <= sizeof(long)) { + } else if (sizeof(T) <= sizeof(ssize_t)) { + // This returns a long automatically if needed if (std::is_signed::value) - return PyLong_FromLong((long) src); + return PYBIND11_LONG_FROM_SIGNED(src); else - return PyLong_FromUnsignedLong((unsigned long) src); + return PYBIND11_LONG_FROM_UNSIGNED(src); } else { if (std::is_signed::value) return PyLong_FromLongLong((long long) src); diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 7692063abe3..7d629c0ff87 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -158,6 +158,8 @@ #define PYBIND11_BYTES_SIZE PyBytes_Size #define PYBIND11_LONG_CHECK(o) PyLong_Check(o) #define PYBIND11_LONG_AS_LONGLONG(o) PyLong_AsLongLong(o) +#define PYBIND11_LONG_FROM_SIGNED(o) PyLong_FromSsize_t((ssize_t) o) +#define PYBIND11_LONG_FROM_UNSIGNED(o) PyLong_FromSize_t((size_t) o) #define PYBIND11_BYTES_NAME "bytes" #define PYBIND11_STRING_NAME "str" #define PYBIND11_SLICE_OBJECT PyObject @@ -180,6 +182,8 @@ #define PYBIND11_BYTES_SIZE PyString_Size #define PYBIND11_LONG_CHECK(o) (PyInt_Check(o) || PyLong_Check(o)) #define PYBIND11_LONG_AS_LONGLONG(o) (PyInt_Check(o) ? (long long) PyLong_AsLong(o) : PyLong_AsLongLong(o)) +#define PYBIND11_LONG_FROM_SIGNED(o) PyInt_FromSsize_t((ssize_t) o) // Returns long if needed. +#define PYBIND11_LONG_FROM_UNSIGNED(o) PyInt_FromSize_t((size_t) o) // Returns long if needed. #define PYBIND11_BYTES_NAME "str" #define PYBIND11_STRING_NAME "unicode" #define PYBIND11_SLICE_OBJECT PySliceObject diff --git a/tests/test_builtin_casters.cpp b/tests/test_builtin_casters.cpp index e5413c2cccd..450813403fa 100644 --- a/tests/test_builtin_casters.cpp +++ b/tests/test_builtin_casters.cpp @@ -155,4 +155,9 @@ TEST_SUBMODULE(builtin_casters, m) { // test_complex m.def("complex_cast", [](float x) { return "{}"_s.format(x); }); m.def("complex_cast", [](std::complex x) { return "({}, {})"_s.format(x.real(), x.imag()); }); + + // test int vs. long (Python 2) + m.def("int_cast", []() {return (int) 42;}); + m.def("long_cast", []() {return (long) 42;}); + m.def("longlong_cast", []() {return ULLONG_MAX;}); } diff --git a/tests/test_builtin_casters.py b/tests/test_builtin_casters.py index 2f311f152f4..01d0437b58c 100644 --- a/tests/test_builtin_casters.py +++ b/tests/test_builtin_casters.py @@ -323,3 +323,16 @@ def test_numpy_bool(): assert convert(np.bool_(False)) is False assert noconvert(np.bool_(True)) is True assert noconvert(np.bool_(False)) is False + + +def test_int_long(): + """In Python 2, a C++ int should return a Python int rather than long + if possible: longs are not always accepted where ints are used (such + as the argument to sys.exit()). A C++ long long is always a Python + long.""" + + import sys + must_be_long = type(getattr(sys, 'maxint', 1) + 1) + assert isinstance(m.int_cast(), int) + assert isinstance(m.long_cast(), int) + assert isinstance(m.longlong_cast(), must_be_long) From a303c6fc479662fd53eaa8990dbc65b7de9b7deb Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 3 Dec 2017 18:17:16 -0800 Subject: [PATCH 8/8] Remove spurious quote in error message. (#1202) --- include/pybind11/cast.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index b3ab7f23601..af6d4c716c4 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1576,7 +1576,7 @@ template type_caster &load_type(type_ca throw cast_error("Unable to cast Python instance to C++ type (compile in debug mode for details)"); #else throw cast_error("Unable to cast Python instance of type " + - (std::string) str(handle.get_type()) + " to C++ type '" + type_id() + "''"); + (std::string) str(handle.get_type()) + " to C++ type '" + type_id() + "'"); #endif } return conv;