diff --git a/.gitignore b/.gitignore index 979fd4431b..244bbcaa7a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ cmake_install.cmake *.sdf *.opensdf *.vcxproj +*.vcxproj.user *.filters example.dir Win32 diff --git a/.travis.yml b/.travis.yml index 0e98a2c59c..cd5c80a032 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,8 @@ matrix: - $PY_CMD -m pip install --user --upgrade pip wheel setuptools install: # 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 + # Latest breathe + Sphinx causes warnings and errors out + - $PY_CMD -m pip install --user --upgrade "sphinx<3" 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: @@ -112,7 +113,7 @@ matrix: - 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 + name: Python 3.8, c++17, gcc 7 addons: apt: sources: @@ -122,13 +123,21 @@ matrix: - 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.8 - - $PY_CMD -m ensurepip --user - - $PY_CMD -m pip install --user --upgrade pip wheel setuptools + - os: linux + dist: xenial + env: PYTHON=3.9 CPP=17 GCC=7 + name: Python 3.9 beta, 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.9-dev + - python3.9-venv + # Currently there are no numpy/scipy wheels available for python3.9 + # TODO: remove next install and script clause when the wheels become available install: - $PY_CMD -m pip install --user --upgrade pytest script: @@ -145,18 +154,28 @@ matrix: name: Python 3.7, c++14, AppleClang 9, Debug build osx_image: xcode9.4 env: PYTHON=3.7 CPP=14 CLANG DEBUG=1 - # WARNING: This current fails on download. Should try to upgrade OS + Python version? - # # Test a PyPy 2.7 build - # - os: linux - # dist: trusty - # env: PYPY=5.8 PYTHON=2.7 CPP=11 GCC=4.8 - # name: PyPy 5.8, Python 2.7, c++11, gcc 4.8 - # addons: - # apt: - # packages: - # - libblas-dev - # - liblapack-dev - # - gfortran + # Test a PyPy 2.7 build + - os: linux + dist: trusty + env: PYPY=7.3.1 PYTHON=2.7 CPP=11 GCC=4.8 + name: PyPy 7.3, Python 2.7, c++11, gcc 4.8 + addons: + apt: + packages: + - libblas-dev + - liblapack-dev + - gfortran + - os: linux + dist: xenial + env: PYPY=7.3.1 PYTHON=3.6 CPP=11 GCC=5 + name: PyPy 7.3, Python 3.6, c++11, gcc 5 + addons: + apt: + packages: + - libblas-dev + - liblapack-dev + - gfortran + - g++-5 # Build in 32-bit mode and tests against the CMake-installed version - os: linux dist: trusty @@ -176,6 +195,10 @@ matrix: cmake ../pybind11-tests ${CMAKE_EXTRA_ARGS} -DPYBIND11_WERROR=ON make pytest -j 2" set +ex + allow_failures: + - name: PyPy 7.3, Python 2.7, c++11, gcc 4.8 + - name: PyPy 7.3, Python 3.6, c++11, gcc 5 + - name: Python 3.9 beta, c++17, gcc 7 (w/o numpy/scipy) cache: directories: - $HOME/.local/bin @@ -217,9 +240,9 @@ before_install: SCRIPT_RUN_PREFIX="docker exec --tty $containerid" $SCRIPT_RUN_PREFIX sh -c 'for s in 0 15; do sleep $s; apt-get update && apt-get -qy dist-upgrade && break; done' else - 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) + if [ -n "$PYPY" ]; then + curl -fSL https://bitbucket.org/pypy/pypy/downloads/pypy$PYTHON-v$PYPY-linux64.tar.bz2 | tar xj + PY_CMD=$(echo `pwd`/pypy$PYTHON-v$PYPY-linux64/bin/pypy$PY) CMAKE_EXTRA_ARGS+=" -DPYTHON_EXECUTABLE:FILEPATH=$PY_CMD" else PY_CMD=python$PYTHON @@ -261,11 +284,12 @@ install: export NPY_NUM_BUILD_JOBS=2 echo "Installing pytest, numpy, scipy..." local PIP_CMD="" - if [ -n $PYPY ]; then + if [ -n "$PYPY" ]; then # For expediency, install only versions that are available on the extra index. travis_wait 30 \ - $PY_CMD -m pip install --user --upgrade --extra-index-url https://imaginary.ca/trusty-pypi \ - pytest numpy==1.15.4 scipy==1.2.0 + $PY_CMD -m pip install --user --upgrade --extra-index-url https://antocuni.github.io/pypy-wheels/manylinux2010 \ + numpy scipy + $PY_CMD -m pip install --user --upgrade pytest else $PY_CMD -m pip install --user --upgrade pytest numpy scipy fi diff --git a/docs/advanced/classes.rst b/docs/advanced/classes.rst index 243d5e5898..3053ead692 100644 --- a/docs/advanced/classes.rst +++ b/docs/advanced/classes.rst @@ -784,13 +784,17 @@ An instance can now be pickled as follows: p.setExtra(15) data = pickle.dumps(p, 2) -Note that only the cPickle module is supported on Python 2.7. The second -argument to ``dumps`` is also crucial: it selects the pickle protocol version -2, since the older version 1 is not supported. Newer versions are also fine—for -instance, specify ``-1`` to always use the latest available version. Beware: -failure to follow these instructions will cause important pybind11 memory -allocation routines to be skipped during unpickling, which will likely lead to -memory corruption and/or segmentation faults. + +.. note:: + Note that only the cPickle module is supported on Python 2.7. + + The second argument to ``dumps`` is also crucial: it selects the pickle + protocol version 2, since the older version 1 is not supported. Newer + versions are also fine—for instance, specify ``-1`` to always use the + latest available version. Beware: failure to follow these instructions + will cause important pybind11 memory allocation routines to be skipped + during unpickling, which will likely lead to memory corruption and/or + segmentation faults. .. seealso:: @@ -800,6 +804,38 @@ memory corruption and/or segmentation faults. .. [#f3] http://docs.python.org/3/library/pickle.html#pickling-class-instances +Deepcopy support +================ + +Python normally uses references in assignments. Sometimes a real copy is needed +to prevent changing all copies. The ``copy`` module [#f5]_ provides these +capabilities. + +On Python 3, a class with pickle support is automatically also (deep)copy +compatible. However, performance can be improved by adding custom +``__copy__`` and ``__deepcopy__`` methods. With Python 2.7, these custom methods +are mandatory for (deep)copy compatibility, because pybind11 only supports +cPickle. + +For simple classes (deep)copy can be enabled by using the copy constructor, +which should look as follows: + +.. code-block:: cpp + + py::class_(m, "Copyable") + .def("__copy__", [](const Copyable &self) { + return Copyable(self); + }) + .def("__deepcopy__", [](const Copyable &self, py::dict) { + return Copyable(self); + }, "memo"_a); + +.. note:: + + Dynamic attributes will not be copied in this example. + +.. [#f5] https://docs.python.org/3/library/copy.html + Multiple Inheritance ==================== @@ -1058,6 +1094,32 @@ described trampoline: ``.def("foo", static_cast(&Publicist::foo));`` where ``int (A::*)() const`` is the type of ``A::foo``. +Binding final classes +===================== + +Some classes may not be appropriate to inherit from. In C++11, classes can +use the ``final`` specifier to ensure that a class cannot be inherited from. +The ``py::is_final`` attribute can be used to ensure that Python classes +cannot inherit from a specified type. The underlying C++ type does not need +to be declared final. + +.. code-block:: cpp + + class IsFinal final {}; + + py::class_(m, "IsFinal", py::is_final()); + +When you try to inherit from such a class in Python, you will now get this +error: + +.. code-block:: pycon + + >>> class PyFinalChild(IsFinal): + ... pass + TypeError: type 'IsFinal' is not an acceptable base type + +.. note:: This attribute is currently ignored on PyPy + Custom automatic downcasters ============================ diff --git a/docs/advanced/functions.rst b/docs/advanced/functions.rst index 3e1a3ff0e8..984b046efa 100644 --- a/docs/advanced/functions.rst +++ b/docs/advanced/functions.rst @@ -362,6 +362,34 @@ like so: py::class_("MyClass") .def("myFunction", py::arg("arg") = (SomeType *) nullptr); +Keyword-only arguments +====================== + +Python 3 introduced keyword-only arguments by specifying an unnamed ``*`` +argument in a function definition: + +.. code-block:: python + + def f(a, *, b): # a can be positional or via keyword; b must be via keyword + pass + + f(a=1, b=2) # good + f(b=2, a=1) # good + f(1, b=2) # good + f(1, 2) # TypeError: f() takes 1 positional argument but 2 were given + +Pybind11 provides a ``py::kwonly`` object that allows you to implement +the same behaviour by specifying the object between positional and keyword-only +argument annotations when registering the function: + +.. code-block:: cpp + + m.def("f", [](int a, int b) { /* ... */ }, + py::arg("a"), py::kwonly(), py::arg("b")); + +Note that, as in Python, you cannot combine this with a ``py::args`` argument. +This feature does *not* require Python 3 to work. + .. _nonconverting_arguments: Non-converting arguments diff --git a/docs/changelog.rst b/docs/changelog.rst index d65c2d8000..2def2b0719 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,6 +6,51 @@ Changelog Starting with version 1.8.0, pybind11 releases use a `semantic versioning `_ policy. +v2.5.0 (Mar 31, 2020) +----------------------------------------------------- + +* Use C++17 fold expressions in type casters, if available. This can + improve performance during overload resolution when functions have + multiple arguments. + `#2043 `_. + +* Changed include directory resolution in ``pybind11/__init__.py`` + and installation in ``setup.py``. This fixes a number of open issues + where pybind11 headers could not be found in certain environments. + `#1995 `_. + +* C++20 ``char8_t`` and ``u8string`` support. `#2026 + `_. + +* CMake: search for Python 3.9. `bb9c91 + `_. + +* Fixes for MSYS-based build environments. + `#2087 `_, + `#2053 `_. + +* STL bindings for ``std::vector<...>::clear``. `#2074 + `_. + +* Read-only flag for ``py::buffer``. `#1466 + `_. + +* Exception handling during module initialization. + `bf2b031 `_. + +* Support linking against a CPython debug build. + `#2025 `_. + +* Fixed issues involving the availability and use of aligned ``new`` and + ``delete``. `#1988 `_, + `759221 `_. + +* Fixed a resource leak upon interpreter shutdown. + `#2020 `_. + +* Fixed error handling in the boolean caster. + `#1976 `_. + v2.4.3 (Oct 15, 2019) ----------------------------------------------------- diff --git a/docs/compiling.rst b/docs/compiling.rst index da569f986a..f3b6332c9f 100644 --- a/docs/compiling.rst +++ b/docs/compiling.rst @@ -105,18 +105,14 @@ on the target compiler, falling back to C++11 if C++14 support is not available. Note, however, that this default is subject to change: future pybind11 releases are expected to migrate to newer C++ standards as they become available. To override this, the standard flag can be given explicitly in -``PYBIND11_CPP_STANDARD``: +`CMAKE_CXX_STANDARD `_: .. code-block:: cmake # Use just one of these: - # GCC/clang: - set(PYBIND11_CPP_STANDARD -std=c++11) - set(PYBIND11_CPP_STANDARD -std=c++14) - set(PYBIND11_CPP_STANDARD -std=c++1z) # Experimental C++17 support - # MSVC: - set(PYBIND11_CPP_STANDARD /std:c++14) - set(PYBIND11_CPP_STANDARD /std:c++latest) # Enables some MSVC C++17 features + set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD 14) + set(CMAKE_CXX_STANDARD 17) # Experimental C++17 support add_subdirectory(pybind11) # or find_package(pybind11) @@ -292,3 +288,11 @@ code by introspecting existing C++ codebases using LLVM/Clang. See the [binder]_ documentation for details. .. [binder] http://cppbinder.readthedocs.io/en/latest/about.html + +[AutoWIG]_ is a Python library that wraps automatically compiled libraries into +high-level languages. It parses C++ code using LLVM/Clang technologies and +generates the wrappers using the Mako templating engine. The approach is automatic, +extensible, and applies to very complex C++ libraries, composed of thousands of +classes or incorporating modern meta-programming constructs. + +.. [AutoWIG] https://github.com/StatisKit/AutoWIG diff --git a/docs/conf.py b/docs/conf.py index a1e4e00583..585987ec6d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,9 +61,9 @@ # built documents. # # The short X.Y version. -version = '2.4' +version = '2.5' # The full version, including alpha/beta/rc tags. -release = '2.4.dev4' +release = '2.5.dev1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h index 7fd5a9eb49..95301fb350 100644 --- a/include/pybind11/attr.h +++ b/include/pybind11/attr.h @@ -23,6 +23,9 @@ struct is_method { handle class_; is_method(const handle &c) : class_(c) { } }; /// Annotation for operators struct is_operator { }; +/// Annotation for classes that cannot be subclassed +struct is_final { }; + /// Annotation for parent scope struct scope { handle value; scope(const handle &s) : value(s) { } }; @@ -134,7 +137,8 @@ struct argument_record { struct function_record { function_record() : is_constructor(false), is_new_style_constructor(false), is_stateless(false), - is_operator(false), has_args(false), has_kwargs(false), is_method(false) { } + is_operator(false), is_method(false), + has_args(false), has_kwargs(false), has_kwonly_args(false) { } /// Function name char *name = nullptr; /* why no C++ strings? They generate heavier code.. */ @@ -172,18 +176,24 @@ struct function_record { /// True if this is an operator (__add__), etc. bool is_operator : 1; + /// True if this is a method + bool is_method : 1; + /// True if the function has a '*args' argument bool has_args : 1; /// True if the function has a '**kwargs' argument bool has_kwargs : 1; - /// True if this is a method - bool is_method : 1; + /// True once a 'py::kwonly' is encountered (any following args are keyword-only) + bool has_kwonly_args : 1; /// Number of arguments (including py::args and/or py::kwargs, if present) std::uint16_t nargs; + /// Number of trailing arguments (counted in `nargs`) that are keyword-only + std::uint16_t nargs_kwonly = 0; + /// Python method object PyMethodDef *def = nullptr; @@ -201,7 +211,7 @@ struct function_record { struct type_record { PYBIND11_NOINLINE type_record() : multiple_inheritance(false), dynamic_attr(false), buffer_protocol(false), - default_holder(true), module_local(false) { } + default_holder(true), module_local(false), is_final(false) { } /// Handle to the parent scope handle scope; @@ -257,6 +267,9 @@ struct type_record { /// Is the class definition local to the module shared object? bool module_local : 1; + /// Is the class inheritable from python classes? + bool is_final : 1; + PYBIND11_NOINLINE void add_base(const std::type_info &base, void *(*caster)(void *)) { auto base_info = detail::get_type_info(base, false); if (!base_info) { @@ -356,12 +369,20 @@ template <> struct process_attribute : process_attribu static void init(const is_new_style_constructor &, function_record *r) { r->is_new_style_constructor = true; } }; +inline void process_kwonly_arg(const arg &a, function_record *r) { + if (!a.name || strlen(a.name) == 0) + pybind11_fail("arg(): cannot specify an unnamed argument after an kwonly() annotation"); + ++r->nargs_kwonly; +} + /// Process a keyword argument attribute (*without* a default value) template <> struct process_attribute : process_attribute_default { static void init(const arg &a, function_record *r) { if (r->is_method && r->args.empty()) r->args.emplace_back("self", nullptr, handle(), true /*convert*/, false /*none not allowed*/); r->args.emplace_back(a.name, nullptr, handle(), !a.flag_noconvert, a.flag_none); + + if (r->has_kwonly_args) process_kwonly_arg(a, r); } }; @@ -393,6 +414,15 @@ template <> struct process_attribute : process_attribute_default { #endif } r->args.emplace_back(a.name, a.descr, a.value.inc_ref(), !a.flag_noconvert, a.flag_none); + + if (r->has_kwonly_args) process_kwonly_arg(a, r); + } +}; + +/// Process a keyword-only-arguments-follow pseudo argument +template <> struct process_attribute : process_attribute_default { + static void init(const kwonly &, function_record *r) { + r->has_kwonly_args = true; } }; @@ -419,6 +449,11 @@ struct process_attribute : process_attribute_default static void init(const dynamic_attr &, type_record *r) { r->dynamic_attr = true; } }; +template <> +struct process_attribute : process_attribute_default { + static void init(const is_final &, type_record *r) { r->is_final = true; } +}; + template <> struct process_attribute : process_attribute_default { static void init(const buffer_protocol &, type_record *r) { r->buffer_protocol = true; } diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 4367735eab..a5a99e0b3b 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -293,8 +293,8 @@ struct values_and_holders { // Past-the-end iterator: iterator(size_t end) : curr(end) {} public: - bool operator==(const iterator &other) { return curr.index == other.curr.index; } - bool operator!=(const iterator &other) { return curr.index != other.curr.index; } + bool operator==(const iterator &other) const { return curr.index == other.curr.index; } + bool operator!=(const iterator &other) const { return curr.index != other.curr.index; } iterator &operator++() { if (!inst->simple_layout) curr.vh += 1 + (*types)[curr.index]->holder_size_in_ptrs; @@ -948,19 +948,25 @@ NAMESPACE_END(detail) // You may specialize polymorphic_type_hook yourself for types that want to appear // polymorphic to Python but do not use C++ RTTI. (This is a not uncommon pattern // in performance-sensitive applications, used most notably in LLVM.) +// +// polymorphic_type_hook_base allows users to specialize polymorphic_type_hook with +// std::enable_if. User provided specializations will always have higher priority than +// the default implementation and specialization provided in polymorphic_type_hook_base. template -struct polymorphic_type_hook +struct polymorphic_type_hook_base { static const void *get(const itype *src, const std::type_info*&) { return src; } }; template -struct polymorphic_type_hook::value>> +struct polymorphic_type_hook_base::value>> { static const void *get(const itype *src, const std::type_info*& type) { type = src ? &typeid(*src) : nullptr; return dynamic_cast(src); } }; +template +struct polymorphic_type_hook : public polymorphic_type_hook_base {}; NAMESPACE_BEGIN(detail) @@ -1617,7 +1623,9 @@ struct copyable_holder_caster : public type_caster_base { } explicit operator type*() { return this->value; } - explicit operator type&() { return *(this->value); } + // static_cast works around compiler error with MSVC 17 and CUDA 10.2 + // see issue #2180 + explicit operator type&() { return *(static_cast(this->value)); } explicit operator holder_type*() { return std::addressof(holder); } // Workaround for Intel compiler bug @@ -1871,6 +1879,9 @@ template struct is_holder_type struct handle_type_name { static constexpr auto name = _(); }; template <> struct handle_type_name { static constexpr auto name = _(PYBIND11_BYTES_NAME); }; +template <> struct handle_type_name { static constexpr auto name = _("int"); }; +template <> struct handle_type_name { static constexpr auto name = _("Iterable"); }; +template <> struct handle_type_name { static constexpr auto name = _("Iterator"); }; template <> struct handle_type_name { static constexpr auto name = _("*args"); }; template <> struct handle_type_name { static constexpr auto name = _("**kwargs"); }; @@ -2178,6 +2189,11 @@ struct arg_v : arg { #endif }; +/// \ingroup annotations +/// Annotation indicating that all following arguments are keyword-only; the is the equivalent of an +/// unnamed '*' argument (in Python 3) +struct kwonly {}; + template arg_v arg::operator=(T &&value) const { return {std::move(*this), std::forward(value)}; } diff --git a/include/pybind11/detail/class.h b/include/pybind11/detail/class.h index 94029066af..16254a6c64 100644 --- a/include/pybind11/detail/class.h +++ b/include/pybind11/detail/class.h @@ -604,10 +604,12 @@ inline PyObject* make_new_python_type(const type_record &rec) { #endif /* Flags */ - type->tp_flags |= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE; + type->tp_flags |= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE; #if PY_MAJOR_VERSION < 3 type->tp_flags |= Py_TPFLAGS_CHECKTYPES; #endif + if (!rec.is_final) + type->tp_flags |= Py_TPFLAGS_BASETYPE; if (rec.dynamic_attr) enable_dynamic_attributes(heap_type); diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 6a233341da..3cc73417e6 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -92,9 +92,23 @@ # define PYBIND11_DEPRECATED(reason) __attribute__((deprecated(reason))) #endif +#if defined(PYBIND11_CPP17) +# define PYBIND11_MAYBE_UNUSED [[maybe_unused]] +#elif defined(_MSC_VER) && !defined(__clang__) +# define PYBIND11_MAYBE_UNUSED +#else +# define PYBIND11_MAYBE_UNUSED __attribute__ ((__unused__)) +#endif + #define PYBIND11_VERSION_MAJOR 2 -#define PYBIND11_VERSION_MINOR 4 -#define PYBIND11_VERSION_PATCH dev4 +#define PYBIND11_VERSION_MINOR 5 +#define PYBIND11_VERSION_PATCH dev1 + +/* Don't let Python.h #define (v)snprintf as macro because they are implemented + properly in Visual Studio since 2015. */ +#if defined(_MSC_VER) && _MSC_VER >= 1900 +# define HAVE_SNPRINTF 1 +#endif /// Include Python header, disable linking to pythonX_d.lib on Windows in debug mode #if defined(_MSC_VER) @@ -171,9 +185,10 @@ #define PYBIND11_STR_TYPE ::pybind11::str #define PYBIND11_BOOL_ATTR "__bool__" #define PYBIND11_NB_BOOL(ptr) ((ptr)->nb_bool) -// Providing a separate declaration to make Clang's -Wmissing-prototypes happy +// Providing a separate declaration to make Clang's -Wmissing-prototypes happy. +// See comment for PYBIND11_MODULE below for why this is marked "maybe unused". #define PYBIND11_PLUGIN_IMPL(name) \ - extern "C" PYBIND11_EXPORT PyObject *PyInit_##name(); \ + extern "C" PYBIND11_MAYBE_UNUSED PYBIND11_EXPORT PyObject *PyInit_##name(); \ extern "C" PYBIND11_EXPORT PyObject *PyInit_##name() #else @@ -197,13 +212,14 @@ #define PYBIND11_STR_TYPE ::pybind11::bytes #define PYBIND11_BOOL_ATTR "__nonzero__" #define PYBIND11_NB_BOOL(ptr) ((ptr)->nb_nonzero) -// Providing a separate PyInit decl to make Clang's -Wmissing-prototypes happy +// Providing a separate PyInit decl to make Clang's -Wmissing-prototypes happy. +// See comment for PYBIND11_MODULE below for why this is marked "maybe unused". #define PYBIND11_PLUGIN_IMPL(name) \ - static PyObject *pybind11_init_wrapper(); \ - extern "C" PYBIND11_EXPORT void init##name(); \ - extern "C" PYBIND11_EXPORT void init##name() { \ - (void)pybind11_init_wrapper(); \ - } \ + static PyObject *pybind11_init_wrapper(); \ + extern "C" PYBIND11_MAYBE_UNUSED PYBIND11_EXPORT void init##name(); \ + extern "C" PYBIND11_EXPORT void init##name() { \ + (void)pybind11_init_wrapper(); \ + } \ PyObject *pybind11_init_wrapper() #endif @@ -279,6 +295,10 @@ extern "C" { should not be in quotes. The second macro argument defines a variable of type `py::module` which can be used to initialize the module. + The entry point is marked as "maybe unused" to aid dead-code detection analysis: + since the entry point is typically only looked up at runtime and not referenced + during translation, it would otherwise appear as unused ("dead") code. + .. code-block:: cpp PYBIND11_MODULE(example, m) { @@ -291,6 +311,7 @@ extern "C" { } \endrst */ #define PYBIND11_MODULE(name, variable) \ + PYBIND11_MAYBE_UNUSED \ static void PYBIND11_CONCAT(pybind11_init_, name)(pybind11::module &); \ PYBIND11_PLUGIN_IMPL(name) { \ PYBIND11_CHECK_PYTHON_VERSION \ diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index e079d3923a..54210cfba6 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -40,6 +40,9 @@ NAMESPACE_BEGIN(PYBIND11_NAMESPACE) class array; // Forward declaration NAMESPACE_BEGIN(detail) + +template <> struct handle_type_name { static constexpr auto name = _("numpy.ndarray"); }; + template struct npy_format_descriptor; struct PyArrayDescr_Proxy { @@ -1007,14 +1010,14 @@ struct npy_format_descriptor_name; template struct npy_format_descriptor_name::value>> { static constexpr auto name = _::value>( - _("bool"), _::value>("int", "uint") + _() + _("bool"), _::value>("numpy.int", "numpy.uint") + _() ); }; template struct npy_format_descriptor_name::value>> { static constexpr auto name = _::value || std::is_same::value>( - _("float") + _(), _("longdouble") + _("numpy.float") + _(), _("numpy.longdouble") ); }; @@ -1022,7 +1025,7 @@ template struct npy_format_descriptor_name::value>> { static constexpr auto name = _::value || std::is_same::value>( - _("complex") + _(), _("longcomplex") + _("numpy.complex") + _(), _("numpy.longcomplex") ); }; @@ -1218,7 +1221,7 @@ template struct npy_format_descriptor { #define PYBIND11_MAP_NEXT0(test, next, ...) next PYBIND11_MAP_OUT #define PYBIND11_MAP_NEXT1(test, next) PYBIND11_MAP_NEXT0 (test, next, 0) #define PYBIND11_MAP_NEXT(test, next) PYBIND11_MAP_NEXT1 (PYBIND11_MAP_GET_END test, next) -#ifdef _MSC_VER // MSVC is not as eager to expand macros, hence this workaround +#if defined(_MSC_VER) && !defined(__clang__) // MSVC is not as eager to expand macros, hence this workaround #define PYBIND11_MAP_LIST_NEXT1(test, next) \ PYBIND11_EVAL0 (PYBIND11_MAP_NEXT0 (test, PYBIND11_MAP_COMMA next, 0)) #else @@ -1240,7 +1243,7 @@ template struct npy_format_descriptor { (::std::vector<::pybind11::detail::field_descriptor> \ {PYBIND11_MAP_LIST (PYBIND11_FIELD_DESCRIPTOR, Type, __VA_ARGS__)}) -#ifdef _MSC_VER +#if defined(_MSC_VER) && !defined(__clang__) #define PYBIND11_MAP2_LIST_NEXT1(test, next) \ PYBIND11_EVAL0 (PYBIND11_MAP_NEXT0 (test, PYBIND11_MAP_COMMA next, 0)) #else diff --git a/include/pybind11/operators.h b/include/pybind11/operators.h index b3dd62c3b6..293d5abd29 100644 --- a/include/pybind11/operators.h +++ b/include/pybind11/operators.h @@ -147,6 +147,9 @@ PYBIND11_INPLACE_OPERATOR(ixor, operator^=, l ^= r) PYBIND11_INPLACE_OPERATOR(ior, operator|=, l |= r) PYBIND11_UNARY_OPERATOR(neg, operator-, -l) PYBIND11_UNARY_OPERATOR(pos, operator+, +l) +// WARNING: This usage of `abs` should only be done for existing STL overloads. +// Adding overloads directly in to the `std::` namespace is advised against: +// https://en.cppreference.com/w/cpp/language/extending_std PYBIND11_UNARY_OPERATOR(abs, abs, std::abs(l)) PYBIND11_UNARY_OPERATOR(hash, hash, std::hash()(l)) PYBIND11_UNARY_OPERATOR(invert, operator~, (~l)) @@ -160,6 +163,8 @@ PYBIND11_UNARY_OPERATOR(float, float_, (double) l) NAMESPACE_END(detail) using detail::self; +// Add named operators so that they are accessible via `py::`. +using detail::hash; NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 2c4713d5e1..462f8f906c 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -75,20 +75,38 @@ class cpp_function : public function { (detail::function_signature_t *) nullptr, extra...); } - /// Construct a cpp_function from a class method (non-const) + /// Construct a cpp_function from a class method (non-const, no ref-qualifier) template cpp_function(Return (Class::*f)(Arg...), const Extra&... extra) { initialize([f](Class *c, Arg... args) -> Return { return (c->*f)(std::forward(args)...); }, (Return (*) (Class *, Arg...)) nullptr, extra...); } - /// Construct a cpp_function from a class method (const) + /// Construct a cpp_function from a class method (non-const, lvalue ref-qualifier) + /// A copy of the overload for non-const functions without explicit ref-qualifier + /// but with an added `&`. + template + cpp_function(Return (Class::*f)(Arg...)&, const Extra&... extra) { + initialize([f](Class *c, Arg... args) -> Return { return (c->*f)(args...); }, + (Return (*) (Class *, Arg...)) nullptr, extra...); + } + + /// Construct a cpp_function from a class method (const, no ref-qualifier) template cpp_function(Return (Class::*f)(Arg...) const, const Extra&... extra) { initialize([f](const Class *c, Arg... args) -> Return { return (c->*f)(std::forward(args)...); }, (Return (*)(const Class *, Arg ...)) nullptr, extra...); } + /// Construct a cpp_function from a class method (const, lvalue ref-qualifier) + /// A copy of the overload for const functions without explicit ref-qualifier + /// but with an added `&`. + template + cpp_function(Return (Class::*f)(Arg...) const&, const Extra&... extra) { + initialize([f](const Class *c, Arg... args) -> Return { return (c->*f)(args...); }, + (Return (*)(const Class *, Arg ...)) nullptr, extra...); + } + /// Return the function name object name() const { return attr("__name__"); } @@ -171,6 +189,14 @@ class cpp_function : public function { /* Process any user-provided function attributes */ process_attributes::init(extra..., rec); + { + constexpr bool has_kwonly_args = any_of...>::value, + has_args = any_of...>::value, + has_arg_annotations = any_of...>::value; + static_assert(has_arg_annotations || !has_kwonly_args, "py::kwonly requires the use of argument annotations"); + static_assert(!(has_args && has_kwonly_args), "py::kwonly cannot be combined with a py::args argument"); + } + /* Generate a readable signature describing the function's arguments and return value types */ static constexpr auto signature = _("(") + cast_in::arg_names + _(") -> ") + cast_out::name; PYBIND11_DESCR_CONSTEXPR auto types = decltype(signature)::types(); @@ -486,15 +512,16 @@ class cpp_function : public function { */ const function_record &func = *it; - size_t pos_args = func.nargs; // Number of positional arguments that we need - if (func.has_args) --pos_args; // (but don't count py::args - if (func.has_kwargs) --pos_args; // or py::kwargs) + size_t num_args = func.nargs; // Number of positional arguments that we need + if (func.has_args) --num_args; // (but don't count py::args + if (func.has_kwargs) --num_args; // or py::kwargs) + size_t pos_args = num_args - func.nargs_kwonly; if (!func.has_args && n_args_in > pos_args) - continue; // Too many arguments for this overload + continue; // Too many positional arguments for this overload if (n_args_in < pos_args && func.args.size() < pos_args) - continue; // Not enough arguments given, and not enough defaults to fill in the blanks + continue; // Not enough positional arguments given, and not enough defaults to fill in the blanks function_call call(func, parent); @@ -538,10 +565,10 @@ class cpp_function : public function { dict kwargs = reinterpret_borrow(kwargs_in); // 2. Check kwargs and, failing that, defaults that may help complete the list - if (args_copied < pos_args) { + if (args_copied < num_args) { bool copied_kwargs = false; - for (; args_copied < pos_args; ++args_copied) { + for (; args_copied < num_args; ++args_copied) { const auto &arg = func.args[args_copied]; handle value; @@ -567,7 +594,7 @@ class cpp_function : public function { break; } - if (args_copied < pos_args) + if (args_copied < num_args) continue; // Not enough arguments, defaults, or kwargs to fill the positional arguments } @@ -1820,7 +1847,7 @@ struct enum_base { return pybind11::str("{}.{}").format(type_name, kv.first); } return pybind11::str("{}.???").format(type_name); - }, is_method(m_base) + }, name("__repr__"), is_method(m_base) ); m_base.attr("name") = property(cpp_function( @@ -1831,7 +1858,7 @@ struct enum_base { return pybind11::str(kv.first); } return "???"; - }, is_method(m_base) + }, name("name"), is_method(m_base) )); m_base.attr("__doc__") = static_property(cpp_function( @@ -1849,7 +1876,7 @@ struct enum_base { docstring += " : " + (std::string) pybind11::str(comment); } return docstring; - } + }, name("__doc__") ), none(), none(), ""); m_base.attr("__members__") = static_property(cpp_function( @@ -1858,7 +1885,7 @@ struct enum_base { for (const auto &kv : entries) m[kv.first] = kv.second[int_(0)]; return m; - }), none(), none(), "" + }, name("__members__")), none(), none(), "" ); #define PYBIND11_ENUM_OP_STRICT(op, expr, strict_behavior) \ @@ -1868,7 +1895,7 @@ struct enum_base { strict_behavior; \ return expr; \ }, \ - is_method(m_base)) + name(op), is_method(m_base)) #define PYBIND11_ENUM_OP_CONV(op, expr) \ m_base.attr(op) = cpp_function( \ @@ -1876,7 +1903,7 @@ struct enum_base { int_ a(a_), b(b_); \ return expr; \ }, \ - is_method(m_base)) + name(op), is_method(m_base)) #define PYBIND11_ENUM_OP_CONV_LHS(op, expr) \ m_base.attr(op) = cpp_function( \ @@ -1884,7 +1911,7 @@ struct enum_base { int_ a(a_); \ return expr; \ }, \ - is_method(m_base)) + name(op), is_method(m_base)) if (is_convertible) { PYBIND11_ENUM_OP_CONV_LHS("__eq__", !b.is_none() && a.equal(b)); @@ -1902,7 +1929,7 @@ struct enum_base { 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)); + [](object arg) { return ~(int_(arg)); }, name("__invert__"), is_method(m_base)); } } else { PYBIND11_ENUM_OP_STRICT("__eq__", int_(a).equal(int_(b)), return false); @@ -1922,11 +1949,11 @@ struct enum_base { #undef PYBIND11_ENUM_OP_CONV #undef PYBIND11_ENUM_OP_STRICT - object getstate = cpp_function( - [](object arg) { return int_(arg); }, is_method(m_base)); + m_base.attr("__getstate__") = cpp_function( + [](object arg) { return int_(arg); }, name("__getstate__"), is_method(m_base)); - m_base.attr("__getstate__") = getstate; - m_base.attr("__hash__") = getstate; + m_base.attr("__hash__") = cpp_function( + [](object arg) { return int_(arg); }, name("__hash__"), is_method(m_base)); } PYBIND11_NOINLINE void value(char const* name_, object value, const char *doc = nullptr) { @@ -1979,10 +2006,12 @@ template class enum_ : public class_ { def("__index__", [](Type value) { return (Scalar) value; }); #endif - cpp_function setstate( - [](Type &value, Scalar arg) { value = static_cast(arg); }, - is_method(*this)); - attr("__setstate__") = setstate; + attr("__setstate__") = cpp_function( + [](detail::value_and_holder &v_h, Scalar arg) { + detail::initimpl::setstate(v_h, static_cast(arg), + Py_TYPE(v_h.inst) != v_h.type->type); }, + detail::is_new_style_constructor(), + pybind11::name("__setstate__"), is_method(*this)); } /// Export enumeration entries into the parent scope @@ -2528,8 +2557,8 @@ template function get_overload(const T *this_ptr, const char *name) { PYBIND11_OVERLOAD_NAME( std::string, // Return type (ret_type) Animal, // Parent class (cname) - toString, // Name of function in C++ (name) - "__str__", // Name of method in Python (fn) + "__str__", // Name of method in Python (name) + toString, // Name of function in C++ (fn) ); } \endrst */ diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index b958a00060..cabab0c09c 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -981,6 +981,9 @@ class bytes : public object { return std::string(buffer, (size_t) length); } }; +// Note: breathe >= 4.17.0 will fail to build docs if the below two constructors +// are included in the doxygen group; close here and reopen after as a workaround +/// @} pytypes inline bytes::bytes(const pybind11::str &s) { object temp = s; @@ -1010,6 +1013,8 @@ inline str::str(const bytes& b) { m_ptr = obj.release().ptr(); } +/// \addtogroup pytypes +/// @{ class none : public object { public: PYBIND11_OBJECT(none, object, detail::PyNone_Check) @@ -1243,7 +1248,12 @@ class dict : public object { class sequence : public object { public: PYBIND11_OBJECT_DEFAULT(sequence, object, PySequence_Check) - size_t size() const { return (size_t) PySequence_Size(m_ptr); } + size_t size() const { + ssize_t result = PySequence_Size(m_ptr); + if (result == -1) + throw error_already_set(); + return (size_t) result; + } bool empty() const { return size() == 0; } detail::sequence_accessor operator[](size_t index) const { return {*this, index}; } detail::item_accessor operator[](handle h) const { return object::operator[](h); } diff --git a/include/pybind11/stl_bind.h b/include/pybind11/stl_bind.h index 62bd908196..da233eca99 100644 --- a/include/pybind11/stl_bind.h +++ b/include/pybind11/stl_bind.h @@ -136,6 +136,13 @@ void vector_modifiers(enable_if_t(m, "IsFinal", py::is_final()); + + // test_non_final_final + struct IsNonFinalFinal {}; + py::class_(m, "IsNonFinalFinal", py::is_final()); + // Test #1922 (drake#11424). class ExampleVirt2 { public: diff --git a/tests/test_class.py b/tests/test_class.py index 21fe65e846..6587f1dec2 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -292,6 +292,24 @@ def test_aligned(): assert p % 1024 == 0 +# https://bitbucket.org/pypy/pypy/issues/2742 +@pytest.unsupported_on_pypy +def test_final(): + with pytest.raises(TypeError) as exc_info: + class PyFinalChild(m.IsFinal): + pass + assert str(exc_info.value).endswith("is not an acceptable base type") + + +# https://bitbucket.org/pypy/pypy/issues/2742 +@pytest.unsupported_on_pypy +def test_non_final_final(): + with pytest.raises(TypeError) as exc_info: + class PyNonFinalFinalChild(m.IsNonFinalFinal): + pass + assert str(exc_info.value).endswith("is not an acceptable base type") + + @pytest.mark.skip( reason="Generally reproducible in CPython, Python 3, non-debug, on Linux. " "However, hard to pin this down for CI.") diff --git a/tests/test_eigen.py b/tests/test_eigen.py index c88ffd6670..e2da2d45d4 100644 --- a/tests/test_eigen.py +++ b/tests/test_eigen.py @@ -79,15 +79,17 @@ def test_mutator_descriptors(): m.fixed_mutator_a(zc) with pytest.raises(TypeError) as excinfo: m.fixed_mutator_r(zc) - assert ('(arg0: numpy.ndarray[float32[5, 6], flags.writeable, flags.c_contiguous]) -> None' + assert ('(arg0: numpy.ndarray[numpy.float32[5, 6],' + ' flags.writeable, flags.c_contiguous]) -> None' in str(excinfo.value)) with pytest.raises(TypeError) as excinfo: m.fixed_mutator_c(zr) - assert ('(arg0: numpy.ndarray[float32[5, 6], flags.writeable, flags.f_contiguous]) -> None' + assert ('(arg0: numpy.ndarray[numpy.float32[5, 6],' + ' flags.writeable, flags.f_contiguous]) -> None' in str(excinfo.value)) with pytest.raises(TypeError) as excinfo: m.fixed_mutator_a(np.array([[1, 2], [3, 4]], dtype='float32')) - assert ('(arg0: numpy.ndarray[float32[5, 6], flags.writeable]) -> None' + assert ('(arg0: numpy.ndarray[numpy.float32[5, 6], flags.writeable]) -> None' in str(excinfo.value)) zr.flags.writeable = False with pytest.raises(TypeError): @@ -271,7 +273,7 @@ def test_negative_stride_from_python(msg): m.double_threer(second_row) assert msg(excinfo.value) == """ double_threer(): incompatible function arguments. The following argument types are supported: - 1. (arg0: numpy.ndarray[float32[1, 3], flags.writeable]) -> None + 1. (arg0: numpy.ndarray[numpy.float32[1, 3], flags.writeable]) -> None Invoked with: """ + repr(np.array([ 5., 4., 3.], dtype='float32')) # noqa: E501 line too long @@ -279,7 +281,7 @@ def test_negative_stride_from_python(msg): m.double_threec(second_col) assert msg(excinfo.value) == """ double_threec(): incompatible function arguments. The following argument types are supported: - 1. (arg0: numpy.ndarray[float32[3, 1], flags.writeable]) -> None + 1. (arg0: numpy.ndarray[numpy.float32[3, 1], flags.writeable]) -> None Invoked with: """ + repr(np.array([ 7., 4., 1.], dtype='float32')) # noqa: E501 line too long @@ -699,17 +701,19 @@ def test_special_matrix_objects(): def test_dense_signature(doc): assert doc(m.double_col) == """ - double_col(arg0: numpy.ndarray[float32[m, 1]]) -> numpy.ndarray[float32[m, 1]] + double_col(arg0: numpy.ndarray[numpy.float32[m, 1]]) -> numpy.ndarray[numpy.float32[m, 1]] """ assert doc(m.double_row) == """ - double_row(arg0: numpy.ndarray[float32[1, n]]) -> numpy.ndarray[float32[1, n]] - """ - assert doc(m.double_complex) == """ - double_complex(arg0: numpy.ndarray[complex64[m, 1]]) -> numpy.ndarray[complex64[m, 1]] - """ - assert doc(m.double_mat_rm) == """ - double_mat_rm(arg0: numpy.ndarray[float32[m, n]]) -> numpy.ndarray[float32[m, n]] + double_row(arg0: numpy.ndarray[numpy.float32[1, n]]) -> numpy.ndarray[numpy.float32[1, n]] """ + assert doc(m.double_complex) == (""" + double_complex(arg0: numpy.ndarray[numpy.complex64[m, 1]])""" + """ -> numpy.ndarray[numpy.complex64[m, 1]] + """) + assert doc(m.double_mat_rm) == (""" + double_mat_rm(arg0: numpy.ndarray[numpy.float32[m, n]])""" + """ -> numpy.ndarray[numpy.float32[m, n]] + """) def test_named_arguments(): @@ -746,10 +750,10 @@ def test_sparse(): @pytest.requires_eigen_and_scipy def test_sparse_signature(doc): assert doc(m.sparse_copy_r) == """ - sparse_copy_r(arg0: scipy.sparse.csr_matrix[float32]) -> scipy.sparse.csr_matrix[float32] + sparse_copy_r(arg0: scipy.sparse.csr_matrix[numpy.float32]) -> scipy.sparse.csr_matrix[numpy.float32] """ # noqa: E501 line too long assert doc(m.sparse_copy_c) == """ - sparse_copy_c(arg0: scipy.sparse.csc_matrix[float32]) -> scipy.sparse.csc_matrix[float32] + sparse_copy_c(arg0: scipy.sparse.csc_matrix[numpy.float32]) -> scipy.sparse.csc_matrix[numpy.float32] """ # noqa: E501 line too long diff --git a/tests/test_kwargs_and_defaults.cpp b/tests/test_kwargs_and_defaults.cpp index 6563fb9ad3..8f095fe4a6 100644 --- a/tests/test_kwargs_and_defaults.cpp +++ b/tests/test_kwargs_and_defaults.cpp @@ -94,6 +94,30 @@ TEST_SUBMODULE(kwargs_and_defaults, m) { // m.def("bad_args6", [](py::args, py::args) {}); // m.def("bad_args7", [](py::kwargs, py::kwargs) {}); + // test_keyword_only_args + m.def("kwonly_all", [](int i, int j) { return py::make_tuple(i, j); }, + py::kwonly(), py::arg("i"), py::arg("j")); + m.def("kwonly_some", [](int i, int j, int k) { return py::make_tuple(i, j, k); }, + py::arg(), py::kwonly(), py::arg("j"), py::arg("k")); + m.def("kwonly_with_defaults", [](int i, int j, int k, int z) { return py::make_tuple(i, j, k, z); }, + py::arg() = 3, "j"_a = 4, py::kwonly(), "k"_a = 5, "z"_a); + m.def("kwonly_mixed", [](int i, int j) { return py::make_tuple(i, j); }, + "i"_a, py::kwonly(), "j"_a); + m.def("kwonly_plus_more", [](int i, int j, int k, py::kwargs kwargs) { + return py::make_tuple(i, j, k, kwargs); }, + py::arg() /* positional */, py::arg("j") = -1 /* both */, py::kwonly(), py::arg("k") /* kw-only */); + + m.def("register_invalid_kwonly", [](py::module m) { + m.def("bad_kwonly", [](int i, int j) { return py::make_tuple(i, j); }, + py::kwonly(), py::arg() /* invalid unnamed argument */, "j"_a); + }); + + // These should fail to compile: + // argument annotations are required when using kwonly +// m.def("bad_kwonly1", [](int) {}, py::kwonly()); + // can't specify both `py::kwonly` and a `py::args` argument +// m.def("bad_kwonly2", [](int i, py::args) {}, py::kwonly(), "i"_a); + // test_function_signatures (along with most of the above) struct KWClass { void foo(int, float) {} }; py::class_(m, "KWClass") diff --git a/tests/test_kwargs_and_defaults.py b/tests/test_kwargs_and_defaults.py index 27a05a0241..bad6636cb4 100644 --- a/tests/test_kwargs_and_defaults.py +++ b/tests/test_kwargs_and_defaults.py @@ -107,6 +107,44 @@ def test_mixed_args_and_kwargs(msg): """ # noqa: E501 line too long +def test_keyword_only_args(msg): + assert m.kwonly_all(i=1, j=2) == (1, 2) + assert m.kwonly_all(j=1, i=2) == (2, 1) + + with pytest.raises(TypeError) as excinfo: + assert m.kwonly_all(i=1) == (1,) + assert "incompatible function arguments" in str(excinfo.value) + + with pytest.raises(TypeError) as excinfo: + assert m.kwonly_all(1, 2) == (1, 2) + assert "incompatible function arguments" in str(excinfo.value) + + assert m.kwonly_some(1, k=3, j=2) == (1, 2, 3) + + assert m.kwonly_with_defaults(z=8) == (3, 4, 5, 8) + assert m.kwonly_with_defaults(2, z=8) == (2, 4, 5, 8) + assert m.kwonly_with_defaults(2, j=7, k=8, z=9) == (2, 7, 8, 9) + assert m.kwonly_with_defaults(2, 7, z=9, k=8) == (2, 7, 8, 9) + + assert m.kwonly_mixed(1, j=2) == (1, 2) + assert m.kwonly_mixed(j=2, i=3) == (3, 2) + assert m.kwonly_mixed(i=2, j=3) == (2, 3) + + assert m.kwonly_plus_more(4, 5, k=6, extra=7) == (4, 5, 6, {'extra': 7}) + assert m.kwonly_plus_more(3, k=5, j=4, extra=6) == (3, 4, 5, {'extra': 6}) + assert m.kwonly_plus_more(2, k=3, extra=4) == (2, -1, 3, {'extra': 4}) + + with pytest.raises(TypeError) as excinfo: + assert m.kwonly_mixed(i=1) == (1,) + assert "incompatible function arguments" in str(excinfo.value) + + with pytest.raises(RuntimeError) as excinfo: + m.register_invalid_kwonly(m) + assert msg(excinfo.value) == """ + arg(): cannot specify an unnamed argument after an kwonly() annotation + """ + + def test_args_refcount(): """Issue/PR #1216 - py::args elements get double-inc_ref()ed when combined with regular arguments""" diff --git a/tests/test_methods_and_attributes.cpp b/tests/test_methods_and_attributes.cpp index c7b82f13d0..1337620941 100644 --- a/tests/test_methods_and_attributes.cpp +++ b/tests/test_methods_and_attributes.cpp @@ -207,6 +207,14 @@ class RegisteredDerived : public UnregisteredBase { double sum() const { return rw_value + ro_value; } }; +// Test explicit lvalue ref-qualification +struct RefQualified { + int value = 0; + + void refQualified(int other) & { value += other; } + int constRefQualified(int other) const & { return value + other; } +}; + TEST_SUBMODULE(methods_and_attributes, m) { // test_methods_and_attributes py::class_ emna(m, "ExampleMandA"); @@ -457,4 +465,11 @@ TEST_SUBMODULE(methods_and_attributes, m) { m.def("custom_caster_destroy_const", []() -> const DestructionTester * { return new DestructionTester(); }, py::return_value_policy::take_ownership); // Likewise (const doesn't inhibit destruction) m.def("destruction_tester_cstats", &ConstructorStats::get, py::return_value_policy::reference); + + // test_methods_and_attributes + py::class_(m, "RefQualified") + .def(py::init<>()) + .def_readonly("value", &RefQualified::value) + .def("refQualified", &RefQualified::refQualified) + .def("constRefQualified", &RefQualified::constRefQualified); } diff --git a/tests/test_methods_and_attributes.py b/tests/test_methods_and_attributes.py index fc3b0ad3af..2604b6ea5a 100644 --- a/tests/test_methods_and_attributes.py +++ b/tests/test_methods_and_attributes.py @@ -510,3 +510,14 @@ def test_custom_caster_destruction(): # Make sure we still only have the original object (from ..._no_destroy()) alive: assert cstats.alive() == 1 + + +def test_ref_qualified(): + """Tests that explicit lvalue ref-qualified methods can be called just like their + non ref-qualified counterparts.""" + + r = m.RefQualified() + assert r.value == 0 + r.refQualified(17) + assert r.value == 17 + assert r.constRefQualified(23) == 40 diff --git a/tests/test_numpy_array.py b/tests/test_numpy_array.py index d0a6324dfd..55571964d0 100644 --- a/tests/test_numpy_array.py +++ b/tests/test_numpy_array.py @@ -286,13 +286,13 @@ def test_overload_resolution(msg): m.overloaded("not an array") assert msg(excinfo.value) == """ overloaded(): incompatible function arguments. The following argument types are supported: - 1. (arg0: numpy.ndarray[float64]) -> str - 2. (arg0: numpy.ndarray[float32]) -> str - 3. (arg0: numpy.ndarray[int32]) -> str - 4. (arg0: numpy.ndarray[uint16]) -> str - 5. (arg0: numpy.ndarray[int64]) -> str - 6. (arg0: numpy.ndarray[complex128]) -> str - 7. (arg0: numpy.ndarray[complex64]) -> str + 1. (arg0: numpy.ndarray[numpy.float64]) -> str + 2. (arg0: numpy.ndarray[numpy.float32]) -> str + 3. (arg0: numpy.ndarray[numpy.int32]) -> str + 4. (arg0: numpy.ndarray[numpy.uint16]) -> str + 5. (arg0: numpy.ndarray[numpy.int64]) -> str + 6. (arg0: numpy.ndarray[numpy.complex128]) -> str + 7. (arg0: numpy.ndarray[numpy.complex64]) -> str Invoked with: 'not an array' """ @@ -307,8 +307,8 @@ def test_overload_resolution(msg): assert m.overloaded3(np.array([1], dtype='intc')) == 'int' expected_exc = """ overloaded3(): incompatible function arguments. The following argument types are supported: - 1. (arg0: numpy.ndarray[int32]) -> str - 2. (arg0: numpy.ndarray[float64]) -> str + 1. (arg0: numpy.ndarray[numpy.int32]) -> str + 2. (arg0: numpy.ndarray[numpy.float64]) -> str Invoked with: """ diff --git a/tests/test_numpy_vectorize.py b/tests/test_numpy_vectorize.py index 0e9c883978..c078ee7cf0 100644 --- a/tests/test_numpy_vectorize.py +++ b/tests/test_numpy_vectorize.py @@ -109,7 +109,7 @@ def test_type_selection(): def test_docs(doc): assert doc(m.vectorized_func) == """ - vectorized_func(arg0: numpy.ndarray[int32], arg1: numpy.ndarray[float32], arg2: numpy.ndarray[float64]) -> object + vectorized_func(arg0: numpy.ndarray[numpy.int32], arg1: numpy.ndarray[numpy.float32], arg2: numpy.ndarray[numpy.float64]) -> object """ # noqa: E501 line too long @@ -160,12 +160,12 @@ def test_passthrough_arguments(doc): assert doc(m.vec_passthrough) == ( "vec_passthrough(" + ", ".join([ "arg0: float", - "arg1: numpy.ndarray[float64]", - "arg2: numpy.ndarray[float64]", - "arg3: numpy.ndarray[int32]", + "arg1: numpy.ndarray[numpy.float64]", + "arg2: numpy.ndarray[numpy.float64]", + "arg3: numpy.ndarray[numpy.int32]", "arg4: int", "arg5: m.numpy_vectorize.NonPODClass", - "arg6: numpy.ndarray[float64]"]) + ") -> object") + "arg6: numpy.ndarray[numpy.float64]"]) + ") -> object") b = np.array([[10, 20, 30]], dtype='float64') c = np.array([100, 200]) # NOT a vectorized argument diff --git a/tests/test_operator_overloading.cpp b/tests/test_operator_overloading.cpp index 7b111704b8..52fcd33836 100644 --- a/tests/test_operator_overloading.cpp +++ b/tests/test_operator_overloading.cpp @@ -43,6 +43,13 @@ class Vector2 { friend Vector2 operator-(float f, const Vector2 &v) { return Vector2(f - v.x, f - v.y); } friend Vector2 operator*(float f, const Vector2 &v) { return Vector2(f * v.x, f * v.y); } friend Vector2 operator/(float f, const Vector2 &v) { return Vector2(f / v.x, f / v.y); } + + bool operator==(const Vector2 &v) const { + return x == v.x && y == v.y; + } + bool operator!=(const Vector2 &v) const { + return x != v.x || y != v.y; + } private: float x, y; }; @@ -55,6 +62,11 @@ int operator+(const C2 &, const C2 &) { return 22; } int operator+(const C2 &, const C1 &) { return 21; } int operator+(const C1 &, const C2 &) { return 12; } +// Note: Specializing explicit within `namespace std { ... }` is done due to a +// bug in GCC<7. If you are supporting compilers later than this, consider +// specializing `using template<> struct std::hash<...>` in the global +// namespace instead, per this recommendation: +// https://en.cppreference.com/w/cpp/language/extending_std#Adding_template_specializations namespace std { template<> struct hash { @@ -63,6 +75,11 @@ namespace std { }; } +// Not a good abs function, but easy to test. +std::string abs(const Vector2&) { + return "abs(Vector2)"; +} + // MSVC warns about unknown pragmas, and warnings are errors. #ifndef _MSC_VER #pragma GCC diagnostic push @@ -107,7 +124,13 @@ TEST_SUBMODULE(operators, m) { .def(float() / py::self) .def(-py::self) .def("__str__", &Vector2::toString) - .def(hash(py::self)) + .def("__repr__", &Vector2::toString) + .def(py::self == py::self) + .def(py::self != py::self) + .def(py::hash(py::self)) + // N.B. See warning about usage of `py::detail::abs(py::self)` in + // `operators.h`. + .def("__abs__", [](const Vector2& v) { return abs(v); }) ; m.attr("Vector") = m.attr("Vector2"); diff --git a/tests/test_operator_overloading.py b/tests/test_operator_overloading.py index bd36ac2a52..1cee29889c 100644 --- a/tests/test_operator_overloading.py +++ b/tests/test_operator_overloading.py @@ -6,6 +6,9 @@ def test_operator_overloading(): v1 = m.Vector2(1, 2) v2 = m.Vector(3, -1) + v3 = m.Vector2(1, 2) # Same value as v1, but different instance. + assert v1 is not v3 + assert str(v1) == "[1.000000, 2.000000]" assert str(v2) == "[3.000000, -1.000000]" @@ -24,6 +27,12 @@ def test_operator_overloading(): assert str(v1 * v2) == "[3.000000, -2.000000]" assert str(v2 / v1) == "[3.000000, -0.500000]" + assert v1 == v3 + assert v1 != v2 + assert hash(v1) == 4 + # TODO(eric.cousineau): Make this work. + # assert abs(v1) == "abs(Vector2)" + v1 += 2 * v2 assert str(v1) == "[7.000000, 0.000000]" v1 -= v2 @@ -37,22 +46,33 @@ def test_operator_overloading(): v2 /= v1 assert str(v2) == "[2.000000, 8.000000]" - assert hash(v1) == 4 - cstats = ConstructorStats.get(m.Vector2) - assert cstats.alive() == 2 + assert cstats.alive() == 3 del v1 - assert cstats.alive() == 1 + assert cstats.alive() == 2 del v2 + assert cstats.alive() == 1 + del v3 assert cstats.alive() == 0 - assert cstats.values() == ['[1.000000, 2.000000]', '[3.000000, -1.000000]', - '[-3.000000, 1.000000]', '[4.000000, 1.000000]', - '[-2.000000, 3.000000]', '[-7.000000, -6.000000]', - '[9.000000, 10.000000]', '[8.000000, 16.000000]', - '[0.125000, 0.250000]', '[7.000000, 6.000000]', - '[9.000000, 10.000000]', '[8.000000, 16.000000]', - '[8.000000, 4.000000]', '[3.000000, -2.000000]', - '[3.000000, -0.500000]', '[6.000000, -2.000000]'] + assert cstats.values() == [ + '[1.000000, 2.000000]', + '[3.000000, -1.000000]', + '[1.000000, 2.000000]', + '[-3.000000, 1.000000]', + '[4.000000, 1.000000]', + '[-2.000000, 3.000000]', + '[-7.000000, -6.000000]', + '[9.000000, 10.000000]', + '[8.000000, 16.000000]', + '[0.125000, 0.250000]', + '[7.000000, 6.000000]', + '[9.000000, 10.000000]', + '[8.000000, 16.000000]', + '[8.000000, 4.000000]', + '[3.000000, -2.000000]', + '[3.000000, -0.500000]', + '[6.000000, -2.000000]', + ] assert cstats.default_constructions == 0 assert cstats.copy_constructions == 0 assert cstats.move_constructions >= 10 diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 244e1db0d2..e70ffae9e6 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -11,6 +11,12 @@ TEST_SUBMODULE(pytypes, m) { + // test_int + m.def("get_int", []{return py::int_(0);}); + // test_iterator + m.def("get_iterator", []{return py::iterator();}); + // test_iterable + m.def("get_iterable", []{return py::iterable();}); // test_list m.def("get_list", []() { py::list list; diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 0e8d6c33a7..d6223b9ba8 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -6,6 +6,18 @@ from pybind11_tests import debug_enabled +def test_int(doc): + assert doc(m.get_int) == "get_int() -> int" + + +def test_iterator(doc): + assert doc(m.get_iterator) == "get_iterator() -> Iterator" + + +def test_iterable(doc): + assert doc(m.get_iterable) == "get_iterable() -> Iterable" + + def test_list(capture, doc): with capture: lst = m.get_list() diff --git a/tests/test_sequences_and_iterators.cpp b/tests/test_sequences_and_iterators.cpp index 87ccf99d62..05f999bb3b 100644 --- a/tests/test_sequences_and_iterators.cpp +++ b/tests/test_sequences_and_iterators.cpp @@ -319,6 +319,9 @@ TEST_SUBMODULE(sequences_and_iterators, m) { return l; }); + // test_sequence_length: check that Python sequences can be converted to py::sequence. + m.def("sequence_length", [](py::sequence seq) { return seq.size(); }); + // Make sure that py::iterator works with std algorithms m.def("count_none", [](py::object o) { return std::count_if(o.begin(), o.end(), [](py::handle h) { return h.is_none(); }); diff --git a/tests/test_sequences_and_iterators.py b/tests/test_sequences_and_iterators.py index 6bd1606405..d839dbef6a 100644 --- a/tests/test_sequences_and_iterators.py +++ b/tests/test_sequences_and_iterators.py @@ -100,6 +100,25 @@ def test_sequence(): assert cstats.move_assignments == 0 +def test_sequence_length(): + """#2076: Exception raised by len(arg) should be propagated """ + class BadLen(RuntimeError): + pass + + class SequenceLike(): + def __getitem__(self, i): + return None + + def __len__(self): + raise BadLen() + + with pytest.raises(BadLen): + m.sequence_length(SequenceLike()) + + assert m.sequence_length([1, 2, 3]) == 3 + assert m.sequence_length("hello") == 5 + + def test_map_iterator(): sm = m.StringMap({'hi': 'bye', 'black': 'white'}) assert sm['hi'] == 'bye' diff --git a/tests/test_stl_binders.py b/tests/test_stl_binders.py index b83a587f26..c1264c01f2 100644 --- a/tests/test_stl_binders.py +++ b/tests/test_stl_binders.py @@ -64,6 +64,10 @@ def test_vector_int(): del v_int2[-1] assert v_int2 == m.VectorInt([0, 99, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 88]) + v_int2.clear() + assert len(v_int2) == 0 + + # related to the PyPy's buffer protocol. @pytest.unsupported_on_pypy def test_vector_buffer(): diff --git a/tests/test_tagbased_polymorphic.cpp b/tests/test_tagbased_polymorphic.cpp index 272e460c99..dcc005126e 100644 --- a/tests/test_tagbased_polymorphic.cpp +++ b/tests/test_tagbased_polymorphic.cpp @@ -12,6 +12,12 @@ struct Animal { + // Make this type also a "standard" polymorphic type, to confirm that + // specializing polymorphic_type_hook using enable_if_t still works + // (https://github.com/pybind/pybind11/pull/2016/). + virtual ~Animal() = default; + + // Enum for tag-based polymorphism. enum class Kind { Unknown = 0, Dog = 100, Labrador, Chihuahua, LastDog = 199, diff --git a/tools/FindPythonLibsNew.cmake b/tools/FindPythonLibsNew.cmake index c9b95a9a16..9ea6036e33 100644 --- a/tools/FindPythonLibsNew.cmake +++ b/tools/FindPythonLibsNew.cmake @@ -144,7 +144,7 @@ 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 DEFINED ENV{MSYSTEM}) +if(CMAKE_HOST_WIN32 AND NOT (MINGW AND DEFINED ENV{MSYSTEM})) set(PYTHON_LIBRARY "${PYTHON_PREFIX}/libs/Python${PYTHON_LIBRARY_SUFFIX}.lib") diff --git a/tools/mkdoc.py b/tools/mkdoc.py index 44164af3d6..c85e09d4d2 100755 --- a/tools/mkdoc.py +++ b/tools/mkdoc.py @@ -254,6 +254,13 @@ def read_args(args): parameters.append('-isysroot') parameters.append(sysroot_dir) elif platform.system() == 'Linux': + # cython.util.find_library does not find `libclang` for all clang + # versions and distributions. LLVM switched to a monolithical setup + # that includes everything under /usr/lib/llvm{version_number}/ + # We therefore glob for the library and select the highest version + library_file = sorted(glob("/usr/lib/llvm-*/lib/libclang.so"), reverse=True)[0] + cindex.Config.set_library_file(library_file) + # clang doesn't find its own base includes by default on Linux, # but different distros install them in different paths. # Try to autodetect, preferring the highest numbered version. diff --git a/tools/pybind11Config.cmake.in b/tools/pybind11Config.cmake.in index 8a7272ff96..58426887a5 100644 --- a/tools/pybind11Config.cmake.in +++ b/tools/pybind11Config.cmake.in @@ -28,8 +28,8 @@ # # Python headers, libraries (as needed by platform), and the C++ standard # are attached to the target. Set PythonLibsNew variables to influence -# python detection and PYBIND11_CPP_STANDARD (-std=c++11 or -std=c++14) to -# influence standard setting. :: +# python detection and CMAKE_CXX_STANDARD (11 or 14) to influence standard +# setting. :: # # find_package(pybind11 CONFIG REQUIRED) # message(STATUS "Found pybind11 v${pybind11_VERSION}: ${pybind11_INCLUDE_DIRS}") diff --git a/tools/pybind11Tools.cmake b/tools/pybind11Tools.cmake index d0b4c12af9..c0c3056c81 100644 --- a/tools/pybind11Tools.cmake +++ b/tools/pybind11Tools.cmake @@ -12,30 +12,46 @@ if(NOT PYBIND11_PYTHON_VERSION) set(PYBIND11_PYTHON_VERSION "" CACHE STRING "Python version to use for compiling modules") endif() -set(Python_ADDITIONAL_VERSIONS 3.8 3.7 3.6 3.5 3.4) +set(Python_ADDITIONAL_VERSIONS 3.9 3.8 3.7 3.6 3.5 3.4) find_package(PythonLibsNew ${PYBIND11_PYTHON_VERSION} REQUIRED) include(CheckCXXCompilerFlag) include(CMakeParseArguments) +# Use the language standards abstraction if CMake supports it with the current compiler +if(NOT CMAKE_VERSION VERSION_LESS 3.1) + if(NOT CMAKE_CXX_STANDARD) + if(CMAKE_CXX14_STANDARD_COMPILE_OPTION) + set(CMAKE_CXX_STANDARD 14) + elseif(CMAKE_CXX11_STANDARD_COMPILE_OPTION) + set(CMAKE_CXX_STANDARD 11) + endif() + endif() + if(CMAKE_CXX_STANDARD) + set(CMAKE_CXX_EXTENSIONS OFF) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + endif() +endif() + +# Fall back to heuristics if(NOT PYBIND11_CPP_STANDARD AND NOT CMAKE_CXX_STANDARD) - if(NOT MSVC) + if(MSVC) + set(PYBIND11_CPP_STANDARD /std:c++14) + else() check_cxx_compiler_flag("-std=c++14" HAS_CPP14_FLAG) - - if (HAS_CPP14_FLAG) + if(HAS_CPP14_FLAG) set(PYBIND11_CPP_STANDARD -std=c++14) else() check_cxx_compiler_flag("-std=c++11" HAS_CPP11_FLAG) - if (HAS_CPP11_FLAG) + if(HAS_CPP11_FLAG) set(PYBIND11_CPP_STANDARD -std=c++11) - else() - message(FATAL_ERROR "Unsupported compiler -- pybind11 requires C++11 support!") endif() endif() - elseif(MSVC) - set(PYBIND11_CPP_STANDARD /std:c++14) endif() + if(NOT PYBIND11_CPP_STANDARD) + message(FATAL_ERROR "Unsupported compiler -- pybind11 requires C++11 support!") + endif() set(PYBIND11_CPP_STANDARD ${PYBIND11_CPP_STANDARD} CACHE STRING "C++ standard flag, e.g. -std=c++11, -std=c++14, /std:c++14. Defaults to C++14 mode." FORCE) endif()