diff --git a/.clang-tidy b/.clang-tidy index 23018386c..96cb6f582 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -57,10 +57,12 @@ Checks: | readability-string-compare, readability-suspicious-call-argument, readability-uniqueptr-delete-release, + -bugprone-chained-comparison, -bugprone-easily-swappable-parameters, -bugprone-exception-escape, -bugprone-reserved-identifier, -bugprone-unused-raii, + -performance-enum-size, CheckOptions: - key: modernize-use-equals-default.IgnoreMacros diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3054d842a..06c53bf10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -340,6 +340,12 @@ jobs: - clang: 16 std: 20 container_suffix: "-bullseye" + - clang: 17 + std: 20 + container_suffix: "-bookworm" + - clang: 18 + std: 20 + container_suffix: "-bookworm" name: "🐍 3 • Clang ${{ matrix.clang }} • C++${{ matrix.std }} • x64" container: "silkeh/clang:${{ matrix.clang }}${{ matrix.container_suffix }}" @@ -501,7 +507,9 @@ jobs: - { gcc: 7, std: 17 } - { gcc: 8, std: 14 } - { gcc: 8, std: 17 } + - { gcc: 9, std: 20 } - { gcc: 10, std: 17 } + - { gcc: 10, std: 20 } - { gcc: 11, std: 20 } - { gcc: 12, std: 20 } - { gcc: 13, std: 20 } diff --git a/.github/workflows/emscripten.yaml b/.github/workflows/emscripten.yaml new file mode 100644 index 000000000..14b2b9dc7 --- /dev/null +++ b/.github/workflows/emscripten.yaml @@ -0,0 +1,30 @@ +name: WASM + +on: + workflow_dispatch: + pull_request: + branches: + - master + - stable + - v* + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-wasm-emscripten: + name: Pyodide wheel + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 0 + + - uses: pypa/cibuildwheel@v2.20 + env: + PYODIDE_BUILD_EXPORTS: whole_archive + with: + package-dir: tests + only: cp312-pyodide_wasm32 diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 1eaa56e1c..e50dc0bb7 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -41,7 +41,7 @@ jobs: # in .github/CONTRIBUTING.md and update as needed. name: Clang-Tidy runs-on: ubuntu-latest - container: silkeh/clang:15-bullseye + container: silkeh/clang:18-bookworm steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml index a054ce695..3edfa612d 100644 --- a/.github/workflows/pip.yml +++ b/.github/workflows/pip.yml @@ -102,7 +102,7 @@ jobs: - uses: actions/download-artifact@v4 - name: Generate artifact attestation for sdist and wheel - uses: actions/attest-build-provenance@173725a1209d09b31f9d30a3890cf2757ebbff0d # v1.1.2 + uses: actions/attest-build-provenance@310b0a4a3b0b78ef57ecda988ee04b132db73ef8 # v1.4.1 with: subject-path: "*/pybind11*" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3cec1ebe0..ecac1cbaf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,14 +25,14 @@ repos: # Clang format the codebase automatically - repo: https://github.com/pre-commit/mirrors-clang-format - rev: "v18.1.5" + rev: "v18.1.8" hooks: - id: clang-format types_or: [c++, c, cuda] # Ruff, the Python auto-correcting linter/formatter written in Rust - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.7 + rev: v0.5.6 hooks: - id: ruff args: ["--fix", "--show-fixes"] @@ -40,7 +40,7 @@ repos: # Check static types with mypy - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.10.0" + rev: "v1.11.1" hooks: - id: mypy args: [] @@ -79,7 +79,7 @@ repos: # Also code format the docs - repo: https://github.com/adamchainz/blacken-docs - rev: "1.16.0" + rev: "1.18.0" hooks: - id: blacken-docs additional_dependencies: @@ -142,14 +142,14 @@ repos: # PyLint has native support - not always usable, but works for us - repo: https://github.com/PyCQA/pylint - rev: "v3.2.2" + rev: "v3.2.6" hooks: - id: pylint files: ^pybind11 # Check schemas on some of our YAML files - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.28.4 + rev: 0.29.1 hooks: - id: check-readthedocs - id: check-github-workflows diff --git a/CMakeLists.txt b/CMakeLists.txt index 3526a1a66..6e5b8c8f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -154,6 +154,7 @@ set(PYBIND11_HEADERS include/pybind11/detail/internals.h include/pybind11/detail/type_caster_base.h include/pybind11/detail/typeid.h + include/pybind11/detail/value_and_holder.h include/pybind11/attr.h include/pybind11/buffer_info.h include/pybind11/cast.h diff --git a/docs/advanced/cast/eigen.rst b/docs/advanced/cast/eigen.rst index a5c11a3f1..894ce97f3 100644 --- a/docs/advanced/cast/eigen.rst +++ b/docs/advanced/cast/eigen.rst @@ -259,7 +259,7 @@ copying to take place: "small"_a // <- This one can be copied if needed ); -With the above binding code, attempting to call the the ``some_method(m)`` +With the above binding code, attempting to call the ``some_method(m)`` method on a ``MyClass`` object, or attempting to call ``some_function(m, m2)`` will raise a ``RuntimeError`` rather than making a temporary copy of the array. It will, however, allow the ``m2`` argument to be copied into a temporary if diff --git a/docs/changelog.rst b/docs/changelog.rst index ab6c713c1..031d31582 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -15,6 +15,98 @@ IN DEVELOPMENT Changes will be summarized here periodically. +New Features: + +* Support for Python 3.7 was removed. (Official end-of-life: 2023-06-27). + `#5191 `_ + +* stl.h ``list|set|map_caster`` were made more user friendly: it is no longer + necessary to explicitly convert Python iterables to ``tuple()``, ``set()``, + or ``map()`` in many common situations. + `#4686 `_ + +* Support for CMake older than 3.15 removed. CMake 3.15-3.30 supported. + `#5304 `_ + +Version 2.13.4 (August 14, 2024) +-------------------------------- + +Bug fixes: + +* Fix paths with spaces, including on Windows. + (Replaces regression from `#5302 `_) + `#4874 `_ + +Documentation: + +* Remove repetitive words. + `#5308 `_ + + +Version 2.13.3 (August 13, 2024) +-------------------------------- + +Bug fixes: + +* Quote paths from pybind11-config + `#5302 `_ + + +* Fix typo in Emscripten support when in config mode (CMake) + `#5301 `_ + + +Version 2.13.2 (August 13, 2024) +-------------------------------- + +New Features: + +* A ``pybind11::detail::type_caster_std_function_specializations`` feature was added, to support specializations for + ``std::function``'s with return types that require custom to-Python conversion behavior (to primary use case is to catch and + convert exceptions). + `#4597 `_ + + +Changes: + + +* Use ``PyMutex`` instead of ``std::mutex`` for internal locking in the free-threaded build. + `#5219 `_ + +* Add a special type annotation for C++ empty tuple. + `#5214 `_ + +* When compiling for WebAssembly, add the required exception flags (CMake 3.13+). + `#5298 `_ + +Bug fixes: + +* Make ``gil_safe_call_once_and_store`` thread-safe in free-threaded CPython. + `#5246 `_ + +* A missing ``#include `` in pybind11/typing.h was added to fix build errors (in case user code does not already depend + on that include). + `#5208 `_ + +* Fix regression introduced in #5201 for GCC<10.3 in C++20 mode. + `#5205 `_ + + +.. fix(cmake) + +* Remove extra = when assigning flto value in the case for Clang in CMake. + `#5207 `_ + + +Tests: + +* Adding WASM testing to our CI (Pyodide / Emscripten via scikit-build-core). + `#4745 `_ + +* clang-tidy (in GitHub Actions) was updated from clang 15 to clang 18. + `#5272 `_ + + Version 2.13.1 (June 26, 2024) ------------------------------ diff --git a/docs/compiling.rst b/docs/compiling.rst index 0b7c178b0..448ea11ca 100644 --- a/docs/compiling.rst +++ b/docs/compiling.rst @@ -25,7 +25,7 @@ A Python extension module can be created with just a few lines of code: find_package(pybind11 CONFIG REQUIRED) pybind11_add_module(example example.cpp) - install(TARGET example DESTINATION .) + install(TARGETS example DESTINATION .) (You use the ``add_subdirectory`` instead, see the example in :ref:`cmake`.) In this example, the code is located in a file named :file:`example.cpp`. Either @@ -388,7 +388,7 @@ that will be respected instead of the built-in flag search. The ``OPT_SIZE`` flag enables size-based optimization equivalent to the standard ``/Os`` or ``-Os`` compiler flags and the ``MinSizeRel`` build type, -which avoid optimizations that that can substantially increase the size of the +which avoid optimizations that can substantially increase the size of the resulting binary. This flag is particularly useful in projects that are split into performance-critical parts and associated bindings. In this case, we can compile the project in release mode (and hence, optimize performance globally), @@ -719,7 +719,7 @@ customizable pybind11-based wrappers by parsing C++ header files. [litgen]_ is an automatic python bindings generator with a focus on generating documented and discoverable bindings: bindings will nicely reproduce the documentation -found in headers. It is is based on srcML (srcml.org), a highly scalable, multi-language +found in headers. It is based on srcML (srcml.org), a highly scalable, multi-language parsing tool with a developer centric approach. The API that you want to expose to python must be C++14 compatible (but your implementation can use more modern constructs). diff --git a/docs/limitations.rst b/docs/limitations.rst index def5ad659..1b06ea872 100644 --- a/docs/limitations.rst +++ b/docs/limitations.rst @@ -50,10 +50,6 @@ clean, well written patch would likely be accepted to solve them. One consequence is that containers of ``char *`` are currently not supported. `#2245 `_ -- The ``cpptest`` does not run on Windows with Python 3.8 or newer, due to DLL - loader changes. User code that is correctly installed should not be affected. - `#2560 `_ - Python 3.9.0 warning ^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/requirements.txt b/docs/requirements.txt index 293db6a06..4e53f0352 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -16,9 +16,9 @@ breathe==4.35.0 \ --hash=sha256:5165541c3c67b6c7adde8b3ecfe895c6f7844783c4076b6d8d287e4f33d62386 \ --hash=sha256:52c581f42ca4310737f9e435e3851c3d1f15446205a85fbc272f1f97ed74f5be # via -r requirements.in -certifi==2024.2.2 \ - --hash=sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f \ - --hash=sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1 +certifi==2024.7.4 \ + --hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \ + --hash=sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90 # via requests charset-normalizer==3.3.2 \ --hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \ diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 624b8ebac..0f3091f68 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -740,6 +740,13 @@ class type_caster> : public tuple_caster {} template class type_caster> : public tuple_caster {}; +template <> +class type_caster> : public tuple_caster { +public: + // PEP 484 specifies this syntax for an empty tuple + static constexpr auto name = const_name("tuple[()]"); +}; + /// Helper class which abstracts away certain actions. Users can provide specializations for /// custom holders, but it's only necessary if the type has a non-standard interface. template @@ -787,11 +794,11 @@ protected: } } - bool load_value(value_and_holder &&v_h) { + void load_value(value_and_holder &&v_h) { if (v_h.holder_constructed()) { value = v_h.value_ptr(); holder = v_h.template holder(); - return true; + return; } throw cast_error("Unable to cast from non-held to held instance (T& to Holder) " #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index e37152a9a..44a269e45 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -11,11 +11,11 @@ #define PYBIND11_VERSION_MAJOR 2 #define PYBIND11_VERSION_MINOR 13 -#define PYBIND11_VERSION_PATCH 1 +#define PYBIND11_VERSION_PATCH 4 // Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html // Additional convention: 0xD = dev -#define PYBIND11_VERSION_HEX 0x020D0100 +#define PYBIND11_VERSION_HEX 0x020D0400 // Define some generic pybind11 helper macros for warning management. // @@ -462,6 +462,22 @@ PYBIND11_WARNING_POP return "Hello, World!"; }); } + + The third macro argument is optional (available since 2.13.0), and can be used to + mark the extension module as safe to run without the GIL under a free-threaded CPython + interpreter. Passing this argument has no effect on other interpreters. + + .. code-block:: cpp + + PYBIND11_MODULE(example, m, py::mod_gil_not_used()) { + m.doc() = "pybind11 example module safe to run without the GIL"; + + // Add bindings here + m.def("foo", []() { + return "Hello, Free-threaded World!"; + }); + } + \endrst */ #define PYBIND11_MODULE(name, variable, ...) \ static ::pybind11::module_::module_def PYBIND11_CONCAT(pybind11_module_def_, name) \ @@ -538,7 +554,7 @@ enum class return_value_policy : uint8_t { object without taking ownership similar to the above return_value_policy::reference policy. In contrast to that policy, the function or property's implicit this argument (called the parent) is - considered to be the the owner of the return value (the child). + considered to be the owner of the return value (the child). pybind11 then couples the lifetime of the parent to the child via a reference relationship that ensures that the parent cannot be garbage collected while Python is still using the child. More advanced diff --git a/include/pybind11/detail/init.h b/include/pybind11/detail/init.h index 4509bd131..79cc930c8 100644 --- a/include/pybind11/detail/init.h +++ b/include/pybind11/detail/init.h @@ -128,11 +128,13 @@ void construct(value_and_holder &v_h, Cpp *ptr, bool need_alias) { // the holder and destruction happens when we leave the C++ scope, and the holder // class gets to handle the destruction however it likes. v_h.value_ptr() = ptr; - v_h.set_instance_registered(true); // To prevent init_instance from registering it - v_h.type->init_instance(v_h.inst, nullptr); // Set up the holder + v_h.set_instance_registered(true); // Trick to prevent init_instance from registering it + // DANGER ZONE BEGIN: exceptions will leave v_h in an invalid state. + v_h.type->init_instance(v_h.inst, nullptr); // Set up the holder Holder temp_holder(std::move(v_h.holder>())); // Steal the holder v_h.type->dealloc(v_h); // Destroys the moved-out holder remains, resets value ptr to null v_h.set_instance_registered(false); + // DANGER ZONE END. construct_alias_from_cpp(is_alias_constructible{}, v_h, std::move(*ptr)); } else { diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index e61c1687f..692e8d396 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -148,20 +148,35 @@ struct override_hash { using instance_map = std::unordered_multimap; +#ifdef Py_GIL_DISABLED +// Wrapper around PyMutex to provide BasicLockable semantics +class pymutex { + PyMutex mutex; + +public: + pymutex() : mutex({}) {} + void lock() { PyMutex_Lock(&mutex); } + void unlock() { PyMutex_Unlock(&mutex); } +}; + // Instance map shards are used to reduce mutex contention in free-threaded Python. struct instance_map_shard { - std::mutex mutex; instance_map registered_instances; + pymutex mutex; // alignas(64) would be better, but causes compile errors in macOS before 10.14 (see #5200) - char padding[64 - (sizeof(std::mutex) + sizeof(instance_map)) % 64]; + char padding[64 - (sizeof(instance_map) + sizeof(pymutex)) % 64]; }; +static_assert(sizeof(instance_map_shard) % 64 == 0, + "instance_map_shard size is not a multiple of 64 bytes"); +#endif + /// Internal data structure used to track registered instances and types. /// Whenever binary incompatible changes are made to this structure, /// `PYBIND11_INTERNALS_VERSION` must be incremented. struct internals { #ifdef Py_GIL_DISABLED - std::mutex mutex; + pymutex mutex; #endif // std::type_index -> pybind11's type information type_map registered_types_cpp; @@ -538,7 +553,7 @@ PYBIND11_NOINLINE internals &get_internals() { } #endif internals_ptr->istate = tstate->interp; - state_dict[PYBIND11_INTERNALS_ID] = capsule(internals_pp); + state_dict[PYBIND11_INTERNALS_ID] = capsule(reinterpret_cast(internals_pp)); internals_ptr->registered_exception_translators.push_front(&translate_exception); internals_ptr->static_property_type = make_static_property_type(); internals_ptr->default_metaclass = make_default_metaclass(); @@ -614,7 +629,7 @@ inline local_internals &get_local_internals() { } #ifdef Py_GIL_DISABLED -# define PYBIND11_LOCK_INTERNALS(internals) std::unique_lock lock((internals).mutex) +# define PYBIND11_LOCK_INTERNALS(internals) std::unique_lock lock((internals).mutex) #else # define PYBIND11_LOCK_INTERNALS(internals) #endif @@ -651,7 +666,7 @@ inline auto with_instance_map(const void *ptr, auto idx = static_cast(hash & internals.instance_shards_mask); auto &shard = internals.instance_shards[idx]; - std::unique_lock lock(shard.mutex); + std::unique_lock lock(shard.mutex); return cb(shard.registered_instances); #else (void) ptr; @@ -667,7 +682,7 @@ inline size_t num_registered_instances() { size_t count = 0; for (size_t i = 0; i <= internals.instance_shards_mask; ++i) { auto &shard = internals.instance_shards[i]; - std::unique_lock lock(shard.mutex); + std::unique_lock lock(shard.mutex); count += shard.registered_instances.size(); } return count; diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index fd8c81b9a..481b7c783 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -14,6 +14,7 @@ #include "descr.h" #include "internals.h" #include "typeid.h" +#include "value_and_holder.h" #include #include @@ -259,67 +260,6 @@ PYBIND11_NOINLINE handle find_registered_python_instance(void *src, }); } -struct value_and_holder { - instance *inst = nullptr; - size_t index = 0u; - const detail::type_info *type = nullptr; - void **vh = nullptr; - - // Main constructor for a found value/holder: - value_and_holder(instance *i, const detail::type_info *type, size_t vpos, size_t index) - : inst{i}, index{index}, type{type}, - vh{inst->simple_layout ? inst->simple_value_holder - : &inst->nonsimple.values_and_holders[vpos]} {} - - // Default constructor (used to signal a value-and-holder not found by get_value_and_holder()) - value_and_holder() = default; - - // Used for past-the-end iterator - explicit value_and_holder(size_t index) : index{index} {} - - template - V *&value_ptr() const { - return reinterpret_cast(vh[0]); - } - // True if this `value_and_holder` has a non-null value pointer - explicit operator bool() const { return value_ptr() != nullptr; } - - template - H &holder() const { - return reinterpret_cast(vh[1]); - } - bool holder_constructed() const { - return inst->simple_layout - ? inst->simple_holder_constructed - : (inst->nonsimple.status[index] & instance::status_holder_constructed) != 0u; - } - // NOLINTNEXTLINE(readability-make-member-function-const) - void set_holder_constructed(bool v = true) { - if (inst->simple_layout) { - inst->simple_holder_constructed = v; - } else if (v) { - inst->nonsimple.status[index] |= instance::status_holder_constructed; - } else { - inst->nonsimple.status[index] &= (std::uint8_t) ~instance::status_holder_constructed; - } - } - bool instance_registered() const { - return inst->simple_layout - ? inst->simple_instance_registered - : ((inst->nonsimple.status[index] & instance::status_instance_registered) != 0); - } - // NOLINTNEXTLINE(readability-make-member-function-const) - void set_instance_registered(bool v = true) { - if (inst->simple_layout) { - inst->simple_instance_registered = v; - } else if (v) { - inst->nonsimple.status[index] |= instance::status_instance_registered; - } else { - inst->nonsimple.status[index] &= (std::uint8_t) ~instance::status_instance_registered; - } - } -}; - // Container for accessing and iterating over an instance's values/holders struct values_and_holders { private: @@ -488,7 +428,7 @@ PYBIND11_NOINLINE void instance::allocate_layout() { // NOLINTNEXTLINE(readability-make-member-function-const) PYBIND11_NOINLINE void instance::deallocate_layout() { if (!simple_layout) { - PyMem_Free(nonsimple.values_and_holders); + PyMem_Free(reinterpret_cast(nonsimple.values_and_holders)); } } diff --git a/include/pybind11/detail/value_and_holder.h b/include/pybind11/detail/value_and_holder.h new file mode 100644 index 000000000..ca37d70ad --- /dev/null +++ b/include/pybind11/detail/value_and_holder.h @@ -0,0 +1,77 @@ +// Copyright (c) 2016-2024 The Pybind Development Team. +// All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#pragma once + +#include "common.h" + +#include +#include + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) + +struct value_and_holder { + instance *inst = nullptr; + size_t index = 0u; + const detail::type_info *type = nullptr; + void **vh = nullptr; + + // Main constructor for a found value/holder: + value_and_holder(instance *i, const detail::type_info *type, size_t vpos, size_t index) + : inst{i}, index{index}, type{type}, + vh{inst->simple_layout ? inst->simple_value_holder + : &inst->nonsimple.values_and_holders[vpos]} {} + + // Default constructor (used to signal a value-and-holder not found by get_value_and_holder()) + value_and_holder() = default; + + // Used for past-the-end iterator + explicit value_and_holder(size_t index) : index{index} {} + + template + V *&value_ptr() const { + return reinterpret_cast(vh[0]); + } + // True if this `value_and_holder` has a non-null value pointer + explicit operator bool() const { return value_ptr() != nullptr; } + + template + H &holder() const { + return reinterpret_cast(vh[1]); + } + bool holder_constructed() const { + return inst->simple_layout + ? inst->simple_holder_constructed + : (inst->nonsimple.status[index] & instance::status_holder_constructed) != 0u; + } + // NOLINTNEXTLINE(readability-make-member-function-const) + void set_holder_constructed(bool v = true) { + if (inst->simple_layout) { + inst->simple_holder_constructed = v; + } else if (v) { + inst->nonsimple.status[index] |= instance::status_holder_constructed; + } else { + inst->nonsimple.status[index] &= (std::uint8_t) ~instance::status_holder_constructed; + } + } + bool instance_registered() const { + return inst->simple_layout + ? inst->simple_instance_registered + : ((inst->nonsimple.status[index] & instance::status_instance_registered) != 0); + } + // NOLINTNEXTLINE(readability-make-member-function-const) + void set_instance_registered(bool v = true) { + if (inst->simple_layout) { + inst->simple_instance_registered = v; + } else if (v) { + inst->nonsimple.status[index] |= instance::status_instance_registered; + } else { + inst->nonsimple.status[index] &= (std::uint8_t) ~instance::status_instance_registered; + } + } +}; + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/eigen/tensor.h b/include/pybind11/eigen/tensor.h index d4ed6c0ca..14bb91c40 100644 --- a/include/pybind11/eigen/tensor.h +++ b/include/pybind11/eigen/tensor.h @@ -469,9 +469,6 @@ struct type_caster, parent_object = reinterpret_borrow(parent); break; - case return_value_policy::take_ownership: - delete src; - // fallthrough default: // move, take_ownership don't make any sense for a ref/map: pybind11_fail("Invalid return_value_policy for Eigen Map type, must be either " diff --git a/include/pybind11/functional.h b/include/pybind11/functional.h index 6856119cd..4b3610117 100644 --- a/include/pybind11/functional.h +++ b/include/pybind11/functional.h @@ -9,12 +9,55 @@ #pragma once +#define PYBIND11_HAS_TYPE_CASTER_STD_FUNCTION_SPECIALIZATIONS + #include "pybind11.h" #include PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(detail) +PYBIND11_NAMESPACE_BEGIN(type_caster_std_function_specializations) + +// ensure GIL is held during functor destruction +struct func_handle { + function f; +#if !(defined(_MSC_VER) && _MSC_VER == 1916 && defined(PYBIND11_CPP17)) + // This triggers a syntax error under very special conditions (very weird indeed). + explicit +#endif + func_handle(function &&f_) noexcept + : f(std::move(f_)) { + } + func_handle(const func_handle &f_) { operator=(f_); } + func_handle &operator=(const func_handle &f_) { + gil_scoped_acquire acq; + f = f_.f; + return *this; + } + ~func_handle() { + gil_scoped_acquire acq; + function kill_f(std::move(f)); + } +}; + +// to emulate 'move initialization capture' in C++11 +struct func_wrapper_base { + func_handle hfunc; + explicit func_wrapper_base(func_handle &&hf) noexcept : hfunc(hf) {} +}; + +template +struct func_wrapper : func_wrapper_base { + using func_wrapper_base::func_wrapper_base; + Return operator()(Args... args) const { + gil_scoped_acquire acq; + // casts the returned object as a rvalue to the return type + return hfunc.f(std::forward(args)...).template cast(); + } +}; + +PYBIND11_NAMESPACE_END(type_caster_std_function_specializations) template struct type_caster> { @@ -77,40 +120,8 @@ public: // See PR #1413 for full details } - // ensure GIL is held during functor destruction - struct func_handle { - function f; -#if !(defined(_MSC_VER) && _MSC_VER == 1916 && defined(PYBIND11_CPP17)) - // This triggers a syntax error under very special conditions (very weird indeed). - explicit -#endif - func_handle(function &&f_) noexcept - : f(std::move(f_)) { - } - func_handle(const func_handle &f_) { operator=(f_); } - func_handle &operator=(const func_handle &f_) { - gil_scoped_acquire acq; - f = f_.f; - return *this; - } - ~func_handle() { - gil_scoped_acquire acq; - function kill_f(std::move(f)); - } - }; - - // to emulate 'move initialization capture' in C++11 - struct func_wrapper { - func_handle hfunc; - explicit func_wrapper(func_handle &&hf) noexcept : hfunc(std::move(hf)) {} - Return operator()(Args... args) const { - gil_scoped_acquire acq; - // casts the returned object as a rvalue to the return type - return hfunc.f(std::forward(args)...).template cast(); - } - }; - - value = func_wrapper(func_handle(std::move(func))); + value = type_caster_std_function_specializations::func_wrapper( + type_caster_std_function_specializations::func_handle(std::move(func))); return true; } diff --git a/include/pybind11/gil_safe_call_once.h b/include/pybind11/gil_safe_call_once.h index eaf84d16e..5f9e1b03c 100644 --- a/include/pybind11/gil_safe_call_once.h +++ b/include/pybind11/gil_safe_call_once.h @@ -8,6 +8,10 @@ #include #include +#ifdef Py_GIL_DISABLED +# include +#endif + PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) // Use the `gil_safe_call_once_and_store` class below instead of the naive @@ -82,7 +86,12 @@ public: private: alignas(T) char storage_[sizeof(T)] = {}; std::once_flag once_flag_ = {}; - bool is_initialized_ = false; +#ifdef Py_GIL_DISABLED + std::atomic_bool +#else + bool +#endif + is_initialized_{false}; // The `is_initialized_`-`storage_` pair is very similar to `std::optional`, // but the latter does not have the triviality properties of former, // therefore `std::optional` is not a viable alternative here. diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 05ef3918b..09894cf74 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -901,7 +901,11 @@ public: template array(ShapeContainer shape, StridesContainer strides, const T *ptr, handle base = handle()) - : array(pybind11::dtype::of(), std::move(shape), std::move(strides), ptr, base) {} + : array(pybind11::dtype::of(), + std::move(shape), + std::move(strides), + reinterpret_cast(ptr), + base) {} template array(ShapeContainer shape, const T *ptr, handle base = handle()) @@ -1986,7 +1990,7 @@ private: // Pointers to values the function was called with; the vectorized ones set here will start // out as array_t pointers, but they will be changed them to T pointers before we make // call the wrapped function. Non-vectorized pointers are left as-is. - std::array params{{&args...}}; + std::array params{{reinterpret_cast(&args)...}}; // The array of `buffer_info`s of vectorized arguments: std::array buffers{ diff --git a/include/pybind11/stl/filesystem.h b/include/pybind11/stl/filesystem.h index 85c131efe..935d79481 100644 --- a/include/pybind11/stl/filesystem.h +++ b/include/pybind11/stl/filesystem.h @@ -33,6 +33,13 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(detail) +#ifdef PYPY_VERSION +# define PYBIND11_REINTERPRET_CAST_VOID_PTR_IF_NOT_PYPY(...) (__VA_ARGS__) +#else +# define PYBIND11_REINTERPRET_CAST_VOID_PTR_IF_NOT_PYPY(...) \ + (reinterpret_cast(__VA_ARGS__)) +#endif + #if defined(PYBIND11_HAS_FILESYSTEM) || defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM) template struct path_caster { @@ -72,7 +79,8 @@ public: } PyObject *native = nullptr; if constexpr (std::is_same_v) { - if (PyUnicode_FSConverter(buf, &native) != 0) { + if (PyUnicode_FSConverter(buf, PYBIND11_REINTERPRET_CAST_VOID_PTR_IF_NOT_PYPY(&native)) + != 0) { if (auto *c_str = PyBytes_AsString(native)) { // AsString returns a pointer to the internal buffer, which // must not be free'd. @@ -80,7 +88,8 @@ public: } } } else if constexpr (std::is_same_v) { - if (PyUnicode_FSDecoder(buf, &native) != 0) { + if (PyUnicode_FSDecoder(buf, PYBIND11_REINTERPRET_CAST_VOID_PTR_IF_NOT_PYPY(&native)) + != 0) { if (auto *c_str = PyUnicode_AsWideCharString(native, nullptr)) { // AsWideCharString returns a new string that must be free'd. value = c_str; // Copies the string. diff --git a/include/pybind11/stl_bind.h b/include/pybind11/stl_bind.h index 66c452ea7..fcb48dea3 100644 --- a/include/pybind11/stl_bind.h +++ b/include/pybind11/stl_bind.h @@ -180,7 +180,7 @@ void vector_modifiers( v.end()); try { v.shrink_to_fit(); - } catch (const std::exception &) { + } catch (const std::exception &) { // NOLINT(bugprone-empty-catch) // Do nothing } throw; diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index 1442cdc7f..b0feb9464 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -14,6 +14,8 @@ #include "cast.h" #include "pytypes.h" +#include + PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(typing) @@ -98,7 +100,10 @@ class Never : public none { using none::none; }; -#if defined(__cpp_nontype_template_parameter_class) +#if defined(__cpp_nontype_template_parameter_class) \ + && (/* See #5201 */ !defined(__GNUC__) \ + || (__GNUC__ > 10 || (__GNUC__ == 10 && __GNUC_MINOR__ >= 3))) +# define PYBIND11_TYPING_H_HAS_STRING_LITERAL template struct StringLiteral { constexpr StringLiteral(const char (&str)[N]) { std::copy_n(str, N, name); } @@ -222,7 +227,7 @@ struct handle_type_name { static constexpr auto name = const_name("Never"); }; -#if defined(__cpp_nontype_template_parameter_class) +#if defined(PYBIND11_TYPING_H_HAS_STRING_LITERAL) template struct handle_type_name> { static constexpr auto name = const_name("Literal[") diff --git a/pybind11/__main__.py b/pybind11/__main__.py index b656ce6fe..0abc7e211 100644 --- a/pybind11/__main__.py +++ b/pybind11/__main__.py @@ -2,12 +2,35 @@ from __future__ import annotations import argparse +import re import sys import sysconfig from ._version import __version__ from .commands import get_cmake_dir, get_include, get_pkgconfig_dir +# This is the conditional used for os.path being posixpath +if "posix" in sys.builtin_module_names: + from shlex import quote +elif "nt" in sys.builtin_module_names: + # See https://github.com/mesonbuild/meson/blob/db22551ed9d2dd7889abea01cc1c7bba02bf1c75/mesonbuild/utils/universal.py#L1092-L1121 + # and the original documents: + # https://docs.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments and + # https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ + UNSAFE = re.compile("[ \t\n\r]") + + def quote(s: str) -> str: + if s and not UNSAFE.search(s): + return s + + # Paths cannot contain a '"' on Windows, so we don't need to worry + # about nuanced counting here. + return f'"{s}\\"' if s.endswith("\\") else f'"{s}"' +else: + + def quote(s: str) -> str: + return s + def print_includes() -> None: dirs = [ @@ -22,7 +45,7 @@ def print_includes() -> None: if d and d not in unique_dirs: unique_dirs.append(d) - print(" ".join("-I" + d for d in unique_dirs)) + print(" ".join(quote(f"-I{d}") for d in unique_dirs)) def main() -> None: @@ -54,9 +77,9 @@ def main() -> None: if args.includes: print_includes() if args.cmakedir: - print(get_cmake_dir()) + print(quote(get_cmake_dir())) if args.pkgconfigdir: - print(get_pkgconfig_dir()) + print(quote(get_pkgconfig_dir())) if __name__ == "__main__": diff --git a/pybind11/_version.py b/pybind11/_version.py index 18b72c07c..d44e46e08 100644 --- a/pybind11/_version.py +++ b/pybind11/_version.py @@ -8,5 +8,5 @@ def _to_int(s: str) -> int | str: return s -__version__ = "2.13.1" +__version__ = "2.13.4" version_info = tuple(_to_int(s) for s in __version__.split(".")) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f182e2499..5b7e3f801 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -88,7 +88,12 @@ set(PYBIND11_TEST_FILTER if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) # We're being loaded directly, i.e. not via add_subdirectory, so make this # work as its own project and load the pybind11Config to get the tools we need - find_package(pybind11 REQUIRED CONFIG) + + if(SKBUILD) + add_subdirectory(.. pybind11_src) + else() + find_package(pybind11 REQUIRED CONFIG) + endif() endif() if(NOT CMAKE_BUILD_TYPE AND NOT DEFINED CMAKE_CONFIGURATION_TYPES) @@ -153,6 +158,7 @@ set(PYBIND11_TEST_FILES test_tagbased_polymorphic test_thread test_type_caster_pyobject_ptr + test_type_caster_std_function_specializations test_union test_unnamed_namespace_a test_unnamed_namespace_b @@ -489,6 +495,9 @@ foreach(target ${test_targets}) endforeach() endif() endif() + if(SKBUILD) + install(TARGETS ${target} LIBRARY DESTINATION .) + endif() endforeach() # Provide nice organisation in IDEs diff --git a/tests/constructor_stats.h b/tests/constructor_stats.h index 937f6c233..9a5754fed 100644 --- a/tests/constructor_stats.h +++ b/tests/constructor_stats.h @@ -190,7 +190,7 @@ public: t1 = &p.first; } } - } catch (const std::out_of_range &) { + } catch (const std::out_of_range &) { // NOLINT(bugprone-empty-catch) } if (!t1) { throw std::runtime_error("Unknown class passed to ConstructorStats::get()"); diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py index 5a3f779a7..aedbdf1c1 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -58,6 +58,7 @@ detail_headers = { "include/pybind11/detail/internals.h", "include/pybind11/detail/type_caster_base.h", "include/pybind11/detail/typeid.h", + "include/pybind11/detail/value_and_holder.h", } eigen_headers = { diff --git a/tests/pyproject.toml b/tests/pyproject.toml new file mode 100644 index 000000000..044bf15c0 --- /dev/null +++ b/tests/pyproject.toml @@ -0,0 +1,21 @@ +# Warning: this is currently used for pyodide, and is not a general out-of-tree +# builder for the tests (yet). Specifically, wheels can't be built from SDists. + +[build-system] +requires = ["scikit-build-core"] +build-backend = "scikit_build_core.build" + +[project] +name = "pybind11_tests" +version = "0.0.1" +dependencies = ["pytest", "pytest-timeout", "numpy", "scipy"] + +[tool.scikit-build] +# Hide a warning while we also support CMake < 3.15 +cmake.version = ">=3.15" + +[tool.scikit-build.cmake.define] +PYBIND11_FINDPYTHON = true + +[tool.cibuildwheel] +test-command = "pytest -o timeout=0 -p no:cacheprovider {project}/tests/test_*.py" diff --git a/tests/test_async.py b/tests/test_async.py index 4d33ba65f..1705196d1 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -1,10 +1,15 @@ from __future__ import annotations +import sys + import pytest asyncio = pytest.importorskip("asyncio") m = pytest.importorskip("pybind11_tests.async_module") +if sys.platform.startswith("emscripten"): + pytest.skip("Can't run a new event_loop in pyodide", allow_module_level=True) + @pytest.fixture() def event_loop(): diff --git a/tests/test_builtin_casters.py b/tests/test_builtin_casters.py index 9aa5926e9..b37aacff1 100644 --- a/tests/test_builtin_casters.py +++ b/tests/test_builtin_casters.py @@ -368,6 +368,8 @@ def test_tuple(doc): """ ) + assert doc(m.empty_tuple) == """empty_tuple() -> tuple[()]""" + assert m.rvalue_pair() == ("rvalue", "rvalue") assert m.lvalue_pair() == ("lvalue", "lvalue") assert m.rvalue_tuple() == ("rvalue", "rvalue", "rvalue") diff --git a/tests/test_callbacks.py b/tests/test_callbacks.py index ce2a6d254..db6d8dece 100644 --- a/tests/test_callbacks.py +++ b/tests/test_callbacks.py @@ -1,5 +1,6 @@ from __future__ import annotations +import sys import time from threading import Thread @@ -153,6 +154,7 @@ def test_python_builtins(): assert m.test_sum_builtin(sum, []) == 0 +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_async_callbacks(): # serves as state for async callback class Item: @@ -176,6 +178,7 @@ def test_async_callbacks(): assert sum(res) == sum(x + 3 for x in work) +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_async_async_callbacks(): t = Thread(target=test_async_callbacks) t.start() diff --git a/tests/test_eigen_tensor.inl b/tests/test_eigen_tensor.inl index 25cf29f15..3b3641e8d 100644 --- a/tests/test_eigen_tensor.inl +++ b/tests/test_eigen_tensor.inl @@ -147,33 +147,39 @@ void init_tensor_module(pybind11::module &m) { m.def( "take_fixed_tensor", - []() { Eigen::aligned_allocator< Eigen::TensorFixedSize, Options>> allocator; - return new (allocator.allocate(1)) + static auto *obj = new (allocator.allocate(1)) Eigen::TensorFixedSize, Options>( get_fixed_tensor()); + return obj; // take_ownership will fail. }, py::return_value_policy::take_ownership); m.def( "take_tensor", - []() { return new Eigen::Tensor(get_tensor()); }, + []() { + static auto *obj = new Eigen::Tensor(get_tensor()); + return obj; // take_ownership will fail. + }, py::return_value_policy::take_ownership); m.def( "take_const_tensor", []() -> const Eigen::Tensor * { - return new Eigen::Tensor(get_tensor()); + static auto *obj = new Eigen::Tensor(get_tensor()); + return obj; // take_ownership will fail. }, py::return_value_policy::take_ownership); m.def( "take_view_tensor", []() -> const Eigen::TensorMap> * { - return new Eigen::TensorMap>(get_tensor()); + static auto *obj + = new Eigen::TensorMap>(get_tensor()); + return obj; // take_ownership will fail. }, py::return_value_policy::take_ownership); diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index b33997eee..db2a551e4 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -75,7 +75,7 @@ def test_cross_module_exceptions(msg): # TODO: FIXME @pytest.mark.xfail( - "env.MACOS and (env.PYPY or pybind11_tests.compiler_info.startswith('Homebrew Clang'))", + "env.MACOS and (env.PYPY or pybind11_tests.compiler_info.startswith('Homebrew Clang')) or sys.platform.startswith('emscripten')", raises=RuntimeError, reason="See Issue #2847, PR #2999, PR #4324", ) diff --git a/tests/test_gil_scoped.py b/tests/test_gil_scoped.py index a18387684..eab92093c 100644 --- a/tests/test_gil_scoped.py +++ b/tests/test_gil_scoped.py @@ -71,24 +71,28 @@ def test_cross_module_gil_inner_pybind11_acquired(): m.test_cross_module_gil_inner_pybind11_acquired() +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_cross_module_gil_nested_custom_released(): """Makes sure that the GIL can be nested acquired/released by another module from a GIL-released state using custom locking logic.""" m.test_cross_module_gil_nested_custom_released() +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_cross_module_gil_nested_custom_acquired(): """Makes sure that the GIL can be nested acquired/acquired by another module from a GIL-acquired state using custom locking logic.""" m.test_cross_module_gil_nested_custom_acquired() +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_cross_module_gil_nested_pybind11_released(): """Makes sure that the GIL can be nested acquired/released by another module from a GIL-released state using pybind11 locking logic.""" m.test_cross_module_gil_nested_pybind11_released() +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_cross_module_gil_nested_pybind11_acquired(): """Makes sure that the GIL can be nested acquired/acquired by another module from a GIL-acquired state using pybind11 locking logic.""" @@ -103,6 +107,7 @@ def test_nested_acquire(): assert m.test_nested_acquire(0xAB) == "171" +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_multi_acquire_release_cross_module(): for bits in range(16 * 8): internals_ids = m.test_multi_acquire_release_cross_module(bits) @@ -204,7 +209,7 @@ def _run_in_threads(test_fn, num_threads, parallel): thread.join() -# TODO: FIXME, sometimes returns -11 (segfault) instead of 0 on macOS Python 3.9 +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) def test_run_in_process_one_thread(test_fn): """Makes sure there is no GIL deadlock when running in a thread. @@ -214,7 +219,7 @@ def test_run_in_process_one_thread(test_fn): assert _run_in_process(_run_in_threads, test_fn, num_threads=1, parallel=False) == 0 -# TODO: FIXME on macOS Python 3.9 +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) def test_run_in_process_multiple_threads_parallel(test_fn): """Makes sure there is no GIL deadlock when running in a thread multiple times in parallel. @@ -224,7 +229,7 @@ def test_run_in_process_multiple_threads_parallel(test_fn): assert _run_in_process(_run_in_threads, test_fn, num_threads=8, parallel=True) == 0 -# TODO: FIXME on macOS Python 3.9 +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) def test_run_in_process_multiple_threads_sequential(test_fn): """Makes sure there is no GIL deadlock when running in a thread multiple times sequentially. @@ -234,7 +239,7 @@ def test_run_in_process_multiple_threads_sequential(test_fn): assert _run_in_process(_run_in_threads, test_fn, num_threads=8, parallel=False) == 0 -# TODO: FIXME on macOS Python 3.9 +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) def test_run_in_process_direct(test_fn): """Makes sure there is no GIL deadlock when using processes. diff --git a/tests/test_iostream.py b/tests/test_iostream.py index f7eeff502..c3d987787 100644 --- a/tests/test_iostream.py +++ b/tests/test_iostream.py @@ -1,8 +1,11 @@ from __future__ import annotations +import sys from contextlib import redirect_stderr, redirect_stdout from io import StringIO +import pytest + from pybind11_tests import iostream as m @@ -270,6 +273,7 @@ def test_redirect_both(capfd): assert stream2.getvalue() == msg2 +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_threading(): with m.ostream_redirect(stdout=True, stderr=False): # start some threads diff --git a/tests/test_modules.cpp b/tests/test_modules.cpp index 18a7ec74c..7f01687c7 100644 --- a/tests/test_modules.cpp +++ b/tests/test_modules.cpp @@ -90,32 +90,32 @@ TEST_SUBMODULE(modules, m) { try { py::class_(dm, "Dupe1"); failures.append("Dupe1 class"); - } catch (std::runtime_error &) { + } catch (std::runtime_error &) { // NOLINT(bugprone-empty-catch) } try { dm.def("Dupe1", []() { return Dupe1(); }); failures.append("Dupe1 function"); - } catch (std::runtime_error &) { + } catch (std::runtime_error &) { // NOLINT(bugprone-empty-catch) } try { py::class_(dm, "dupe1_factory"); failures.append("dupe1_factory"); - } catch (std::runtime_error &) { + } catch (std::runtime_error &) { // NOLINT(bugprone-empty-catch) } try { py::exception(dm, "Dupe2"); failures.append("Dupe2"); - } catch (std::runtime_error &) { + } catch (std::runtime_error &) { // NOLINT(bugprone-empty-catch) } try { dm.def("DupeException", []() { return 30; }); failures.append("DupeException1"); - } catch (std::runtime_error &) { + } catch (std::runtime_error &) { // NOLINT(bugprone-empty-catch) } try { py::class_(dm, "DupeException"); failures.append("DupeException2"); - } catch (std::runtime_error &) { + } catch (std::runtime_error &) { // NOLINT(bugprone-empty-catch) } return failures; diff --git a/tests/test_numpy_dtypes.cpp b/tests/test_numpy_dtypes.cpp index ed77ec024..596d90274 100644 --- a/tests/test_numpy_dtypes.cpp +++ b/tests/test_numpy_dtypes.cpp @@ -266,6 +266,8 @@ py::array_t test_array_ctors(int i) { return fill(arr_t(buf_ndim1_null)); case 44: return fill(py::array(buf_ndim1_null)); + default: + break; } return arr_t(); } diff --git a/tests/test_opaque_types.cpp b/tests/test_opaque_types.cpp index 0386dba03..154d0a8d3 100644 --- a/tests/test_opaque_types.cpp +++ b/tests/test_opaque_types.cpp @@ -65,7 +65,7 @@ TEST_SUBMODULE(opaque_types, m) { m.def("return_unique_ptr", []() -> std::unique_ptr { auto *result = new StringList(); - result->push_back("some value"); + result->emplace_back("some value"); return std::unique_ptr(result); }); diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 7c30978ce..ecb44939a 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -109,7 +109,7 @@ void m_defs(py::module_ &m) { } // namespace handle_from_move_only_type_with_operator_PyObject -#if defined(__cpp_nontype_template_parameter_class) +#if defined(PYBIND11_TYPING_H_HAS_STRING_LITERAL) namespace literals { enum Color { RED = 0, BLUE = 1 }; @@ -905,7 +905,7 @@ TEST_SUBMODULE(pytypes, m) { m.def("annotate_optional_to_object", [](py::typing::Optional &o) -> py::object { return o; }); -#if defined(__cpp_nontype_template_parameter_class) +#if defined(PYBIND11_TYPING_H_HAS_STRING_LITERAL) py::enum_(m, "Color") .value("RED", literals::Color::RED) .value("BLUE", literals::Color::BLUE); @@ -919,8 +919,8 @@ TEST_SUBMODULE(pytypes, m) { m.def("annotate_listT_to_T", [](const py::typing::List &l) -> typevar::TypeVarT { return l[0]; }); m.def("annotate_object_to_T", [](const py::object &o) -> typevar::TypeVarT { return o; }); - m.attr("if_defined__cpp_nontype_template_parameter_class") = true; + m.attr("defined_PYBIND11_TYPING_H_HAS_STRING_LITERAL") = true; #else - m.attr("if_defined__cpp_nontype_template_parameter_class") = false; + m.attr("defined_PYBIND11_TYPING_H_HAS_STRING_LITERAL") = false; #endif } diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 30931e0b9..218092b43 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1025,7 +1025,7 @@ def test_optional_object_annotations(doc): @pytest.mark.skipif( - not m.if_defined__cpp_nontype_template_parameter_class, + not m.defined_PYBIND11_TYPING_H_HAS_STRING_LITERAL, reason="C++20 feature not available.", ) def test_literal(doc): @@ -1036,7 +1036,7 @@ def test_literal(doc): @pytest.mark.skipif( - not m.if_defined__cpp_nontype_template_parameter_class, + not m.defined_PYBIND11_TYPING_H_HAS_STRING_LITERAL, reason="C++20 feature not available.", ) def test_typevar(doc): diff --git a/tests/test_tagbased_polymorphic.cpp b/tests/test_tagbased_polymorphic.cpp index 12ba6532f..24b49021b 100644 --- a/tests/test_tagbased_polymorphic.cpp +++ b/tests/test_tagbased_polymorphic.cpp @@ -74,6 +74,7 @@ std::vector> create_zoo() { // simulate some new type of Dog that the Python bindings // haven't been updated for; it should still be considered // a Dog, not just an Animal. + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) ret.emplace_back(new Dog("Ginger", Dog::Kind(150))); ret.emplace_back(new Chihuahua("Hertzl")); diff --git a/tests/test_thread.py b/tests/test_thread.py index 4541a305e..f12020e41 100644 --- a/tests/test_thread.py +++ b/tests/test_thread.py @@ -1,7 +1,10 @@ from __future__ import annotations +import sys import threading +import pytest + from pybind11_tests import thread as m @@ -24,6 +27,7 @@ class Thread(threading.Thread): raise self.e +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_implicit_conversion(): a = Thread(m.test) b = Thread(m.test) @@ -34,6 +38,7 @@ def test_implicit_conversion(): x.join() +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_implicit_conversion_no_gil(): a = Thread(m.test_no_gil) b = Thread(m.test_no_gil) diff --git a/tests/test_type_caster_std_function_specializations.cpp b/tests/test_type_caster_std_function_specializations.cpp new file mode 100644 index 000000000..89213ddb1 --- /dev/null +++ b/tests/test_type_caster_std_function_specializations.cpp @@ -0,0 +1,46 @@ +#include +#include + +#include "pybind11_tests.h" + +namespace py = pybind11; + +namespace { + +struct SpecialReturn { + int value = 99; +}; + +} // namespace + +namespace pybind11 { +namespace detail { +namespace type_caster_std_function_specializations { + +template +struct func_wrapper : func_wrapper_base { + using func_wrapper_base::func_wrapper_base; + SpecialReturn operator()(Args... args) const { + gil_scoped_acquire acq; + SpecialReturn result; + try { + result = hfunc.f(std::forward(args)...).template cast(); + } catch (error_already_set &) { + result.value += 1; + } + result.value += 100; + return result; + } +}; + +} // namespace type_caster_std_function_specializations +} // namespace detail +} // namespace pybind11 + +TEST_SUBMODULE(type_caster_std_function_specializations, m) { + py::class_(m, "SpecialReturn") + .def(py::init<>()) + .def_readwrite("value", &SpecialReturn::value); + m.def("call_callback_with_special_return", + [](const std::function &func) { return func(); }); +} diff --git a/tests/test_type_caster_std_function_specializations.py b/tests/test_type_caster_std_function_specializations.py new file mode 100644 index 000000000..9e45d4f59 --- /dev/null +++ b/tests/test_type_caster_std_function_specializations.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from pybind11_tests import type_caster_std_function_specializations as m + + +def test_callback_with_special_return(): + def return_special(): + return m.SpecialReturn() + + def raise_exception(): + raise ValueError("called raise_exception.") + + assert return_special().value == 99 + assert m.call_callback_with_special_return(return_special).value == 199 + assert m.call_callback_with_special_return(raise_exception).value == 200 diff --git a/tests/test_virtual_functions.py b/tests/test_virtual_functions.py index 08acaa190..bc0177787 100644 --- a/tests/test_virtual_functions.py +++ b/tests/test_virtual_functions.py @@ -1,5 +1,7 @@ from __future__ import annotations +import sys + import pytest import env # noqa: F401 @@ -435,6 +437,7 @@ def test_inherited_virtuals(): assert obj.say_everything() == "BT -7" +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_issue_1454(): # Fix issue #1454 (crash when acquiring/releasing GIL on another thread in Python 2.7) m.test_gil() diff --git a/tools/pybind11Common.cmake b/tools/pybind11Common.cmake index d8e18e67b..7d8d94b11 100644 --- a/tools/pybind11Common.cmake +++ b/tools/pybind11Common.cmake @@ -2,7 +2,7 @@ Adds the following targets:: - pybind11::pybind11 - link to headers and pybind11 + pybind11::pybind11 - link to Python headers and pybind11::headers pybind11::module - Adds module links pybind11::embed - Adds embed links pybind11::lto - Link time optimizations (only if CMAKE_INTERPROCEDURAL_OPTIMIZATION is not set) @@ -75,6 +75,32 @@ set_property( APPEND PROPERTY INTERFACE_LINK_LIBRARIES pybind11::pybind11) +# -------------- emscripten requires exceptions enabled ------------- +# _pybind11_no_exceptions is a private mechanism to disable this addition. +# Please open an issue if you need to use it; it will be removed if no one +# needs it. +if(CMAKE_SYSTEM_NAME MATCHES Emscripten AND NOT _pybind11_no_exceptions) + if(CMAKE_VERSION VERSION_LESS 3.13) + message(WARNING "CMake 3.13+ is required to build for Emscripten. Some flags will be missing") + else() + if(is_config) + set(_tmp_config_target pybind11::pybind11_headers) + else() + set(_tmp_config_target pybind11_headers) + endif() + + set_property( + TARGET ${_tmp_config_target} + APPEND + PROPERTY INTERFACE_LINK_OPTIONS -fexceptions) + set_property( + TARGET ${_tmp_config_target} + APPEND + PROPERTY INTERFACE_COMPILE_OPTIONS -fexceptions) + unset(_tmp_config_target) + endif() +endif() + # --------------------------- link helper --------------------------- add_library(pybind11::python_link_helper IMPORTED INTERFACE ${optional_global}) @@ -329,13 +355,13 @@ function(_pybind11_generate_lto target prefer_thin_lto) if(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64le" OR CMAKE_SYSTEM_PROCESSOR MATCHES "mips64") # Do nothing - elseif(CMAKE_SYSTEM_PROCESSOR MATCHES emscripten) + elseif(CMAKE_SYSTEM_NAME MATCHES Emscripten) # This compile is very costly when cross-compiling, so set this without checking set(PYBIND11_LTO_CXX_FLAGS "-flto${thin}${cxx_append}") set(PYBIND11_LTO_LINKER_FLAGS "-flto${thin}${linker_append}") elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") _pybind11_return_if_cxx_and_linker_flags_work( - HAS_FLTO_THIN "-flto${thin}${cxx_append}" "-flto=${thin}${linker_append}" + HAS_FLTO_THIN "-flto${thin}${cxx_append}" "-flto${thin}${linker_append}" PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS) endif() if(NOT HAS_FLTO_THIN) diff --git a/tools/pybind11Config.cmake.in b/tools/pybind11Config.cmake.in index 304f1d907..2d9fa94f6 100644 --- a/tools/pybind11Config.cmake.in +++ b/tools/pybind11Config.cmake.in @@ -84,7 +84,7 @@ you can either use the basic targets, or use the FindPython tools: # Python method: Python_add_library(MyModule2 src2.cpp) - target_link_libraries(MyModule2 pybind11::headers) + target_link_libraries(MyModule2 PUBLIC pybind11::headers) set_target_properties(MyModule2 PROPERTIES INTERPROCEDURAL_OPTIMIZATION ON CXX_VISIBILITY_PRESET ON