diff --git a/.travis.yml b/.travis.yml index 966527da13..77c0fe77fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,8 @@ matrix: - PY_CMD=python3 - $PY_CMD -m pip install --user --upgrade pip wheel setuptools install: - - $PY_CMD -m pip install --user --upgrade sphinx sphinx_rtd_theme breathe flake8 pep8-naming pytest + # breathe 4.14 doesn't work with bit fields. See https://github.com/michaeljones/breathe/issues/462 + - $PY_CMD -m pip install --user --upgrade sphinx sphinx_rtd_theme breathe==4.13.1 flake8 pep8-naming pytest - curl -fsSL https://sourceforge.net/projects/doxygen/files/rel-1.8.15/doxygen-1.8.15.linux.bin.tar.gz/download | tar xz - export PATH="$PWD/doxygen-1.8.15/bin:$PATH" script: @@ -33,8 +34,7 @@ matrix: - | # Barebones build cmake -DCMAKE_BUILD_TYPE=Debug -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DPYTHON_EXECUTABLE=$(which $PY_CMD) . - make pytest -j 2 - make cpptest -j 2 + make pytest -j 2 && make cpptest -j 2 # The following are regular test configurations, including optional dependencies. # With regard to each other they differ in Python version, C++ standard and compiler. - os: linux @@ -62,7 +62,7 @@ matrix: - os: linux dist: trusty env: PYTHON=2.7 CPP=14 GCC=6 CMAKE=1 - name: Python 2.7, c++14, gcc 4.8, CMake test + name: Python 2.7, c++14, gcc 6, CMake test addons: apt: sources: @@ -107,13 +107,39 @@ matrix: - lld-7 - libc++-7-dev - libc++abi-7-dev # Why is this necessary??? + - os: linux + dist: xenial + env: PYTHON=3.8 CPP=17 GCC=7 + name: Python 3.8, c++17, gcc 7 (w/o numpy/scipy) # TODO: update build name when the numpy/scipy wheels become available + addons: + apt: + sources: + - deadsnakes + - ubuntu-toolchain-r-test + packages: + - g++-7 + - python3.8-dev + - python3.8-venv + # Currently there is no numpy/scipy wheels available for python3.8 + # TODO: remove next before_install, install and script clause when the wheels become available + before_install: + - pyenv global $(pyenv whence 2to3) # activate all python versions + - PY_CMD=python3 + - $PY_CMD -m pip install --user --upgrade pip wheel setuptools + install: + - $PY_CMD -m pip install --user --upgrade pytest + script: + - | + # Barebones build + cmake -DCMAKE_BUILD_TYPE=Debug -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DPYTHON_EXECUTABLE=$(which $PY_CMD) . + make pytest -j 2 && make cpptest -j 2 - os: osx name: Python 2.7, c++14, AppleClang 7.3, CMake test osx_image: xcode7.3 env: PYTHON=2.7 CPP=14 CLANG CMAKE=1 - os: osx name: Python 3.7, c++14, AppleClang 9, Debug build - osx_image: xcode9 + osx_image: xcode9.4 env: PYTHON=3.7 CPP=14 CLANG DEBUG=1 # Test a PyPy 2.7 build - os: linux @@ -131,7 +157,7 @@ matrix: dist: trusty services: docker env: DOCKER=i386/debian:stretch PYTHON=3.5 CPP=14 GCC=6 INSTALL=1 - name: Python 3.4, c++14, gcc 6, 32-bit + name: Python 3.5, c++14, gcc 6, 32-bit script: - | # Consolidated 32-bit Docker Build + Install @@ -194,7 +220,7 @@ before_install: PY_CMD=python$PYTHON if [ "$TRAVIS_OS_NAME" = "osx" ]; then if [ "$PY" = "3" ]; then - brew update && brew upgrade python + brew update && brew unlink python@2 && brew upgrade python else curl -fsSL https://bootstrap.pypa.io/get-pip.py | $PY_CMD - --user fi diff --git a/docs/advanced/exceptions.rst b/docs/advanced/exceptions.rst index 75ac24ae9a..75ad7f7f4a 100644 --- a/docs/advanced/exceptions.rst +++ b/docs/advanced/exceptions.rst @@ -28,6 +28,8 @@ exceptions: +--------------------------------------+--------------------------------------+ | :class:`std::range_error` | ``ValueError`` | +--------------------------------------+--------------------------------------+ +| :class:`std::overflow_error` | ``OverflowError`` | ++--------------------------------------+--------------------------------------+ | :class:`pybind11::stop_iteration` | ``StopIteration`` (used to implement | | | custom iterators) | +--------------------------------------+--------------------------------------+ diff --git a/docs/basics.rst b/docs/basics.rst index 447250ed9e..7bf4d426d3 100644 --- a/docs/basics.rst +++ b/docs/basics.rst @@ -164,7 +164,7 @@ load and execute the example: Keyword arguments ================= -With a simple modification code, it is possible to inform Python about the +With a simple code modification, it is possible to inform Python about the names of the arguments ("i" and "j" in this case). .. code-block:: cpp diff --git a/docs/changelog.rst b/docs/changelog.rst index 9576a8bc25..d65c2d8000 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,13 +6,86 @@ Changelog Starting with version 1.8.0, pybind11 releases use a `semantic versioning `_ policy. +v2.4.3 (Oct 15, 2019) +----------------------------------------------------- + +* Adapt pybind11 to a C API convention change in Python 3.8. `#1950 + `_. + +v2.4.2 (Sep 21, 2019) +----------------------------------------------------- + +* Replaced usage of a C++14 only construct. `#1929 + `_. + +* Made an ifdef future-proof for Python >= 4. `f3109d + `_. + +v2.4.1 (Sep 20, 2019) +----------------------------------------------------- + +* Fixed a problem involving implicit conversion from enumerations to integers + on Python 3.8. `#1780 `_. -v2.3.1 (Not yet released) +v2.4.0 (Sep 19, 2019) ----------------------------------------------------- +* Try harder to keep pybind11-internal data structures separate when there + are potential ABI incompatibilities. Fixes crashes that occurred when loading + multiple pybind11 extensions that were e.g. compiled by GCC (libstdc++) + and Clang (libc++). + `#1588 `_ and + `c9f5a `_. + +* Added support for ``__await__``, ``__aiter__``, and ``__anext__`` protocols. + `#1842 `_. + +* ``pybind11_add_module()``: don't strip symbols when compiling in + ``RelWithDebInfo`` mode. `#1980 + `_. + +* ``enum_``: Reproduce Python behavior when comparing against invalid values + (e.g. ``None``, strings, etc.). Add back support for ``__invert__()``. + `#1912 `_, + `#1907 `_. + +* List insertion operation for ``py::list``. + Added ``.empty()`` to all collection types. + Added ``py::set::contains()`` and ``py::dict::contains()``. + `#1887 `_, + `#1884 `_, + `#1888 `_. + * ``py::details::overload_cast_impl`` is available in C++11 mode, can be used like ``overload_cast`` with an additional set of parantheses. - `1581 `_. + `#1581 `_. + +* Fixed ``get_include()`` on Conda. + `#1877 `_. + +* ``stl_bind.h``: negative indexing support. + `#1882 `_. + +* Minor CMake fix to add MinGW compatibility. + `#1851 `_. + +* GIL-related fixes. + `#1836 `_, + `8b90b `_. + +* Other very minor/subtle fixes and improvements. + `#1329 `_, + `#1910 `_, + `#1863 `_, + `#1847 `_, + `#1890 `_, + `#1860 `_, + `#1848 `_, + `#1821 `_, + `#1837 `_, + `#1833 `_, + `#1748 `_, + `#1852 `_. v2.3.0 (June 11, 2019) ----------------------------------------------------- diff --git a/docs/conf.py b/docs/conf.py index d17e4ba309..a1e4e00583 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,9 +61,9 @@ # built documents. # # The short X.Y version. -version = '2.3' +version = '2.4' # The full version, including alpha/beta/rc tags. -release = '2.3.dev1' +release = '2.4.dev4' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/faq.rst b/docs/faq.rst index 93ccf10e57..4d491fb87f 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -248,6 +248,33 @@ 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 can I properly handle Ctrl-C in long-running functions? +=========================================================== + +Ctrl-C is received by the Python interpreter, and holds it until the GIL +is released, so a long-running function won't be interrupted. + +To interrupt from inside your function, you can use the ``PyErr_CheckSignals()`` +function, that will tell if a signal has been raised on the Python side. This +function merely checks a flag, so its impact is negligible. When a signal has +been received, you must either explicitly interrupt execution by throwing +``py::error_already_set`` (which will propagate the existing +``KeyboardInterrupt``), or clear the error (which you usually will not want): + +.. code-block:: cpp + + PYBIND11_MODULE(example, m) + { + m.def("long running_func", []() + { + for (;;) { + if (PyErr_CheckSignals() != 0) + throw py::error_already_set(); + // Long running iteration + } + }); + } + Inconsistent detection of Python version in CMake and pybind11 ============================================================== diff --git a/docs/release.rst b/docs/release.rst index b31bbe97eb..9846f971a6 100644 --- a/docs/release.rst +++ b/docs/release.rst @@ -13,10 +13,6 @@ To release a new version of pybind11: - ``git push --tags``. - ``python setup.py sdist upload``. - ``python setup.py bdist_wheel upload``. -- Update conda-forge (https://github.com/conda-forge/pybind11-feedstock) via PR - - download release package from Github: ``wget https://github.com/pybind/pybind11/archive/vX.Y.Z.tar.gz`` - - compute checksum: ``shasum -a 256 vX.Y.Z.tar.gz`` - - change version number and checksum in ``recipe/meta.yml`` - Get back to work - Update ``_version.py`` (add 'dev' and increment minor). - Update version in ``docs/conf.py`` diff --git a/include/pybind11/buffer_info.h b/include/pybind11/buffer_info.h index 9f072fa738..1f4115a1fa 100644 --- a/include/pybind11/buffer_info.h +++ b/include/pybind11/buffer_info.h @@ -21,14 +21,15 @@ struct buffer_info { std::string format; // For homogeneous buffers, this should be set to format_descriptor::format() ssize_t ndim = 0; // Number of dimensions std::vector shape; // Shape of the tensor (1 entry per dimension) - std::vector strides; // Number of entries between adjacent entries (for each per dimension) + std::vector strides; // Number of bytes between adjacent entries (for each per dimension) + bool readonly = false; // flag to indicate if the underlying storage may be written to buffer_info() { } buffer_info(void *ptr, ssize_t itemsize, const std::string &format, ssize_t ndim, - detail::any_container shape_in, detail::any_container strides_in) + detail::any_container shape_in, detail::any_container strides_in, bool readonly=false) : ptr(ptr), itemsize(itemsize), size(1), format(format), ndim(ndim), - shape(std::move(shape_in)), strides(std::move(strides_in)) { + shape(std::move(shape_in)), strides(std::move(strides_in)), readonly(readonly) { if (ndim != (ssize_t) shape.size() || ndim != (ssize_t) strides.size()) pybind11_fail("buffer_info: ndim doesn't match shape and/or strides length"); for (size_t i = 0; i < (size_t) ndim; ++i) @@ -36,19 +37,23 @@ struct buffer_info { } template - buffer_info(T *ptr, detail::any_container shape_in, detail::any_container strides_in) - : buffer_info(private_ctr_tag(), ptr, sizeof(T), format_descriptor::format(), static_cast(shape_in->size()), std::move(shape_in), std::move(strides_in)) { } + buffer_info(T *ptr, detail::any_container shape_in, detail::any_container strides_in, bool readonly=false) + : buffer_info(private_ctr_tag(), ptr, sizeof(T), format_descriptor::format(), static_cast(shape_in->size()), std::move(shape_in), std::move(strides_in), readonly) { } - buffer_info(void *ptr, ssize_t itemsize, const std::string &format, ssize_t size) - : buffer_info(ptr, itemsize, format, 1, {size}, {itemsize}) { } + buffer_info(void *ptr, ssize_t itemsize, const std::string &format, ssize_t size, bool readonly=false) + : buffer_info(ptr, itemsize, format, 1, {size}, {itemsize}, readonly) { } template - buffer_info(T *ptr, ssize_t size) - : buffer_info(ptr, sizeof(T), format_descriptor::format(), size) { } + buffer_info(T *ptr, ssize_t size, bool readonly=false) + : buffer_info(ptr, sizeof(T), format_descriptor::format(), size, readonly) { } + + template + buffer_info(const T *ptr, ssize_t size, bool readonly=true) + : buffer_info(const_cast(ptr), sizeof(T), format_descriptor::format(), size, readonly) { } explicit buffer_info(Py_buffer *view, bool ownview = true) : buffer_info(view->buf, view->itemsize, view->format, view->ndim, - {view->shape, view->shape + view->ndim}, {view->strides, view->strides + view->ndim}) { + {view->shape, view->shape + view->ndim}, {view->strides, view->strides + view->ndim}, view->readonly) { this->view = view; this->ownview = ownview; } @@ -70,6 +75,7 @@ struct buffer_info { strides = std::move(rhs.strides); std::swap(view, rhs.view); std::swap(ownview, rhs.ownview); + readonly = rhs.readonly; return *this; } @@ -81,8 +87,8 @@ struct buffer_info { struct private_ctr_tag { }; buffer_info(private_ctr_tag, void *ptr, ssize_t itemsize, const std::string &format, ssize_t ndim, - detail::any_container &&shape_in, detail::any_container &&strides_in) - : buffer_info(ptr, itemsize, format, ndim, std::move(shape_in), std::move(strides_in)) { } + detail::any_container &&shape_in, detail::any_container &&strides_in, bool readonly) + : buffer_info(ptr, itemsize, format, ndim, std::move(shape_in), std::move(strides_in), readonly) { } Py_buffer *view = nullptr; bool ownview = false; diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 6416a02855..4367735eab 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -32,6 +32,10 @@ #include #endif +#if defined(__cpp_lib_char8_t) && __cpp_lib_char8_t >= 201811L +# define PYBIND11_HAS_U8STRING +#endif + NAMESPACE_BEGIN(PYBIND11_NAMESPACE) NAMESPACE_BEGIN(detail) @@ -660,9 +664,17 @@ class type_caster_generic { case return_value_policy::copy: if (copy_constructor) valueptr = copy_constructor(src); - else - throw cast_error("return_value_policy = copy, but the " - "object is non-copyable!"); + else { +#if defined(NDEBUG) + throw cast_error("return_value_policy = copy, but type is " + "non-copyable! (compile in debug mode for details)"); +#else + std::string type_name(tinfo->cpptype->name()); + detail::clean_type_id(type_name); + throw cast_error("return_value_policy = copy, but type " + + type_name + " is non-copyable!"); +#endif + } wrapper->owned = true; break; @@ -671,9 +683,18 @@ class type_caster_generic { valueptr = move_constructor(src); else if (copy_constructor) valueptr = copy_constructor(src); - else - throw cast_error("return_value_policy = move, but the " - "object is neither movable nor copyable!"); + else { +#if defined(NDEBUG) + throw cast_error("return_value_policy = move, but type is neither " + "movable nor copyable! " + "(compile in debug mode for details)"); +#else + std::string type_name(tinfo->cpptype->name()); + detail::clean_type_id(type_name); + throw cast_error("return_value_policy = move, but type " + + type_name + " is neither movable nor copyable!"); +#endif + } wrapper->owned = true; break; @@ -701,7 +722,7 @@ class type_caster_generic { if (type->operator_new) { vptr = type->operator_new(type->type_size); } else { - #ifdef __cpp_aligned_new + #if defined(__cpp_aligned_new) && (!defined(_MSC_VER) || _MSC_VER >= 1912) if (type->type_align > __STDCPP_DEFAULT_NEW_ALIGNMENT__) vptr = ::operator new(type->type_size, std::align_val_t(type->type_align)); @@ -888,15 +909,25 @@ template struct is_copy_constructible : std // so, copy constructability depends on whether the value_type is copy constructible. template struct is_copy_constructible, - std::is_same + std::is_same, + // Avoid infinite recursion + negation> >::value>> : is_copy_constructible {}; -#if !defined(PYBIND11_CPP17) -// Likewise for std::pair before C++17 (which mandates that the copy constructor not exist when the -// two types aren't themselves copy constructible). +// Likewise for std::pair +// (after C++17 it is mandatory that the copy constructor not exist when the two types aren't themselves +// copy constructible, but this can not be relied upon when T1 or T2 are themselves containers). template struct is_copy_constructible> : all_of, is_copy_constructible> {}; -#endif + +// The same problems arise with std::is_copy_assignable, so we use the same workaround. +template struct is_copy_assignable : std::is_copy_assignable {}; +template struct is_copy_assignable, + std::is_same + >::value>> : is_copy_assignable {}; +template struct is_copy_assignable> + : all_of, is_copy_assignable> {}; NAMESPACE_END(detail) @@ -1077,6 +1108,9 @@ template class type_caster> { template using is_std_char_type = any_of< std::is_same, /* std::string */ +#if defined(PYBIND11_HAS_U8STRING) + std::is_same, /* std::u8string */ +#endif std::is_same, /* std::u16string */ std::is_same, /* std::u32string */ std::is_same /* std::wstring */ @@ -1261,6 +1295,8 @@ template <> class type_caster { if (res == 0 || res == 1) { value = (bool) res; return true; + } else { + PyErr_Clear(); } } return false; @@ -1278,6 +1314,9 @@ template struct string_caster { // Simplify life by being able to assume standard char sizes (the standard only guarantees // minimums, but Python requires exact sizes) static_assert(!std::is_same::value || sizeof(CharT) == 1, "Unsupported char size != 1"); +#if defined(PYBIND11_HAS_U8STRING) + static_assert(!std::is_same::value || sizeof(CharT) == 1, "Unsupported char8_t size != 1"); +#endif static_assert(!std::is_same::value || sizeof(CharT) == 2, "Unsupported char16_t size != 2"); static_assert(!std::is_same::value || sizeof(CharT) == 4, "Unsupported char32_t size != 4"); // wchar_t can be either 16 bits (Windows) or 32 (everywhere else) @@ -1296,7 +1335,7 @@ template struct string_caster { #if PY_MAJOR_VERSION >= 3 return load_bytes(load_src); #else - if (sizeof(CharT) == 1) { + if (std::is_same::value) { return load_bytes(load_src); } @@ -1356,7 +1395,7 @@ template struct string_caster { // without any encoding/decoding attempt). For other C++ char sizes this is a no-op. // which supports loading a unicode from a str, doesn't take this path. template - bool load_bytes(enable_if_t src) { + bool load_bytes(enable_if_t::value, handle> src) { if (PYBIND11_BYTES_CHECK(src.ptr())) { // We were passed a Python 3 raw bytes; accept it into a std::string or char* // without any encoding attempt. @@ -1371,7 +1410,7 @@ template struct string_caster { } template - bool load_bytes(enable_if_t) { return false; } + bool load_bytes(enable_if_t::value, handle>) { return false; } }; template @@ -1509,9 +1548,14 @@ template class Tuple, typename... Ts> class tuple_caster template bool load_impl(const sequence &seq, bool convert, index_sequence) { +#ifdef __cpp_fold_expressions + if ((... || !std::get(subcasters).load(seq[Is], convert))) + return false; +#else for (bool r : {std::get(subcasters).load(seq[Is], convert)...}) if (!r) return false; +#endif return true; } @@ -2219,14 +2263,19 @@ class argument_loader { template bool load_impl_sequence(function_call &call, index_sequence) { +#ifdef __cpp_fold_expressions + if ((... || !std::get(argcasters).load(call.args[Is], call.args_convert[Is]))) + return false; +#else for (bool r : {std::get(argcasters).load(call.args[Is], call.args_convert[Is])...}) if (!r) return false; +#endif return true; } template - Return call_impl(Func &&f, index_sequence, Guard &&) { + Return call_impl(Func &&f, index_sequence, Guard &&) && { return std::forward(f)(cast_op(std::move(std::get(argcasters)))...); } diff --git a/include/pybind11/detail/class.h b/include/pybind11/detail/class.h index db3d71cdd7..94029066af 100644 --- a/include/pybind11/detail/class.h +++ b/include/pybind11/detail/class.h @@ -350,6 +350,7 @@ extern "C" inline void pybind11_object_dealloc(PyObject *self) { auto type = Py_TYPE(self); type->tp_free(self); +#if PY_VERSION_HEX < 0x03080000 // `type->tp_dealloc != pybind11_object_dealloc` means that we're being called // as part of a derived type's dealloc, in which case we're not allowed to decref // the type here. For cross-module compatibility, we shouldn't compare directly @@ -357,6 +358,11 @@ extern "C" inline void pybind11_object_dealloc(PyObject *self) { auto pybind11_object_type = (PyTypeObject *) get_internals().instance_base; if (type->tp_dealloc == pybind11_object_type->tp_dealloc) Py_DECREF(type); +#else + // This was not needed before Python 3.8 (Python issue 35810) + // https://github.com/pybind/pybind11/issues/1946 + Py_DECREF(type); +#endif } /** Create the type which can be used as a common base for all classes. This is @@ -485,6 +491,13 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla view->len = view->itemsize; for (auto s : info->shape) view->len *= s; + view->readonly = info->readonly; + if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE && info->readonly) { + if (view) + view->obj = nullptr; + PyErr_SetString(PyExc_BufferError, "Writable buffer requested for readonly storage"); + return -1; + } if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) view->format = const_cast(info->format.c_str()); if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) { diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 57c287a0ff..6a233341da 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -93,8 +93,8 @@ #endif #define PYBIND11_VERSION_MAJOR 2 -#define PYBIND11_VERSION_MINOR 3 -#define PYBIND11_VERSION_PATCH dev1 +#define PYBIND11_VERSION_MINOR 4 +#define PYBIND11_VERSION_PATCH dev4 /// Include Python header, disable linking to pythonX_d.lib on Windows in debug mode #if defined(_MSC_VER) @@ -103,7 +103,7 @@ # endif # pragma warning(push) # pragma warning(disable: 4510 4610 4512 4005) -# if defined(_DEBUG) +# if defined(_DEBUG) && !defined(Py_DEBUG) # define PYBIND11_DEBUG_MARKER # undef _DEBUG # endif @@ -113,6 +113,9 @@ #include #include +/* Python #defines overrides on all sorts of core functions, which + tends to weak havok in C++ codebases that expect these to work + like regular functions (potentially with several overloads) */ #if defined(isalnum) # undef isalnum # undef isalpha @@ -123,6 +126,10 @@ # undef toupper #endif +#if defined(copysign) +# undef copysign +#endif + #if defined(_MSC_VER) # if defined(PYBIND11_DEBUG_MARKER) # define _DEBUG @@ -211,6 +218,8 @@ extern "C" { #define PYBIND11_STRINGIFY(x) #x #define PYBIND11_TOSTRING(x) PYBIND11_STRINGIFY(x) #define PYBIND11_CONCAT(first, second) first##second +#define PYBIND11_ENSURE_INTERNALS_READY \ + pybind11::detail::get_internals(); #define PYBIND11_CHECK_PYTHON_VERSION \ { \ @@ -257,6 +266,7 @@ extern "C" { static PyObject *pybind11_init(); \ PYBIND11_PLUGIN_IMPL(name) { \ PYBIND11_CHECK_PYTHON_VERSION \ + PYBIND11_ENSURE_INTERNALS_READY \ try { \ return pybind11_init(); \ } PYBIND11_CATCH_INIT_EXCEPTIONS \ @@ -284,6 +294,7 @@ extern "C" { static void PYBIND11_CONCAT(pybind11_init_, name)(pybind11::module &); \ PYBIND11_PLUGIN_IMPL(name) { \ PYBIND11_CHECK_PYTHON_VERSION \ + PYBIND11_ENSURE_INTERNALS_READY \ auto m = pybind11::module(PYBIND11_TOSTRING(name)); \ try { \ PYBIND11_CONCAT(pybind11_init_, name)(m); \ @@ -775,6 +786,7 @@ PYBIND11_RUNTIME_EXCEPTION(key_error, PyExc_KeyError) PYBIND11_RUNTIME_EXCEPTION(value_error, PyExc_ValueError) PYBIND11_RUNTIME_EXCEPTION(type_error, PyExc_TypeError) PYBIND11_RUNTIME_EXCEPTION(buffer_error, PyExc_BufferError) +PYBIND11_RUNTIME_EXCEPTION(import_error, PyExc_ImportError) PYBIND11_RUNTIME_EXCEPTION(cast_error, PyExc_RuntimeError) /// Thrown when pybind11::cast or handle::call fail due to a type casting error PYBIND11_RUNTIME_EXCEPTION(reference_cast_error, PyExc_RuntimeError) /// Used internally diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index 3809505995..cedb360f9e 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -25,6 +25,7 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass); # define PYBIND11_TLS_GET_VALUE(key) PyThread_tss_get((key)) # define PYBIND11_TLS_REPLACE_VALUE(key, value) PyThread_tss_set((key), (value)) # define PYBIND11_TLS_DELETE_VALUE(key) PyThread_tss_set((key), nullptr) +# define PYBIND11_TLS_FREE(key) PyThread_tss_free(key) #else // Usually an int but a long on Cygwin64 with Python 3.x # define PYBIND11_TLS_KEY_INIT(var) decltype(PyThread_create_key()) var = 0 @@ -43,6 +44,7 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass); # define PYBIND11_TLS_REPLACE_VALUE(key, value) \ PyThread_set_key_value((key), (value)) # endif +# define PYBIND11_TLS_FREE(key) (void)key #endif // Python loads modules by default with dlopen with the RTLD_LOCAL flag; under libc++ and possibly @@ -108,6 +110,16 @@ struct internals { #if defined(WITH_THREAD) PYBIND11_TLS_KEY_INIT(tstate); PyInterpreterState *istate = nullptr; + ~internals() { + // This destructor is called *after* Py_Finalize() in finalize_interpreter(). + // That *SHOULD BE* fine. The following details what happens whe PyThread_tss_free is called. + // PYBIND11_TLS_FREE is PyThread_tss_free on python 3.7+. On older python, it does nothing. + // PyThread_tss_free calls PyThread_tss_delete and PyMem_RawFree. + // PyThread_tss_delete just calls TlsFree (on Windows) or pthread_key_delete (on *NIX). Neither + // of those have anything to do with CPython internals. + // PyMem_RawFree *requires* that the `tstate` be allocated with the CPython allocator. + PYBIND11_TLS_FREE(tstate); + } #endif }; @@ -140,7 +152,7 @@ struct type_info { }; /// Tracks the `internals` and `type_info` ABI version independent of the main library version -#define PYBIND11_INTERNALS_VERSION 3 +#define PYBIND11_INTERNALS_VERSION 4 /// On MSVC, debug and release builds are not ABI-compatible! #if defined(_MSC_VER) && defined(_DEBUG) @@ -149,6 +161,33 @@ struct type_info { # define PYBIND11_BUILD_TYPE "" #endif +/// Let's assume that different compilers are ABI-incompatible. +#if defined(_MSC_VER) +# define PYBIND11_COMPILER_TYPE "_msvc" +#elif defined(__INTEL_COMPILER) +# define PYBIND11_COMPILER_TYPE "_icc" +#elif defined(__clang__) +# define PYBIND11_COMPILER_TYPE "_clang" +#elif defined(__PGI) +# define PYBIND11_COMPILER_TYPE "_pgi" +#elif defined(__MINGW32__) +# define PYBIND11_COMPILER_TYPE "_mingw" +#elif defined(__CYGWIN__) +# define PYBIND11_COMPILER_TYPE "_gcc_cygwin" +#elif defined(__GNUC__) +# define PYBIND11_COMPILER_TYPE "_gcc" +#else +# define PYBIND11_COMPILER_TYPE "_unknown" +#endif + +#if defined(_LIBCPP_VERSION) +# define PYBIND11_STDLIB "_libcpp" +#elif defined(__GLIBCXX__) || defined(__GLIBCPP__) +# define PYBIND11_STDLIB "_libstdcpp" +#else +# define PYBIND11_STDLIB "" +#endif + /// On Linux/OSX, changes in __GXX_ABI_VERSION__ indicate ABI incompatibility. #if defined(__GXX_ABI_VERSION) # define PYBIND11_BUILD_ABI "_cxxabi" PYBIND11_TOSTRING(__GXX_ABI_VERSION) @@ -163,10 +202,10 @@ struct type_info { #endif #define PYBIND11_INTERNALS_ID "__pybind11_internals_v" \ - PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) PYBIND11_INTERNALS_KIND PYBIND11_BUILD_ABI PYBIND11_BUILD_TYPE "__" + PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI PYBIND11_BUILD_TYPE "__" #define PYBIND11_MODULE_LOCAL_ID "__pybind11_module_local_v" \ - PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) PYBIND11_INTERNALS_KIND PYBIND11_BUILD_ABI PYBIND11_BUILD_TYPE "__" + PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI PYBIND11_BUILD_TYPE "__" /// Each module locally stores a pointer to the `internals` data. The data /// itself is shared among modules with the same `PYBIND11_INTERNALS_ID`. @@ -186,6 +225,7 @@ inline void translate_exception(std::exception_ptr p) { } catch (const std::length_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return; } catch (const std::out_of_range &e) { PyErr_SetString(PyExc_IndexError, e.what()); return; } catch (const std::range_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return; + } catch (const std::overflow_error &e) { PyErr_SetString(PyExc_OverflowError, e.what()); return; } catch (const std::exception &e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return; } catch (...) { PyErr_SetString(PyExc_RuntimeError, "Caught an unknown exception!"); diff --git a/include/pybind11/embed.h b/include/pybind11/embed.h index 72655885eb..f814c783e7 100644 --- a/include/pybind11/embed.h +++ b/include/pybind11/embed.h @@ -18,11 +18,13 @@ #if PY_MAJOR_VERSION >= 3 # define PYBIND11_EMBEDDED_MODULE_IMPL(name) \ + extern "C" PyObject *pybind11_init_impl_##name(); \ extern "C" PyObject *pybind11_init_impl_##name() { \ return pybind11_init_wrapper_##name(); \ } #else # define PYBIND11_EMBEDDED_MODULE_IMPL(name) \ + extern "C" void pybind11_init_impl_##name(); \ extern "C" void pybind11_init_impl_##name() { \ pybind11_init_wrapper_##name(); \ } diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 75e4fa87ac..e079d3923a 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -113,12 +113,12 @@ template struct same_size { template using as = bool_constant; }; +template constexpr int platform_lookup() { return -1; } + // Lookup a type according to its size, and return a value corresponding to the NumPy typenum. -template -constexpr int platform_lookup(Int... codes) { - using code_index = std::integral_constant::template as, Check...>()>; - static_assert(code_index::value != sizeof...(Check), "Unable to match type on this platform"); - return std::get(std::make_tuple(codes...)); +template +constexpr int platform_lookup(int I, Ints... Is) { + return sizeof(Concrete) == sizeof(T) ? I : platform_lookup(Is...); } struct npy_api { diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 88b1a7ae3e..bc1104281f 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1008,7 +1008,7 @@ void call_operator_delete(T *p, size_t s, size_t) { T::operator delete(p, s); } inline void call_operator_delete(void *p, size_t s, size_t a) { (void)s; (void)a; - #ifdef __cpp_aligned_new + #if defined(__cpp_aligned_new) && (!defined(_MSC_VER) || _MSC_VER >= 1912) if (a > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { #ifdef __cpp_sized_deallocation ::operator delete(p, s, std::align_val_t(a)); @@ -1869,9 +1869,17 @@ struct enum_base { }, \ is_method(m_base)) + #define PYBIND11_ENUM_OP_CONV_LHS(op, expr) \ + m_base.attr(op) = cpp_function( \ + [](object a_, object b) { \ + int_ a(a_); \ + return expr; \ + }, \ + is_method(m_base)) + if (is_convertible) { - PYBIND11_ENUM_OP_CONV("__eq__", !b.is_none() && a.equal(b)); - PYBIND11_ENUM_OP_CONV("__ne__", b.is_none() || !a.equal(b)); + PYBIND11_ENUM_OP_CONV_LHS("__eq__", !b.is_none() && a.equal(b)); + PYBIND11_ENUM_OP_CONV_LHS("__ne__", b.is_none() || !a.equal(b)); if (is_arithmetic) { PYBIND11_ENUM_OP_CONV("__lt__", a < b); @@ -1884,6 +1892,8 @@ struct enum_base { PYBIND11_ENUM_OP_CONV("__ror__", a | b); PYBIND11_ENUM_OP_CONV("__xor__", a ^ b); PYBIND11_ENUM_OP_CONV("__rxor__", a ^ b); + m_base.attr("__invert__") = cpp_function( + [](object arg) { return ~(int_(arg)); }, is_method(m_base)); } } else { PYBIND11_ENUM_OP_STRICT("__eq__", int_(a).equal(int_(b)), return false); @@ -1899,6 +1909,7 @@ struct enum_base { } } + #undef PYBIND11_ENUM_OP_CONV_LHS #undef PYBIND11_ENUM_OP_CONV #undef PYBIND11_ENUM_OP_STRICT @@ -1955,6 +1966,10 @@ template class enum_ : public class_ { #if PY_MAJOR_VERSION < 3 def("__long__", [](Type value) { return (Scalar) value; }); #endif + #if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 8) + def("__index__", [](Type value) { return (Scalar) value; }); + #endif + cpp_function setstate( [](Type &value, Scalar arg) { value = static_cast(arg); }, is_method(*this)); diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 1d6308c178..1a43f578cb 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -1313,7 +1313,7 @@ class buffer : public object { public: PYBIND11_OBJECT_DEFAULT(buffer, object, PyObject_CheckBuffer) - buffer_info request(bool writable = false) { + buffer_info request(bool writable = false) const { int flags = PyBUF_STRIDES | PyBUF_FORMAT; if (writable) flags |= PyBUF_WRITABLE; Py_buffer *view = new Py_buffer(); @@ -1346,7 +1346,7 @@ class memoryview : public object { buf.strides = py_strides.data(); buf.shape = py_shape.data(); buf.suboffsets = nullptr; - buf.readonly = false; + buf.readonly = info.readonly; buf.internal = nullptr; m_ptr = PyMemoryView_FromBuffer(&buf); diff --git a/include/pybind11/stl_bind.h b/include/pybind11/stl_bind.h index d3adaed3a2..62bd908196 100644 --- a/include/pybind11/stl_bind.h +++ b/include/pybind11/stl_bind.h @@ -512,7 +512,7 @@ template void map_assignment(const Args & // Map assignment when copy-assignable: just copy the value template -void map_assignment(enable_if_t::value, Class_> &cl) { +void map_assignment(enable_if_t::value, Class_> &cl) { using KeyType = typename Map::key_type; using MappedType = typename Map::mapped_type; @@ -528,7 +528,7 @@ void map_assignment(enable_if_t void map_assignment(enable_if_t< - !std::is_copy_assignable::value && + !is_copy_assignable::value && is_copy_constructible::value, Class_> &cl) { using KeyType = typename Map::key_type; diff --git a/pybind11/__init__.py b/pybind11/__init__.py index c625e8c948..4b1de3efaa 100644 --- a/pybind11/__init__.py +++ b/pybind11/__init__.py @@ -2,35 +2,11 @@ def get_include(user=False): - from distutils.dist import Distribution import os - import sys - - # Are we running in a virtual environment? - virtualenv = hasattr(sys, 'real_prefix') or \ - sys.prefix != getattr(sys, "base_prefix", sys.prefix) - - # Are we running in a conda environment? - conda = os.path.exists(os.path.join(sys.prefix, 'conda-meta')) - - if virtualenv: - return os.path.join(sys.prefix, 'include', 'site', - 'python' + sys.version[:3]) - elif conda: - if os.name == 'nt': - return os.path.join(sys.prefix, 'Library', 'include') - else: - return os.path.join(sys.prefix, 'include') + d = os.path.dirname(__file__) + if os.path.exists(os.path.join(d, "include")): + # Package is installed + return os.path.join(d, "include") else: - dist = Distribution({'name': 'pybind11'}) - dist.parse_config_files() - - dist_cobj = dist.get_command_obj('install', create=True) - - # Search for packages in user's home directory? - if user: - dist_cobj.user = user - dist_cobj.prefix = "" - dist_cobj.finalize_options() - - return os.path.dirname(dist_cobj.install_headers) + # Package is from a source directory + return os.path.join(os.path.dirname(d), "include") diff --git a/pybind11/__main__.py b/pybind11/__main__.py index 9ef8378029..89b263a8ad 100644 --- a/pybind11/__main__.py +++ b/pybind11/__main__.py @@ -10,8 +10,7 @@ def print_includes(): dirs = [sysconfig.get_path('include'), sysconfig.get_path('platinclude'), - get_include(), - get_include(True)] + get_include()] # Make unique but preserve order unique_dirs = [] diff --git a/pybind11/_version.py b/pybind11/_version.py index fef541bdbc..5bf3483d2e 100644 --- a/pybind11/_version.py +++ b/pybind11/_version.py @@ -1,2 +1,2 @@ -version_info = (2, 3, 'dev1') +version_info = (2, 4, 'dev4') __version__ = '.'.join(map(str, version_info)) diff --git a/setup.py b/setup.py index f677f2af4a..473ea1ee08 100644 --- a/setup.py +++ b/setup.py @@ -4,40 +4,43 @@ from setuptools import setup from distutils.command.install_headers import install_headers +from distutils.command.build_py import build_py from pybind11 import __version__ import os +package_data = [ + 'include/pybind11/detail/class.h', + 'include/pybind11/detail/common.h', + 'include/pybind11/detail/descr.h', + 'include/pybind11/detail/init.h', + 'include/pybind11/detail/internals.h', + 'include/pybind11/detail/typeid.h', + 'include/pybind11/attr.h', + 'include/pybind11/buffer_info.h', + 'include/pybind11/cast.h', + 'include/pybind11/chrono.h', + 'include/pybind11/common.h', + 'include/pybind11/complex.h', + 'include/pybind11/eigen.h', + 'include/pybind11/embed.h', + 'include/pybind11/eval.h', + 'include/pybind11/functional.h', + 'include/pybind11/iostream.h', + 'include/pybind11/numpy.h', + 'include/pybind11/operators.h', + 'include/pybind11/options.h', + 'include/pybind11/pybind11.h', + 'include/pybind11/pytypes.h', + 'include/pybind11/stl.h', + 'include/pybind11/stl_bind.h', +] + # Prevent installation of pybind11 headers by setting # PYBIND11_USE_CMAKE. if os.environ.get('PYBIND11_USE_CMAKE'): headers = [] else: - headers = [ - 'include/pybind11/detail/class.h', - 'include/pybind11/detail/common.h', - 'include/pybind11/detail/descr.h', - 'include/pybind11/detail/init.h', - 'include/pybind11/detail/internals.h', - 'include/pybind11/detail/typeid.h', - 'include/pybind11/attr.h', - 'include/pybind11/buffer_info.h', - 'include/pybind11/cast.h', - 'include/pybind11/chrono.h', - 'include/pybind11/common.h', - 'include/pybind11/complex.h', - 'include/pybind11/eigen.h', - 'include/pybind11/embed.h', - 'include/pybind11/eval.h', - 'include/pybind11/functional.h', - 'include/pybind11/iostream.h', - 'include/pybind11/numpy.h', - 'include/pybind11/operators.h', - 'include/pybind11/options.h', - 'include/pybind11/pybind11.h', - 'include/pybind11/pytypes.h', - 'include/pybind11/stl.h', - 'include/pybind11/stl_bind.h', - ] + headers = package_data class InstallHeaders(install_headers): @@ -55,6 +58,16 @@ def run(self): self.outfiles.append(out) +# Install the headers inside the package as well +class BuildPy(build_py): + def build_package_data(self): + build_py.build_package_data(self) + for header in package_data: + target = os.path.join(self.build_lib, 'pybind11', header) + self.mkpath(os.path.dirname(target)) + self.copy_file(header, target, preserve_mode=False) + + setup( name='pybind11', version=__version__, @@ -66,7 +79,8 @@ def run(self): packages=['pybind11'], license='BSD', headers=headers, - cmdclass=dict(install_headers=InstallHeaders), + zip_safe=False, + cmdclass=dict(install_headers=InstallHeaders, build_py=BuildPy), classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', diff --git a/tests/constructor_stats.h b/tests/constructor_stats.h index f026e70f98..431e5acef9 100644 --- a/tests/constructor_stats.h +++ b/tests/constructor_stats.h @@ -180,7 +180,7 @@ class ConstructorStats { } } } - catch (const std::out_of_range &) {} + catch (const std::out_of_range&) {} if (!t1) throw std::runtime_error("Unknown class passed to ConstructorStats::get()"); auto &cs1 = get(*t1); // If we have both a t1 and t2 match, one is probably the trampoline class; return whichever diff --git a/tests/test_buffers.cpp b/tests/test_buffers.cpp index 5199cf6467..1bc67ff7b6 100644 --- a/tests/test_buffers.cpp +++ b/tests/test_buffers.cpp @@ -78,7 +78,7 @@ TEST_SUBMODULE(buffers, m) { py::class_(m, "Matrix", py::buffer_protocol()) .def(py::init()) /// Construct from a buffer - .def(py::init([](py::buffer b) { + .def(py::init([](py::buffer const b) { py::buffer_info info = b.request(); if (info.format != py::format_descriptor::format() || info.ndim != 2) throw std::runtime_error("Incompatible buffer format!"); @@ -166,4 +166,30 @@ TEST_SUBMODULE(buffers, m) { .def_readwrite("value", (int32_t DerivedBuffer::*) &DerivedBuffer::value) .def_buffer(&DerivedBuffer::get_buffer_info); + struct BufferReadOnly { + const uint8_t value = 0; + BufferReadOnly(uint8_t value): value(value) {} + + py::buffer_info get_buffer_info() { + return py::buffer_info(&value, 1); + } + }; + py::class_(m, "BufferReadOnly", py::buffer_protocol()) + .def(py::init()) + .def_buffer(&BufferReadOnly::get_buffer_info); + + struct BufferReadOnlySelect { + uint8_t value = 0; + bool readonly = false; + + py::buffer_info get_buffer_info() { + return py::buffer_info(&value, 1, readonly); + } + }; + py::class_(m, "BufferReadOnlySelect", py::buffer_protocol()) + .def(py::init<>()) + .def_readwrite("value", &BufferReadOnlySelect::value) + .def_readwrite("readonly", &BufferReadOnlySelect::readonly) + .def_buffer(&BufferReadOnlySelect::get_buffer_info); + } diff --git a/tests/test_buffers.py b/tests/test_buffers.py index f006552bf7..bf7aaed70d 100644 --- a/tests/test_buffers.py +++ b/tests/test_buffers.py @@ -1,8 +1,14 @@ +import io import struct +import sys + import pytest + from pybind11_tests import buffers as m from pybind11_tests import ConstructorStats +PY3 = sys.version_info[0] >= 3 + pytestmark = pytest.requires_numpy with pytest.suppress(ImportError): @@ -85,3 +91,28 @@ def test_pointer_to_member_fn(): buf.value = 0x12345678 value = struct.unpack('i', bytearray(buf))[0] assert value == 0x12345678 + + +@pytest.unsupported_on_pypy +def test_readonly_buffer(): + buf = m.BufferReadOnly(0x64) + view = memoryview(buf) + assert view[0] == 0x64 if PY3 else b'd' + assert view.readonly + + +@pytest.unsupported_on_pypy +def test_selective_readonly_buffer(): + buf = m.BufferReadOnlySelect() + + memoryview(buf)[0] = 0x64 if PY3 else b'd' + assert buf.value == 0x64 + + io.BytesIO(b'A').readinto(buf) + assert buf.value == ord(b'A') + + buf.readonly = True + with pytest.raises(TypeError): + memoryview(buf)[0] = 0 if PY3 else b'\0' + with pytest.raises(TypeError): + io.BytesIO(b'1').readinto(buf) diff --git a/tests/test_builtin_casters.cpp b/tests/test_builtin_casters.cpp index db48fd4836..9c0e45549f 100644 --- a/tests/test_builtin_casters.cpp +++ b/tests/test_builtin_casters.cpp @@ -30,7 +30,7 @@ TEST_SUBMODULE(builtin_casters, m) { else { wstr.push_back((wchar_t) mathbfA32); } // 𝐀, utf32 wstr.push_back(0x7a); // z - m.def("good_utf8_string", []() { return std::string(u8"Say utf8\u203d \U0001f382 \U0001d400"); }); // Say utf8β€½ πŸŽ‚ 𝐀 + m.def("good_utf8_string", []() { return std::string((const char*)u8"Say utf8\u203d \U0001f382 \U0001d400"); }); // Say utf8β€½ πŸŽ‚ 𝐀 m.def("good_utf16_string", [=]() { return std::u16string({ b16, ib16, cake16_1, cake16_2, mathbfA16_1, mathbfA16_2, z16 }); }); // bβ€½πŸŽ‚π€z m.def("good_utf32_string", [=]() { return std::u32string({ a32, mathbfA32, cake32, ib32, z32 }); }); // aπ€πŸŽ‚β€½z m.def("good_wchar_string", [=]() { return wstr; }); // a‽𝐀z @@ -60,6 +60,18 @@ TEST_SUBMODULE(builtin_casters, m) { m.def("strlen", [](char *s) { return strlen(s); }); m.def("string_length", [](std::string s) { return s.length(); }); +#ifdef PYBIND11_HAS_U8STRING + m.attr("has_u8string") = true; + m.def("good_utf8_u8string", []() { return std::u8string(u8"Say utf8\u203d \U0001f382 \U0001d400"); }); // Say utf8β€½ πŸŽ‚ 𝐀 + m.def("bad_utf8_u8string", []() { return std::u8string((const char8_t*)"abc\xd0" "def"); }); + + m.def("u8_char8_Z", []() -> char8_t { return u8'Z'; }); + + // test_single_char_arguments + m.def("ord_char8", [](char8_t c) -> int { return static_cast(c); }); + m.def("ord_char8_lv", [](char8_t &c) -> int { return static_cast(c); }); +#endif + // test_string_view #ifdef PYBIND11_HAS_STRING_VIEW m.attr("has_string_view") = true; @@ -69,9 +81,15 @@ TEST_SUBMODULE(builtin_casters, m) { m.def("string_view_chars", [](std::string_view s) { py::list l; for (auto c : s) l.append((std::uint8_t) c); return l; }); m.def("string_view16_chars", [](std::u16string_view s) { py::list l; for (auto c : s) l.append((int) c); return l; }); m.def("string_view32_chars", [](std::u32string_view s) { py::list l; for (auto c : s) l.append((int) c); return l; }); - m.def("string_view_return", []() { return std::string_view(u8"utf8 secret \U0001f382"); }); + m.def("string_view_return", []() { return std::string_view((const char*)u8"utf8 secret \U0001f382"); }); m.def("string_view16_return", []() { return std::u16string_view(u"utf16 secret \U0001f382"); }); m.def("string_view32_return", []() { return std::u32string_view(U"utf32 secret \U0001f382"); }); + +# ifdef PYBIND11_HAS_U8STRING + m.def("string_view8_print", [](std::u8string_view s) { py::print(s, s.size()); }); + m.def("string_view8_chars", [](std::u8string_view s) { py::list l; for (auto c : s) l.append((std::uint8_t) c); return l; }); + m.def("string_view8_return", []() { return std::u8string_view(u8"utf8 secret \U0001f382"); }); +# endif #endif // test_integer_casting diff --git a/tests/test_builtin_casters.py b/tests/test_builtin_casters.py index 1905c90a8a..3d1470593a 100644 --- a/tests/test_builtin_casters.py +++ b/tests/test_builtin_casters.py @@ -15,6 +15,8 @@ def test_unicode_conversion(): assert m.good_utf16_string() == u"bβ€½πŸŽ‚π€z" assert m.good_utf32_string() == u"aπ€πŸŽ‚β€½z" assert m.good_wchar_string() == u"aβΈ˜π€z" + if hasattr(m, "has_u8string"): + assert m.good_utf8_u8string() == u"Say utf8β€½ πŸŽ‚ 𝐀" with pytest.raises(UnicodeDecodeError): m.bad_utf8_string() @@ -29,12 +31,17 @@ def test_unicode_conversion(): if hasattr(m, "bad_wchar_string"): with pytest.raises(UnicodeDecodeError): m.bad_wchar_string() + if hasattr(m, "has_u8string"): + with pytest.raises(UnicodeDecodeError): + m.bad_utf8_u8string() assert m.u8_Z() == 'Z' assert m.u8_eacute() == u'Γ©' assert m.u16_ibang() == u'β€½' assert m.u32_mathbfA() == u'𝐀' assert m.wchar_heart() == u'β™₯' + if hasattr(m, "has_u8string"): + assert m.u8_char8_Z() == 'Z' def test_single_char_arguments(): @@ -92,6 +99,17 @@ def toobig_message(r): assert m.ord_wchar(u'aa') assert str(excinfo.value) == toolong_message + if hasattr(m, "has_u8string"): + assert m.ord_char8(u'a') == 0x61 # simple ASCII + assert m.ord_char8_lv(u'b') == 0x62 + assert m.ord_char8(u'Γ©') == 0xE9 # requires 2 bytes in utf-8, but can be stuffed in a char + with pytest.raises(ValueError) as excinfo: + assert m.ord_char8(u'Δ€') == 0x100 # requires 2 bytes, doesn't fit in a char + assert str(excinfo.value) == toobig_message(0x100) + with pytest.raises(ValueError) as excinfo: + assert m.ord_char8(u'ab') + assert str(excinfo.value) == toolong_message + def test_bytes_to_string(): """Tests the ability to pass bytes to C++ string-accepting functions. Note that this is @@ -116,10 +134,15 @@ def test_string_view(capture): assert m.string_view_chars("Hi πŸŽ‚") == [72, 105, 32, 0xf0, 0x9f, 0x8e, 0x82] assert m.string_view16_chars("Hi πŸŽ‚") == [72, 105, 32, 0xd83c, 0xdf82] assert m.string_view32_chars("Hi πŸŽ‚") == [72, 105, 32, 127874] + if hasattr(m, "has_u8string"): + assert m.string_view8_chars("Hi") == [72, 105] + assert m.string_view8_chars("Hi πŸŽ‚") == [72, 105, 32, 0xf0, 0x9f, 0x8e, 0x82] assert m.string_view_return() == "utf8 secret πŸŽ‚" assert m.string_view16_return() == "utf16 secret πŸŽ‚" assert m.string_view32_return() == "utf32 secret πŸŽ‚" + if hasattr(m, "has_u8string"): + assert m.string_view8_return() == "utf8 secret πŸŽ‚" with capture: m.string_view_print("Hi") @@ -132,6 +155,14 @@ def test_string_view(capture): utf16 πŸŽ‚ 8 utf32 πŸŽ‚ 7 """ + if hasattr(m, "has_u8string"): + with capture: + m.string_view8_print("Hi") + m.string_view8_print("utf8 πŸŽ‚") + assert capture == """ + Hi 2 + utf8 πŸŽ‚ 9 + """ with capture: m.string_view_print("Hi, ascii") @@ -144,6 +175,14 @@ def test_string_view(capture): Hi, utf16 πŸŽ‚ 12 Hi, utf32 πŸŽ‚ 11 """ + if hasattr(m, "has_u8string"): + with capture: + m.string_view8_print("Hi, ascii") + m.string_view8_print("Hi, utf8 πŸŽ‚") + assert capture == """ + Hi, ascii 9 + Hi, utf8 πŸŽ‚ 13 + """ def test_integer_casting(): @@ -318,11 +357,15 @@ def test_numpy_bool(): import numpy as np convert, noconvert = m.bool_passthrough, m.bool_passthrough_noconvert + def cant_convert(v): + pytest.raises(TypeError, convert, v) + # np.bool_ is not considered implicit assert convert(np.bool_(True)) is True assert convert(np.bool_(False)) is False assert noconvert(np.bool_(True)) is True assert noconvert(np.bool_(False)) is False + cant_convert(np.zeros(2, dtype='int')) def test_int_long(): diff --git a/tests/test_copy_move.py b/tests/test_copy_move.py index aff2d99f2c..0e671d9696 100644 --- a/tests/test_copy_move.py +++ b/tests/test_copy_move.py @@ -5,13 +5,13 @@ def test_lacking_copy_ctor(): with pytest.raises(RuntimeError) as excinfo: m.lacking_copy_ctor.get_one() - assert "the object is non-copyable!" in str(excinfo.value) + assert "is non-copyable!" in str(excinfo.value) def test_lacking_move_ctor(): with pytest.raises(RuntimeError) as excinfo: m.lacking_move_ctor.get_one() - assert "the object is neither movable nor copyable!" in str(excinfo.value) + assert "is neither movable nor copyable!" in str(excinfo.value) def test_move_and_copy_casts(): @@ -98,7 +98,7 @@ def test_private_op_new(): with pytest.raises(RuntimeError) as excinfo: m.private_op_new_value() - assert "the object is neither movable nor copyable" in str(excinfo.value) + assert "is neither movable nor copyable" in str(excinfo.value) assert m.private_op_new_reference().value == 1 diff --git a/tests/test_enum.cpp b/tests/test_enum.cpp index 498a00e169..3153089208 100644 --- a/tests/test_enum.cpp +++ b/tests/test_enum.cpp @@ -13,11 +13,13 @@ TEST_SUBMODULE(enums, m) { // test_unscoped_enum enum UnscopedEnum { EOne = 1, - ETwo + ETwo, + EThree }; py::enum_(m, "UnscopedEnum", py::arithmetic(), "An unscoped enumeration") .value("EOne", EOne, "Docstring for EOne") .value("ETwo", ETwo, "Docstring for ETwo") + .value("EThree", EThree, "Docstring for EThree") .export_values(); // test_scoped_enum diff --git a/tests/test_enum.py b/tests/test_enum.py index d0989adcdb..7fe9b618d6 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -21,7 +21,7 @@ def test_unscoped_enum(): # __members__ property assert m.UnscopedEnum.__members__ == \ - {"EOne": m.UnscopedEnum.EOne, "ETwo": m.UnscopedEnum.ETwo} + {"EOne": m.UnscopedEnum.EOne, "ETwo": m.UnscopedEnum.ETwo, "EThree": m.UnscopedEnum.EThree} # __members__ readonly with pytest.raises(AttributeError): m.UnscopedEnum.__members__ = {} @@ -29,23 +29,18 @@ def test_unscoped_enum(): foo = m.UnscopedEnum.__members__ foo["bar"] = "baz" assert m.UnscopedEnum.__members__ == \ - {"EOne": m.UnscopedEnum.EOne, "ETwo": m.UnscopedEnum.ETwo} + {"EOne": m.UnscopedEnum.EOne, "ETwo": m.UnscopedEnum.ETwo, "EThree": m.UnscopedEnum.EThree} - assert m.UnscopedEnum.__doc__ == \ - '''An unscoped enumeration + for docstring_line in '''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''' + EThree : Docstring for EThree'''.split('\n'): + assert docstring_line in m.UnscopedEnum.__doc__ # Unscoped enums will accept ==/!= int comparisons y = m.UnscopedEnum.ETwo @@ -53,6 +48,38 @@ def test_unscoped_enum(): assert 2 == y assert y != 3 assert 3 != y + # Compare with None + assert (y != None) # noqa: E711 + assert not (y == None) # noqa: E711 + # Compare with an object + assert (y != object()) + assert not (y == object()) + # Compare with string + assert y != "2" + assert "2" != y + assert not ("2" == y) + assert not (y == "2") + + with pytest.raises(TypeError): + y < object() + + with pytest.raises(TypeError): + y <= object() + + with pytest.raises(TypeError): + y > object() + + with pytest.raises(TypeError): + y >= object() + + with pytest.raises(TypeError): + y | object() + + with pytest.raises(TypeError): + y & object() + + with pytest.raises(TypeError): + y ^ object() assert int(m.UnscopedEnum.ETwo) == 2 assert str(m.UnscopedEnum(2)) == "UnscopedEnum.ETwo" @@ -71,6 +98,11 @@ def test_unscoped_enum(): assert not (m.UnscopedEnum.ETwo < m.UnscopedEnum.EOne) assert not (2 < m.UnscopedEnum.EOne) + # arithmetic + assert m.UnscopedEnum.EOne & m.UnscopedEnum.EThree == m.UnscopedEnum.EOne + assert m.UnscopedEnum.EOne | m.UnscopedEnum.ETwo == m.UnscopedEnum.EThree + assert m.UnscopedEnum.EOne ^ m.UnscopedEnum.EThree == m.UnscopedEnum.ETwo + def test_scoped_enum(): assert m.test_scoped_enum(m.ScopedEnum.Three) == "ScopedEnum::Three" @@ -82,6 +114,12 @@ def test_scoped_enum(): assert not 3 == z assert z != 3 assert 3 != z + # Compare with None + assert (z != None) # noqa: E711 + assert not (z == None) # noqa: E711 + # Compare with an object + assert (z != object()) + assert not (z == object()) # Scoped enums will *NOT* accept >, <, >= and <= int comparisons (Will throw exceptions) with pytest.raises(TypeError): z > 3 @@ -140,6 +178,7 @@ def test_binary_operators(): assert int(m.Flags.Read | m.Flags.Execute) == 5 assert int(m.Flags.Write | m.Flags.Execute) == 3 assert int(m.Flags.Write | 1) == 3 + assert ~m.Flags.Write == -3 state = m.Flags.Read | m.Flags.Write assert (state & m.Flags.Read) != 0 diff --git a/tests/test_exceptions.cpp b/tests/test_exceptions.cpp index d30139037f..56cd9bc48f 100644 --- a/tests/test_exceptions.cpp +++ b/tests/test_exceptions.cpp @@ -116,6 +116,7 @@ TEST_SUBMODULE(exceptions, m) { m.def("throws5", []() { throw MyException5("this is a helper-defined translated exception"); }); m.def("throws5_1", []() { throw MyException5_1("MyException5 subclass"); }); m.def("throws_logic_error", []() { throw std::logic_error("this error should fall through to the standard handler"); }); + m.def("throws_overflow_error", []() {throw std::overflow_error(""); }); m.def("exception_matches", []() { py::dict foo; try { diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 6edff9fe4b..ac2b3603ec 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -79,6 +79,10 @@ def test_custom(msg): m.throws_logic_error() assert msg(excinfo.value) == "this error should fall through to the standard handler" + # OverFlow error translation. + with pytest.raises(OverflowError) as excinfo: + m.throws_overflow_error() + # Can we handle a helper-declared exception? with pytest.raises(m.MyException5) as excinfo: m.throws5() diff --git a/tests/test_stl_binders.cpp b/tests/test_stl_binders.cpp index a88b589e13..8688874091 100644 --- a/tests/test_stl_binders.cpp +++ b/tests/test_stl_binders.cpp @@ -54,6 +54,14 @@ template Map *times_ten(int n) { return m; } +template NestMap *times_hundred(int n) { + auto m = new NestMap(); + for (int i = 1; i <= n; i++) + for (int j = 1; j <= n; j++) + (*m)[i].emplace(int(j*10), E_nc(100*j)); + return m; +} + TEST_SUBMODULE(stl_binders, m) { // test_vector_int py::bind_vector>(m, "VectorInt", py::buffer_protocol()); @@ -85,6 +93,20 @@ TEST_SUBMODULE(stl_binders, m) { m.def("get_mnc", ×_ten>, py::return_value_policy::reference); py::bind_map>(m, "UmapENC"); m.def("get_umnc", ×_ten>, py::return_value_policy::reference); + // Issue #1885: binding nested std::map> with E non-copyable + py::bind_map>>(m, "MapVecENC"); + m.def("get_nvnc", [](int n) + { + auto m = new std::map>(); + for (int i = 1; i <= n; i++) + for (int j = 1; j <= n; j++) + (*m)[i].emplace_back(j); + return m; + }, py::return_value_policy::reference); + py::bind_map>>(m, "MapMapENC"); + m.def("get_nmnc", ×_hundred>>, py::return_value_policy::reference); + py::bind_map>>(m, "UmapUmapENC"); + m.def("get_numnc", ×_hundred>>, py::return_value_policy::reference); // test_vector_buffer py::bind_vector>(m, "VectorUChar", py::buffer_protocol()); diff --git a/tests/test_stl_binders.py b/tests/test_stl_binders.py index 6d5a159833..b83a587f26 100644 --- a/tests/test_stl_binders.py +++ b/tests/test_stl_binders.py @@ -212,6 +212,44 @@ def test_noncopyable_containers(): assert vsum == 150 + # nested std::map + nvnc = m.get_nvnc(5) + for i in range(1, 6): + for j in range(0, 5): + assert nvnc[i][j].value == j + 1 + + for k, v in nvnc.items(): + for i, j in enumerate(v, start=1): + assert j.value == i + + # nested std::map + nmnc = m.get_nmnc(5) + for i in range(1, 6): + for j in range(10, 60, 10): + assert nmnc[i][j].value == 10 * j + + vsum = 0 + for k_o, v_o in nmnc.items(): + for k_i, v_i in v_o.items(): + assert v_i.value == 10 * k_i + vsum += v_i.value + + assert vsum == 7500 + + # nested std::unordered_map + numnc = m.get_numnc(5) + for i in range(1, 6): + for j in range(10, 60, 10): + assert numnc[i][j].value == 10 * j + + vsum = 0 + for k_o, v_o in numnc.items(): + for k_i, v_i in v_o.items(): + assert v_i.value == 10 * k_i + vsum += v_i.value + + assert vsum == 7500 + def test_map_delitem(): mm = m.MapStringDouble() diff --git a/tools/FindPythonLibsNew.cmake b/tools/FindPythonLibsNew.cmake index e660c5f3eb..c9b95a9a16 100644 --- a/tools/FindPythonLibsNew.cmake +++ b/tools/FindPythonLibsNew.cmake @@ -140,11 +140,11 @@ list(GET _PYTHON_VERSION_LIST 1 PYTHON_VERSION_MINOR) list(GET _PYTHON_VERSION_LIST 2 PYTHON_VERSION_PATCH) # Make sure all directory separators are '/' -string(REGEX REPLACE "\\\\" "/" PYTHON_PREFIX ${PYTHON_PREFIX}) -string(REGEX REPLACE "\\\\" "/" PYTHON_INCLUDE_DIR ${PYTHON_INCLUDE_DIR}) -string(REGEX REPLACE "\\\\" "/" PYTHON_SITE_PACKAGES ${PYTHON_SITE_PACKAGES}) +string(REGEX REPLACE "\\\\" "/" PYTHON_PREFIX "${PYTHON_PREFIX}") +string(REGEX REPLACE "\\\\" "/" PYTHON_INCLUDE_DIR "${PYTHON_INCLUDE_DIR}") +string(REGEX REPLACE "\\\\" "/" PYTHON_SITE_PACKAGES "${PYTHON_SITE_PACKAGES}") -if(CMAKE_HOST_WIN32 AND NOT (MSYS OR MINGW)) +if(CMAKE_HOST_WIN32 AND NOT DEFINED ENV{MSYSTEM}) set(PYTHON_LIBRARY "${PYTHON_PREFIX}/libs/Python${PYTHON_LIBRARY_SUFFIX}.lib") diff --git a/tools/pybind11Tools.cmake b/tools/pybind11Tools.cmake index 11b7db523c..d0b4c12af9 100644 --- a/tools/pybind11Tools.cmake +++ b/tools/pybind11Tools.cmake @@ -12,7 +12,7 @@ if(NOT PYBIND11_PYTHON_VERSION) set(PYBIND11_PYTHON_VERSION "" CACHE STRING "Python version to use for compiling modules") endif() -set(Python_ADDITIONAL_VERSIONS 3.7 3.6 3.5 3.4) +set(Python_ADDITIONAL_VERSIONS 3.8 3.7 3.6 3.5 3.4) find_package(PythonLibsNew ${PYBIND11_PYTHON_VERSION} REQUIRED) include(CheckCXXCompilerFlag) @@ -197,7 +197,7 @@ function(pybind11_add_module target_name) _pybind11_add_lto_flags(${target_name} ${ARG_THIN_LTO}) - if (NOT MSVC AND NOT ${CMAKE_BUILD_TYPE} MATCHES Debug) + if (NOT MSVC AND NOT ${CMAKE_BUILD_TYPE} MATCHES Debug|RelWithDebInfo) # Strip unnecessary sections of the binary on Linux/Mac OS if(CMAKE_STRIP) if(APPLE)