diff --git a/.appveyor.yml b/.appveyor.yml index b150f10148..2d37fc7ae1 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -34,6 +34,7 @@ install: if ($env:APPVEYOR_JOB_NAME -like "*Visual Studio 2017*") { $env:CMAKE_GENERATOR = "Visual Studio 15 2017" $env:CMAKE_INCLUDE_PATH = "C:\Libraries\boost_1_64_0" + $env:CXXFLAGS = "-permissive-" } else { $env:CMAKE_GENERATOR = "Visual Studio 14 2015" } diff --git a/.travis.yml b/.travis.yml index 2853ac7ad8..8532f15b50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -100,22 +100,22 @@ before_install: if [ "$TRAVIS_OS_NAME" = "linux" ]; then if [ -n "$CLANG" ]; then export CXX=clang++-$CLANG CC=clang-$CLANG - COMPILER_PACKAGES="clang-$CLANG llvm-$CLANG-dev" + EXTRA_PACKAGES+=" clang-$CLANG llvm-$CLANG-dev" else if [ -z "$GCC" ]; then GCC=4.8 - else COMPILER_PACKAGES=g++-$GCC + else EXTRA_PACKAGES+=" g++-$GCC" fi export CXX=g++-$GCC CC=gcc-$GCC fi if [ "$GCC" = "6" ]; then DOCKER=${ARCH:+$ARCH/}debian:stretch - elif [ "$GCC" = "7" ]; then DOCKER=debian:buster + elif [ "$GCC" = "7" ]; then DOCKER=debian:buster EXTRA_PACKAGES+=" catch" DOWNLOAD_CATCH=OFF fi elif [ "$TRAVIS_OS_NAME" = "osx" ]; then export CXX=clang++ CC=clang; fi if [ -n "$CPP" ]; then CPP=-std=c++$CPP; fi if [ "${PYTHON:0:1}" = "3" ]; then PY=3; fi - if [ -n "$DEBUG" ]; then CMAKE_EXTRA_ARGS="${CMAKE_EXTRA_ARGS} -DCMAKE_BUILD_TYPE=Debug"; fi + if [ -n "$DEBUG" ]; then CMAKE_EXTRA_ARGS+=" -DCMAKE_BUILD_TYPE=Debug"; fi - | # Initialize environment set -e @@ -133,7 +133,7 @@ before_install: if [ "$PYPY" = "5.8" ]; then curl -fSL https://bitbucket.org/pypy/pypy/downloads/pypy2-v5.8.0-linux64.tar.bz2 | tar xj PY_CMD=$(echo `pwd`/pypy2-v5.8.0-linux64/bin/pypy) - CMAKE_EXTRA_ARGS="${CMAKE_EXTRA_ARGS} -DPYTHON_EXECUTABLE:FILEPATH=$PY_CMD" + CMAKE_EXTRA_ARGS+=" -DPYTHON_EXECUTABLE:FILEPATH=$PY_CMD" else PY_CMD=python$PYTHON if [ "$TRAVIS_OS_NAME" = "osx" ]; then @@ -157,12 +157,12 @@ install: if [ -n "$DOCKER" ]; then if [ -n "$DEBUG" ]; then PY_DEBUG="python$PYTHON-dbg python$PY-scipy-dbg" - CMAKE_EXTRA_ARGS="${CMAKE_EXTRA_ARGS} -DPYTHON_EXECUTABLE=/usr/bin/python${PYTHON}dm" + CMAKE_EXTRA_ARGS+=" -DPYTHON_EXECUTABLE=/usr/bin/python${PYTHON}dm" fi $SCRIPT_RUN_PREFIX sh -c "for s in 0 15; do sleep \$s; \ apt-get -qy --no-install-recommends install \ $PY_DEBUG python$PYTHON-dev python$PY-pytest python$PY-scipy \ - libeigen3-dev libboost-dev cmake make ${COMPILER_PACKAGES} && break; done" + libeigen3-dev libboost-dev cmake make ${EXTRA_PACKAGES} && break; done" else if [ "$CLANG" = "5.0" ]; then @@ -195,7 +195,7 @@ install: 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:+:}$PWD/eigen-eigen-67e894c6cd8f" + export CMAKE_INCLUDE_PATH="${CMAKE_INCLUDE_PATH:+$CMAKE_INCLUDE_PATH:}$PWD/eigen-eigen-67e894c6cd8f" fi set +e script: @@ -203,7 +203,7 @@ script: -DPYBIND11_PYTHON_VERSION=$PYTHON -DPYBIND11_CPP_STANDARD=$CPP -DPYBIND11_WERROR=${WERROR:-ON} - -DDOWNLOAD_CATCH=ON + -DDOWNLOAD_CATCH=${DOWNLOAD_CATCH:-ON} - $SCRIPT_RUN_PREFIX make pytest -j 2 - $SCRIPT_RUN_PREFIX make cpptest -j 2 - if [ -n "$CMAKE" ]; then $SCRIPT_RUN_PREFIX make test_cmake_build; fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2beaf8d4d0..375735f6ce 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 ccf4e97878..6f15578cc4 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. diff --git a/docs/advanced/cast/eigen.rst b/docs/advanced/cast/eigen.rst index acdb51de63..9c7cbd22c0 100644 --- a/docs/advanced/cast/eigen.rst +++ b/docs/advanced/cast/eigen.rst @@ -41,7 +41,7 @@ consideration: by default, numpy matrices and eigen matrices are *not* storage compatible. If the numpy matrix cannot be used as is (either because its types differ, e.g. -passing an array of integers to an Eigen paramater requiring doubles, or +passing an array of integers to an Eigen parameter requiring doubles, or because the storage is incompatible), pybind11 makes a temporary copy and passes the copy instead. @@ -89,7 +89,7 @@ as dictated by the binding function's return value policy (see the documentation on :ref:`return_value_policies` for full details). That means, without an explicit return value policy, lvalue references will be copied and pointers will be managed by pybind11. In order to avoid copying, you should -explictly specify an appropriate return value policy, as in the following +explicitly specify an appropriate return value policy, as in the following example: .. code-block:: cpp @@ -287,7 +287,7 @@ On the other hand, pybind11 allows you to pass 1-dimensional arrays of length N as Eigen parameters. If the Eigen type can hold a column vector of length N it will be passed as such a column vector. If not, but the Eigen type constraints will accept a row vector, it will be passed as a row vector. (The column -vector takes precendence when both are supported, for example, when passing a +vector takes precedence when both are supported, for example, when passing a 1D numpy array to a MatrixXd argument). Note that the type need not be expicitly a vector: it is permitted to pass a 1D numpy array of size 5 to an Eigen ``Matrix``: you would end up with a 1x5 Eigen matrix. diff --git a/docs/advanced/exceptions.rst b/docs/advanced/exceptions.rst index 348337916d..3122c3721d 100644 --- a/docs/advanced/exceptions.rst +++ b/docs/advanced/exceptions.rst @@ -138,5 +138,5 @@ section. error return without exception set``. Exceptions that you do not plan to handle should simply not be caught, or - may be explicity (re-)thrown to delegate it to the other, + may be explicitly (re-)thrown to delegate it to the other, previously-declared existing exception translators. diff --git a/docs/advanced/functions.rst b/docs/advanced/functions.rst index c7892b5d30..e3acff06bf 100644 --- a/docs/advanced/functions.rst +++ b/docs/advanced/functions.rst @@ -126,7 +126,7 @@ targeted arguments can be passed through the :class:`cpp_function` constructor: .. warning:: - Code with invalid return value policies might access unitialized memory or + Code with invalid return value policies might access uninitialized memory or free data structures multiple times, which can lead to hard-to-debug non-determinism and segmentation faults, hence it is worth spending the time to understand all the different options in the table above. @@ -473,7 +473,7 @@ Overload resolution order When a function or method with multiple overloads is called from Python, pybind11 determines which overload to call in two passes. The first pass attempts to call each overload without allowing argument conversion (as if -every argument had been specified as ``py::arg().noconvert()`` as decribed +every argument had been specified as ``py::arg().noconvert()`` as described above). If no overload succeeds in the no-conversion first pass, a second pass is diff --git a/docs/changelog.rst b/docs/changelog.rst index 1ca501d15b..b1d772163f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,10 +6,70 @@ Changelog Starting with version 1.8.0, pybind11 releases use a `semantic versioning `_ policy. -v2.3.0 (Not yet released) +v2.2.2 (Not yet released) ----------------------------------------------------- -* TBD +* Fixed a segfault when combining embedded interpreter + shutdown/reinitialization with external loaded pybind11 modules. + `#1092 `_. + +* Eigen support: fixed a bug where Nx1/1xN numpy inputs couldn't be passed as + arguments to Eigen vectors (which for Eigen are simply compile-time fixed + Nx1/1xN matrices). + `#1106 `_. + +* Clarified to license by moving the licensing of contributions from + ``LICENSE`` into ``CONTRIBUTING.md``: the licensing of contributions is not + actually part of the software license as distributed. This isn't meant to be + a substantial change in the licensing of the project, but addresses concerns + that the clause made the license non-standard. + `#1109 `_. + +* Fixed a regression introduced in 2.1 that broke binding functions with lvalue + character literal arguments. + `#1128 `_. + +* MSVC: fix for compilation failures under /permissive-, and added the flag to + the appveyor test suite. + `#1155 `_. + +* Fixed ``__qualname__`` generation, and in turn, fixes how class names + (especially nested class names) are shown in generated docstrings. + `#1171 `_. + +* Updated the FAQ with a suggested project citation reference. + `#1189 `_. + +* Added fixes for deprecation warnings when compiled under C++17 with + ``-Wdeprecated`` turned on, and add ``-Wdeprecated`` to the test suite + compilation flags. + `#1191 `_. + +* Fixed outdated PyPI URLs in ``setup.py``. + `#1213 `_. + +* Fixed a refcount leak for arguments that end up in a ``py::args`` argument + for functions with both fixed positional and ``py::args`` arguments. + `#1216 `_. + +* Fixed a potential segfault resulting from possible premature destruction of + ``py::args``/``py::kwargs`` arguments with overloaded functions. + `#1223 `_. + +* Fixed ``del map[item]`` for a ``stl_bind.h`` bound stl map. + `#1229 `_. + +* Fixed a regression from v2.1.x where the aggregate initialization could + unintentionally end up at a constructor taking a templated + ``std::initializer_list`` argument. + `#1249 `_. + +* Fixed an issue where calling a function with a keep_alive policy on the same + nurse/patient pair would cause the internal patient storage to needlessly + grow (unboundedly, if the nurse is long-lived). + `#1251 `_. + +* Various other minor fixes. v2.2.1 (September 14, 2017) ----------------------------------------------------- diff --git a/docs/conf.py b/docs/conf.py index cd0e17eb78..0a7e9df4df 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -63,7 +63,7 @@ # The short X.Y version. version = '2.2' # The full version, including alpha/beta/rc tags. -release = '2.2.1' +release = '2.2.2' # 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 8f33eb014a..7a6073c908 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -271,3 +271,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} + } + diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index eab904bee0..a722a9e81d 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1216,6 +1216,7 @@ template struct type_caster; StringCaster str_caster; bool none = false; + CharT one_char = 0; public: bool load(handle src, bool convert) { if (!src) return false; @@ -1243,7 +1244,7 @@ template struct type_caster(static_cast(str_caster).c_str()); } - operator CharT() { + operator CharT&() { if (none) throw value_error("Cannot convert None to a character"); @@ -1267,7 +1268,8 @@ template struct type_caster(((v0 & 3) << 6) + (static_cast(value[1]) & 0x3F)); + one_char = static_cast(((v0 & 3) << 6) + (static_cast(value[1]) & 0x3F)); + return one_char; } // Otherwise we have a single character, but it's > U+00FF throw value_error("Character code point not in range(0x100)"); @@ -1278,19 +1280,20 @@ template struct type_caster(value[0]); - if (v0 >= 0xD800 && v0 < 0xE000) + one_char = static_cast(value[0]); + if (one_char >= 0xD800 && one_char < 0xE000) throw value_error("Character code point not in range(0x10000)"); } if (str_len != 1) throw value_error("Expected a character, but multi-character string found"); - return value[0]; + one_char = value[0]; + return one_char; } static PYBIND11_DESCR name() { return type_descr(_(PYBIND11_STRING_NAME)); } - template using cast_op_type = remove_reference_t>; + template using cast_op_type = pybind11::detail::cast_op_type<_T>; }; // Base implementation for std::tuple and std::pair @@ -1414,7 +1417,7 @@ struct copyable_holder_caster : public type_caster_base { bool load_value(value_and_holder &&v_h) { if (v_h.holder_constructed()) { value = v_h.value_ptr(); - holder = v_h.holder(); + holder = v_h.template holder(); return true; } else { throw cast_error("Unable to cast from non-held to held instance (T& to Holder) " @@ -1574,7 +1577,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; @@ -1683,6 +1686,9 @@ template <> inline void cast_safe(object &&) {} NAMESPACE_END(detail) +template +tuple make_tuple() { return tuple(0); } + template tuple make_tuple(Args&&... args_) { constexpr size_t size = sizeof...(Args); @@ -1799,6 +1805,10 @@ struct function_call { /// The `convert` value the arguments should be loaded with std::vector args_convert; + /// Extra references for the optional `py::args` and/or `py::kwargs` arguments (which, if + /// present, are also in `args` but without a reference). + object args_ref, kwargs_ref; + /// The parent, if any handle parent; diff --git a/include/pybind11/detail/class.h b/include/pybind11/detail/class.h index f745992a0b..ff06370fa0 100644 --- a/include/pybind11/detail/class.h +++ b/include/pybind11/detail/class.h @@ -14,6 +14,15 @@ NAMESPACE_BEGIN(PYBIND11_NAMESPACE) NAMESPACE_BEGIN(detail) +#if PY_VERSION_HEX >= 0x03030000 +# define PYBIND11_BUILTIN_QUALNAME +# define PYBIND11_SET_OLDPY_QUALNAME(obj, nameobj) +#else +// In pre-3.3 Python, we still set __qualname__ so that we can produce reliable function type +// signatures; in 3.3+ this macro expands to nothing: +# define PYBIND11_SET_OLDPY_QUALNAME(obj, nameobj) setattr((PyObject *) obj, "__qualname__", nameobj) +#endif + inline PyTypeObject *type_incref(PyTypeObject *type) { Py_INCREF(type); return type; @@ -48,7 +57,7 @@ inline PyTypeObject *make_static_property_type() { pybind11_fail("make_static_property_type(): error allocating type!"); heap_type->ht_name = name_obj.inc_ref().ptr(); -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3 +#ifdef PYBIND11_BUILTIN_QUALNAME heap_type->ht_qualname = name_obj.inc_ref().ptr(); #endif @@ -63,6 +72,7 @@ inline PyTypeObject *make_static_property_type() { pybind11_fail("make_static_property_type(): failure in PyType_Ready()!"); setattr((PyObject *) type, "__module__", str("pybind11_builtins")); + PYBIND11_SET_OLDPY_QUALNAME(type, name_obj); return type; } @@ -161,7 +171,7 @@ inline PyTypeObject* make_default_metaclass() { pybind11_fail("make_default_metaclass(): error allocating metaclass!"); heap_type->ht_name = name_obj.inc_ref().ptr(); -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3 +#ifdef PYBIND11_BUILTIN_QUALNAME heap_type->ht_qualname = name_obj.inc_ref().ptr(); #endif @@ -179,6 +189,7 @@ inline PyTypeObject* make_default_metaclass() { pybind11_fail("make_default_metaclass(): failure in PyType_Ready()!"); setattr((PyObject *) type, "__module__", str("pybind11_builtins")); + PYBIND11_SET_OLDPY_QUALNAME(type, name_obj); return type; } @@ -278,9 +289,13 @@ extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject inline void add_patient(PyObject *nurse, PyObject *patient) { auto &internals = get_internals(); auto instance = reinterpret_cast(nurse); + auto ¤t_patients = internals.patients[nurse]; instance->has_patients = true; + for (auto &p : current_patients) + if (p == patient) + return; Py_INCREF(patient); - internals.patients[nurse].push_back(patient); + current_patients.push_back(patient); } inline void clear_patients(PyObject *self) { @@ -363,7 +378,7 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass) { pybind11_fail("make_object_base_type(): error allocating type!"); heap_type->ht_name = name_obj.inc_ref().ptr(); -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3 +#ifdef PYBIND11_BUILTIN_QUALNAME heap_type->ht_qualname = name_obj.inc_ref().ptr(); #endif @@ -384,6 +399,7 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass) { pybind11_fail("PyType_Ready failed in make_object_base_type():" + error_string()); setattr((PyObject *) type, "__module__", str("pybind11_builtins")); + PYBIND11_SET_OLDPY_QUALNAME(type, name_obj); assert(!PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC)); return (PyObject *) heap_type; @@ -504,13 +520,15 @@ inline void enable_buffer_protocol(PyHeapTypeObject *heap_type) { inline PyObject* make_new_python_type(const type_record &rec) { auto name = reinterpret_steal(PYBIND11_FROM_STRING(rec.name)); -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3 - auto ht_qualname = name; - if (rec.scope && hasattr(rec.scope, "__qualname__")) { - ht_qualname = reinterpret_steal( + auto qualname = name; + if (rec.scope && !PyModule_Check(rec.scope.ptr()) && hasattr(rec.scope, "__qualname__")) { +#if PY_MAJOR_VERSION >= 3 + qualname = reinterpret_steal( PyUnicode_FromFormat("%U.%U", rec.scope.attr("__qualname__").ptr(), name.ptr())); - } +#else + qualname = str(rec.scope.attr("__qualname__").cast() + "." + rec.name); #endif + } object module; if (rec.scope) { @@ -552,8 +570,8 @@ inline PyObject* make_new_python_type(const type_record &rec) { pybind11_fail(std::string(rec.name) + ": Unable to create type object!"); heap_type->ht_name = name.release().ptr(); -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3 - heap_type->ht_qualname = ht_qualname.release().ptr(); +#ifdef PYBIND11_BUILTIN_QUALNAME + heap_type->ht_qualname = qualname.inc_ref().ptr(); #endif auto type = &heap_type->ht_type; @@ -599,6 +617,8 @@ inline PyObject* make_new_python_type(const type_record &rec) { if (module) // Needed by pydoc setattr((PyObject *) type, "__module__", module); + PYBIND11_SET_OLDPY_QUALNAME(type, qualname); + return (PyObject *) type; } diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 8f763f08aa..7d41cd63b3 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -93,7 +93,7 @@ #define PYBIND11_VERSION_MAJOR 2 #define PYBIND11_VERSION_MINOR 2 -#define PYBIND11_VERSION_PATCH 1 +#define PYBIND11_VERSION_PATCH 2 /// Include Python header, disable linking to pythonX_d.lib on Windows in debug mode #if defined(_MSC_VER) @@ -377,16 +377,18 @@ constexpr size_t instance_simple_holder_in_ptrs() { struct type_info; struct value_and_holder; +struct nonsimple_values_and_holders { + void **values_and_holders; + uint8_t *status; +}; + /// The 'instance' type which needs to be standard layout (need to be able to use 'offsetof') struct instance { PyObject_HEAD /// Storage for pointers and holder; see simple_layout, below, for a description union { void *simple_value_holder[1 + instance_simple_holder_in_ptrs()]; - struct { - void **values_and_holders; - uint8_t *status; - } nonsimple; + nonsimple_values_and_holders nonsimple; }; /// Weak references (needed for keep alive): PyObject *weakrefs; @@ -471,7 +473,7 @@ template struct select_indices_i : select_indices_impl, index_sequence>, I + 1, Bs...> {}; template using select_indices = typename select_indices_impl, 0, Bs...>::type; -/// Backports of std::bool_constant and std::negation to accomodate older compilers +/// Backports of std::bool_constant and std::negation to accommodate older compilers template using bool_constant = std::integral_constant; template struct negation : bool_constant { }; diff --git a/include/pybind11/detail/init.h b/include/pybind11/detail/init.h index c3594a1901..82f7407606 100644 --- a/include/pybind11/detail/init.h +++ b/include/pybind11/detail/init.h @@ -52,6 +52,16 @@ bool is_alias(Cpp *ptr) { template constexpr bool is_alias(void *) { return false; } +// Constructs and returns a new object; if the given arguments don't map to a constructor, we fall +// back to brace aggregate initiailization so that for aggregate initialization can be used with +// py::init, e.g. `py::init` to initialize a `struct T { int a; int b; }`. For +// non-aggregate types, we need to use an ordinary T(...) constructor (invoking as `T{...}` usually +// works, but will not do the expected thing when `T` has an `initializer_list` constructor). +template ::value, int> = 0> +inline Class *construct_or_initialize(Args &&...args) { return new Class(std::forward(args)...); } +template ::value, int> = 0> +inline Class *construct_or_initialize(Args &&...args) { return new Class{std::forward(args)...}; } + // Attempts to constructs an alias using a `Alias(Cpp &&)` constructor. This allows types with // an alias to provide only a single Cpp factory function as long as the Alias can be // constructed from an rvalue reference of the base Cpp type. This means that Alias classes @@ -161,7 +171,7 @@ struct constructor { template = 0> static void execute(Class &cl, const Extra&... extra) { cl.def("__init__", [](value_and_holder &v_h, Args... args) { - v_h.value_ptr() = new Cpp{std::forward(args)...}; + v_h.value_ptr() = construct_or_initialize>(std::forward(args)...); }, is_new_style_constructor(), extra...); } @@ -171,9 +181,9 @@ struct constructor { static void execute(Class &cl, const Extra&... extra) { cl.def("__init__", [](value_and_holder &v_h, Args... args) { if (Py_TYPE(v_h.inst) == v_h.type->type) - v_h.value_ptr() = new Cpp{std::forward(args)...}; + v_h.value_ptr() = construct_or_initialize>(std::forward(args)...); else - v_h.value_ptr() = new Alias{std::forward(args)...}; + v_h.value_ptr() = construct_or_initialize>(std::forward(args)...); }, is_new_style_constructor(), extra...); } @@ -182,7 +192,7 @@ struct constructor { !std::is_constructible, Args...>::value, int> = 0> static void execute(Class &cl, const Extra&... extra) { cl.def("__init__", [](value_and_holder &v_h, Args... args) { - v_h.value_ptr() = new Alias{std::forward(args)...}; + v_h.value_ptr() = construct_or_initialize>(std::forward(args)...); }, is_new_style_constructor(), extra...); } }; @@ -193,7 +203,7 @@ template struct alias_constructor { enable_if_t, Args...>::value, int> = 0> static void execute(Class &cl, const Extra&... extra) { cl.def("__init__", [](value_and_holder &v_h, Args... args) { - v_h.value_ptr() = new Alias{std::forward(args)...}; + v_h.value_ptr() = construct_or_initialize>(std::forward(args)...); }, is_new_style_constructor(), extra...); } }; diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index 213cbaeb21..e39f38695f 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -127,21 +127,21 @@ struct type_info { /// Each module locally stores a pointer to the `internals` data. The data /// itself is shared among modules with the same `PYBIND11_INTERNALS_ID`. -inline internals *&get_internals_ptr() { - static internals *internals_ptr = nullptr; - return internals_ptr; +inline internals **&get_internals_pp() { + static internals **internals_pp = nullptr; + return internals_pp; } /// Return a reference to the current `internals` data PYBIND11_NOINLINE inline internals &get_internals() { - auto *&internals_ptr = get_internals_ptr(); - if (internals_ptr) - return *internals_ptr; + auto **&internals_pp = get_internals_pp(); + if (internals_pp && *internals_pp) + return **internals_pp; constexpr auto *id = PYBIND11_INTERNALS_ID; auto builtins = handle(PyEval_GetBuiltins()); if (builtins.contains(id) && isinstance(builtins[id])) { - internals_ptr = *static_cast(capsule(builtins[id])); + internals_pp = static_cast(capsule(builtins[id])); // We loaded builtins through python's builtins, which means that our `error_already_set` // and `builtin_exception` may be different local classes than the ones set up in the @@ -149,7 +149,7 @@ PYBIND11_NOINLINE inline internals &get_internals() { // // libstdc++ doesn't require this (types there are identified only by name) #if !defined(__GLIBCXX__) - internals_ptr->registered_exception_translators.push_front( + (*internals_pp)->registered_exception_translators.push_front( [](std::exception_ptr p) -> void { try { if (p) std::rethrow_exception(p); @@ -160,6 +160,8 @@ PYBIND11_NOINLINE inline internals &get_internals() { ); #endif } else { + if (!internals_pp) internals_pp = new internals*(); + auto *&internals_ptr = *internals_pp; internals_ptr = new internals(); #if defined(WITH_THREAD) PyEval_InitThreads(); @@ -168,7 +170,7 @@ PYBIND11_NOINLINE inline internals &get_internals() { PyThread_set_key_value(internals_ptr->tstate, tstate); internals_ptr->istate = tstate->interp; #endif - builtins[id] = capsule(&internals_ptr); + builtins[id] = capsule(internals_pp); internals_ptr->registered_exception_translators.push_front( [](std::exception_ptr p) -> void { try { @@ -192,7 +194,7 @@ PYBIND11_NOINLINE inline internals &get_internals() { internals_ptr->default_metaclass = make_default_metaclass(); internals_ptr->instance_base = make_object_base_type(internals_ptr->default_metaclass); } - return *internals_ptr; + return **internals_pp; } /// Works like `internals.registered_types_cpp`, but for module-local registered types: diff --git a/include/pybind11/eigen.h b/include/pybind11/eigen.h index a702bf39eb..693a484dc8 100644 --- a/include/pybind11/eigen.h +++ b/include/pybind11/eigen.h @@ -22,14 +22,15 @@ # endif #endif -#include -#include - #if defined(_MSC_VER) # pragma warning(push) # pragma warning(disable: 4127) // warning C4127: Conditional expression is constant +# pragma warning(disable: 4996) // warning C4996: std::unary_negate is deprecated in C++17 #endif +#include +#include + // Eigen prior to 3.2.7 doesn't have proper move constructors--but worse, some classes get implicit // move constructors that break things. We could detect this an explicitly copy, but an extra copy // of matrices seems highly undesirable. @@ -272,6 +273,7 @@ struct type_caster::value>> { value = Type(fits.rows, fits.cols); auto ref = reinterpret_steal(eigen_ref_array(value)); if (dims == 1) ref = ref.squeeze(); + else if (ref.ndim() == 1) buf = buf.squeeze(); int result = detail::npy_api::get().PyArray_CopyInto_(ref.ptr(), buf.ptr()); diff --git a/include/pybind11/embed.h b/include/pybind11/embed.h index 6664967c10..9abc61c345 100644 --- a/include/pybind11/embed.h +++ b/include/pybind11/embed.h @@ -145,7 +145,7 @@ inline void finalize_interpreter() { // Get the internals pointer (without creating it if it doesn't exist). It's possible for the // internals to be created during Py_Finalize() (e.g. if a py::capsule calls `get_internals()` // during destruction), so we get the pointer-pointer here and check it after Py_Finalize(). - detail::internals **internals_ptr_ptr = &detail::get_internals_ptr(); + detail::internals **internals_ptr_ptr = detail::get_internals_pp(); // It could also be stashed in builtins, so look there too: if (builtins.contains(id) && isinstance(builtins[id])) internals_ptr_ptr = capsule(builtins[id]); diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 55bb816984..b1600dc2ee 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -946,9 +946,9 @@ struct format_descriptor::value>> { template struct format_descriptor::is_array>> { static std::string format() { - using detail::_; - PYBIND11_DESCR extents = _("(") + detail::array_info::extents() + _(")"); - return extents.text() + format_descriptor>::format(); + using namespace detail; + PYBIND11_DESCR extents = _("(") + array_info::extents() + _(")"); + return extents.text() + format_descriptor>::format(); } }; diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 613135a7a5..7723d2a8e9 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -92,8 +92,9 @@ class cpp_function : public function { /// Special internal constructor for functors, lambda functions, etc. template void initialize(Func &&f, Return (*)(Args...), const Extra&... extra) { + using namespace detail; - struct capture { detail::remove_reference_t f; }; + struct capture { remove_reference_t f; }; /* Store the function including any extra state it might have (e.g. a lambda capture object) */ auto rec = make_function_record(); @@ -112,23 +113,23 @@ class cpp_function : public function { # pragma GCC diagnostic pop #endif if (!std::is_trivially_destructible::value) - rec->free_data = [](detail::function_record *r) { ((capture *) &r->data)->~capture(); }; + rec->free_data = [](function_record *r) { ((capture *) &r->data)->~capture(); }; } else { rec->data[0] = new capture { std::forward(f) }; - rec->free_data = [](detail::function_record *r) { delete ((capture *) r->data[0]); }; + rec->free_data = [](function_record *r) { delete ((capture *) r->data[0]); }; } /* Type casters for the function arguments and return value */ - using cast_in = detail::argument_loader; - using cast_out = detail::make_caster< - detail::conditional_t::value, detail::void_type, Return> + using cast_in = argument_loader; + using cast_out = make_caster< + conditional_t::value, void_type, Return> >; - static_assert(detail::expected_num_args(sizeof...(Args), cast_in::has_args, cast_in::has_kwargs), + static_assert(expected_num_args(sizeof...(Args), cast_in::has_args, cast_in::has_kwargs), "The number of argument annotations does not match the number of function arguments"); /* Dispatch code which converts function arguments and performs the actual function call */ - rec->impl = [](detail::function_call &call) -> handle { + rec->impl = [](function_call &call) -> handle { cast_in args_converter; /* Try to cast the function arguments into the C++ domain */ @@ -136,7 +137,7 @@ class cpp_function : public function { return PYBIND11_TRY_NEXT_OVERLOAD; /* Invoke call policy pre-call hook */ - detail::process_attributes::precall(call); + process_attributes::precall(call); /* Get a pointer to the capture object */ auto data = (sizeof(capture) <= sizeof(call.func.data) @@ -144,26 +145,25 @@ class cpp_function : public function { capture *cap = const_cast(reinterpret_cast(data)); /* Override policy for rvalues -- usually to enforce rvp::move on an rvalue */ - const auto policy = detail::return_value_policy_override::policy(call.func.policy); + const auto policy = return_value_policy_override::policy(call.func.policy); /* Function scope guard -- defaults to the compile-to-nothing `void_type` */ - using Guard = detail::extract_guard_t; + using Guard = extract_guard_t; /* Perform the function call */ handle result = cast_out::cast( std::move(args_converter).template call(cap->f), policy, call.parent); /* Invoke call policy post-call hook */ - detail::process_attributes::postcall(call, result); + process_attributes::postcall(call, result); return result; }; /* Process any user-provided function attributes */ - detail::process_attributes::init(extra..., rec); + process_attributes::init(extra..., rec); /* Generate a readable signature describing the function's arguments and return value types */ - using detail::descr; using detail::_; PYBIND11_DESCR signature = _("(") + cast_in::arg_names() + _(") -> ") + cast_out::name(); /* Register the function with Python from generic (non-templated) code */ @@ -250,19 +250,16 @@ class cpp_function : public function { if (!t) pybind11_fail("Internal error while parsing type signature (1)"); if (auto tinfo = detail::get_type_info(*t)) { -#if defined(PYPY_VERSION) - signature += handle((PyObject *) tinfo->type) - .attr("__module__") - .cast() + "."; -#endif - signature += tinfo->type->tp_name; + handle th((PyObject *) tinfo->type); + signature += + th.attr("__module__").cast() + "." + + th.attr("__qualname__").cast(); // Python 3.3+, but we backport it to earlier versions } else if (rec->is_new_style_constructor && arg_index == 0) { // A new-style `__init__` takes `self` as `value_and_holder`. // Rewrite it to the proper class type. -#if defined(PYPY_VERSION) - signature += rec->scope.attr("__module__").cast() + "."; -#endif - signature += ((PyTypeObject *) rec->scope.ptr())->tp_name; + signature += + rec->scope.attr("__module__").cast() + "." + + rec->scope.attr("__qualname__").cast(); } else { std::string tname(t->name()); detail::clean_type_id(tname); @@ -579,8 +576,8 @@ class cpp_function : public function { continue; // Unconsumed kwargs, but no py::kwargs argument to accept them // 4a. If we have a py::args argument, create a new tuple with leftovers - tuple extra_args; if (func.has_args) { + tuple extra_args; if (args_to_copy == 0) { // We didn't copy out any position arguments from the args_in tuple, so we // can reuse it directly without copying: @@ -591,12 +588,12 @@ class cpp_function : public function { size_t args_size = n_args_in - args_copied; extra_args = tuple(args_size); for (size_t i = 0; i < args_size; ++i) { - handle item = PyTuple_GET_ITEM(args_in, args_copied + i); - extra_args[i] = item.inc_ref().ptr(); + extra_args[i] = PyTuple_GET_ITEM(args_in, args_copied + i); } } call.args.push_back(extra_args); call.args_convert.push_back(false); + call.args_ref = std::move(extra_args); } // 4b. If we have a py::kwargs, pass on any remaining kwargs @@ -605,6 +602,7 @@ class cpp_function : public function { kwargs = dict(); // If we didn't get one, send an empty one call.args.push_back(kwargs); call.args_convert.push_back(false); + call.kwargs_ref = std::move(kwargs); } // 5. Put everything in a vector. Not technically step 5, we've been building it diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index db900e674e..90eb7ea2ee 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -30,7 +30,8 @@ # define PYBIND11_HAS_OPTIONAL 1 # endif // std::experimental::optional (but not allowed in c++11 mode) -# if defined(PYBIND11_CPP14) && __has_include() +# if defined(PYBIND11_CPP14) && (__has_include() && \ + !__has_include()) # include # define PYBIND11_HAS_EXP_OPTIONAL 1 # endif diff --git a/include/pybind11/stl_bind.h b/include/pybind11/stl_bind.h index 7ef6878787..38dd68f695 100644 --- a/include/pybind11/stl_bind.h +++ b/include/pybind11/stl_bind.h @@ -587,7 +587,7 @@ class_ bind_map(handle scope, const std::string &name, Args&&. auto it = m.find(k); if (it == m.end()) throw key_error(); - return m.erase(it); + m.erase(it); } ); diff --git a/pybind11/_version.py b/pybind11/_version.py index 924115060d..9fe707f611 100644 --- a/pybind11/_version.py +++ b/pybind11/_version.py @@ -1,2 +1,2 @@ -version_info = (2, 2, 1) +version_info = (2, 2, 2) __version__ = '.'.join(map(str, version_info)) diff --git a/setup.cfg b/setup.cfg index 9e5e88d824..369788b283 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,5 +6,5 @@ max-line-length = 99 show_source = True exclude = .git, __pycache__, build, dist, docs, tools, venv ignore = - # required for pretty matrix formating: multiple spaces after `,` and `[` + # required for pretty matrix formatting: multiple spaces after `,` and `[` E201, E241 diff --git a/setup.py b/setup.py index b761205737..5b03bb2ce6 100644 --- a/setup.py +++ b/setup.py @@ -61,8 +61,8 @@ def run(self): description='Seamless operability between C++11 and Python', author='Wenzel Jakob', author_email='wenzel.jakob@epfl.ch', - url='https://github.com/wjakob/pybind11', - download_url='https://github.com/wjakob/pybind11/tarball/v' + __version__, + url='https://github.com/pybind11/pybind11', + download_url='https://github.com/pybind11/pybind11/tarball/v' + __version__, packages=['pybind11'], license='BSD', headers=headers, diff --git a/tests/test_builtin_casters.cpp b/tests/test_builtin_casters.cpp index b73e96ea57..e5413c2ccc 100644 --- a/tests/test_builtin_casters.cpp +++ b/tests/test_builtin_casters.cpp @@ -50,7 +50,9 @@ TEST_SUBMODULE(builtin_casters, m) { // test_single_char_arguments m.attr("wchar_size") = py::cast(sizeof(wchar_t)); m.def("ord_char", [](char c) -> int { return static_cast(c); }); + m.def("ord_char_lv", [](char &c) -> int { return static_cast(c); }); m.def("ord_char16", [](char16_t c) -> uint16_t { return c; }); + m.def("ord_char16_lv", [](char16_t &c) -> uint16_t { return c; }); m.def("ord_char32", [](char32_t c) -> uint32_t { return c; }); m.def("ord_wchar", [](wchar_t c) -> int { return c; }); diff --git a/tests/test_builtin_casters.py b/tests/test_builtin_casters.py index bc094a3815..2f311f152f 100644 --- a/tests/test_builtin_casters.py +++ b/tests/test_builtin_casters.py @@ -44,6 +44,7 @@ def toobig_message(r): toolong_message = "Expected a character, but multi-character string found" assert m.ord_char(u'a') == 0x61 # simple ASCII + assert m.ord_char_lv(u'b') == 0x62 assert m.ord_char(u'é') == 0xE9 # requires 2 bytes in utf-8, but can be stuffed in a char with pytest.raises(ValueError) as excinfo: assert m.ord_char(u'Ā') == 0x100 # requires 2 bytes, doesn't fit in a char @@ -54,9 +55,11 @@ def toobig_message(r): assert m.ord_char16(u'a') == 0x61 assert m.ord_char16(u'é') == 0xE9 + assert m.ord_char16_lv(u'ê') == 0xEA assert m.ord_char16(u'Ā') == 0x100 assert m.ord_char16(u'‽') == 0x203d assert m.ord_char16(u'♥') == 0x2665 + assert m.ord_char16_lv(u'♡') == 0x2661 with pytest.raises(ValueError) as excinfo: assert m.ord_char16(u'🎂') == 0x1F382 # requires surrogate pair assert str(excinfo.value) == toobig_message(0x10000) diff --git a/tests/test_call_policies.cpp b/tests/test_call_policies.cpp index 8642188f9e..81fb1702a5 100644 --- a/tests/test_call_policies.cpp +++ b/tests/test_call_policies.cpp @@ -8,6 +8,7 @@ */ #include "pybind11_tests.h" +#include "constructor_stats.h" struct CustomGuard { static bool enabled; @@ -59,6 +60,21 @@ TEST_SUBMODULE(call_policies, m) { .def("returnNullChildKeepAliveChild", &Parent::returnNullChild, py::keep_alive<1, 0>()) .def("returnNullChildKeepAliveParent", &Parent::returnNullChild, py::keep_alive<0, 1>()); + // test_keep_alive_single + m.def("add_patient", [](py::object /*nurse*/, py::object /*patient*/) { }, py::keep_alive<1, 2>()); + m.def("get_patients", [](py::object nurse) { + py::list patients; + for (PyObject *p : pybind11::detail::get_internals().patients[nurse.ptr()]) + patients.append(py::reinterpret_borrow(p)); + return patients; + }); + m.def("refcount", [](py::handle h) { +#ifdef PYPY_VERSION + ConstructorStats::gc(); // PyPy doesn't update ref counts until GC occurs +#endif + return h.ref_count(); + }); + #if !defined(PYPY_VERSION) // test_alive_gc class ParentGC : public Parent { diff --git a/tests/test_call_policies.py b/tests/test_call_policies.py index 7c835599c2..8d64afdb7f 100644 --- a/tests/test_call_policies.py +++ b/tests/test_call_policies.py @@ -1,6 +1,6 @@ import pytest from pybind11_tests import call_policies as m -from pybind11_tests import ConstructorStats +from pybind11_tests import ConstructorStats, UserType def test_keep_alive_argument(capture): @@ -69,6 +69,35 @@ def test_keep_alive_return_value(capture): """ +def test_keep_alive_single(): + """Issue #1251 - patients are stored multiple times when given to the same nurse""" + + nurse, p1, p2 = UserType(), UserType(), UserType() + b = m.refcount(nurse) + assert [m.refcount(nurse), m.refcount(p1), m.refcount(p2)] == [b, b, b] + m.add_patient(nurse, p1) + assert m.get_patients(nurse) == [p1, ] + assert [m.refcount(nurse), m.refcount(p1), m.refcount(p2)] == [b, b + 1, b] + m.add_patient(nurse, p1) + assert m.get_patients(nurse) == [p1, ] + assert [m.refcount(nurse), m.refcount(p1), m.refcount(p2)] == [b, b + 1, b] + m.add_patient(nurse, p1) + assert m.get_patients(nurse) == [p1, ] + assert [m.refcount(nurse), m.refcount(p1), m.refcount(p2)] == [b, b + 1, b] + m.add_patient(nurse, p2) + assert m.get_patients(nurse) == [p1, p2] + assert [m.refcount(nurse), m.refcount(p1), m.refcount(p2)] == [b, b + 1, b + 1] + m.add_patient(nurse, p2) + assert m.get_patients(nurse) == [p1, p2] + assert [m.refcount(nurse), m.refcount(p1), m.refcount(p2)] == [b, b + 1, b + 1] + m.add_patient(nurse, p2) + m.add_patient(nurse, p1) + assert m.get_patients(nurse) == [p1, p2] + assert [m.refcount(nurse), m.refcount(p1), m.refcount(p2)] == [b, b + 1, b + 1] + del nurse + assert [m.refcount(p1), m.refcount(p2)] == [b, b] + + # https://bitbucket.org/pypy/pypy/issues/2447 @pytest.unsupported_on_pypy def test_alive_gc(capture): diff --git a/tests/test_class.cpp b/tests/test_class.cpp index 2221906170..f0b5873dfd 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -10,6 +10,16 @@ #include "pybind11_tests.h" #include "constructor_stats.h" #include "local_bindings.h" +#include + +// test_brace_initialization +struct NoBraceInitialization { + NoBraceInitialization(std::vector v) : vec{std::move(v)} {} + template + NoBraceInitialization(std::initializer_list l) : vec(l) {} + + std::vector vec; +}; TEST_SUBMODULE(class_, m) { // test_instance @@ -291,6 +301,12 @@ TEST_SUBMODULE(class_, m) { .def(py::init()) .def_readwrite("field1", &BraceInitialization::field1) .def_readwrite("field2", &BraceInitialization::field2); + // We *don't* want to construct using braces when the given constructor argument maps to a + // constructor, because brace initialization could go to the wrong place (in particular when + // there is also an `initializer_list`-accept constructor): + py::class_(m, "NoBraceInitialization") + .def(py::init>()) + .def_readonly("vec", &NoBraceInitialization::vec); // test_reentrant_implicit_conversion_failure // #1035: issue with runaway reentrant implicit conversion @@ -302,6 +318,21 @@ TEST_SUBMODULE(class_, m) { .def(py::init()); py::implicitly_convertible(); + + // test_qualname + // #1166: nested class docstring doesn't show nested name + // Also related: tests that __qualname__ is set properly + struct NestBase {}; + struct Nested {}; + py::class_ base(m, "NestBase"); + base.def(py::init<>()); + py::class_(base, "Nested") + .def(py::init<>()) + .def("fn", [](Nested &, int, NestBase &, Nested &) {}) + .def("fa", [](Nested &, int, NestBase &, Nested &) {}, + "a"_a, "b"_a, "c"_a); + base.def("g", [](NestBase &, Nested &) {}); + base.def("h", []() { return NestBase(); }); } template class BreaksBase { public: virtual ~BreaksBase() = default; }; diff --git a/tests/test_class.py b/tests/test_class.py index 412d6798e9..8cf4757cbd 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -44,6 +44,31 @@ def test_docstrings(doc): """ +def test_qualname(doc): + """Tests that a properly qualified name is set in __qualname__ (even in pre-3.3, where we + backport the attribute) and that generated docstrings properly use it and the module name""" + assert m.NestBase.__qualname__ == "NestBase" + assert m.NestBase.Nested.__qualname__ == "NestBase.Nested" + + assert doc(m.NestBase.__init__) == """ + __init__(self: m.class_.NestBase) -> None + """ + assert doc(m.NestBase.g) == """ + g(self: m.class_.NestBase, arg0: m.class_.NestBase.Nested) -> None + """ + assert doc(m.NestBase.Nested.__init__) == """ + __init__(self: m.class_.NestBase.Nested) -> None + """ + assert doc(m.NestBase.Nested.fn) == """ + fn(self: m.class_.NestBase.Nested, arg0: int, arg1: m.class_.NestBase, arg2: m.class_.NestBase.Nested) -> None + """ # noqa: E501 line too long + assert doc(m.NestBase.Nested.fa) == """ + fa(self: m.class_.NestBase.Nested, a: int, b: m.class_.NestBase, c: m.class_.NestBase.Nested) -> None + """ # noqa: E501 line too long + assert m.NestBase.__module__ == "pybind11_tests.class_" + assert m.NestBase.Nested.__module__ == "pybind11_tests.class_" + + def test_inheritance(msg): roger = m.Rabbit('Rabbit') assert roger.name() + " is a " + roger.species() == "Rabbit is a parrot" @@ -203,6 +228,12 @@ def test_brace_initialization(): assert a.field1 == 123 assert a.field2 == "test" + # Tests that a non-simple class doesn't get brace initialization (if the + # class defines an initializer_list constructor, in particular, it would + # win over the expected constructor). + b = m.NoBraceInitialization([123, 456]) + assert b.vec == [123, 456] + @pytest.unsupported_on_pypy def test_class_refcount(): @@ -229,7 +260,9 @@ def test_reentrant_implicit_conversion_failure(msg): # ensure that there is no runaway reentrant implicit conversion (#1035) with pytest.raises(TypeError) as excinfo: m.BogusImplicitConversion(0) - assert msg(excinfo.value) == '''__init__(): incompatible constructor arguments. The following argument types are supported: - 1. m.class_.BogusImplicitConversion(arg0: m.class_.BogusImplicitConversion) + assert msg(excinfo.value) == ''' + __init__(): incompatible constructor arguments. The following argument types are supported: + 1. m.class_.BogusImplicitConversion(arg0: m.class_.BogusImplicitConversion) -Invoked with: 0''' + Invoked with: 0 + ''' diff --git a/tests/test_eigen.cpp b/tests/test_eigen.cpp index 17b156ce4b..22141df020 100644 --- a/tests/test_eigen.cpp +++ b/tests/test_eigen.cpp @@ -11,6 +11,11 @@ #include "constructor_stats.h" #include #include + +#if defined(_MSC_VER) +# pragma warning(disable: 4996) // C4996: std::unary_negation is deprecated +#endif + #include using MatrixXdR = Eigen::Matrix; @@ -288,6 +293,13 @@ TEST_SUBMODULE(eigen, m) { m.def("iss738_f1", &adjust_matrix &>, py::arg().noconvert()); m.def("iss738_f2", &adjust_matrix> &>, py::arg().noconvert()); + // test_issue1105 + // Issue #1105: when converting from a numpy two-dimensional (Nx1) or (1xN) value into a dense + // eigen Vector or RowVector, the argument would fail to load because the numpy copy would fail: + // numpy won't broadcast a Nx1 into a 1-dimensional vector. + m.def("iss1105_col", [](Eigen::VectorXd) { return true; }); + m.def("iss1105_row", [](Eigen::RowVectorXd) { return true; }); + // test_named_arguments // Make sure named arguments are working properly: m.def("matrix_multiply", [](const py::EigenDRef A, const py::EigenDRef B) diff --git a/tests/test_eigen.py b/tests/test_eigen.py index 4ac8cbf5da..64fb2e5ade 100644 --- a/tests/test_eigen.py +++ b/tests/test_eigen.py @@ -181,8 +181,7 @@ def test_negative_stride_from_python(msg): double_threer(): incompatible function arguments. The following argument types are supported: 1. (arg0: numpy.ndarray[float32[1, 3], flags.writeable]) -> None - Invoked with: array([ 5., 4., 3.], dtype=float32) - """ # noqa: E501 line too long + Invoked with: """ + repr(np.array([ 5., 4., 3.], dtype='float32')) # noqa: E501 line too long with pytest.raises(TypeError) as excinfo: m.double_threec(second_col) @@ -190,8 +189,7 @@ def test_negative_stride_from_python(msg): double_threec(): incompatible function arguments. The following argument types are supported: 1. (arg0: numpy.ndarray[float32[3, 1], flags.writeable]) -> None - Invoked with: array([ 7., 4., 1.], dtype=float32) - """ # noqa: E501 line too long + Invoked with: """ + repr(np.array([ 7., 4., 1.], dtype='float32')) # noqa: E501 line too long def test_nonunit_stride_to_python(): @@ -672,6 +670,21 @@ def test_issue738(): assert np.all(m.iss738_f2(np.array([[1.], [2], [3]])) == np.array([[1.], [12], [23]])) +def test_issue1105(): + """Issue 1105: 1xN or Nx1 input arrays weren't accepted for eigen + compile-time row vectors or column vector""" + assert m.iss1105_row(np.ones((1, 7))) + assert m.iss1105_col(np.ones((7, 1))) + + # These should still fail (incompatible dimensions): + with pytest.raises(TypeError) as excinfo: + m.iss1105_row(np.ones((7, 1))) + assert "incompatible function arguments" in str(excinfo) + with pytest.raises(TypeError) as excinfo: + m.iss1105_col(np.ones((1, 7))) + assert "incompatible function arguments" in str(excinfo) + + def test_custom_operator_new(): """Using Eigen types as member variables requires a class-specific operator new with proper alignment""" diff --git a/tests/test_embed/CMakeLists.txt b/tests/test_embed/CMakeLists.txt index 0a43e0e22e..8b4f1f843e 100644 --- a/tests/test_embed/CMakeLists.txt +++ b/tests/test_embed/CMakeLists.txt @@ -5,7 +5,9 @@ if(${PYTHON_MODULE_EXTENSION} MATCHES "pypy") endif() find_package(Catch 1.9.3) -if(NOT CATCH_FOUND) +if(CATCH_FOUND) + message(STATUS "Building interpreter tests using Catch v${CATCH_VERSION}") +else() message(STATUS "Catch not detected. Interpreter tests will be skipped. Install Catch headers" " manually or use `cmake -DDOWNLOAD_CATCH=1` to fetch them automatically.") return() @@ -31,4 +33,9 @@ target_link_libraries(test_embed PUBLIC ${CMAKE_THREAD_LIBS_INIT}) add_custom_target(cpptest COMMAND $ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +pybind11_add_module(external_module THIN_LTO external_module.cpp) +set_target_properties(external_module PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +add_dependencies(cpptest external_module) + add_dependencies(check cpptest) diff --git a/tests/test_embed/catch.cpp b/tests/test_embed/catch.cpp index cface485df..236409dbd6 100644 --- a/tests/test_embed/catch.cpp +++ b/tests/test_embed/catch.cpp @@ -3,6 +3,12 @@ #include +#ifdef _MSC_VER +// Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to catch +// 2.0.1; this should be fixed in the next catch release after 2.0.1). +# pragma warning(disable: 4996) +#endif + #define CATCH_CONFIG_RUNNER #include diff --git a/tests/test_embed/external_module.cpp b/tests/test_embed/external_module.cpp new file mode 100644 index 0000000000..e9a6058b17 --- /dev/null +++ b/tests/test_embed/external_module.cpp @@ -0,0 +1,23 @@ +#include + +namespace py = pybind11; + +/* Simple test module/test class to check that the referenced internals data of external pybind11 + * modules aren't preserved over a finalize/initialize. + */ + +PYBIND11_MODULE(external_module, m) { + class A { + public: + A(int value) : v{value} {}; + int v; + }; + + py::class_(m, "A") + .def(py::init()) + .def_readwrite("value", &A::v); + + m.def("internals_at", []() { + return reinterpret_cast(&py::detail::get_internals()); + }); +} diff --git a/tests/test_embed/test_interpreter.cpp b/tests/test_embed/test_interpreter.cpp index 6b5f051f29..222bd565fb 100644 --- a/tests/test_embed/test_interpreter.cpp +++ b/tests/test_embed/test_interpreter.cpp @@ -1,4 +1,11 @@ #include + +#ifdef _MSC_VER +// Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to catch +// 2.0.1; this should be fixed in the next catch release after 2.0.1). +# pragma warning(disable: 4996) +#endif + #include #include @@ -94,7 +101,8 @@ bool has_pybind11_internals_builtin() { }; bool has_pybind11_internals_static() { - return py::detail::get_internals_ptr() != nullptr; + auto **&ipp = py::detail::get_internals_pp(); + return ipp && *ipp; } TEST_CASE("Restart the interpreter") { @@ -102,6 +110,11 @@ TEST_CASE("Restart the interpreter") { REQUIRE(py::module::import("widget_module").attr("add")(1, 2).cast() == 3); REQUIRE(has_pybind11_internals_builtin()); REQUIRE(has_pybind11_internals_static()); + REQUIRE(py::module::import("external_module").attr("A")(123).attr("value").cast() == 123); + + // local and foreign module internals should point to the same internals: + REQUIRE(reinterpret_cast(*py::detail::get_internals_pp()) == + py::module::import("external_module").attr("internals_at")().cast()); // Restart the interpreter. py::finalize_interpreter(); @@ -116,6 +129,8 @@ TEST_CASE("Restart the interpreter") { pybind11::detail::get_internals(); REQUIRE(has_pybind11_internals_builtin()); REQUIRE(has_pybind11_internals_static()); + REQUIRE(reinterpret_cast(*py::detail::get_internals_pp()) == + py::module::import("external_module").attr("internals_at")().cast()); // Make sure that an interpreter with no get_internals() created until finalize still gets the // internals destroyed diff --git a/tests/test_exceptions.cpp b/tests/test_exceptions.cpp index ae28abb488..cf202143dd 100644 --- a/tests/test_exceptions.cpp +++ b/tests/test_exceptions.cpp @@ -9,7 +9,7 @@ #include "pybind11_tests.h" -// A type that should be raised as an exeption in Python +// A type that should be raised as an exception in Python class MyException : public std::exception { public: explicit MyException(const char * m) : message{m} {} diff --git a/tests/test_factory_constructors.cpp b/tests/test_factory_constructors.cpp index fb33377b29..687a5bf43f 100644 --- a/tests/test_factory_constructors.cpp +++ b/tests/test_factory_constructors.cpp @@ -13,7 +13,7 @@ #include // Classes for testing python construction via C++ factory function: -// Not publically constructible, copyable, or movable: +// Not publicly constructible, copyable, or movable: class TestFactory1 { friend class TestFactoryHelper; TestFactory1() : value("(empty)") { print_default_created(this); } diff --git a/tests/test_kwargs_and_defaults.cpp b/tests/test_kwargs_and_defaults.cpp index 165f8017e8..2263b6b7a4 100644 --- a/tests/test_kwargs_and_defaults.cpp +++ b/tests/test_kwargs_and_defaults.cpp @@ -8,6 +8,7 @@ */ #include "pybind11_tests.h" +#include "constructor_stats.h" #include TEST_SUBMODULE(kwargs_and_defaults, m) { @@ -53,6 +54,34 @@ TEST_SUBMODULE(kwargs_and_defaults, m) { m.def("mixed_plus_args_kwargs_defaults", mixed_plus_both, py::arg("i") = 1, py::arg("j") = 3.14159); + // test_args_refcount + // PyPy needs a garbage collection to get the reference count values to match CPython's behaviour + #ifdef PYPY_VERSION + #define GC_IF_NEEDED ConstructorStats::gc() + #else + #define GC_IF_NEEDED + #endif + m.def("arg_refcount_h", [](py::handle h) { GC_IF_NEEDED; return h.ref_count(); }); + m.def("arg_refcount_h", [](py::handle h, py::handle, py::handle) { GC_IF_NEEDED; return h.ref_count(); }); + m.def("arg_refcount_o", [](py::object o) { GC_IF_NEEDED; return o.ref_count(); }); + m.def("args_refcount", [](py::args a) { + GC_IF_NEEDED; + py::tuple t(a.size()); + for (size_t i = 0; i < a.size(); i++) + // Use raw Python API here to avoid an extra, intermediate incref on the tuple item: + t[i] = (int) Py_REFCNT(PyTuple_GET_ITEM(a.ptr(), static_cast(i))); + return t; + }); + m.def("mixed_args_refcount", [](py::object o, py::args a) { + GC_IF_NEEDED; + py::tuple t(a.size() + 1); + t[0] = o.ref_count(); + for (size_t i = 0; i < a.size(); i++) + // Use raw Python API here to avoid an extra, intermediate incref on the tuple item: + t[i + 1] = (int) Py_REFCNT(PyTuple_GET_ITEM(a.ptr(), static_cast(i))); + return t; + }); + // pybind11 won't allow these to be bound: args and kwargs, if present, must be at the end. // Uncomment these to test that the static_assert is indeed working: // m.def("bad_args1", [](py::args, int) {}); diff --git a/tests/test_kwargs_and_defaults.py b/tests/test_kwargs_and_defaults.py index 733fe8593f..269587656c 100644 --- a/tests/test_kwargs_and_defaults.py +++ b/tests/test_kwargs_and_defaults.py @@ -105,3 +105,43 @@ def test_mixed_args_and_kwargs(msg): Invoked with: 1, 2; kwargs: j=1 """ # noqa: E501 line too long + + +def test_args_refcount(): + """Issue/PR #1216 - py::args elements get double-inc_ref()ed when combined with regular + arguments""" + refcount = m.arg_refcount_h + + myval = 54321 + expected = refcount(myval) + assert m.arg_refcount_h(myval) == expected + assert m.arg_refcount_o(myval) == expected + 1 + assert m.arg_refcount_h(myval) == expected + assert refcount(myval) == expected + + assert m.mixed_plus_args(1, 2.0, "a", myval) == (1, 2.0, ("a", myval)) + assert refcount(myval) == expected + + assert m.mixed_plus_kwargs(3, 4.0, a=1, b=myval) == (3, 4.0, {"a": 1, "b": myval}) + assert refcount(myval) == expected + + assert m.args_function(-1, myval) == (-1, myval) + assert refcount(myval) == expected + + assert m.mixed_plus_args_kwargs(5, 6.0, myval, a=myval) == (5, 6.0, (myval,), {"a": myval}) + assert refcount(myval) == expected + + assert m.args_kwargs_function(7, 8, myval, a=1, b=myval) == \ + ((7, 8, myval), {"a": 1, "b": myval}) + assert refcount(myval) == expected + + exp3 = refcount(myval, myval, myval) + assert m.args_refcount(myval, myval, myval) == (exp3, exp3, exp3) + assert refcount(myval) == expected + + # This function takes the first arg as a `py::object` and the rest as a `py::args`. Unlike the + # previous case, when we have both positional and `py::args` we need to construct a new tuple + # for the `py::args`; in the previous case, we could simply inc_ref and pass on Python's input + # tuple without having to inc_ref the individual elements, but here we can't, hence the extra + # refs. + assert m.mixed_args_refcount(myval, myval, myval) == (exp3 + 3, exp3 + 3, exp3 + 3) diff --git a/tests/test_numpy_array.py b/tests/test_numpy_array.py index 27433934f0..50848989c3 100644 --- a/tests/test_numpy_array.py +++ b/tests/test_numpy_array.py @@ -137,6 +137,7 @@ def test_make_c_f_array(): def test_wrap(): def assert_references(a, b, base=None): + from distutils.version import LooseVersion if base is None: base = a assert a is not b @@ -147,7 +148,10 @@ def assert_references(a, b, base=None): assert a.flags.f_contiguous == b.flags.f_contiguous assert a.flags.writeable == b.flags.writeable assert a.flags.aligned == b.flags.aligned - assert a.flags.updateifcopy == b.flags.updateifcopy + if LooseVersion(np.__version__) >= LooseVersion("1.14.0"): + assert a.flags.writebackifcopy == b.flags.writebackifcopy + else: + assert a.flags.updateifcopy == b.flags.updateifcopy assert np.all(a == b) assert not b.flags.owndata assert b.base is base @@ -282,17 +286,17 @@ def test_overload_resolution(msg): 1. (arg0: numpy.ndarray[int32]) -> str 2. (arg0: numpy.ndarray[float64]) -> str - Invoked with:""" + Invoked with: """ with pytest.raises(TypeError) as excinfo: m.overloaded3(np.array([1], dtype='uintc')) - assert msg(excinfo.value) == expected_exc + " array([1], dtype=uint32)" + assert msg(excinfo.value) == expected_exc + repr(np.array([1], dtype='uint32')) with pytest.raises(TypeError) as excinfo: m.overloaded3(np.array([1], dtype='float32')) - assert msg(excinfo.value) == expected_exc + " array([ 1.], dtype=float32)" + assert msg(excinfo.value) == expected_exc + repr(np.array([1.], dtype='float32')) with pytest.raises(TypeError) as excinfo: m.overloaded3(np.array([1], dtype='complex')) - assert msg(excinfo.value) == expected_exc + " array([ 1.+0.j])" + assert msg(excinfo.value) == expected_exc + repr(np.array([1. + 0.j])) # Exact matches: assert m.overloaded4(np.array([1], dtype='double')) == 'double' diff --git a/tests/test_opaque_types.py b/tests/test_opaque_types.py index 2d3aef5d13..6b3802fdba 100644 --- a/tests/test_opaque_types.py +++ b/tests/test_opaque_types.py @@ -4,21 +4,21 @@ def test_string_list(): - l = m.StringList() - l.push_back("Element 1") - l.push_back("Element 2") - assert m.print_opaque_list(l) == "Opaque list: [Element 1, Element 2]" - assert l.back() == "Element 2" + lst = m.StringList() + lst.push_back("Element 1") + lst.push_back("Element 2") + assert m.print_opaque_list(lst) == "Opaque list: [Element 1, Element 2]" + assert lst.back() == "Element 2" - for i, k in enumerate(l, start=1): + for i, k in enumerate(lst, start=1): assert k == "Element {}".format(i) - l.pop_back() - assert m.print_opaque_list(l) == "Opaque list: [Element 1]" + lst.pop_back() + assert m.print_opaque_list(lst) == "Opaque list: [Element 1]" cvp = m.ClassWithSTLVecProperty() assert m.print_opaque_list(cvp.stringList) == "Opaque list: []" - cvp.stringList = l + cvp.stringList = lst cvp.stringList.push_back("Element 3") assert m.print_opaque_list(cvp.stringList) == "Opaque list: [Element 1, Element 3]" diff --git a/tests/test_operator_overloading.py b/tests/test_operator_overloading.py index 0d80e5ed37..86827d2baf 100644 --- a/tests/test_operator_overloading.py +++ b/tests/test_operator_overloading.py @@ -98,7 +98,7 @@ def test_nested(): del c pytest.gc_collect() - del a # Should't delete while abase is still alive + del a # Shouldn't delete while abase is still alive pytest.gc_collect() assert abase.value == 42 diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 94c90a909b..992e7fc8e1 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -7,11 +7,11 @@ def test_list(capture, doc): with capture: - l = m.get_list() - assert l == ["overwritten"] + lst = m.get_list() + assert lst == ["overwritten"] - l.append("value2") - m.print_list(l) + lst.append("value2") + m.print_list(lst) assert capture.unordered == """ Entry at position 0: value list item 0: overwritten diff --git a/tests/test_sequences_and_iterators.py b/tests/test_sequences_and_iterators.py index 640ca07bd5..f6c0620944 100644 --- a/tests/test_sequences_and_iterators.py +++ b/tests/test_sequences_and_iterators.py @@ -131,9 +131,9 @@ def bad_next_call(): m.iterator_to_list(iter(bad_next_call, None)) assert str(excinfo.value) == "py::iterator::advance() should propagate errors" - l = [1, None, 0, None] - assert m.count_none(l) == 2 - assert m.find_none(l) is True + lst = [1, None, 0, None] + assert m.count_none(lst) == 2 + assert m.find_none(lst) is True assert m.count_nonzeros({"a": 0, "b": 1, "c": 2}) == 2 r = range(5) diff --git a/tests/test_stl.py b/tests/test_stl.py index db8515e7a4..fbf95ff06e 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -6,11 +6,11 @@ def test_vector(doc): """std::vector <-> list""" - l = m.cast_vector() - assert l == [1] - l.append(2) - assert m.load_vector(l) - assert m.load_vector(tuple(l)) + lst = m.cast_vector() + assert lst == [1] + lst.append(2) + assert m.load_vector(lst) + assert m.load_vector(tuple(lst)) assert m.cast_bool_vector() == [True, False] assert m.load_bool_vector([True, False]) @@ -24,9 +24,9 @@ def test_vector(doc): def test_array(doc): """std::array <-> list""" - l = m.cast_array() - assert l == [1, 2] - assert m.load_array(l) + lst = m.cast_array() + assert lst == [1, 2] + assert m.load_array(lst) assert doc(m.cast_array) == "cast_array() -> List[int[2]]" assert doc(m.load_array) == "load_array(arg0: List[int[2]]) -> bool" @@ -34,9 +34,9 @@ def test_array(doc): def test_valarray(doc): """std::valarray <-> list""" - l = m.cast_valarray() - assert l == [1, 4, 9] - assert m.load_valarray(l) + lst = m.cast_valarray() + assert lst == [1, 4, 9] + assert m.load_valarray(lst) assert doc(m.cast_valarray) == "cast_valarray() -> List[int]" assert doc(m.load_valarray) == "load_valarray(arg0: List[int]) -> bool" diff --git a/tests/test_stl_binders.py b/tests/test_stl_binders.py index bf1aa674c0..0030924fb7 100644 --- a/tests/test_stl_binders.py +++ b/tests/test_stl_binders.py @@ -181,3 +181,25 @@ def test_noncopyable_containers(): vsum += v.value assert vsum == 150 + + +def test_map_delitem(): + mm = m.MapStringDouble() + mm['a'] = 1 + mm['b'] = 2.5 + + assert list(mm) == ['a', 'b'] + assert list(mm.items()) == [('a', 1), ('b', 2.5)] + del mm['a'] + assert list(mm) == ['b'] + assert list(mm.items()) == [('b', 2.5)] + + um = m.UnorderedMapStringDouble() + um['ua'] = 1.1 + um['ub'] = 2.6 + + assert sorted(list(um)) == ['ua', 'ub'] + assert sorted(list(um.items())) == [('ua', 1.1), ('ub', 2.6)] + del um['ua'] + assert sorted(list(um)) == ['ub'] + assert sorted(list(um.items())) == [('ub', 2.6)] diff --git a/tests/test_virtual_functions.cpp b/tests/test_virtual_functions.cpp index 953b390b88..336f5e45c6 100644 --- a/tests/test_virtual_functions.cpp +++ b/tests/test_virtual_functions.cpp @@ -249,7 +249,7 @@ TEST_SUBMODULE(virtual_functions, m) { m.def("dispatch_issue_go", [](const Base * b) { return b->dispatch(); }); // test_override_ref - // #392/397: overridding reference-returning functions + // #392/397: overriding reference-returning functions class OverrideTest { public: struct A { std::string value = "hi"; }; diff --git a/tests/test_virtual_functions.py b/tests/test_virtual_functions.py index b91ebfa3ec..2a92476f99 100644 --- a/tests/test_virtual_functions.py +++ b/tests/test_virtual_functions.py @@ -227,7 +227,7 @@ def dispatch(self): def test_override_ref(): - """#392/397: overridding reference-returning functions""" + """#392/397: overriding reference-returning functions""" o = m.OverrideTest("asdf") # Not allowed (see associated .cpp comment) diff --git a/tools/FindPythonLibsNew.cmake b/tools/FindPythonLibsNew.cmake index ad3ed48fae..b29b287de7 100644 --- a/tools/FindPythonLibsNew.cmake +++ b/tools/FindPythonLibsNew.cmake @@ -1,5 +1,5 @@ # - Find python libraries -# This module finds the libraries corresponding to the Python interpeter +# This module finds the libraries corresponding to the Python interpreter # FindPythonInterp provides. # This code sets the following variables: # diff --git a/tools/check-style.sh b/tools/check-style.sh index a9eeb170ba..0a9f7d24fc 100755 --- a/tools/check-style.sh +++ b/tools/check-style.sh @@ -10,7 +10,7 @@ # 4. missing space between keyword and parenthesis, e.g.: for(, if(, while( # 5. Missing space between right parenthesis and brace, e.g. 'for (...){' # 6. opening brace on its own line. It should always be on the same line as the -# if/while/for/do statment. +# if/while/for/do statement. # # Invoke as: tools/check-style.sh #