From c727db52c134486ca24e5374927f41b2fc0291b7 Mon Sep 17 00:00:00 2001 From: Fan Jiang Date: Sun, 16 Mar 2025 13:41:44 -0700 Subject: [PATCH] Squashed 'wrap/' changes from 4cca84a07..4cefad122 4cefad122 Merge pull request #172 from ProfFan/fan/revert_and_update_pybind 385f78e16 Merging 'v2.13.6' into 'pybind11' 3a9158898 Squashed 'pybind11/' changes from d3c999c7..a2e59f0e 147e3ce03 Revert "Merge pull request #169 from borglab/pybind11-upgrade" git-subtree-dir: wrap git-subtree-split: 4cefad122866ab8f476c143211b0e620ad066c25 --- pybind11/.codespell-ignore-lines | 11 - pybind11/.github/CONTRIBUTING.md | 10 +- pybind11/.github/workflows/ci.yml | 68 +- pybind11/.github/workflows/configure.yml | 10 +- pybind11/.github/workflows/emscripten.yaml | 2 +- pybind11/.github/workflows/pip.yml | 2 +- pybind11/.pre-commit-config.yaml | 22 +- pybind11/CMakeLists.txt | 67 +- pybind11/README.rst | 39 +- pybind11/docs/advanced/cast/custom.rst | 156 ++-- pybind11/docs/advanced/cast/overview.rst | 4 +- pybind11/docs/advanced/cast/stl.rst | 4 +- pybind11/docs/advanced/classes.rst | 76 +- pybind11/docs/advanced/deadlock.md | 391 --------- pybind11/docs/advanced/embedding.rst | 2 +- pybind11/docs/advanced/exceptions.rst | 3 +- pybind11/docs/advanced/functions.rst | 8 +- pybind11/docs/advanced/misc.rst | 19 +- pybind11/docs/advanced/smart_ptrs.rst | 241 ++---- pybind11/docs/basics.rst | 2 +- pybind11/docs/benchmark.py | 4 +- pybind11/docs/classes.rst | 19 +- pybind11/docs/compiling.rst | 36 +- pybind11/docs/faq.rst | 16 +- pybind11/docs/installing.rst | 105 +++ pybind11/docs/reference.rst | 4 +- pybind11/docs/requirements.txt | 6 +- pybind11/docs/upgrade.rst | 3 +- pybind11/include/pybind11/attr.h | 34 +- pybind11/include/pybind11/cast.h | 347 +------- pybind11/include/pybind11/conduit/README.txt | 15 - .../pybind11/conduit/pybind11_conduit_v1.h | 111 --- .../conduit/pybind11_platform_abi_id.h | 87 -- .../pybind11/conduit/wrap_include_python_h.h | 72 -- pybind11/include/pybind11/detail/class.h | 103 +-- pybind11/include/pybind11/detail/common.h | 130 +-- pybind11/include/pybind11/detail/descr.h | 22 +- .../detail/dynamic_raw_ptr_cast_if_possible.h | 39 - .../pybind11/detail/exception_translation.h | 22 +- pybind11/include/pybind11/detail/init.h | 73 +- pybind11/include/pybind11/detail/internals.h | 223 +++-- .../pybind11/detail/struct_smart_holder.h | 349 -------- .../pybind11/detail/type_caster_base.h | 439 +--------- .../pybind11/detail/using_smart_holder.h | 22 - .../pybind11/detail/value_and_holder.h | 1 - pybind11/include/pybind11/eigen/matrix.h | 26 +- pybind11/include/pybind11/eigen/tensor.h | 20 +- pybind11/include/pybind11/embed.h | 12 +- pybind11/include/pybind11/eval.h | 10 +- pybind11/include/pybind11/gil.h | 8 +- .../include/pybind11/gil_safe_call_once.h | 2 - pybind11/include/pybind11/numpy.h | 101 +-- pybind11/include/pybind11/pybind11.h | 565 ++----------- pybind11/include/pybind11/pytypes.h | 63 +- pybind11/include/pybind11/stl.h | 308 ++----- pybind11/include/pybind11/stl/filesystem.h | 2 +- pybind11/include/pybind11/stl_bind.h | 44 +- .../pybind11/trampoline_self_life_support.h | 60 -- pybind11/include/pybind11/typing.h | 71 +- pybind11/include/pybind11/warnings.h | 75 -- pybind11/pybind11/__init__.py | 4 +- pybind11/pybind11/__main__.py | 7 - pybind11/pybind11/_version.py | 2 +- pybind11/pybind11/setup_helpers.py | 2 +- pybind11/pyproject.toml | 5 +- pybind11/setup.cfg | 3 +- pybind11/setup.py | 4 - pybind11/tests/CMakeLists.txt | 81 +- pybind11/tests/conftest.py | 19 +- pybind11/tests/constructor_stats.h | 8 - pybind11/tests/custom_exceptions.py | 10 - pybind11/tests/env.py | 1 - pybind11/tests/exo_planet_c_api.cpp | 69 +- .../tests/extra_python_package/test_files.py | 28 +- pybind11/tests/pure_cpp/CMakeLists.txt | 20 - pybind11/tests/pure_cpp/smart_holder_poc.h | 51 -- .../tests/pure_cpp/smart_holder_poc_test.cpp | 415 ---------- pybind11/tests/pybind11_tests.cpp | 5 - pybind11/tests/pybind11_tests.h | 21 - pybind11/tests/pyproject.toml | 4 + pybind11/tests/requirements.txt | 16 +- pybind11/tests/test_buffers.cpp | 171 ---- pybind11/tests/test_buffers.py | 164 ---- pybind11/tests/test_builtin_casters.py | 2 +- pybind11/tests/test_call_policies.cpp | 4 +- pybind11/tests/test_call_policies.py | 7 - pybind11/tests/test_callbacks.cpp | 5 + pybind11/tests/test_callbacks.py | 6 +- pybind11/tests/test_class.cpp | 29 +- pybind11/tests/test_class.py | 48 +- ...ss_release_gil_before_calling_cpp_dtor.cpp | 53 -- ...ass_release_gil_before_calling_cpp_dtor.py | 21 - pybind11/tests/test_class_sh_basic.cpp | 247 ------ pybind11/tests/test_class_sh_basic.py | 246 ------ pybind11/tests/test_class_sh_disowning.cpp | 41 - pybind11/tests/test_class_sh_disowning.py | 78 -- pybind11/tests/test_class_sh_disowning_mi.cpp | 85 -- pybind11/tests/test_class_sh_disowning_mi.py | 246 ------ .../test_class_sh_factory_constructors.cpp | 164 ---- .../test_class_sh_factory_constructors.py | 53 -- pybind11/tests/test_class_sh_inheritance.cpp | 90 -- pybind11/tests/test_class_sh_inheritance.py | 63 -- pybind11/tests/test_class_sh_mi_thunks.cpp | 93 --- pybind11/tests/test_class_sh_mi_thunks.py | 53 -- pybind11/tests/test_class_sh_property.cpp | 94 --- pybind11/tests/test_class_sh_property.py | 166 ---- .../test_class_sh_property_non_owning.cpp | 63 -- .../test_class_sh_property_non_owning.py | 30 - .../test_class_sh_shared_ptr_copy_move.cpp | 103 --- .../test_class_sh_shared_ptr_copy_move.py | 41 - .../tests/test_class_sh_trampoline_basic.cpp | 82 -- .../tests/test_class_sh_trampoline_basic.py | 59 -- ..._class_sh_trampoline_self_life_support.cpp | 86 -- ...t_class_sh_trampoline_self_life_support.py | 38 - ...t_class_sh_trampoline_shared_from_this.cpp | 137 ---- ...st_class_sh_trampoline_shared_from_this.py | 247 ------ ...class_sh_trampoline_shared_ptr_cpp_arg.cpp | 92 --- ..._class_sh_trampoline_shared_ptr_cpp_arg.py | 154 ---- .../test_class_sh_trampoline_unique_ptr.cpp | 63 -- .../test_class_sh_trampoline_unique_ptr.py | 31 - ...est_class_sh_unique_ptr_custom_deleter.cpp | 30 - ...test_class_sh_unique_ptr_custom_deleter.py | 8 - .../tests/test_class_sh_unique_ptr_member.cpp | 50 -- .../tests/test_class_sh_unique_ptr_member.py | 26 - .../test_class_sh_virtual_py_cpp_mix.cpp | 58 -- .../tests/test_class_sh_virtual_py_cpp_mix.py | 66 -- .../tests/test_cmake_build/CMakeLists.txt | 16 +- .../installed_embed/CMakeLists.txt | 11 +- .../installed_function/CMakeLists.txt | 12 +- .../installed_target/CMakeLists.txt | 11 +- .../subdirectory_embed/CMakeLists.txt | 11 +- .../subdirectory_function/CMakeLists.txt | 11 +- .../subdirectory_target/CMakeLists.txt | 11 +- pybind11/tests/test_copy_move.py | 4 - pybind11/tests/test_cpp_conduit.py | 6 +- pybind11/tests/test_custom_type_casters.py | 2 - pybind11/tests/test_custom_type_setup.py | 4 +- .../tests/test_docs_advanced_cast_custom.cpp | 68 -- .../tests/test_docs_advanced_cast_custom.py | 37 - pybind11/tests/test_eigen.cpp | 401 --------- pybind11/tests/test_eigen.py | 775 ------------------ pybind11/tests/test_eigen_matrix.cpp | 26 +- pybind11/tests/test_eigen_matrix.py | 69 +- pybind11/tests/test_eigen_tensor.py | 42 +- pybind11/tests/test_embed/CMakeLists.txt | 8 +- pybind11/tests/test_enum.py | 63 -- pybind11/tests/test_eval.py | 2 +- pybind11/tests/test_exceptions.cpp | 39 - pybind11/tests/test_exceptions.py | 38 +- pybind11/tests/test_factory_constructors.py | 13 - pybind11/tests/test_gil_scoped.py | 20 - pybind11/tests/test_kwargs_and_defaults.cpp | 6 - pybind11/tests/test_kwargs_and_defaults.py | 13 - .../tests/test_methods_and_attributes.cpp | 2 +- pybind11/tests/test_methods_and_attributes.py | 15 +- pybind11/tests/test_modules.py | 14 +- pybind11/tests/test_multiple_inheritance.py | 24 +- pybind11/tests/test_numpy_array.cpp | 93 +-- pybind11/tests/test_numpy_array.py | 89 +- pybind11/tests/test_numpy_dtypes.cpp | 104 --- pybind11/tests/test_numpy_dtypes.py | 24 +- pybind11/tests/test_numpy_vectorize.py | 10 +- pybind11/tests/test_opaque_types.py | 5 +- pybind11/tests/test_operator_overloading.py | 6 - pybind11/tests/test_pickling.py | 19 +- pybind11/tests/test_pytypes.cpp | 205 ----- pybind11/tests/test_pytypes.py | 253 +----- .../tests/test_sequences_and_iterators.py | 49 +- pybind11/tests/test_smart_ptr.py | 17 +- pybind11/tests/test_stl.cpp | 104 +-- pybind11/tests/test_stl.py | 185 +---- pybind11/tests/test_stl_binders.py | 19 - pybind11/tests/test_thread.cpp | 6 - pybind11/tests/test_thread.py | 19 - .../tests/test_type_caster_pyobject_ptr.cpp | 3 +- pybind11/tests/test_unnamed_namespace_a.cpp | 1 + pybind11/tests/test_unnamed_namespace_a.py | 8 +- pybind11/tests/test_virtual_functions.py | 9 +- pybind11/tests/test_warnings.cpp | 46 -- pybind11/tests/test_warnings.py | 68 -- pybind11/tools/FindPythonLibsNew.cmake | 12 +- pybind11/tools/make_changelog.py | 4 +- pybind11/tools/pybind11Common.cmake | 101 ++- .../tools/pybind11GuessPythonExtSuffix.cmake | 2 +- pybind11/tools/pybind11NewTools.cmake | 23 +- pybind11/tools/pybind11Tools.cmake | 53 +- pybind11/tools/setup_global.py.in | 5 +- pybind11/tools/setup_main.py.in | 8 +- .../test-pybind11GuessPythonExtSuffix.cmake | 2 +- 189 files changed, 1439 insertions(+), 11141 deletions(-) delete mode 100644 pybind11/docs/advanced/deadlock.md create mode 100644 pybind11/docs/installing.rst delete mode 100644 pybind11/include/pybind11/conduit/README.txt delete mode 100644 pybind11/include/pybind11/conduit/pybind11_conduit_v1.h delete mode 100644 pybind11/include/pybind11/conduit/pybind11_platform_abi_id.h delete mode 100644 pybind11/include/pybind11/conduit/wrap_include_python_h.h delete mode 100644 pybind11/include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h delete mode 100644 pybind11/include/pybind11/detail/struct_smart_holder.h delete mode 100644 pybind11/include/pybind11/detail/using_smart_holder.h delete mode 100644 pybind11/include/pybind11/trampoline_self_life_support.h delete mode 100644 pybind11/include/pybind11/warnings.h delete mode 100644 pybind11/tests/custom_exceptions.py delete mode 100644 pybind11/tests/pure_cpp/CMakeLists.txt delete mode 100644 pybind11/tests/pure_cpp/smart_holder_poc.h delete mode 100644 pybind11/tests/pure_cpp/smart_holder_poc_test.cpp delete mode 100644 pybind11/tests/test_class_release_gil_before_calling_cpp_dtor.cpp delete mode 100644 pybind11/tests/test_class_release_gil_before_calling_cpp_dtor.py delete mode 100644 pybind11/tests/test_class_sh_basic.cpp delete mode 100644 pybind11/tests/test_class_sh_basic.py delete mode 100644 pybind11/tests/test_class_sh_disowning.cpp delete mode 100644 pybind11/tests/test_class_sh_disowning.py delete mode 100644 pybind11/tests/test_class_sh_disowning_mi.cpp delete mode 100644 pybind11/tests/test_class_sh_disowning_mi.py delete mode 100644 pybind11/tests/test_class_sh_factory_constructors.cpp delete mode 100644 pybind11/tests/test_class_sh_factory_constructors.py delete mode 100644 pybind11/tests/test_class_sh_inheritance.cpp delete mode 100644 pybind11/tests/test_class_sh_inheritance.py delete mode 100644 pybind11/tests/test_class_sh_mi_thunks.cpp delete mode 100644 pybind11/tests/test_class_sh_mi_thunks.py delete mode 100644 pybind11/tests/test_class_sh_property.cpp delete mode 100644 pybind11/tests/test_class_sh_property.py delete mode 100644 pybind11/tests/test_class_sh_property_non_owning.cpp delete mode 100644 pybind11/tests/test_class_sh_property_non_owning.py delete mode 100644 pybind11/tests/test_class_sh_shared_ptr_copy_move.cpp delete mode 100644 pybind11/tests/test_class_sh_shared_ptr_copy_move.py delete mode 100644 pybind11/tests/test_class_sh_trampoline_basic.cpp delete mode 100644 pybind11/tests/test_class_sh_trampoline_basic.py delete mode 100644 pybind11/tests/test_class_sh_trampoline_self_life_support.cpp delete mode 100644 pybind11/tests/test_class_sh_trampoline_self_life_support.py delete mode 100644 pybind11/tests/test_class_sh_trampoline_shared_from_this.cpp delete mode 100644 pybind11/tests/test_class_sh_trampoline_shared_from_this.py delete mode 100644 pybind11/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp delete mode 100644 pybind11/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.py delete mode 100644 pybind11/tests/test_class_sh_trampoline_unique_ptr.cpp delete mode 100644 pybind11/tests/test_class_sh_trampoline_unique_ptr.py delete mode 100644 pybind11/tests/test_class_sh_unique_ptr_custom_deleter.cpp delete mode 100644 pybind11/tests/test_class_sh_unique_ptr_custom_deleter.py delete mode 100644 pybind11/tests/test_class_sh_unique_ptr_member.cpp delete mode 100644 pybind11/tests/test_class_sh_unique_ptr_member.py delete mode 100644 pybind11/tests/test_class_sh_virtual_py_cpp_mix.cpp delete mode 100644 pybind11/tests/test_class_sh_virtual_py_cpp_mix.py delete mode 100644 pybind11/tests/test_docs_advanced_cast_custom.cpp delete mode 100644 pybind11/tests/test_docs_advanced_cast_custom.py delete mode 100644 pybind11/tests/test_eigen.cpp delete mode 100644 pybind11/tests/test_eigen.py delete mode 100644 pybind11/tests/test_warnings.cpp delete mode 100644 pybind11/tests/test_warnings.py diff --git a/pybind11/.codespell-ignore-lines b/pybind11/.codespell-ignore-lines index e8cbf3144..2a01d63eb 100644 --- a/pybind11/.codespell-ignore-lines +++ b/pybind11/.codespell-ignore-lines @@ -12,17 +12,6 @@ template template class_ &def(const detail::op_ &op, const Extra &...extra) { class_ &def_cast(const detail::op_ &op, const Extra &...extra) { - int valu; - explicit movable_int(int v) : valu{v} {} - movable_int(movable_int &&other) noexcept : valu(other.valu) { other.valu = 91; } - explicit indestructible_int(int v) : valu{v} {} - REQUIRE(hld.as_raw_ptr_unowned()->valu == 19); - REQUIRE(othr.valu == 19); - REQUIRE(orig.valu == 91); - (m.pass_valu, "Valu", "pass_valu:Valu(_MvCtor)*_CpCtor"), -atyp_valu rtrn_valu() { atyp_valu obj{"Valu"}; return obj; } - assert m.atyp_valu().get_mtxt() == "Valu" -// valu(e), ref(erence), ptr or p (pointer), r = rvalue, m = mutable, c = const, @pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"]) struct IntStruct { explicit IntStruct(int v) : value(v){}; diff --git a/pybind11/.github/CONTRIBUTING.md b/pybind11/.github/CONTRIBUTING.md index f2d8007df..f5a08e2d7 100644 --- a/pybind11/.github/CONTRIBUTING.md +++ b/pybind11/.github/CONTRIBUTING.md @@ -81,7 +81,7 @@ nox -s build ### Full setup To setup an ideal development environment, run the following commands on a -system with CMake 3.15+: +system with CMake 3.14+: ```bash python3 -m venv venv @@ -96,8 +96,8 @@ Tips: * You can use `virtualenv` (faster, from PyPI) instead of `venv`. * You can select any name for your environment folder; if it contains "env" it will be ignored by git. -* If you don't have CMake 3.15+, just add "cmake" to the pip install command. -* You can use `-DPYBIND11_FINDPYTHON=ON` to use FindPython. +* If you don't have CMake 3.14+, just add "cmake" to the pip install command. +* You can use `-DPYBIND11_FINDPYTHON=ON` to use FindPython on CMake 3.12+ * In classic mode, you may need to set `-DPYTHON_EXECUTABLE=/path/to/python`. FindPython uses `-DPython_ROOT_DIR=/path/to` or `-DPython_EXECUTABLE=/path/to/python`. @@ -149,8 +149,8 @@ To run the tests, you can "build" the check target: cmake --build build --target check ``` -`--target` can be spelled `-t`. You can also run individual tests with these -targets: +`--target` can be spelled `-t` in CMake 3.15+. You can also run individual +tests with these targets: * `pytest`: Python tests only, using the [pytest](https://docs.pytest.org/en/stable/) framework diff --git a/pybind11/.github/workflows/ci.yml b/pybind11/.github/workflows/ci.yml index 80f1f0d74..3f7e8a6c7 100644 --- a/pybind11/.github/workflows/ci.yml +++ b/pybind11/.github/workflows/ci.yml @@ -39,8 +39,6 @@ jobs: - 'pypy-3.8' - 'pypy-3.9' - 'pypy-3.10' - - 'pypy-3.11' - - 'graalpy-24.1' # Items in here will either be added to the build matrix (if not # present), or add new keys to an existing matrix element if all the @@ -66,45 +64,9 @@ jobs: # Inject a couple Windows 2019 runs - runs-on: windows-2019 python: '3.9' - # Inject a few runs with different runtime libraries - - runs-on: windows-2022 - python: '3.9' - args: > - -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded - - runs-on: windows-2022 - python: '3.10' - args: > - -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL - # This needs a python built with MTd - # - runs-on: windows-2022 - # python: '3.11' - # args: > - # -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebug - - runs-on: windows-2022 - python: '3.12' - args: > - -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebugDLL # Extra ubuntu latest job - runs-on: ubuntu-latest python: '3.11' - # Run tests with py::smart_holder as the default holder - # with recent (or ideally latest) released Python version. - - runs-on: ubuntu-latest - python: '3.12' - args: > - -DCMAKE_CXX_FLAGS="-DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE" - - runs-on: macos-13 - python: '3.12' - args: > - -DCMAKE_CXX_FLAGS="-DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE" - - runs-on: windows-2022 - python: '3.12' - args: > - -DCMAKE_CXX_FLAGS="/DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE /GR /EHsc" - exclude: - # The setup-python action currently doesn't have graalpy for windows - - python: 'graalpy-24.1' - runs-on: 'windows-2022' name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • x64 ${{ matrix.args }}" @@ -160,7 +122,6 @@ jobs: -DPYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION=ON -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON -DPYBIND11_NUMPY_1_ONLY=ON - -DPYBIND11_PYTEST_ARGS=-v -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=11 @@ -190,7 +151,6 @@ jobs: -DPYBIND11_WERROR=ON -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF -DPYBIND11_NUMPY_1_ONLY=ON - -DPYBIND11_PYTEST_ARGS=-v -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=17 @@ -210,7 +170,6 @@ jobs: run: > cmake -S . -B build3 -DPYBIND11_WERROR=ON - -DPYBIND11_PYTEST_ARGS=-v -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=17 @@ -351,11 +310,22 @@ jobs: strategy: fail-fast: false matrix: + clang: + - 3.6 + - 3.7 + - 3.9 + - 7 + - 9 + - dev + std: + - 11 container_suffix: - "" include: - clang: 5 std: 14 + - clang: 10 + std: 17 - clang: 11 std: 20 - clang: 12 @@ -533,6 +503,10 @@ jobs: fail-fast: false matrix: include: + - { gcc: 7, std: 11 } + - { gcc: 7, std: 17 } + - { gcc: 8, std: 14 } + - { gcc: 8, std: 17 } - { gcc: 9, std: 20 } - { gcc: 10, std: 17 } - { gcc: 10, std: 20 } @@ -753,9 +727,9 @@ jobs: # This tests an "install" with the CMake tools install-classic: - name: "🐍 3.9 • Debian • x86 • Install" + name: "🐍 3.7 • Debian • x86 • Install" runs-on: ubuntu-latest - container: i386/debian:bullseye + container: i386/debian:buster steps: - uses: actions/checkout@v1 # v1 is required to run inside docker @@ -835,6 +809,7 @@ jobs: fail-fast: false matrix: python: + - '3.7' - '3.8' - '3.9' - '3.10' @@ -852,6 +827,8 @@ jobs: args: -DCMAKE_CXX_STANDARD=20 - python: '3.8' args: -DCMAKE_CXX_STANDARD=17 + - python: '3.7' + args: -DCMAKE_CXX_STANDARD=14 name: "🐍 ${{ matrix.python }} • MSVC 2019 • x86 ${{ matrix.args }}" @@ -1030,6 +1007,7 @@ jobs: git mingw-w64-${{matrix.env}}-gcc mingw-w64-${{matrix.env}}-python-pip + mingw-w64-${{matrix.env}}-python-numpy mingw-w64-${{matrix.env}}-cmake mingw-w64-${{matrix.env}}-make mingw-w64-${{matrix.env}}-python-pytest @@ -1041,7 +1019,7 @@ jobs: with: msystem: ${{matrix.sys}} install: >- - mingw-w64-${{matrix.env}}-python-numpy + git mingw-w64-${{matrix.env}}-python-scipy mingw-w64-${{matrix.env}}-eigen3 @@ -1139,7 +1117,7 @@ jobs: uses: jwlawson/actions-setup-cmake@v2.0 - name: Install ninja-build tool - uses: seanmiddleditch/gha-setup-ninja@v6 + uses: seanmiddleditch/gha-setup-ninja@v5 - name: Run pip installs run: | diff --git a/pybind11/.github/workflows/configure.yml b/pybind11/.github/workflows/configure.yml index 2031ec823..0e55a0795 100644 --- a/pybind11/.github/workflows/configure.yml +++ b/pybind11/.github/workflows/configure.yml @@ -31,7 +31,7 @@ jobs: include: - runs-on: ubuntu-20.04 arch: x64 - cmake: "3.15" + cmake: "3.5" - runs-on: ubuntu-20.04 arch: x64 @@ -39,22 +39,22 @@ jobs: - runs-on: macos-13 arch: x64 - cmake: "3.15" + cmake: "3.7" - runs-on: windows-2019 arch: x64 # x86 compilers seem to be missing on 2019 image cmake: "3.18" - name: 🐍 3.8 • CMake ${{ matrix.cmake }} • ${{ matrix.runs-on }} + name: 🐍 3.7 • CMake ${{ matrix.cmake }} • ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }} steps: - uses: actions/checkout@v4 - - name: Setup Python 3.8 + - name: Setup Python 3.7 uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: 3.7 architecture: ${{ matrix.arch }} - name: Prepare env diff --git a/pybind11/.github/workflows/emscripten.yaml b/pybind11/.github/workflows/emscripten.yaml index aaaae1a19..14b2b9dc7 100644 --- a/pybind11/.github/workflows/emscripten.yaml +++ b/pybind11/.github/workflows/emscripten.yaml @@ -22,7 +22,7 @@ jobs: submodules: true fetch-depth: 0 - - uses: pypa/cibuildwheel@v2.23 + - uses: pypa/cibuildwheel@v2.20 env: PYODIDE_BUILD_EXPORTS: whole_archive with: diff --git a/pybind11/.github/workflows/pip.yml b/pybind11/.github/workflows/pip.yml index 497870c99..371353737 100644 --- a/pybind11/.github/workflows/pip.yml +++ b/pybind11/.github/workflows/pip.yml @@ -103,7 +103,7 @@ jobs: - uses: actions/download-artifact@v4 - name: Generate artifact attestation for sdist and wheel - uses: actions/attest-build-provenance@bd77c077858b8d561b7a36cbe48ef4cc642ca39d # v2.2.2 + uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 with: subject-path: "*/pybind11*" diff --git a/pybind11/.pre-commit-config.yaml b/pybind11/.pre-commit-config.yaml index e5002e8fd..a8190df44 100644 --- a/pybind11/.pre-commit-config.yaml +++ b/pybind11/.pre-commit-config.yaml @@ -25,14 +25,14 @@ repos: # Clang format the codebase automatically - repo: https://github.com/pre-commit/mirrors-clang-format - rev: "v19.1.7" + 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.9.9 + rev: v0.6.3 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.15.0" + rev: "v1.11.2" hooks: - id: mypy args: [] @@ -62,7 +62,7 @@ repos: # Standard hooks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: "v5.0.0" + rev: "v4.6.0" hooks: - id: check-added-large-files - id: check-case-conflict @@ -76,11 +76,10 @@ repos: - id: mixed-line-ending - id: requirements-txt-fixer - id: trailing-whitespace - exclude: \.patch?$ # Also code format the docs - repo: https://github.com/adamchainz/blacken-docs - rev: "1.19.1" + rev: "1.18.0" hooks: - id: blacken-docs additional_dependencies: @@ -91,11 +90,10 @@ repos: rev: "v1.5.5" hooks: - id: remove-tabs - exclude: (^docs/.*|\.patch)?$ # Avoid directional quotes - repo: https://github.com/sirosen/texthooks - rev: "0.6.8" + rev: "0.6.7" hooks: - id: fix-ligatures - id: fix-smartquotes @@ -110,7 +108,7 @@ repos: # Checks the manifest for missing files (native support) - repo: https://github.com/mgedmin/check-manifest - rev: "0.50" + rev: "0.49" hooks: - id: check-manifest # This is a slow hook, so only run this if --hook-stage manual is passed @@ -121,7 +119,7 @@ repos: # Use tools/codespell_ignore_lines_from_errors.py # to rebuild .codespell-ignore-lines - repo: https://github.com/codespell-project/codespell - rev: "v2.4.1" + rev: "v2.3.0" hooks: - id: codespell exclude: ".supp$" @@ -144,14 +142,14 @@ repos: # PyLint has native support - not always usable, but works for us - repo: https://github.com/PyCQA/pylint - rev: "v3.3.4" + rev: "v3.2.7" hooks: - id: pylint files: ^pybind11 # Check schemas on some of our YAML files - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.31.2 + rev: 0.29.2 hooks: - id: check-readthedocs - id: check-github-workflows diff --git a/pybind11/CMakeLists.txt b/pybind11/CMakeLists.txt index 8783dba6e..f53aa2098 100644 --- a/pybind11/CMakeLists.txt +++ b/pybind11/CMakeLists.txt @@ -10,7 +10,16 @@ if(NOT CMAKE_VERSION VERSION_LESS "3.27") cmake_policy(GET CMP0148 _pybind11_cmp0148) endif() -cmake_minimum_required(VERSION 3.15...3.30) +cmake_minimum_required(VERSION 3.5) + +# The `cmake_minimum_required(VERSION 3.5...3.29)` syntax does not work with +# some versions of VS that have a patched CMake 3.11. This forces us to emulate +# the behavior using the following workaround: +if(${CMAKE_VERSION} VERSION_LESS 3.29) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.29) +endif() if(_pybind11_cmp0148) cmake_policy(SET CMP0148 ${_pybind11_cmp0148}) @@ -18,7 +27,9 @@ if(_pybind11_cmp0148) endif() # Avoid infinite recursion if tests include this as a subdirectory -include_guard(GLOBAL) +if(DEFINED PYBIND11_MASTER_PROJECT) + return() +endif() # Extract project version from source file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/pybind11/detail/common.h" @@ -63,6 +74,14 @@ if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) set(PYBIND11_MASTER_PROJECT ON) + if(OSX AND CMAKE_VERSION VERSION_LESS 3.7) + # Bug in macOS CMake < 3.7 is unable to download catch + message(WARNING "CMAKE 3.7+ needed on macOS to download catch, and newer HIGHLY recommended") + elseif(WINDOWS AND CMAKE_VERSION VERSION_LESS 3.8) + # Only tested with 3.8+ in CI. + message(WARNING "CMAKE 3.8+ tested on Windows, previous versions untested") + endif() + message(STATUS "CMake ${CMAKE_VERSION}") if(CMAKE_CXX_STANDARD) @@ -114,7 +133,8 @@ cmake_dependent_option( "Install pybind11 headers in Python include directory instead of default installation prefix" OFF "PYBIND11_INSTALL" OFF) -option(PYBIND11_FINDPYTHON "Force new FindPython" ${_pybind11_findpython_default}) +cmake_dependent_option(PYBIND11_FINDPYTHON "Force new FindPython" ${_pybind11_findpython_default} + "NOT CMAKE_VERSION VERSION_LESS 3.12" OFF) # Allow PYTHON_EXECUTABLE if in FINDPYTHON mode and building pybind11's tests # (makes transition easier while we support both modes). @@ -131,13 +151,10 @@ set(PYBIND11_HEADERS include/pybind11/detail/common.h include/pybind11/detail/cpp_conduit.h include/pybind11/detail/descr.h - include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h include/pybind11/detail/init.h include/pybind11/detail/internals.h - include/pybind11/detail/struct_smart_holder.h include/pybind11/detail/type_caster_base.h include/pybind11/detail/typeid.h - include/pybind11/detail/using_smart_holder.h include/pybind11/detail/value_and_holder.h include/pybind11/detail/exception_translation.h include/pybind11/attr.h @@ -146,9 +163,6 @@ set(PYBIND11_HEADERS include/pybind11/chrono.h include/pybind11/common.h include/pybind11/complex.h - include/pybind11/conduit/pybind11_conduit_v1.h - include/pybind11/conduit/pybind11_platform_abi_id.h - include/pybind11/conduit/wrap_include_python_h.h include/pybind11/options.h include/pybind11/eigen.h include/pybind11/eigen/common.h @@ -167,13 +181,11 @@ set(PYBIND11_HEADERS include/pybind11/stl.h include/pybind11/stl_bind.h include/pybind11/stl/filesystem.h - include/pybind11/trampoline_self_life_support.h include/pybind11/type_caster_pyobject_ptr.h - include/pybind11/typing.h - include/pybind11/warnings.h) + include/pybind11/typing.h) # Compare with grep and warn if mismatched -if(PYBIND11_MASTER_PROJECT) +if(PYBIND11_MASTER_PROJECT AND NOT CMAKE_VERSION VERSION_LESS 3.12) file( GLOB_RECURSE _pybind11_header_check LIST_DIRECTORIES false @@ -191,7 +203,10 @@ if(PYBIND11_MASTER_PROJECT) endif() endif() -list(TRANSFORM PYBIND11_HEADERS PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/") +# CMake 3.12 added list(TRANSFORM PREPEND +# But we can't use it yet +string(REPLACE "include/" "${CMAKE_CURRENT_SOURCE_DIR}/include/" PYBIND11_HEADERS + "${PYBIND11_HEADERS}") # Cache variable so this can be used in parent projects set(pybind11_INCLUDE_DIR @@ -261,11 +276,25 @@ if(PYBIND11_INSTALL) tools/${PROJECT_NAME}Config.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" INSTALL_DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR}) - # CMake natively supports header-only libraries - write_basic_package_version_file( - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake - VERSION ${PROJECT_VERSION} - COMPATIBILITY AnyNewerVersion ARCH_INDEPENDENT) + if(CMAKE_VERSION VERSION_LESS 3.14) + # Remove CMAKE_SIZEOF_VOID_P from ConfigVersion.cmake since the library does + # not depend on architecture specific settings or libraries. + set(_PYBIND11_CMAKE_SIZEOF_VOID_P ${CMAKE_SIZEOF_VOID_P}) + unset(CMAKE_SIZEOF_VOID_P) + + write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion) + + set(CMAKE_SIZEOF_VOID_P ${_PYBIND11_CMAKE_SIZEOF_VOID_P}) + else() + # CMake 3.14+ natively supports header-only libraries + write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion ARCH_INDEPENDENT) + endif() install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake diff --git a/pybind11/README.rst b/pybind11/README.rst index eaea399ab..0d1e1d291 100644 --- a/pybind11/README.rst +++ b/pybind11/README.rst @@ -1,7 +1,7 @@ .. figure:: https://github.com/pybind/pybind11/raw/master/docs/pybind11-logo.png :alt: pybind11 logo -**pybind11 (v3) — Seamless interoperability between C++ and Python** +**pybind11 — Seamless operability between C++11 and Python** |Latest Documentation Status| |Stable Documentation Status| |Gitter chat| |GitHub Discussions| |CI| |Build status| @@ -34,7 +34,7 @@ dependency. Think of this library as a tiny self-contained version of Boost.Python with everything stripped away that isn't relevant for binding generation. Without comments, the core header files only require ~4K -lines of code and depend on Python (3.8+, or PyPy) and the C++ +lines of code and depend on Python (3.7+, or PyPy) and the C++ standard library. This compact implementation was possible thanks to some C++11 language features (specifically: tuples, lambda functions and variadic templates). Since its creation, this library has grown beyond @@ -79,7 +79,7 @@ Goodies In addition to the core functionality, pybind11 provides some extra goodies: -- Python 3.8+, and PyPy3 7.3 are supported with an implementation-agnostic +- Python 3.7+, and PyPy3 7.3 are supported with an implementation-agnostic interface (pybind11 2.9 was the last version to support Python 2 and 3.5). - It is possible to bind C++11 lambda functions with captured @@ -134,34 +134,11 @@ About This project was created by `Wenzel Jakob `_. Significant features and/or -improvements to the code were contributed by -Jonas Adler, -Lori A. Burns, -Sylvain Corlay, -Eric Cousineau, -Aaron Gokaslan, -Ralf Grosse-Kunstleve, -Trent Houliston, -Axel Huebl, -@hulucc, -Yannick Jadoul, -Sergey Lyskov, -Johan Mabille, -Tomasz Miąsko, -Dean Moldovan, -Ben Pritchard, -Jason Rhinelander, -Boris Schäling, -Pim Schellart, -Henry Schreiner, -Ivan Smirnov, -Dustin Spicuzza, -Boris Staletic, -Ethan Steinberg, -Patrick Stewart, -Ivor Wanders, -and -Xiaofei Wang. +improvements to the code were contributed by Jonas Adler, Lori A. Burns, +Sylvain Corlay, Eric Cousineau, Aaron Gokaslan, Ralf Grosse-Kunstleve, Trent Houliston, Axel +Huebl, @hulucc, Yannick Jadoul, Sergey Lyskov, Johan Mabille, Tomasz Miąsko, +Dean Moldovan, Ben Pritchard, Jason Rhinelander, Boris Schäling, Pim +Schellart, Henry Schreiner, Ivan Smirnov, Boris Staletic, and Patrick Stewart. We thank Google for a generous financial contribution to the continuous integration infrastructure used by this project. diff --git a/pybind11/docs/advanced/cast/custom.rst b/pybind11/docs/advanced/cast/custom.rst index 5a626f3ba..8138cac61 100644 --- a/pybind11/docs/advanced/cast/custom.rst +++ b/pybind11/docs/advanced/cast/custom.rst @@ -1,53 +1,35 @@ Custom type casters =================== -Some applications may prefer custom type casters that convert between existing -Python types and C++ types, similar to the ``list`` ↔ ``std::vector`` -and ``dict`` ↔ ``std::map`` conversions which are built into pybind11. -Implementing custom type casters is fairly advanced usage. -While it is recommended to use the pybind11 API as much as possible, more complex examples may -require familiarity with the intricacies of the Python C API. -You can refer to the `Python/C API Reference Manual `_ -for more information. +In very rare cases, applications may require custom type casters that cannot be +expressed using the abstractions provided by pybind11, thus requiring raw +Python C API calls. This is fairly advanced usage and should only be pursued by +experts who are familiar with the intricacies of Python reference counting. -The following snippets demonstrate how this works for a very simple ``Point2D`` type. -We want this type to be convertible to C++ from Python types implementing the -``Sequence`` protocol and having two elements of type ``float``. -When returned from C++ to Python, it should be converted to a Python ``tuple[float, float]``. -For this type we could provide Python bindings for different arithmetic functions implemented -in C++ (here demonstrated by a simple ``negate`` function). - -.. - PLEASE KEEP THE CODE BLOCKS IN SYNC WITH - tests/test_docs_advanced_cast_custom.cpp - tests/test_docs_advanced_cast_custom.py - Ideally, change the test, run pre-commit (incl. clang-format), - then copy the changed code back here. - Also use TEST_SUBMODULE in tests, but PYBIND11_MODULE in docs. +The following snippets demonstrate how this works for a very simple ``inty`` +type that that should be convertible from Python types that provide a +``__int__(self)`` method. .. code-block:: cpp - namespace user_space { + struct inty { long long_value; }; - struct Point2D { - double x; - double y; - }; + void print(inty s) { + std::cout << s.long_value << std::endl; + } - Point2D negate(const Point2D &point) { return Point2D{-point.x, -point.y}; } - - } // namespace user_space - - -The following Python snippet demonstrates the intended usage of ``negate`` from the Python side: +The following Python snippet demonstrates the intended usage from the Python side: .. code-block:: python - from my_math_module import docs_advanced_cast_custom as m + class A: + def __int__(self): + return 123 - point1 = [1.0, -1.0] - point2 = m.negate(point1) - assert point2 == (-1.0, 1.0) + + from example import print + + print(A()) To register the necessary conversion routines, it is necessary to add an instantiation of the ``pybind11::detail::type_caster`` template. @@ -56,57 +38,47 @@ type is explicitly allowed. .. code-block:: cpp - namespace pybind11 { - namespace detail { + namespace PYBIND11_NAMESPACE { namespace detail { + template <> struct type_caster { + public: + /** + * This macro establishes the name 'inty' in + * function signatures and declares a local variable + * 'value' of type inty + */ + PYBIND11_TYPE_CASTER(inty, const_name("inty")); - template <> - struct type_caster { - // This macro inserts a lot of boilerplate code and sets the type hint. - // `io_name` is used to specify different type hints for arguments and return values. - // The signature of our negate function would then look like: - // `negate(Sequence[float]) -> tuple[float, float]` - PYBIND11_TYPE_CASTER(user_space::Point2D, io_name("Sequence[float]", "tuple[float, float]")); - - // C++ -> Python: convert `Point2D` to `tuple[float, float]`. The second and third arguments - // are used to indicate the return value policy and parent object (for - // return_value_policy::reference_internal) and are often ignored by custom casters. - // The return value should reflect the type hint specified by the second argument of `io_name`. - static handle - cast(const user_space::Point2D &number, return_value_policy /*policy*/, handle /*parent*/) { - return py::make_tuple(number.x, number.y).release(); - } - - // Python -> C++: convert a `PyObject` into a `Point2D` and return false upon failure. The - // second argument indicates whether implicit conversions should be allowed. - // The accepted types should reflect the type hint specified by the first argument of - // `io_name`. - bool load(handle src, bool /*convert*/) { - // Check if handle is a Sequence - if (!py::isinstance(src)) { - return false; - } - auto seq = py::reinterpret_borrow(src); - // Check if exactly two values are in the Sequence - if (seq.size() != 2) { - return false; - } - // Check if each element is either a float or an int - for (auto item : seq) { - if (!py::isinstance(item) && !py::isinstance(item)) { + /** + * Conversion part 1 (Python->C++): convert a PyObject into a inty + * instance or return false upon failure. The second argument + * indicates whether implicit conversions should be applied. + */ + bool load(handle src, bool) { + /* Extract PyObject from handle */ + PyObject *source = src.ptr(); + /* Try converting into a Python integer value */ + PyObject *tmp = PyNumber_Long(source); + if (!tmp) return false; - } + /* Now try to convert into a C++ int */ + value.long_value = PyLong_AsLong(tmp); + Py_DECREF(tmp); + /* Ensure return code was OK (to avoid out-of-range errors etc) */ + return !(value.long_value == -1 && !PyErr_Occurred()); } - value.x = seq[0].cast(); - value.y = seq[1].cast(); - return true; - } - }; - } // namespace detail - } // namespace pybind11 - - // Bind the negate function - PYBIND11_MODULE(docs_advanced_cast_custom, m) { m.def("negate", user_space::negate); } + /** + * Conversion part 2 (C++ -> Python): convert an inty instance into + * a Python object. The second and third arguments are used to + * indicate the return value policy and parent object (for + * ``return_value_policy::reference_internal``) and are generally + * ignored by implicit casters. + */ + static handle cast(inty src, return_value_policy /* policy */, handle /* parent */) { + return PyLong_FromLong(src.long_value); + } + }; + }} // namespace PYBIND11_NAMESPACE::detail .. note:: @@ -114,22 +86,8 @@ type is explicitly allowed. that ``T`` is default-constructible (``value`` is first default constructed and then ``load()`` assigns to it). -.. note:: - For further information on the ``return_value_policy`` argument of ``cast`` refer to :ref:`return_value_policies`. - To learn about the ``convert`` argument of ``load`` see :ref:`nonconverting_arguments`. - .. warning:: When using custom type casters, it's important to declare them consistently - in every compilation unit of the Python extension module to satisfy the C++ One Definition Rule - (`ODR `_). Otherwise, + in every compilation unit of the Python extension module. Otherwise, undefined behavior can ensue. - -.. note:: - - Using the type hint ``Sequence[float]`` signals to static type checkers, that not only tuples may be - passed, but any type implementing the Sequence protocol, e.g., ``list[float]``. - Unfortunately, that loses the length information ``tuple[float, float]`` provides. - One way of still providing some length information in type hints is using ``typing.Annotated``, e.g., - ``Annotated[Sequence[float], 2]``, or further add libraries like - `annotated-types `_. diff --git a/pybind11/docs/advanced/cast/overview.rst b/pybind11/docs/advanced/cast/overview.rst index d5a34ef94..011bd4c7a 100644 --- a/pybind11/docs/advanced/cast/overview.rst +++ b/pybind11/docs/advanced/cast/overview.rst @@ -151,7 +151,7 @@ as arguments and return values, refer to the section on binding :ref:`classes`. +------------------------------------+---------------------------+-----------------------------------+ | ``std::variant<...>`` | Type-safe union (C++17) | :file:`pybind11/stl.h` | +------------------------------------+---------------------------+-----------------------------------+ -| ``std::filesystem::path`` | STL path (C++17) [#]_ | :file:`pybind11/stl/filesystem.h` | +| ``std::filesystem::path`` | STL path (C++17) [#]_ | :file:`pybind11/stl/filesystem.h` | +------------------------------------+---------------------------+-----------------------------------+ | ``std::function<...>`` | STL polymorphic function | :file:`pybind11/functional.h` | +------------------------------------+---------------------------+-----------------------------------+ @@ -167,4 +167,4 @@ as arguments and return values, refer to the section on binding :ref:`classes`. +------------------------------------+---------------------------+-----------------------------------+ .. [#] ``std::filesystem::path`` is converted to ``pathlib.Path`` and - can be loaded from ``os.PathLike``, ``str``, and ``bytes``. + ``os.PathLike`` is converted to ``std::filesystem::path``. diff --git a/pybind11/docs/advanced/cast/stl.rst b/pybind11/docs/advanced/cast/stl.rst index 1e17bc389..42b85532d 100644 --- a/pybind11/docs/advanced/cast/stl.rst +++ b/pybind11/docs/advanced/cast/stl.rst @@ -169,8 +169,8 @@ macro must be specified at the top level (and outside of any namespaces), since it adds a template instantiation of ``type_caster``. If your binding code consists of multiple compilation units, it must be present in every file (typically via a common header) preceding any usage of ``std::vector``. Opaque types must -also have a corresponding ``py::class_`` declaration to associate them with a -name in Python, and to define a set of available operations, e.g.: +also have a corresponding ``class_`` declaration to associate them with a name +in Python, and to define a set of available operations, e.g.: .. code-block:: cpp diff --git a/pybind11/docs/advanced/classes.rst b/pybind11/docs/advanced/classes.rst index 0ada0cb60..01a490b72 100644 --- a/pybind11/docs/advanced/classes.rst +++ b/pybind11/docs/advanced/classes.rst @@ -64,7 +64,7 @@ helper class that is defined as follows: .. code-block:: cpp - class PyAnimal : public Animal, py::trampoline_self_life_support { + class PyAnimal : public Animal { public: /* Inherit the constructors */ using Animal::Animal; @@ -80,18 +80,6 @@ helper class that is defined as follows: } }; -The ``py::trampoline_self_life_support`` base class is needed to ensure -that a ``std::unique_ptr`` can safely be passed between Python and C++. To -steer clear of notorious pitfalls (e.g. inheritance slicing), it is best -practice to always use the base class, in combination with -``py::smart_holder``. - -.. note:: - For completeness, the base class has no effect if a holder other than - ``py::smart_holder`` used, including the default ``std::unique_ptr``. - Please think twice, though, the pitfalls are very real, and the overhead - for using the safer ``py::smart_holder`` is very likely to be in the noise. - The macro :c:macro:`PYBIND11_OVERRIDE_PURE` should be used for pure virtual functions, and :c:macro:`PYBIND11_OVERRIDE` should be used for functions which have a default implementation. There are also two alternate macros @@ -107,18 +95,18 @@ The binding code also needs a few minor adaptations (highlighted): :emphasize-lines: 2,3 PYBIND11_MODULE(example, m) { - py::class_(m, "Animal") + py::class_(m, "Animal") .def(py::init<>()) .def("go", &Animal::go); - py::class_(m, "Dog") + py::class_(m, "Dog") .def(py::init<>()); m.def("call_go", &call_go); } Importantly, pybind11 is made aware of the trampoline helper class by -specifying it as an extra template argument to ``py::class_``. (This can also +specifying it as an extra template argument to :class:`class_`. (This can also be combined with other template arguments such as a custom holder type; the order of template types does not matter). Following this, we are able to define a constructor as usual. @@ -128,9 +116,9 @@ Bindings should be made against the actual class, not the trampoline helper clas .. code-block:: cpp :emphasize-lines: 3 - py::class_(m, "Animal"); + py::class_(m, "Animal"); .def(py::init<>()) - .def("go", &Animal::go); /* <--- DO NOT USE &PyAnimal::go HERE */ + .def("go", &PyAnimal::go); /* <--- THIS IS WRONG, use &Animal::go */ Note, however, that the above is sufficient for allowing python classes to extend ``Animal``, but not ``Dog``: see :ref:`virtual_and_inheritance` for the @@ -256,13 +244,13 @@ override the ``name()`` method): .. code-block:: cpp - class PyAnimal : public Animal, py::trampoline_self_life_support { + class PyAnimal : public Animal { public: using Animal::Animal; // Inherit constructors std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE(std::string, Animal, go, n_times); } std::string name() override { PYBIND11_OVERRIDE(std::string, Animal, name, ); } }; - class PyDog : public Dog, py::trampoline_self_life_support { + class PyDog : public Dog { public: using Dog::Dog; // Inherit constructors std::string go(int n_times) override { PYBIND11_OVERRIDE(std::string, Dog, go, n_times); } @@ -284,7 +272,7 @@ declare or override any virtual methods itself: .. code-block:: cpp class Husky : public Dog {}; - class PyHusky : public Husky, py::trampoline_self_life_support { + class PyHusky : public Husky { public: using Husky::Husky; // Inherit constructors std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE(std::string, Husky, go, n_times); } @@ -299,15 +287,13 @@ follows: .. code-block:: cpp - template - class PyAnimal : public AnimalBase, py::trampoline_self_life_support { + template class PyAnimal : public AnimalBase { public: using AnimalBase::AnimalBase; // Inherit constructors std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE(std::string, AnimalBase, go, n_times); } std::string name() override { PYBIND11_OVERRIDE(std::string, AnimalBase, name, ); } }; - template - class PyDog : public PyAnimal, py::trampoline_self_life_support { + template class PyDog : public PyAnimal { public: using PyAnimal::PyAnimal; // Inherit constructors // Override PyAnimal's pure virtual go() with a non-pure one: @@ -325,9 +311,9 @@ The classes are then registered with pybind11 using: .. code-block:: cpp - py::class_, py::smart_holder> animal(m, "Animal"); - py::class_, py::smart_holder> dog(m, "Dog"); - py::class_, py::smart_holder> husky(m, "Husky"); + py::class_> animal(m, "Animal"); + py::class_> dog(m, "Dog"); + py::class_> husky(m, "Husky"); // ... add animal, dog, husky definitions Note that ``Husky`` did not require a dedicated trampoline template class at @@ -513,12 +499,12 @@ an alias: // ... virtual ~Example() = default; }; - class PyExample : public Example, py::trampoline_self_life_support { + class PyExample : public Example { public: using Example::Example; PyExample(Example &&base) : Example(std::move(base)) {} }; - py::class_(m, "Example") + py::class_(m, "Example") // Returns an Example pointer. If a PyExample is needed, the Example // instance will be moved via the extra constructor in PyExample, above. .def(py::init([]() { return new Example(); })) @@ -564,10 +550,9 @@ pybind11. The underlying issue is that the ``std::unique_ptr`` holder type that is responsible for managing the lifetime of instances will reference the destructor even if no deallocations ever take place. In order to expose classes with private or protected destructors, it is possible to override the holder -type via a holder type argument to ``py::class_``. Pybind11 provides a helper -class ``py::nodelete`` that disables any destructor invocations. In this case, -it is crucial that instances are deallocated on the C++ side to avoid memory -leaks. +type via a holder type argument to ``class_``. Pybind11 provides a helper class +``py::nodelete`` that disables any destructor invocations. In this case, it is +crucial that instances are deallocated on the C++ side to avoid memory leaks. .. code-block:: cpp @@ -841,7 +826,8 @@ An instance can now be pickled as follows: 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. + and/or segmentation faults. Python defaults to version 3 (Python 3-3.7) and + version 4 for Python 3.8+. .. seealso:: @@ -886,7 +872,7 @@ Multiple Inheritance pybind11 can create bindings for types that derive from multiple base types (aka. *multiple inheritance*). To do so, specify all bases in the template -arguments of the ``py::class_`` declaration: +arguments of the ``class_`` declaration: .. code-block:: cpp @@ -961,11 +947,11 @@ because of conflicting definitions on the external type: // dogs.cpp // Binding for external library class: - py::class_(m, "Pet") + py::class(m, "Pet") .def("name", &pets::Pet::name); // Binding for local extension class: - py::class_(m, "Dog") + py::class(m, "Dog") .def(py::init()); .. code-block:: cpp @@ -973,11 +959,11 @@ because of conflicting definitions on the external type: // cats.cpp, in a completely separate project from the above dogs.cpp. // Binding for external library class: - py::class_(m, "Pet") + py::class(m, "Pet") .def("get_name", &pets::Pet::name); // Binding for local extending class: - py::class_(m, "Cat") + py::class(m, "Cat") .def(py::init()); .. code-block:: pycon @@ -995,13 +981,13 @@ the ``py::class_`` constructor: .. code-block:: cpp // Pet binding in dogs.cpp: - py::class_(m, "Pet", py::module_local()) + py::class(m, "Pet", py::module_local()) .def("name", &pets::Pet::name); .. code-block:: cpp // Pet binding in cats.cpp: - py::class_(m, "Pet", py::module_local()) + py::class(m, "Pet", py::module_local()) .def("get_name", &pets::Pet::name); This makes the Python-side ``dogs.Pet`` and ``cats.Pet`` into distinct classes, @@ -1119,7 +1105,7 @@ described trampoline: virtual int foo() const { return 42; } }; - class Trampoline : public A, py::trampoline_self_life_support { + class Trampoline : public A { public: int foo() const override { PYBIND11_OVERRIDE(int, A, foo, ); } }; @@ -1129,7 +1115,7 @@ described trampoline: using A::foo; }; - py::class_(m, "A") // <-- `Trampoline` here + py::class_(m, "A") // <-- `Trampoline` here .def("foo", &Publicist::foo); // <-- `Publicist` here, not `Trampoline`! Binding final classes @@ -1210,7 +1196,7 @@ but once again each instantiation must be explicitly specified: T fn(V v); }; - py::class_>(m, "MyClassT") + py::class>(m, "MyClassT") .def("fn", &MyClass::fn); Custom automatic downcasters diff --git a/pybind11/docs/advanced/deadlock.md b/pybind11/docs/advanced/deadlock.md deleted file mode 100644 index f1bab5bdb..000000000 --- a/pybind11/docs/advanced/deadlock.md +++ /dev/null @@ -1,391 +0,0 @@ -# Double locking, deadlocking, GIL - -[TOC] - -## Introduction - -### Overview - -In concurrent programming with locks, *deadlocks* can arise when more than one -mutex is locked at the same time, and careful attention has to be paid to lock -ordering to avoid this. Here we will look at a common situation that occurs in -native extensions for CPython written in C++. - -### Deadlocks - -A deadlock can occur when more than one thread attempts to lock more than one -mutex, and two of the threads lock two of the mutexes in different orders. For -example, consider mutexes `mu1` and `mu2`, and threads T1 and T2, executing: - -| | T1 | T2 | -|--- | ------------------- | -------------------| -|1 | `mu1.lock()`{.good} | `mu2.lock()`{.good}| -|2 | `mu2.lock()`{.bad} | `mu1.lock()`{.bad} | -|3 | `/* work */` | `/* work */` | -|4 | `mu2.unlock()` | `mu1.unlock()` | -|5 | `mu1.unlock()` | `mu2.unlock()` | - -Now if T1 manages to lock `mu1` and T2 manages to lock `mu2` (as indicated in -green), then both threads will block while trying to lock the respective other -mutex (as indicated in red), but they are also unable to release the mutex that -they have locked (step 5). - -**The problem** is that it is possible for one thread to attempt to lock `mu1` -and then `mu2`, and for another thread to attempt to lock `mu2` and then `mu1`. -Note that it does not matter if either mutex is unlocked at any intermediate -point; what matters is only the order of any attempt to *lock* the mutexes. For -example, the following, more complex series of operations is just as prone to -deadlock: - -| | T1 | T2 | -|--- | ------------------- | -------------------| -|1 | `mu1.lock()`{.good} | `mu1.lock()`{.good}| -|2 | waiting for T2 | `mu2.lock()`{.good}| -|3 | waiting for T2 | `/* work */` | -|3 | waiting for T2 | `mu1.unlock()` | -|3 | `mu2.lock()`{.bad} | `/* work */` | -|3 | `/* work */` | `mu1.lock()`{.bad} | -|3 | `/* work */` | `/* work */` | -|4 | `mu2.unlock()` | `mu1.unlock()` | -|5 | `mu1.unlock()` | `mu2.unlock()` | - -When the mutexes involved in a locking sequence are known at compile-time, then -avoiding deadlocks is “merely” a matter of arranging the lock -operations carefully so as to only occur in one single, fixed order. However, it -is also possible for mutexes to only be determined at runtime. A typical example -of this is a database where each row has its own mutex. An operation that -modifies two rows in a single transaction (e.g. “transferring an amount -from one account to another”) must lock two row mutexes, but the locking -order cannot be established at compile time. In this case, a dynamic -“deadlock avoidance algorithm” is needed. (In C++, `std::lock` -provides such an algorithm. An algorithm might use a non-blocking `try_lock` -operation on a mutex, which can either succeed or fail to lock the mutex, but -returns without blocking.) - -Conceptually, one could also consider it a deadlock if _the same_ thread -attempts to lock a mutex that it has already locked (e.g. when some locked -operation accidentally recurses into itself): `mu.lock();`{.good} -`mu.lock();`{.bad} However, this is a slightly separate issue: Typical mutexes -are either of _recursive_ or _non-recursive_ kind. A recursive mutex allows -repeated locking and requires balanced unlocking. A non-recursive mutex can be -implemented more efficiently, and/but for efficiency reasons does not actually -guarantee a deadlock on second lock. Instead, the API simply forbids such use, -making it a precondition that the thread not already hold the mutex, with -undefined behaviour on violation. - -### “Once” initialization - -A common programming problem is to have an operation happen precisely once, even -if requested concurrently. While it is clear that we need to track in some -shared state somewhere whether the operation has already happened, it is worth -noting that this state only ever transitions, once, from `false` to `true`. This -is considerably simpler than a general shared state that can change values -arbitrarily. Next, we also need a mechanism for all but one thread to block -until the initialization has completed, which we can provide with a mutex. The -simplest solution just always locks the mutex: - -```c++ -// The "once" mechanism: -constinit absl::Mutex mu(absl::kConstInit); -constinit bool init_done = false; - -// The operation of interest: -void f(); - -void InitOnceNaive() { - absl::MutexLock lock(&mu); - if (!init_done) { - f(); - init_done = true; - } -} -``` - -This works, but the efficiency-minded reader will observe that once the -operation has completed, all future lock contention on the mutex is -unnecessary. This leads to the (in)famous “double-locking” -algorithm, which was historically hard to write correctly. The idea is to check -the boolean *before* locking the mutex, and avoid locking if the operation has -already completed. However, accessing shared state concurrently when at least -one access is a write is prone to causing a data race and needs to be done -according to an appropriate concurrent programming model. In C++ we use atomic -variables: - -```c++ -// The "once" mechanism: -constinit absl::Mutex mu(absl::kConstInit); -constinit std::atomic init_done = false; - -// The operation of interest: -void f(); - -void InitOnceWithFastPath() { - if (!init_done.load(std::memory_order_acquire)) { - absl::MutexLock lock(&mu); - if (!init_done.load(std::memory_order_relaxed)) { - f(); - init_done.store(true, std::memory_order_release); - } - } -} -``` - -Checking the flag now happens without holding the mutex lock, and if the -operation has already completed, we return immediately. After locking the mutex, -we need to check the flag again, since multiple threads can reach this point. - -*Atomic details.* Since the atomic flag variable is accessed concurrently, we -have to think about the memory order of the accesses. There are two separate -cases: The first, outer check outside the mutex lock, and the second, inner -check under the lock. The outer check and the flag update form an -acquire/release pair: *if* the load sees the value `true` (which must have been -written by the store operation), then it also sees everything that happened -before the store, namely the operation `f()`. By contrast, the inner check can -use relaxed memory ordering, since in that case the mutex operations provide the -necessary ordering: if the inner load sees the value `true`, it happened after -the `lock()`, which happened after the `unlock()`, which happened after the -store. - -The C++ standard library, and Abseil, provide a ready-made solution of this -algorithm called `std::call_once`/`absl::call_once`. (The interface is the same, -but the Abseil implementation is possibly better.) - -```c++ -// The "once" mechanism: -constinit absl::once_flag init_flag; - -// The operation of interest: -void f(); - -void InitOnceWithCallOnce() { - absl::call_once(once_flag, f); -} -``` - -Even though conceptually this is performing the same algorithm, this -implementation has some considerable advantages: The `once_flag` type is a small -and trivial, integer-like type and is trivially destructible. Not only does it -take up less space than a mutex, it also generates less code since it does not -have to run a destructor, which would need to be added to the program's global -destructor list. - -The final clou comes with the C++ semantics of a `static` variable declared at -block scope: According to [[stmt.dcl]](https://eel.is/c++draft/stmt.dcl#3): - -> Dynamic initialization of a block variable with static storage duration or -> thread storage duration is performed the first time control passes through its -> declaration; such a variable is considered initialized upon the completion of -> its initialization. [...] If control enters the declaration concurrently while -> the variable is being initialized, the concurrent execution shall wait for -> completion of the initialization. - -This is saying that the initialization of a local, `static` variable precisely -has the “once” semantics that we have been discussing. We can -therefore write the above example as follows: - -```c++ -// The operation of interest: -void f(); - -void InitOnceWithStatic() { - static int unused = (f(), 0); -} -``` - -This approach is by far the simplest and easiest, but the big difference is that -the mutex (or mutex-like object) in this implementation is no longer visible or -in the user’s control. This is perfectly fine if the initializer is -simple, but if the initializer itself attempts to lock any other mutex -(including by initializing another static variable!), then we have no control -over the lock ordering! - -Finally, you may have noticed the `constinit`s around the earlier code. Both -`constinit` and `constexpr` specifiers on a declaration mean that the variable -is *constant-initialized*, which means that no initialization is performed at -runtime (the initial value is already known at compile time). This in turn means -that a static variable guard mutex may not be needed, and static initialization -never blocks. The difference between the two is that a `constexpr`-specified -variable is also `const`, and a variable cannot be `constexpr` if it has a -non-trivial destructor. Such a destructor also means that the guard mutex is -needed after all, since the destructor must be registered to run at exit, -conditionally on initialization having happened. - -## Python, CPython, GIL - -With CPython, a Python program can call into native code. To this end, the -native code registers callback functions with the Python runtime via the CPython -API. In order to ensure that the internal state of the Python runtime remains -consistent, there is a single, shared mutex called the “global interpreter -lock”, or GIL for short. Upon entry of one of the user-provided callback -functions, the GIL is locked (or “held”), so that no other mutations -of the Python runtime state can occur until the native callback returns. - -Many native extensions do not interact with the Python runtime for at least some -part of them, and so it is common for native extensions to _release_ the GIL, do -some work, and then reacquire the GIL before returning. Similarly, when code is -generally not holding the GIL but needs to interact with the runtime briefly, it -will first reacquire the GIL. The GIL is reentrant, and constructions to acquire -and subsequently release the GIL are common, and often don't worry about whether -the GIL is already held. - -If the native code is written in C++ and contains local, `static` variables, -then we are now dealing with at least _two_ mutexes: the static variable guard -mutex, and the GIL from CPython. - -A common problem in such code is an operation with “only once” -semantics that also ends up requiring the GIL to be held at some point. As per -the above description of “once”-style techniques, one might find a -static variable: - -```c++ -// CPython callback, assumes that the GIL is held on entry. -PyObject* InvokeWidget(PyObject* self) { - static PyObject* impl = CreateWidget(); - return PyObject_CallOneArg(impl, self); -} -``` - -This seems reasonable, but bear in mind that there are two mutexes (the "guard -mutex" and "the GIL"), and we must think about the lock order. Otherwise, if the -callback is called from multiple threads, a deadlock may ensue. - -Let us consider what we can see here: On entry, the GIL is already locked, and -we are locking the guard mutex. This is one lock order. Inside the initializer -`CreateWidget`, with both mutexes already locked, the function can freely access -the Python runtime. - -However, it is entirely possible that `CreateWidget` will want to release the -GIL at one point and reacquire it later: - -```c++ -// Assumes that the GIL is held on entry. -// Ensures that the GIL is held on exit. -PyObject* CreateWidget() { - // ... - Py_BEGIN_ALLOW_THREADS // releases GIL - // expensive work, not accessing the Python runtime - Py_END_ALLOW_THREADS // acquires GIL, #! - // ... - return result; -} -``` - -Now we have a second lock order: the guard mutex is locked, and then the GIL is -locked (at `#!`). To see how this deadlocks, consider threads T1 and T2 both -having the runtime attempt to call `InvokeWidget`. T1 locks the GIL and -proceeds, locking the guard mutex and calling `CreateWidget`; T2 is blocked -waiting for the GIL. Then T1 releases the GIL to do “expensive -work”, and T2 awakes and locks the GIL. Now T2 is blocked trying to -acquire the guard mutex, but T1 is blocked reacquiring the GIL (at `#!`). - -In other words: if we want to support “once-called” functions that -can arbitrarily release and reacquire the GIL, as is very common, then the only -lock order that we can ensure is: guard mutex first, GIL second. - -To implement this, we must rewrite our code. Naively, we could always release -the GIL before a `static` variable with blocking initializer: - -```c++ -// CPython callback, assumes that the GIL is held on entry. -PyObject* InvokeWidget(PyObject* self) { - Py_BEGIN_ALLOW_THREADS // releases GIL - static PyObject* impl = CreateWidget(); - Py_END_ALLOW_THREADS // acquires GIL - - return PyObject_CallOneArg(impl, self); -} -``` - -But similar to the `InitOnceNaive` example above, this code cycles the GIL -(possibly descheduling the thread) even when the static variable has already -been initialized. If we want to avoid this, we need to abandon the use of a -static variable, since we do not control the guard mutex well enough. Instead, -we use an operation whose mutex locking is under our control, such as -`call_once`. For example: - -```c++ -// CPython callback, assumes that the GIL is held on entry. -PyObject* InvokeWidget(PyObject* self) { - static constinit PyObject* impl = nullptr; - static constinit std::atomic init_done = false; - static constinit absl::once_flag init_flag; - - if (!init_done.load(std::memory_order_acquire)) { - Py_BEGIN_ALLOW_THREADS // releases GIL - absl::call_once(init_flag, [&]() { - PyGILState_STATE s = PyGILState_Ensure(); // acquires GIL - impl = CreateWidget(); - PyGILState_Release(s); // releases GIL - init_done.store(true, std::memory_order_release); - }); - Py_END_ALLOW_THREADS // acquires GIL - } - - return PyObject_CallOneArg(impl, self); -} -``` - -The lock order is now always guard mutex first, GIL second. Unfortunately we -have to duplicate the “double-checked done flag”, effectively -leading to triple checking, because the flag state inside the `absl::once_flag` -is not accessible to the user. In other words, we cannot ask `init_flag` whether -it has been used yet. - -However, we can perform one last, minor optimisation: since we assume that the -GIL is held on entry, and again when the initializing operation returns, the GIL -actually serializes access to our done flag variable, which therefore does not -need to be atomic. (The difference to the previous, atomic code may be small, -depending on the architecture. For example, on x86-64, acquire/release on a bool -is nearly free ([demo](https://godbolt.org/z/P9vYWf4fE)).) - -```c++ -// CPython callback, assumes that the GIL is held on entry, and indeed anywhere -// directly in this function (i.e. the GIL can be released inside CreateWidget, -// but must be reaqcuired when that call returns). -PyObject* InvokeWidget(PyObject* self) { - static constinit PyObject* impl = nullptr; - static constinit bool init_done = false; // guarded by GIL - static constinit absl::once_flag init_flag; - - if (!init_done) { - Py_BEGIN_ALLOW_THREADS // releases GIL - // (multiple threads may enter here) - absl::call_once(init_flag, [&]() { - // (only one thread enters here) - PyGILState_STATE s = PyGILState_Ensure(); // acquires GIL - impl = CreateWidget(); - init_done = true; // (GIL is held) - PyGILState_Release(s); // releases GIL - }); - - Py_END_ALLOW_THREADS // acquires GIL - } - - return PyObject_CallOneArg(impl, self); -} -``` - -## Debugging tips - -* Build with symbols. -* Ctrl-C sends `SIGINT`, Ctrl-\\ - sends `SIGQUIT`. Both have their uses. -* Useful `gdb` commands: - * `py-bt` prints a Python backtrace if you are in a Python frame. - * `thread apply all bt 10` prints the top-10 frames for each thread. A - full backtrace can be prohibitively expensive, and the top few frames - are often good enough. - * `p PyGILState_Check()` shows whether a thread is holding the GIL. For - all threads, run `thread apply all p PyGILState_Check()` to find out - which thread is holding the GIL. - * The `static` variable guard mutex is accessed with functions like - `cxa_guard_acquire` (though this depends on ABI details and can vary). - The guard mutex itself contains information about which thread is - currently holding it. - -## Links - -* Article on - [double-checked locking](https://preshing.com/20130930/double-checked-locking-is-fixed-in-cpp11/) -* [The Deadlock Empire](https://deadlockempire.github.io/), hands-on exercises - to construct deadlocks diff --git a/pybind11/docs/advanced/embedding.rst b/pybind11/docs/advanced/embedding.rst index e78a8f4ce..cbed82158 100644 --- a/pybind11/docs/advanced/embedding.rst +++ b/pybind11/docs/advanced/embedding.rst @@ -18,7 +18,7 @@ information, see :doc:`/compiling`. .. code-block:: cmake - cmake_minimum_required(VERSION 3.15...3.30) + cmake_minimum_required(VERSION 3.5...3.29) project(example) find_package(pybind11 REQUIRED) # or `add_subdirectory(pybind11)` diff --git a/pybind11/docs/advanced/exceptions.rst b/pybind11/docs/advanced/exceptions.rst index 8f0e9c93a..e20f42b5f 100644 --- a/pybind11/docs/advanced/exceptions.rst +++ b/pybind11/docs/advanced/exceptions.rst @@ -368,7 +368,8 @@ Should they throw or fail to catch any exceptions in their call graph, the C++ runtime calls ``std::terminate()`` to abort immediately. Similarly, Python exceptions raised in a class's ``__del__`` method do not -propagate, but ``sys.unraisablehook()`` `is triggered +propagate, but are logged by Python as an unraisable error. In Python 3.8+, a +`system hook is triggered `_ and an auditing event is logged. diff --git a/pybind11/docs/advanced/functions.rst b/pybind11/docs/advanced/functions.rst index ff00c9c8a..372934b09 100644 --- a/pybind11/docs/advanced/functions.rst +++ b/pybind11/docs/advanced/functions.rst @@ -81,11 +81,9 @@ The following table provides an overview of available policies: | | it is no longer used. Warning: undefined behavior will ensue when the C++ | | | side deletes an object that is still referenced and used by Python. | +--------------------------------------------------+----------------------------------------------------------------------------+ -| :enum:`return_value_policy::reference_internal` | If the return value is an lvalue reference or a pointer, the parent object | -| | (the implicit ``this``, or ``self`` argument of the called method or | -| | property) is kept alive for at least the lifespan of the return value. | -| | **Otherwise this policy falls back to :enum:`return_value_policy::move` | -| | (see #5528).** Internally, this policy works just like | +| :enum:`return_value_policy::reference_internal` | Indicates that the lifetime of the return value is tied to the lifetime | +| | of a parent object, namely the implicit ``this``, or ``self`` argument of | +| | the called method or property. Internally, this policy works just like | | | :enum:`return_value_policy::reference` but additionally applies a | | | ``keep_alive<0, 1>`` *call policy* (described in the next section) that | | | prevents the parent object from being garbage collected as long as the | diff --git a/pybind11/docs/advanced/misc.rst b/pybind11/docs/advanced/misc.rst index 3aeb8ab9d..ddd7f3937 100644 --- a/pybind11/docs/advanced/misc.rst +++ b/pybind11/docs/advanced/misc.rst @@ -62,11 +62,7 @@ will acquire the GIL before calling the Python callback. Similarly, the back into Python. When writing C++ code that is called from other C++ code, if that code accesses -Python state, it must explicitly acquire and release the GIL. A separate -document on deadlocks [#f8]_ elaborates on a particularly subtle interaction -with C++'s block-scope static variable initializer guard mutexes. - -.. [#f8] See docs/advanced/deadlock.md +Python state, it must explicitly acquire and release the GIL. The classes :class:`gil_scoped_release` and :class:`gil_scoped_acquire` can be used to acquire and release the global interpreter lock in the body of a C++ @@ -80,7 +76,7 @@ could be realized as follows (important changes highlighted): .. code-block:: cpp :emphasize-lines: 8,30,31 - class PyAnimal : public Animal, py::trampoline_self_life_support { + class PyAnimal : public Animal { public: /* Inherit the constructors */ using Animal::Animal; @@ -98,12 +94,12 @@ could be realized as follows (important changes highlighted): }; PYBIND11_MODULE(example, m) { - py::class_ animal(m, "Animal"); + py::class_ animal(m, "Animal"); animal .def(py::init<>()) .def("go", &Animal::go); - py::class_(m, "Dog", animal) + py::class_(m, "Dog", animal) .def(py::init<>()); m.def("call_go", [](Animal *animal) -> std::string { @@ -146,9 +142,6 @@ following checklist. destructors can sometimes get invoked in weird and unexpected circumstances as a result of exceptions. -- C++ static block-scope variable initialization that calls back into Python can - cause deadlocks; see [#f8]_ for a detailed discussion. - - You should try running your code in a debug build. That will enable additional assertions within pybind11 that will throw exceptions on certain GIL handling errors (reference counting operations). @@ -188,7 +181,7 @@ from Section :ref:`inheritance`. Suppose now that ``Pet`` bindings are defined in a module named ``basic``, whereas the ``Dog`` bindings are defined somewhere else. The challenge is of course that the variable ``pet`` is not available anymore though it is needed -to indicate the inheritance relationship to the constructor of ``py::class_``. +to indicate the inheritance relationship to the constructor of ``class_``. However, it can be acquired as follows: .. code-block:: cpp @@ -200,7 +193,7 @@ However, it can be acquired as follows: .def("bark", &Dog::bark); Alternatively, you can specify the base class as a template parameter option to -``py::class_``, which performs an automated lookup of the corresponding Python +``class_``, which performs an automated lookup of the corresponding Python type. Like the above code, however, this also requires invoking the ``import`` function once to ensure that the pybind11 binding code of the module ``basic`` has been executed: diff --git a/pybind11/docs/advanced/smart_ptrs.rst b/pybind11/docs/advanced/smart_ptrs.rst index 599f384db..b9f100cf8 100644 --- a/pybind11/docs/advanced/smart_ptrs.rst +++ b/pybind11/docs/advanced/smart_ptrs.rst @@ -1,70 +1,11 @@ -Smart pointers & ``py::class_`` -############################### +Smart pointers +############## -The binding generator for classes, ``py::class_``, can be passed a template -type that denotes a special *holder* type that is used to manage references to -the object. If no such holder type template argument is given, the default for -a type ``T`` is ``std::unique_ptr``. +std::unique_ptr +=============== -.. note:: - - A ``py::class_`` for a given C++ type ``T`` — and all its derived types — - can only use a single holder type. - - -.. _smart_holder: - -``py::smart_holder`` -==================== - -Starting with pybind11v3, ``py::smart_holder`` is built into pybind11. It is -the recommended ``py::class_`` holder for most situations. However, for -backward compatibility it is **not** the default holder, and there are no -plans to make it the default holder in the future. - -It is extremely easy to use the safer and more versatile ``py::smart_holder``: -simply add ``py::smart_holder`` to ``py::class_``: - -* ``py::class_`` to - -* ``py::class_``. - -.. note:: - - A shorthand, ``py::classh``, is provided for - ``py::class_``. The ``h`` in ``py::classh`` stands - for **smart_holder** but is shortened for brevity, ensuring it has the - same number of characters as ``py::class_``. This design choice facilitates - easy experimentation with ``py::smart_holder`` without introducing - distracting whitespace noise in diffs. - -The ``py::smart_holder`` functionality includes the following: - -* Support for **two-way** Python/C++ conversions for both - ``std::unique_ptr`` and ``std::shared_ptr`` **simultaneously**. - -* Passing a Python object back to C++ via ``std::unique_ptr``, safely - **disowning** the Python object. - -* Safely passing "trampoline" objects (objects with C++ virtual function - overrides implemented in Python, see :ref:`overriding_virtuals`) via - ``std::unique_ptr`` or ``std::shared_ptr`` back to C++: - associated Python objects are automatically kept alive for the lifetime - of the smart-pointer. - -* Full support for ``std::enable_shared_from_this`` (`cppreference - `_). - - -``std::unique_ptr`` -=================== - -This is the default ``py::class_`` holder and works as expected in -most situations. However, handling base-and-derived classes involves a -``reinterpret_cast``, which is, strictly speaking, undefined behavior. -Also note that the ``std::unique_ptr`` holder only supports passing a -``std::unique_ptr`` from C++ to Python, but not the other way around. -For example, the following code works as expected with ``py::class_``: +Given a class ``Example`` with Python bindings, it's possible to return +instances wrapped in C++11 unique pointers, like so .. code-block:: cpp @@ -74,50 +15,112 @@ For example, the following code works as expected with ``py::class_``: m.def("create_example", &create_example); -However, this will fail with ``py::class_`` (but works with -``py::class_``): +In other words, there is nothing special that needs to be done. While returning +unique pointers in this way is allowed, it is *illegal* to use them as function +arguments. For instance, the following function signature cannot be processed +by pybind11. .. code-block:: cpp void do_something_with_example(std::unique_ptr ex) { ... } -.. note:: +The above signature would imply that Python needs to give up ownership of an +object that is passed to this function, which is generally not possible (for +instance, the object might be referenced elsewhere). - The ``reinterpret_cast`` mentioned above is `here - `_. - For completeness: The same cast is also applied to ``py::smart_holder``, - but that is safe, because ``py::smart_holder`` is not templated. +std::shared_ptr +=============== +The binding generator for classes, :class:`class_`, can be passed a template +type that denotes a special *holder* type that is used to manage references to +the object. If no such holder type template argument is given, the default for +a type named ``Type`` is ``std::unique_ptr``, which means that the object +is deallocated when Python's reference count goes to zero. -``std::shared_ptr`` -=================== - -It is possible to use ``std::shared_ptr`` as the holder, for example: +It is possible to switch to other types of reference counting wrappers or smart +pointers, which is useful in codebases that rely on them. For instance, the +following snippet causes ``std::shared_ptr`` to be used instead. .. code-block:: cpp - py::class_ /* <- holder type */>(m, "Example"); + py::class_ /* <- holder type */> obj(m, "Example"); -Compared to using ``py::class_``, there are two noteworthy disadvantages: +Note that any particular class can only be associated with a single holder type. -* Because a ``py::class_`` for a given C++ type ``T`` can only use a - single holder type, ``std::unique_ptr`` cannot even be passed from C++ - to Python. This will become apparent only at runtime, often through a - segmentation fault. +One potential stumbling block when using holder types is that they need to be +applied consistently. Can you guess what's broken about the following binding +code? -* Similar to the ``std::unique_ptr`` holder, the handling of base-and-derived - classes involves a ``reinterpret_cast`` that has strictly speaking undefined - behavior, although it works as expected in most situations. +.. code-block:: cpp + class Child { }; + + class Parent { + public: + Parent() : child(std::make_shared()) { } + Child *get_child() { return child.get(); } /* Hint: ** DON'T DO THIS ** */ + private: + std::shared_ptr child; + }; + + PYBIND11_MODULE(example, m) { + py::class_>(m, "Child"); + + py::class_>(m, "Parent") + .def(py::init<>()) + .def("get_child", &Parent::get_child); + } + +The following Python code will cause undefined behavior (and likely a +segmentation fault). + +.. code-block:: python + + from example import Parent + + print(Parent().get_child()) + +The problem is that ``Parent::get_child()`` returns a pointer to an instance of +``Child``, but the fact that this instance is already managed by +``std::shared_ptr<...>`` is lost when passing raw pointers. In this case, +pybind11 will create a second independent ``std::shared_ptr<...>`` that also +claims ownership of the pointer. In the end, the object will be freed **twice** +since these shared pointers have no way of knowing about each other. + +There are two ways to resolve this issue: + +1. For types that are managed by a smart pointer class, never use raw pointers + in function arguments or return values. In other words: always consistently + wrap pointers into their designated holder types (such as + ``std::shared_ptr<...>``). In this case, the signature of ``get_child()`` + should be modified as follows: + +.. code-block:: cpp + + std::shared_ptr get_child() { return child; } + +2. Adjust the definition of ``Child`` by specifying + ``std::enable_shared_from_this`` (see cppreference_ for details) as a + base class. This adds a small bit of information to ``Child`` that allows + pybind11 to realize that there is already an existing + ``std::shared_ptr<...>`` and communicate with it. In this case, the + declaration of ``Child`` should look as follows: + +.. _cppreference: http://en.cppreference.com/w/cpp/memory/enable_shared_from_this + +.. code-block:: cpp + + class Child : public std::enable_shared_from_this { }; .. _smart_pointers: Custom smart pointers ===================== -For custom smart pointers (e.g. ``c10::intrusive_ptr`` in pytorch), transparent -conversions can be enabled using a macro invocation similar to the following. -It must be declared at the top namespace level before any binding code: +pybind11 supports ``std::unique_ptr`` and ``std::shared_ptr`` right out of the +box. For any other custom smart pointer, transparent conversions can be enabled +using a macro invocation similar to the following. It must be declared at the +top namespace level before any binding code: .. code-block:: cpp @@ -164,70 +167,8 @@ specialized: The above specialization informs pybind11 that the custom ``SmartPtr`` class provides ``.get()`` functionality via ``.getPointer()``. -.. note:: - - The two noteworthy disadvantages mentioned under the ``std::shared_ptr`` - section apply similarly to custom smart pointer holders, but there is no - established safe alternative in this case. - .. seealso:: The file :file:`tests/test_smart_ptr.cpp` contains a complete example that demonstrates how to work with custom reference-counting holder types in more detail. - - -Be careful not to accidentally undermine automatic lifetime management -====================================================================== - -``py::class_``-wrapped objects automatically manage the lifetime of the -wrapped C++ object, in collaboration with the chosen holder type. -When wrapping C++ functions involving raw pointers, care needs to be taken -to not inadvertently transfer ownership, resulting in multiple Python -objects acting as owners, causing heap-use-after-free or double-free errors. -For example: - -.. code-block:: cpp - - class Child { }; - - class Parent { - public: - Parent() : child(std::make_shared()) { } - Child *get_child() { return child.get(); } /* DANGER */ - private: - std::shared_ptr child; - }; - - PYBIND11_MODULE(example, m) { - py::class_>(m, "Child"); - - py::class_>(m, "Parent") - .def(py::init<>()) - .def("get_child", &Parent::get_child); /* PROBLEM */ - } - -The following Python code leads to undefined behavior, likely resulting in -a segmentation fault. - -.. code-block:: python - - from example import Parent - - print(Parent().get_child()) - -Part of the ``/* PROBLEM */`` here is that pybind11 falls back to using -``return_value_policy::take_ownership`` as the default (see -:ref:`return_value_policies`). The fact that the ``Child`` instance is -already managed by ``std::shared_ptr`` is lost. Therefore pybind11 -will create a second independent ``std::shared_ptr`` that also -claims ownership of the pointer, eventually leading to heap-use-after-free -or double-free errors. - -There are various ways to resolve this issue, either by changing -the ``Child`` or ``Parent`` C++ implementations (e.g. using -``std::enable_shared_from_this`` as a base class for -``Child``, or adding a member function to ``Parent`` that returns -``std::shared_ptr``), or if that is not feasible, by using -``return_value_policy::reference_internal``. What is the best approach -depends on the exact situation. diff --git a/pybind11/docs/basics.rst b/pybind11/docs/basics.rst index c7a0208c4..cd97c100e 100644 --- a/pybind11/docs/basics.rst +++ b/pybind11/docs/basics.rst @@ -142,7 +142,7 @@ On Linux, the above example can be compiled using the following command: .. code-block:: bash - $ c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3 -m pybind11 --extension-suffix) + $ c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix) .. note:: diff --git a/pybind11/docs/benchmark.py b/pybind11/docs/benchmark.py index 26e390eb4..a273674f4 100644 --- a/pybind11/docs/benchmark.py +++ b/pybind11/docs/benchmark.py @@ -48,7 +48,7 @@ def generate_dummy_code_boost(nclasses=10): decl += "\n" for cl in range(nclasses): - decl += f"class cl{cl:03} {{\n" + decl += "class cl%03i {\n" % cl decl += "public:\n" bindings += f' py::class_("cl{cl:03}")\n' for fn in range(nfns): @@ -85,5 +85,5 @@ for codegen in [generate_dummy_code_pybind11, generate_dummy_code_boost]: n2 = dt.datetime.now() elapsed = (n2 - n1).total_seconds() size = os.stat("test.so").st_size - print(f" {{{nclasses * nfns}, {elapsed:.6f}, {size}}},") + print(" {%i, %f, %i}," % (nclasses * nfns, elapsed, size)) print("}") diff --git a/pybind11/docs/classes.rst b/pybind11/docs/classes.rst index 5406668f0..4f2167dac 100644 --- a/pybind11/docs/classes.rst +++ b/pybind11/docs/classes.rst @@ -34,18 +34,11 @@ The binding code for ``Pet`` looks as follows: .def("getName", &Pet::getName); } -``py::class_`` creates bindings for a C++ *class* or *struct*-style data +:class:`class_` creates bindings for a C++ *class* or *struct*-style data structure. :func:`init` is a convenience function that takes the types of a constructor's parameters as template arguments and wraps the corresponding -constructor (see the :ref:`custom_constructors` section for details). - -.. note:: - - Starting with pybind11v3, it is recommended to include `py::smart_holder` - in most situations for safety, especially if you plan to support conversions - to C++ smart pointers. See :ref:`smart_holder` for more information. - -An interactive Python session demonstrating this example is shown below: +constructor (see the :ref:`custom_constructors` section for details). An +interactive Python session demonstrating this example is shown below: .. code-block:: pycon @@ -265,7 +258,7 @@ inheritance relationship: There are two different ways of indicating a hierarchical relationship to pybind11: the first specifies the C++ base class as an extra template -parameter of the ``py::class_``: +parameter of the :class:`class_`: .. code-block:: cpp @@ -279,7 +272,7 @@ parameter of the ``py::class_``: .def("bark", &Dog::bark); Alternatively, we can also assign a name to the previously bound ``Pet`` -``py::class_`` object and reference it when binding the ``Dog`` class: +:class:`class_` object and reference it when binding the ``Dog`` class: .. code-block:: cpp @@ -505,7 +498,7 @@ The binding code for this example looks as follows: To ensure that the nested types ``Kind`` and ``Attributes`` are created within the scope of ``Pet``, the -``pet`` ``py::class_`` instance must be supplied to the :class:`enum_` and ``py::class_`` +``pet`` :class:`class_` instance must be supplied to the :class:`enum_` and :class:`class_` constructor. The :func:`enum_::export_values` function exports the enum entries into the parent scope, which should be skipped for newer C++11-style strongly typed enums. diff --git a/pybind11/docs/compiling.rst b/pybind11/docs/compiling.rst index 94042c3e5..448ea11ca 100644 --- a/pybind11/docs/compiling.rst +++ b/pybind11/docs/compiling.rst @@ -18,7 +18,7 @@ A Python extension module can be created with just a few lines of code: .. code-block:: cmake - cmake_minimum_required(VERSION 3.15...3.30) + cmake_minimum_required(VERSION 3.15...3.29) project(example LANGUAGES CXX) set(PYBIND11_FINDPYTHON ON) @@ -319,11 +319,11 @@ Building with CMake For C++ codebases that have an existing CMake-based build system, a Python extension module can be created with just a few lines of code, as seen above in -the module section. Pybind11 currently defaults to the old mechanism, though be -aware that CMake 3.27 removed the old mechanism, so pybind11 will automatically -switch if the old mechanism is not available. Please opt into the new mechanism -if at all possible. Our default may change in future versions. This is the -minimum required: +the module section. Pybind11 currently supports a lower minimum if you don't +use the modern FindPython, though be aware that CMake 3.27 removed the old +mechanism, so pybind11 will automatically switch if the old mechanism is not +available. Please opt into the new mechanism if at all possible. Our default +may change in future versions. This is the minimum required: @@ -333,9 +333,6 @@ minimum required: .. versionchanged:: 2.11 CMake 3.5+ is required. -.. versionchanged:: 2.14 - CMake 3.15+ is required. - Further information can be found at :doc:`cmake/index`. @@ -429,7 +426,7 @@ with ``PYTHON_EXECUTABLE``. For example: .. code-block:: bash - cmake -DPYBIND11_PYTHON_VERSION=3.8 .. + cmake -DPYBIND11_PYTHON_VERSION=3.7 .. # Another method: cmake -DPYTHON_EXECUTABLE=/path/to/python .. @@ -447,7 +444,7 @@ See the `Config file`_ docstring for details of relevant CMake variables. .. code-block:: cmake - cmake_minimum_required(VERSION 3.15...3.30) + cmake_minimum_required(VERSION 3.4...3.18) project(example LANGUAGES CXX) find_package(pybind11 REQUIRED) @@ -486,16 +483,17 @@ can refer to the same [cmake_example]_ repository for a full sample project FindPython mode --------------- -Modern CMake (3.18.2+ ideal) added a new module called FindPython that had a -highly improved search algorithm and modern targets and tools. If you use -FindPython, pybind11 will detect this and use the existing targets instead: +CMake 3.12+ (3.15+ recommended, 3.18.2+ ideal) added a new module called +FindPython that had a highly improved search algorithm and modern targets +and tools. If you use FindPython, pybind11 will detect this and use the +existing targets instead: .. code-block:: cmake - cmake_minimum_required(VERSION 3.15...3.30) + cmake_minimum_required(VERSION 3.15...3.22) project(example LANGUAGES CXX) - find_package(Python 3.8 COMPONENTS Interpreter Development REQUIRED) + find_package(Python 3.7 COMPONENTS Interpreter Development REQUIRED) find_package(pybind11 CONFIG REQUIRED) # or add_subdirectory(pybind11) @@ -543,7 +541,7 @@ available in all modes. The targets provided are: Just the "linking" part of pybind11:module ``pybind11::module`` - Everything for extension modules - ``pybind11::pybind11`` + ``Python::Module`` (FindPython) or ``pybind11::python_link_helper`` + Everything for extension modules - ``pybind11::pybind11`` + ``Python::Module`` (FindPython CMake 3.15+) or ``pybind11::python_link_helper`` ``pybind11::embed`` Everything for embedding the Python interpreter - ``pybind11::pybind11`` + ``Python::Python`` (FindPython) or Python libs @@ -570,7 +568,7 @@ You can use these targets to build complex applications. For example, the .. code-block:: cmake - cmake_minimum_required(VERSION 3.15...3.30) + cmake_minimum_required(VERSION 3.5...3.29) project(example LANGUAGES CXX) find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11) @@ -628,7 +626,7 @@ information about usage in C++, see :doc:`/advanced/embedding`. .. code-block:: cmake - cmake_minimum_required(VERSION 3.15...3.30) + cmake_minimum_required(VERSION 3.5...3.29) project(example LANGUAGES CXX) find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11) diff --git a/pybind11/docs/faq.rst b/pybind11/docs/faq.rst index 31e33f8b5..9b688b308 100644 --- a/pybind11/docs/faq.rst +++ b/pybind11/docs/faq.rst @@ -302,9 +302,9 @@ CMake configure line. (Replace ``$(which python)`` with a path to python if your prefer.) You can alternatively try ``-DPYBIND11_FINDPYTHON=ON``, which will activate the -new CMake FindPython support instead of pybind11's custom search. Newer CMake, -like, 3.18.2+, is recommended. You can set this in your ``CMakeLists.txt`` -before adding or finding pybind11, as well. +new CMake FindPython support instead of pybind11's custom search. Requires +CMake 3.12+, and 3.15+ or 3.18.2+ are even better. You can set this in your +``CMakeLists.txt`` before adding or finding pybind11, as well. Inconsistent detection of Python version in CMake and pybind11 ============================================================== @@ -325,11 +325,11 @@ There are three possible solutions: from CMake and rely on pybind11 in detecting Python version. If this is not possible, the CMake machinery should be called *before* including pybind11. 2. Set ``PYBIND11_FINDPYTHON`` to ``True`` or use ``find_package(Python - COMPONENTS Interpreter Development)`` on modern CMake ( 3.18.2+ best). - Pybind11 in these cases uses the new CMake FindPython instead of the old, - deprecated search tools, and these modules are much better at finding the - correct Python. If FindPythonLibs/Interp are not available (CMake 3.27+), - then this will be ignored and FindPython will be used. + COMPONENTS Interpreter Development)`` on modern CMake (3.12+, 3.15+ better, + 3.18.2+ best). Pybind11 in these cases uses the new CMake FindPython instead + of the old, deprecated search tools, and these modules are much better at + finding the correct Python. If FindPythonLibs/Interp are not available + (CMake 3.27+), then this will be ignored and FindPython will be used. 3. Set ``PYBIND11_NOPYTHON`` to ``TRUE``. Pybind11 will not search for Python. However, you will have to use the target-based system, and do more setup yourself, because it does not know about or include things that depend on diff --git a/pybind11/docs/installing.rst b/pybind11/docs/installing.rst new file mode 100644 index 000000000..30b9f1853 --- /dev/null +++ b/pybind11/docs/installing.rst @@ -0,0 +1,105 @@ +.. _installing: + +Installing the library +###################### + +There are several ways to get the pybind11 source, which lives at +`pybind/pybind11 on GitHub `_. The pybind11 +developers recommend one of the first three ways listed here, submodule, PyPI, +or conda-forge, for obtaining pybind11. + +.. _include_as_a_submodule: + +Include as a submodule +====================== + +When you are working on a project in Git, you can use the pybind11 repository +as a submodule. From your git repository, use: + +.. code-block:: bash + + git submodule add -b stable ../../pybind/pybind11 extern/pybind11 + git submodule update --init + +This assumes you are placing your dependencies in ``extern/``, and that you are +using GitHub; if you are not using GitHub, use the full https or ssh URL +instead of the relative URL ``../../pybind/pybind11`` above. Some other servers +also require the ``.git`` extension (GitHub does not). + +From here, you can now include ``extern/pybind11/include``, or you can use +the various integration tools (see :ref:`compiling`) pybind11 provides directly +from the local folder. + +Include with PyPI +================= + +You can download the sources and CMake files as a Python package from PyPI +using Pip. Just use: + +.. code-block:: bash + + pip install pybind11 + +This will provide pybind11 in a standard Python package format. If you want +pybind11 available directly in your environment root, you can use: + +.. code-block:: bash + + pip install "pybind11[global]" + +This is not recommended if you are installing with your system Python, as it +will add files to ``/usr/local/include/pybind11`` and +``/usr/local/share/cmake/pybind11``, so unless that is what you want, it is +recommended only for use in virtual environments or your ``pyproject.toml`` +file (see :ref:`compiling`). + +Include with conda-forge +======================== + +You can use pybind11 with conda packaging via `conda-forge +`_: + +.. code-block:: bash + + conda install -c conda-forge pybind11 + + +Include with vcpkg +================== +You can download and install pybind11 using the Microsoft `vcpkg +`_ dependency manager: + +.. code-block:: bash + + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + vcpkg install pybind11 + +The pybind11 port in vcpkg is kept up to date by Microsoft team members and +community contributors. If the version is out of date, please `create an issue +or pull request `_ on the vcpkg +repository. + +Global install with brew +======================== + +The brew package manager (Homebrew on macOS, or Linuxbrew on Linux) has a +`pybind11 package +`_. +To install: + +.. code-block:: bash + + brew install pybind11 + +.. We should list Conan, and possibly a few other C++ package managers (hunter, +.. perhaps). Conan has a very clean CMake integration that would be good to show. + +Other options +============= + +Other locations you can find pybind11 are `listed here +`_; these are maintained +by various packagers and the community. diff --git a/pybind11/docs/reference.rst b/pybind11/docs/reference.rst index c2757988d..e64a03519 100644 --- a/pybind11/docs/reference.rst +++ b/pybind11/docs/reference.rst @@ -68,8 +68,8 @@ Convenience functions converting to Python types .. _extras: -Passing extra arguments to ``def`` or ``py::class_`` -==================================================== +Passing extra arguments to ``def`` or ``class_`` +================================================ .. doxygengroup:: annotations :members: diff --git a/pybind11/docs/requirements.txt b/pybind11/docs/requirements.txt index aa2d86867..4e53f0352 100644 --- a/pybind11/docs/requirements.txt +++ b/pybind11/docs/requirements.txt @@ -130,9 +130,9 @@ imagesize==1.4.1 \ --hash=sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b \ --hash=sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a # via sphinx -jinja2==3.1.6 \ - --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ - --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 +jinja2==3.1.4 \ + --hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \ + --hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d # via sphinx markupsafe==2.1.5 \ --hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \ diff --git a/pybind11/docs/upgrade.rst b/pybind11/docs/upgrade.rst index 5cef2b81a..17c26aaa9 100644 --- a/pybind11/docs/upgrade.rst +++ b/pybind11/docs/upgrade.rst @@ -24,8 +24,7 @@ changes are that: function is not available anymore. Due to NumPy changes, you may experience difficulties updating to NumPy 2. -Please see the `NumPy 2 migration guide `_ -for details. +Please see the [NumPy 2 migration guide](https://numpy.org/devdocs/numpy_2_0_migration_guide.html) for details. For example, a more direct change could be that the default integer ``"int_"`` (and ``"uint"``) is now ``ssize_t`` and not ``long`` (affects 64bit windows). diff --git a/pybind11/include/pybind11/attr.h b/pybind11/include/pybind11/attr.h index ef2ca1709..1044db94d 100644 --- a/pybind11/include/pybind11/attr.h +++ b/pybind11/include/pybind11/attr.h @@ -81,10 +81,6 @@ struct dynamic_attr {}; /// Annotation which enables the buffer protocol for a type struct buffer_protocol {}; -/// Annotation which enables releasing the GIL before calling the C++ destructor of wrapped -/// instances (pybind/pybind11#1446). -struct release_gil_before_calling_cpp_dtor {}; - /// Annotation which requests that a special metaclass is created for a type struct metaclass { handle value; @@ -276,7 +272,7 @@ struct function_record { struct type_record { PYBIND11_NOINLINE type_record() : multiple_inheritance(false), dynamic_attr(false), buffer_protocol(false), - module_local(false), is_final(false), release_gil_before_calling_cpp_dtor(false) {} + default_holder(true), module_local(false), is_final(false) {} /// Handle to the parent scope handle scope; @@ -326,17 +322,15 @@ struct type_record { /// Does the class implement the buffer protocol? bool buffer_protocol : 1; + /// Is the default (unique_ptr) holder type used? + bool default_holder : 1; + /// 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; - /// Solves pybind/pybind11#1446 - bool release_gil_before_calling_cpp_dtor : 1; - - holder_enum_t holder_enum_v = holder_enum_t::undefined; - 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) { @@ -346,22 +340,18 @@ struct type_record { + "\" referenced unknown base type \"" + tname + "\""); } - // SMART_HOLDER_BAKEIN_FOLLOW_ON: Refine holder compatibility checks. - bool this_has_unique_ptr_holder = (holder_enum_v == holder_enum_t::std_unique_ptr); - bool base_has_unique_ptr_holder - = (base_info->holder_enum_v == holder_enum_t::std_unique_ptr); - if (this_has_unique_ptr_holder != base_has_unique_ptr_holder) { + if (default_holder != base_info->default_holder) { std::string tname(base.name()); detail::clean_type_id(tname); pybind11_fail("generic_type: type \"" + std::string(name) + "\" " - + (this_has_unique_ptr_holder ? "does not have" : "has") + + (default_holder ? "does not have" : "has") + " a non-default holder type while its base \"" + tname + "\" " - + (base_has_unique_ptr_holder ? "does not" : "does")); + + (base_info->default_holder ? "does not" : "does")); } bases.append((PyObject *) base_info->type); -#ifdef PYBIND11_BACKWARD_COMPATIBILITY_TP_DICTOFFSET +#if PY_VERSION_HEX < 0x030B0000 dynamic_attr |= base_info->type->tp_dictoffset != 0; #else dynamic_attr |= (base_info->type->tp_flags & Py_TPFLAGS_MANAGED_DICT) != 0; @@ -613,14 +603,6 @@ struct process_attribute : process_attribute_default static void init(const module_local &l, type_record *r) { r->module_local = l.value; } }; -template <> -struct process_attribute - : process_attribute_default { - static void init(const release_gil_before_calling_cpp_dtor &, type_record *r) { - r->release_gil_before_calling_cpp_dtor = true; - } -}; - /// Process a 'prepend' attribute, putting this at the beginning of the overload chain template <> struct process_attribute : process_attribute_default { diff --git a/pybind11/include/pybind11/cast.h b/pybind11/include/pybind11/cast.h index 47575084c..0f3091f68 100644 --- a/pybind11/include/pybind11/cast.h +++ b/pybind11/include/pybind11/cast.h @@ -158,7 +158,7 @@ public: } else { handle src_or_index = src; // PyPy: 7.3.7's 3.8 does not implement PyLong_*'s __index__ calls. -#if defined(PYPY_VERSION) +#if PY_VERSION_HEX < 0x03080000 || defined(PYPY_VERSION) object index; if (!PYBIND11_LONG_CHECK(src.ptr())) { // So: index_check(src.ptr()) index = reinterpret_steal(PyNumber_Index(src.ptr())); @@ -343,7 +343,7 @@ public: #else // Alternate approach for CPython: this does the same as the above, but optimized // using the CPython API so as to avoid an unneeded attribute lookup. - else if (auto *tp_as_number = Py_TYPE(src.ptr())->tp_as_number) { + else if (auto *tp_as_number = src.ptr()->ob_type->tp_as_number) { if (PYBIND11_NB_BOOL(tp_as_number)) { res = (*PYBIND11_NB_BOOL(tp_as_number))(src.ptr()); } @@ -754,7 +754,6 @@ struct holder_helper { static auto get(const T &p) -> decltype(p.get()) { return p.get(); } }; -// SMART_HOLDER_BAKEIN_FOLLOW_ON: Rewrite comment, with reference to shared_ptr specialization. /// Type caster for holder types like std::shared_ptr, etc. /// The SFINAE hook is provided to help work around the current lack of support /// for smart-pointer interoperability. Please consider it an implementation @@ -790,10 +789,7 @@ public: protected: friend class type_caster_generic; void check_holder_compat() { - // SMART_HOLDER_BAKEIN_FOLLOW_ON: Refine holder compatibility checks. - bool inst_has_unique_ptr_holder - = (typeinfo->holder_enum_v == holder_enum_t::std_unique_ptr); - if (inst_has_unique_ptr_holder) { + if (typeinfo->default_holder) { throw cast_error("Unable to load a custom holder type from a default-holder instance"); } } @@ -839,144 +835,10 @@ protected: holder_type holder; }; -template -struct copyable_holder_caster_shared_ptr_with_smart_holder_support_enabled : std::true_type {}; - -// SMART_HOLDER_BAKEIN_FOLLOW_ON: Refactor copyable_holder_caster to reduce code duplication. -template -struct copyable_holder_caster< - type, - std::shared_ptr, - enable_if_t::value>> - : public type_caster_base { -public: - using base = type_caster_base; - static_assert(std::is_base_of>::value, - "Holder classes are only supported for custom types"); - using base::base; - using base::cast; - using base::typeinfo; - using base::value; - - bool load(handle src, bool convert) { - if (base::template load_impl>>( - src, convert)) { - sh_load_helper.maybe_set_python_instance_is_alias(src); - return true; - } - return false; - } - - explicit operator std::shared_ptr *() { - if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { - pybind11_fail("Passing `std::shared_ptr *` from Python to C++ is not supported " - "(inherently unsafe)."); - } - return std::addressof(shared_ptr_storage); - } - - explicit operator std::shared_ptr &() { - if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { - shared_ptr_storage = sh_load_helper.load_as_shared_ptr(value); - } - return shared_ptr_storage; - } - - static handle - cast(const std::shared_ptr &src, return_value_policy policy, handle parent) { - const auto *ptr = src.get(); - auto st = type_caster_base::src_and_type(ptr); - if (st.second == nullptr) { - return handle(); // no type info: error will be set already - } - if (st.second->holder_enum_v == detail::holder_enum_t::smart_holder) { - return smart_holder_type_caster_support::smart_holder_from_shared_ptr( - src, policy, parent, st); - } - return type_caster_base::cast_holder(ptr, &src); - } - - // This function will succeed even if the `responsible_parent` does not own the - // wrapped C++ object directly. - // It is the responsibility of the caller to ensure that the `responsible_parent` - // has a `keep_alive` relationship with the owner of the wrapped C++ object, or - // that the wrapped C++ object lives for the duration of the process. - static std::shared_ptr shared_ptr_with_responsible_parent(handle responsible_parent) { - copyable_holder_caster loader; - loader.load(responsible_parent, /*convert=*/false); - assert(loader.typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder); - return loader.sh_load_helper.load_as_shared_ptr(loader.value, responsible_parent); - } - -protected: - friend class type_caster_generic; - void check_holder_compat() { - // SMART_HOLDER_BAKEIN_FOLLOW_ON: Refine holder compatibility checks. - bool inst_has_unique_ptr_holder - = (typeinfo->holder_enum_v == holder_enum_t::std_unique_ptr); - if (inst_has_unique_ptr_holder) { - throw cast_error("Unable to load a custom holder type from a default-holder instance"); - } - } - - void load_value(value_and_holder &&v_h) { - if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { - sh_load_helper.loaded_v_h = v_h; - sh_load_helper.was_populated = true; - value = sh_load_helper.get_void_ptr_or_nullptr(); - return; - } - if (v_h.holder_constructed()) { - value = v_h.value_ptr(); - shared_ptr_storage = v_h.template holder>(); - return; - } - throw cast_error("Unable to cast from non-held to held instance (T& to Holder) " -#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) - "(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for " - "type information)"); -#else - "of type '" - + type_id>() + "''"); -#endif - } - - template , - detail::enable_if_t::value, int> = 0> - bool try_implicit_casts(handle, bool) { - return false; - } - - template , - detail::enable_if_t::value, int> = 0> - bool try_implicit_casts(handle src, bool convert) { - for (auto &cast : typeinfo->implicit_casts) { - copyable_holder_caster sub_caster(*cast.first); - if (sub_caster.load(src, convert)) { - value = cast.second(sub_caster.value); - if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { - sh_load_helper.loaded_v_h = sub_caster.sh_load_helper.loaded_v_h; - } else { - shared_ptr_storage - = std::shared_ptr(sub_caster.shared_ptr_storage, (type *) value); - } - return true; - } - } - return false; - } - - static bool try_direct_conversions(handle) { return false; } - - smart_holder_type_caster_support::load_helper> sh_load_helper; // Const2Mutbl - std::shared_ptr shared_ptr_storage; -}; - /// Specialize for the common std::shared_ptr, so users don't need to template class type_caster> : public copyable_holder_caster> {}; -// SMART_HOLDER_BAKEIN_FOLLOW_ON: Rewrite comment, with reference to unique_ptr specialization. /// Type caster for holder types like std::unique_ptr. /// Please consider the SFINAE hook an implementation detail, as explained /// in the comment for the copyable_holder_caster. @@ -992,143 +854,6 @@ struct move_only_holder_caster { static constexpr auto name = type_caster_base::name; }; -template -struct move_only_holder_caster_unique_ptr_with_smart_holder_support_enabled : std::true_type {}; - -// SMART_HOLDER_BAKEIN_FOLLOW_ON: Refactor move_only_holder_caster to reduce code duplication. -template -struct move_only_holder_caster< - type, - std::unique_ptr, - enable_if_t::value>> - : public type_caster_base { -public: - using base = type_caster_base; - static_assert(std::is_base_of>::value, - "Holder classes are only supported for custom types"); - using base::base; - using base::cast; - using base::typeinfo; - using base::value; - - static handle - cast(std::unique_ptr &&src, return_value_policy policy, handle parent) { - auto *ptr = src.get(); - auto st = type_caster_base::src_and_type(ptr); - if (st.second == nullptr) { - return handle(); // no type info: error will be set already - } - if (st.second->holder_enum_v == detail::holder_enum_t::smart_holder) { - return smart_holder_type_caster_support::smart_holder_from_unique_ptr( - std::move(src), policy, parent, st); - } - return type_caster_generic::cast(st.first, - return_value_policy::take_ownership, - {}, - st.second, - nullptr, - nullptr, - std::addressof(src)); - } - - static handle - cast(const std::unique_ptr &src, return_value_policy policy, handle parent) { - if (!src) { - return none().release(); - } - if (policy == return_value_policy::automatic) { - policy = return_value_policy::reference_internal; - } - if (policy != return_value_policy::reference_internal) { - throw cast_error("Invalid return_value_policy for const unique_ptr&"); - } - return type_caster_base::cast(src.get(), policy, parent); - } - - bool load(handle src, bool convert) { - if (base::template load_impl< - move_only_holder_caster>>(src, convert)) { - sh_load_helper.maybe_set_python_instance_is_alias(src); - return true; - } - return false; - } - - void load_value(value_and_holder &&v_h) { - if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { - sh_load_helper.loaded_v_h = v_h; - sh_load_helper.loaded_v_h.type = typeinfo; - sh_load_helper.was_populated = true; - value = sh_load_helper.get_void_ptr_or_nullptr(); - return; - } - pybind11_fail("Passing `std::unique_ptr` from Python to C++ requires `py::class_` (with T = " - + clean_type_id(typeinfo->cpptype->name()) + ")"); - } - - template - using cast_op_type - = conditional_t::type, - const std::unique_ptr &>::value - || std::is_same::type, - const std::unique_ptr &>::value, - const std::unique_ptr &, - std::unique_ptr>; - - explicit operator std::unique_ptr() { - if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { - return sh_load_helper.template load_as_unique_ptr(value); - } - pybind11_fail("Expected to be UNREACHABLE: " __FILE__ ":" PYBIND11_TOSTRING(__LINE__)); - } - - explicit operator const std::unique_ptr &() { - if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { - // Get shared_ptr to ensure that the Python object is not disowned elsewhere. - shared_ptr_storage = sh_load_helper.load_as_shared_ptr(value); - // Build a temporary unique_ptr that is meant to never expire. - unique_ptr_storage = std::shared_ptr>( - new std::unique_ptr{ - sh_load_helper.template load_as_const_unique_ptr( - shared_ptr_storage.get())}, - [](std::unique_ptr *ptr) { - if (!ptr) { - pybind11_fail("FATAL: `const std::unique_ptr &` was disowned " - "(EXPECT UNDEFINED BEHAVIOR)."); - } - (void) ptr->release(); - delete ptr; - }); - return *unique_ptr_storage; - } - pybind11_fail("Expected to be UNREACHABLE: " __FILE__ ":" PYBIND11_TOSTRING(__LINE__)); - } - - bool try_implicit_casts(handle src, bool convert) { - for (auto &cast : typeinfo->implicit_casts) { - move_only_holder_caster sub_caster(*cast.first); - if (sub_caster.load(src, convert)) { - value = cast.second(sub_caster.value); - if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { - sh_load_helper.loaded_v_h = sub_caster.sh_load_helper.loaded_v_h; - } else { - pybind11_fail("Expected to be UNREACHABLE: " __FILE__ - ":" PYBIND11_TOSTRING(__LINE__)); - } - return true; - } - } - return false; - } - - static bool try_direct_conversions(handle) { return false; } - - smart_holder_type_caster_support::load_helper> sh_load_helper; // Const2Mutbl - std::shared_ptr shared_ptr_storage; // Serves as a pseudo lock. - std::shared_ptr> unique_ptr_storage; -}; - template class type_caster> : public move_only_holder_caster> {}; @@ -1138,20 +863,18 @@ using type_caster_holder = conditional_t::val copyable_holder_caster, move_only_holder_caster>; -template -struct always_construct_holder_value { +template +struct always_construct_holder { static constexpr bool value = Value; }; -template -struct always_construct_holder : always_construct_holder_value {}; - /// Create a specialization for custom holder types (silently ignores std::shared_ptr) #define PYBIND11_DECLARE_HOLDER_TYPE(type, holder_type, ...) \ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) \ namespace detail { \ template \ - struct always_construct_holder : always_construct_holder_value<__VA_ARGS__> {}; \ + struct always_construct_holder : always_construct_holder { \ + }; \ template \ class type_caster::value>> \ : public type_caster_holder {}; \ @@ -1162,14 +885,10 @@ struct always_construct_holder : always_construct_holder_value {}; template struct is_holder_type : std::is_base_of, detail::type_caster> {}; - -// Specializations for always-supported holders: +// Specialization for always-supported unique_ptr holders: template struct is_holder_type> : std::true_type {}; -template -struct is_holder_type : std::true_type {}; - #ifdef PYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION // See PR #4888 // This leads to compilation errors if a specialization is missing. @@ -1293,18 +1012,10 @@ template <> struct handle_type_name { static constexpr auto name = const_name("*args"); }; -template -struct handle_type_name> { - static constexpr auto name = const_name("*args: ") + make_caster::name; -}; template <> struct handle_type_name { static constexpr auto name = const_name("**kwargs"); }; -template -struct handle_type_name> { - static constexpr auto name = const_name("**kwargs: ") + make_caster::name; -}; template <> struct handle_type_name { static constexpr auto name = const_name(); @@ -1610,31 +1321,6 @@ object object_or_cast(T &&o) { return pybind11::cast(std::forward(o)); } -// Declared in pytypes.h: -// Implemented here so that make_caster can be used. -template -template -str_attr_accessor object_api::attr_with_type_hint(const char *key) const { -#if !defined(__cpp_inline_variables) - static_assert(always_false::value, - "C++17 feature __cpp_inline_variables not available: " - "https://en.cppreference.com/w/cpp/language/static#Static_data_members"); -#endif - object ann = annotations(); - if (ann.contains(key)) { - throw std::runtime_error("__annotations__[\"" + std::string(key) + "\"] was set already."); - } - ann[key] = make_caster::name.text; - return {derived(), key}; -} - -template -template -obj_attr_accessor object_api::attr_with_type_hint(handle key) const { - (void) attr_with_type_hint(key.cast().c_str()); - return {derived(), reinterpret_borrow(key)}; -} - // Placeholder type for the unneeded (and dead code) static variable in the // PYBIND11_OVERRIDE_OVERRIDE macro struct override_unused {}; @@ -1817,7 +1503,7 @@ struct kw_only {}; /// \ingroup annotations /// Annotation indicating that all previous arguments are positional-only; the is the equivalent of -/// an unnamed '/' argument +/// an unnamed '/' argument (in Python 3.8) struct pos_only {}; template @@ -1878,24 +1564,15 @@ struct function_call { handle init_self; }; -// See PR #5396 for the discussion that led to this -template -struct is_same_or_base_of : std::is_same {}; - -// Only evaluate is_base_of if Derived is complete. -// is_base_of raises a compiler error if Derived is incomplete. -template -struct is_same_or_base_of - : any_of, std::is_base_of> {}; - /// Helper class which loads arguments for C++ functions called from Python template class argument_loader { using indices = make_index_sequence; + template - using argument_is_args = is_same_or_base_of>; + using argument_is_args = std::is_same, args>; template - using argument_is_kwargs = is_same_or_base_of>; + using argument_is_kwargs = std::is_same, kwargs>; // Get kwargs argument position, or -1 if not present: static constexpr auto kwargs_pos = constexpr_last(); diff --git a/pybind11/include/pybind11/conduit/README.txt b/pybind11/include/pybind11/conduit/README.txt deleted file mode 100644 index 9a2c53ba4..000000000 --- a/pybind11/include/pybind11/conduit/README.txt +++ /dev/null @@ -1,15 +0,0 @@ -NOTE ----- - -The C++ code here - -** only depends on ** - -and nothing else. - -DO NOT ADD CODE WITH OTHER EXTERNAL DEPENDENCIES TO THIS DIRECTORY. - -Read on: - -pybind11_conduit_v1.h — Type-safe interoperability between different - independent Python/C++ bindings systems. diff --git a/pybind11/include/pybind11/conduit/pybind11_conduit_v1.h b/pybind11/include/pybind11/conduit/pybind11_conduit_v1.h deleted file mode 100644 index e3a453453..000000000 --- a/pybind11/include/pybind11/conduit/pybind11_conduit_v1.h +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) 2024 The pybind Community. - -/* The pybind11_conduit_v1 feature enables type-safe interoperability between - -* different independent Python/C++ bindings systems, - -* including pybind11 versions with different PYBIND11_INTERNALS_VERSION's. - -The naming of the feature is a bit misleading: - -* The feature is in no way tied to pybind11 internals. - -* It just happens to originate from pybind11 and currently still lives there. - -* The only external dependency is . - -The implementation is a VERY light-weight dependency. It is designed to be -compatible with any ISO C++11 (or higher) compiler, and does NOT require -C++ Exception Handling to be enabled. - -Please see https://github.com/pybind/pybind11/pull/5296 for more background. - -The implementation involves a - -def _pybind11_conduit_v1_( - self, - pybind11_platform_abi_id: bytes, - cpp_type_info_capsule: capsule, - pointer_kind: bytes) -> capsule - -method that is meant to be added to Python objects wrapping C++ objects -(e.g. pybind11::class_-wrapped types). - -The design of the _pybind11_conduit_v1_ feature provides two layers of -protection against C++ ABI mismatches: - -* The first and most important layer is that the pybind11_platform_abi_id's - must match between extensions. — This will never be perfect, but is the same - pragmatic approach used in pybind11 since 2017 - (https://github.com/pybind/pybind11/commit/96997a4b9d4ec3d389a570604394af5d5eee2557, - PYBIND11_INTERNALS_ID). - -* The second layer is that the typeid(std::type_info).name()'s must match - between extensions. - -The implementation below (which is shorter than this comment!), serves as a -battle-tested specification. The main API is this one function: - -auto *cpp_pointer = pybind11_conduit_v1::get_type_pointer_ephemeral(py_obj); - -It is meant to be a minimalistic reference implementation, intentionally -without comprehensive error reporting. It is expected that major bindings -systems will roll their own, compatible implementations, potentially with -system-specific error reporting. The essential specifications all bindings -systems need to agree on are merely: - -* PYBIND11_PLATFORM_ABI_ID (const char* literal). - -* The cpp_type_info capsule (see below: a void *ptr and a const char *name). - -* The cpp_conduit capsule (see below: a void *ptr and a const char *name). - -* "raw_pointer_ephemeral" means: the lifetime of the pointer is the lifetime - of the py_obj. - -*/ - -// THIS MUST STAY AT THE TOP! -#include "pybind11_platform_abi_id.h" - -#include -#include - -namespace pybind11_conduit_v1 { - -inline void *get_raw_pointer_ephemeral(PyObject *py_obj, const std::type_info *cpp_type_info) { - PyObject *cpp_type_info_capsule - = PyCapsule_New(const_cast(static_cast(cpp_type_info)), - typeid(std::type_info).name(), - nullptr); - if (cpp_type_info_capsule == nullptr) { - return nullptr; - } - PyObject *cpp_conduit = PyObject_CallMethod(py_obj, - "_pybind11_conduit_v1_", - "yOy", - PYBIND11_PLATFORM_ABI_ID, - cpp_type_info_capsule, - "raw_pointer_ephemeral"); - Py_DECREF(cpp_type_info_capsule); - if (cpp_conduit == nullptr) { - return nullptr; - } - void *raw_ptr = PyCapsule_GetPointer(cpp_conduit, cpp_type_info->name()); - Py_DECREF(cpp_conduit); - if (PyErr_Occurred()) { - return nullptr; - } - return raw_ptr; -} - -template -T *get_type_pointer_ephemeral(PyObject *py_obj) { - void *raw_ptr = get_raw_pointer_ephemeral(py_obj, &typeid(T)); - if (raw_ptr == nullptr) { - return nullptr; - } - return static_cast(raw_ptr); -} - -} // namespace pybind11_conduit_v1 diff --git a/pybind11/include/pybind11/conduit/pybind11_platform_abi_id.h b/pybind11/include/pybind11/conduit/pybind11_platform_abi_id.h deleted file mode 100644 index d21fdc56d..000000000 --- a/pybind11/include/pybind11/conduit/pybind11_platform_abi_id.h +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -// Copyright (c) 2024 The pybind Community. - -// To maximize reusability: -// DO NOT ADD CODE THAT REQUIRES C++ EXCEPTION HANDLING. - -#include "wrap_include_python_h.h" - -// Implementation details. DO NOT USE ELSEWHERE. (Unfortunately we cannot #undef them.) -// This is duplicated here to maximize portability. -#define PYBIND11_PLATFORM_ABI_ID_STRINGIFY(x) #x -#define PYBIND11_PLATFORM_ABI_ID_TOSTRING(x) PYBIND11_PLATFORM_ABI_ID_STRINGIFY(x) - -#ifdef PYBIND11_COMPILER_TYPE -// // To maintain backward compatibility (see PR #5439). -# define PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE "" -#else -# define PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE "_" -# if defined(__MINGW32__) -# define PYBIND11_COMPILER_TYPE "mingw" -# elif defined(__CYGWIN__) -# define PYBIND11_COMPILER_TYPE "gcc_cygwin" -# elif defined(_MSC_VER) -# define PYBIND11_COMPILER_TYPE "msvc" -# elif defined(__clang__) || defined(__GNUC__) -# define PYBIND11_COMPILER_TYPE "system" // Assumed compatible with system compiler. -# else -# error "Unknown PYBIND11_COMPILER_TYPE: PLEASE REVISE THIS CODE." -# endif -#endif - -// PR #5439 made this macro obsolete. However, there are many manipulations of this macro in the -// wild. Therefore, to maintain backward compatibility, it is kept around. -#ifndef PYBIND11_STDLIB -# define PYBIND11_STDLIB "" -#endif - -#ifndef PYBIND11_BUILD_ABI -# if defined(_MSC_VER) // See PR #4953. -# if defined(_MT) && defined(_DLL) // Corresponding to CL command line options /MD or /MDd. -# if (_MSC_VER) / 100 == 19 -# define PYBIND11_BUILD_ABI "_md_mscver19" -# else -# error "Unknown major version for MSC_VER: PLEASE REVISE THIS CODE." -# endif -# elif defined(_MT) // Corresponding to CL command line options /MT or /MTd. -# define PYBIND11_BUILD_ABI "_mt_mscver" PYBIND11_PLATFORM_ABI_ID_TOSTRING(_MSC_VER) -# else -# if (_MSC_VER) / 100 == 19 -# define PYBIND11_BUILD_ABI "_none_mscver19" -# else -# error "Unknown major version for MSC_VER: PLEASE REVISE THIS CODE." -# endif -# endif -# elif defined(_LIBCPP_ABI_VERSION) // https://libcxx.llvm.org/DesignDocs/ABIVersioning.html -# define PYBIND11_BUILD_ABI \ - "_libcpp_abi" PYBIND11_PLATFORM_ABI_ID_TOSTRING(_LIBCPP_ABI_VERSION) -# elif defined(_GLIBCXX_USE_CXX11_ABI) // See PR #5439. -# if defined(__NVCOMPILER) -// // Assume that NVHPC is in the 1xxx ABI family. -// // THIS ASSUMPTION IS NOT FUTURE PROOF but apparently the best we can do. -// // Please let us know if there is a way to validate the assumption here. -# elif !defined(__GXX_ABI_VERSION) -# error \ - "Unknown platform or compiler (_GLIBCXX_USE_CXX11_ABI): PLEASE REVISE THIS CODE." -# endif -# if defined(__GXX_ABI_VERSION) && __GXX_ABI_VERSION < 1002 || __GXX_ABI_VERSION >= 2000 -# error "Unknown platform or compiler (__GXX_ABI_VERSION): PLEASE REVISE THIS CODE." -# endif -# define PYBIND11_BUILD_ABI \ - "_libstdcpp_gxx_abi_1xxx_use_cxx11_abi_" PYBIND11_PLATFORM_ABI_ID_TOSTRING( \ - _GLIBCXX_USE_CXX11_ABI) -# else -# error "Unknown platform or compiler: PLEASE REVISE THIS CODE." -# endif -#endif - -// On MSVC, debug and release builds are not ABI-compatible! -#if defined(_MSC_VER) && defined(_DEBUG) -# define PYBIND11_BUILD_TYPE "_debug" -#else -# define PYBIND11_BUILD_TYPE "" -#endif - -#define PYBIND11_PLATFORM_ABI_ID \ - PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI PYBIND11_BUILD_TYPE diff --git a/pybind11/include/pybind11/conduit/wrap_include_python_h.h b/pybind11/include/pybind11/conduit/wrap_include_python_h.h deleted file mode 100644 index 316d1afc8..000000000 --- a/pybind11/include/pybind11/conduit/wrap_include_python_h.h +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -// Copyright (c) 2024 The pybind Community. - -// STRONG REQUIREMENT: -// This header is a wrapper around `#include `, therefore it -// MUST BE INCLUDED BEFORE ANY STANDARD HEADERS are included. -// See also: -// https://docs.python.org/3/c-api/intro.html#include-files -// Quoting from there: -// Note: Since Python may define some pre-processor definitions which affect -// the standard headers on some systems, you must include Python.h before -// any standard headers are included. - -// To maximize reusability: -// DO NOT ADD CODE THAT REQUIRES C++ EXCEPTION HANDLING. - -// Disable linking to pythonX_d.lib on Windows in debug mode. -#if defined(_MSC_VER) && defined(_DEBUG) && !defined(Py_DEBUG) -// Workaround for a VS 2022 issue. -// See https://github.com/pybind/pybind11/pull/3497 for full context. -// NOTE: This workaround knowingly violates the Python.h include order -// requirement (see above). -# include -# if _MSVC_STL_VERSION >= 143 -# include -# endif -# define PYBIND11_DEBUG_MARKER -# undef _DEBUG -#endif - -// Don't let Python.h #define (v)snprintf as macro because they are implemented -// properly in Visual Studio since 2015. -#if defined(_MSC_VER) -# define HAVE_SNPRINTF 1 -#endif - -#if defined(_MSC_VER) -# pragma warning(push) -# pragma warning(disable : 4505) -// C4505: 'PySlice_GetIndicesEx': unreferenced local function has been removed -#endif - -#include -#include -#include - -#if defined(_MSC_VER) -# pragma warning(pop) -#endif - -#if defined(PYBIND11_DEBUG_MARKER) -# define _DEBUG -# undef PYBIND11_DEBUG_MARKER -#endif - -// Python #defines overrides on all sorts of core functions, which -// tends to wreak havok in C++ codebases that expect these to work -// like regular functions (potentially with several overloads). -#if defined(isalnum) -# undef isalnum -# undef isalpha -# undef islower -# undef isspace -# undef isupper -# undef tolower -# undef toupper -#endif - -#if defined(copysign) -# undef copysign -#endif diff --git a/pybind11/include/pybind11/detail/class.h b/pybind11/include/pybind11/detail/class.h index 08e23afb5..b990507d6 100644 --- a/pybind11/include/pybind11/detail/class.h +++ b/pybind11/include/pybind11/detail/class.h @@ -312,31 +312,7 @@ inline void traverse_offset_bases(void *valueptr, } } -#ifdef Py_GIL_DISABLED -inline void enable_try_inc_ref(PyObject *obj) { - // TODO: Replace with PyUnstable_Object_EnableTryIncRef when available. - // See https://github.com/python/cpython/issues/128844 - if (_Py_IsImmortal(obj)) { - return; - } - for (;;) { - Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared); - if ((shared & _Py_REF_SHARED_FLAG_MASK) != 0) { - // Nothing to do if it's in WEAKREFS, QUEUED, or MERGED states. - return; - } - if (_Py_atomic_compare_exchange_ssize( - &obj->ob_ref_shared, &shared, shared | _Py_REF_MAYBE_WEAKREF)) { - return; - } - } -} -#endif - inline bool register_instance_impl(void *ptr, instance *self) { -#ifdef Py_GIL_DISABLED - enable_try_inc_ref(reinterpret_cast(self)); -#endif with_instance_map(ptr, [&](instance_map &instances) { instances.emplace(ptr, self); }); return true; // unused, but gives the same signature as the deregister func } @@ -457,8 +433,6 @@ inline void clear_instance(PyObject *self) { if (instance->owned || v_h.holder_constructed()) { v_h.type->dealloc(v_h); } - } else if (v_h.holder_constructed()) { - v_h.type->dealloc(v_h); // Disowned instance. } } // Deallocate the value/holder layout internals: @@ -494,9 +468,19 @@ extern "C" inline void pybind11_object_dealloc(PyObject *self) { type->tp_free(self); +#if PY_VERSION_HEX < 0x03080000 + // `type->tp_dealloc != pybind11_object_dealloc` means that we're being called + // as part of a derived type's dealloc, in which case we're not allowed to decref + // the type here. For cross-module compatibility, we shouldn't compare directly + // with `pybind11_object_dealloc`, but with the common one stashed in internals. + auto pybind11_object_type = (PyTypeObject *) get_internals().instance_base; + if (type->tp_dealloc == pybind11_object_type->tp_dealloc) + Py_DECREF(type); +#else // This was not needed before Python 3.8 (Python issue 35810) // https://github.com/pybind/pybind11/issues/1946 Py_DECREF(type); +#endif } std::string error_string(); @@ -576,7 +560,7 @@ extern "C" inline int pybind11_clear(PyObject *self) { inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type) { auto *type = &heap_type->ht_type; type->tp_flags |= Py_TPFLAGS_HAVE_GC; -#ifdef PYBIND11_BACKWARD_COMPATIBILITY_TP_DICTOFFSET +#if PY_VERSION_HEX < 0x030B0000 type->tp_dictoffset = type->tp_basicsize; // place dict at the end type->tp_basicsize += (ssize_t) sizeof(PyObject *); // and allocate enough space for it #else @@ -609,9 +593,9 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla return -1; } std::memset(view, 0, sizeof(Py_buffer)); - std::unique_ptr info = nullptr; + buffer_info *info = nullptr; try { - info.reset(tinfo->get_buffer(obj, tinfo->get_buffer_data)); + info = tinfo->get_buffer(obj, tinfo->get_buffer_data); } catch (...) { try_translate_exceptions(); raise_from(PyExc_BufferError, "Error getting buffer"); @@ -622,72 +606,29 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla } if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE && info->readonly) { + delete info; // view->obj = nullptr; // Was just memset to 0, so not necessary set_error(PyExc_BufferError, "Writable buffer requested for readonly storage"); return -1; } - - // Fill in all the information, and then downgrade as requested by the caller, or raise an - // error if that's not possible. + view->obj = obj; + view->ndim = 1; + view->internal = info; + view->buf = info->ptr; view->itemsize = info->itemsize; view->len = view->itemsize; for (auto s : info->shape) { view->len *= s; } - view->ndim = static_cast(info->ndim); - view->shape = info->shape.data(); - view->strides = info->strides.data(); view->readonly = static_cast(info->readonly); if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) { view->format = const_cast(info->format.c_str()); } - - // Note, all contiguity flags imply PyBUF_STRIDES and lower. - if ((flags & PyBUF_C_CONTIGUOUS) == PyBUF_C_CONTIGUOUS) { - if (PyBuffer_IsContiguous(view, 'C') == 0) { - std::memset(view, 0, sizeof(Py_buffer)); - set_error(PyExc_BufferError, - "C-contiguous buffer requested for discontiguous storage"); - return -1; - } - } else if ((flags & PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS) { - if (PyBuffer_IsContiguous(view, 'F') == 0) { - std::memset(view, 0, sizeof(Py_buffer)); - set_error(PyExc_BufferError, - "Fortran-contiguous buffer requested for discontiguous storage"); - return -1; - } - } else if ((flags & PyBUF_ANY_CONTIGUOUS) == PyBUF_ANY_CONTIGUOUS) { - if (PyBuffer_IsContiguous(view, 'A') == 0) { - std::memset(view, 0, sizeof(Py_buffer)); - set_error(PyExc_BufferError, "Contiguous buffer requested for discontiguous storage"); - return -1; - } - - } else if ((flags & PyBUF_STRIDES) != PyBUF_STRIDES) { - // If no strides are requested, the buffer must be C-contiguous. - // https://docs.python.org/3/c-api/buffer.html#contiguity-requests - if (PyBuffer_IsContiguous(view, 'C') == 0) { - std::memset(view, 0, sizeof(Py_buffer)); - set_error(PyExc_BufferError, - "C-contiguous buffer requested for discontiguous storage"); - return -1; - } - - view->strides = nullptr; - - // Since this is a contiguous buffer, it can also pretend to be 1D. - if ((flags & PyBUF_ND) != PyBUF_ND) { - view->shape = nullptr; - view->ndim = 0; - } + if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) { + view->ndim = (int) info->ndim; + view->strides = info->strides.data(); + view->shape = info->shape.data(); } - - // Set these after all checks so they don't leak out into the caller, and can be automatically - // cleaned up on error. - view->buf = info->ptr; - view->internal = info.release(); - view->obj = obj; Py_INCREF(view->obj); return 0; } diff --git a/pybind11/include/pybind11/detail/common.h b/pybind11/include/pybind11/detail/common.h index 5e225f8c1..c51d1d60b 100644 --- a/pybind11/include/pybind11/detail/common.h +++ b/pybind11/include/pybind11/detail/common.h @@ -9,18 +9,13 @@ #pragma once -#include -#if PY_VERSION_HEX < 0x03080000 -# error "PYTHON < 3.8 IS UNSUPPORTED. pybind11 v2.13 was the last to support Python 3.7." -#endif - -#define PYBIND11_VERSION_MAJOR 3 -#define PYBIND11_VERSION_MINOR 0 -#define PYBIND11_VERSION_PATCH 0.dev1 +#define PYBIND11_VERSION_MAJOR 2 +#define PYBIND11_VERSION_MINOR 13 +#define PYBIND11_VERSION_PATCH 6 // Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html // Additional convention: 0xD = dev -#define PYBIND11_VERSION_HEX 0x030000D1 +#define PYBIND11_VERSION_HEX 0x020D0600 // Define some generic pybind11 helper macros for warning management. // @@ -46,7 +41,7 @@ # define PYBIND11_COMPILER_CLANG # define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__) # define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(clang diagnostic push) -# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(clang diagnostic pop) +# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(clang diagnostic push) #elif defined(__GNUC__) # define PYBIND11_COMPILER_GCC # define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__) @@ -169,6 +164,14 @@ # endif #endif +#if !defined(PYBIND11_EXPORT_EXCEPTION) +# if defined(__apple_build_version__) +# define PYBIND11_EXPORT_EXCEPTION PYBIND11_EXPORT +# else +# define PYBIND11_EXPORT_EXCEPTION +# endif +#endif + // For CUDA, GCC7, GCC8: // PYBIND11_NOINLINE_FORCED is incompatible with `-Wattributes -Werror`. // When defining PYBIND11_NOINLINE_FORCED, it is best to also use `-Wno-attributes`. @@ -209,6 +212,31 @@ # define PYBIND11_MAYBE_UNUSED __attribute__((__unused__)) #endif +/* Don't let Python.h #define (v)snprintf as macro because they are implemented + properly in Visual Studio since 2015. */ +#if defined(_MSC_VER) +# define HAVE_SNPRINTF 1 +#endif + +/// Include Python header, disable linking to pythonX_d.lib on Windows in debug mode +#if defined(_MSC_VER) +PYBIND11_WARNING_PUSH +PYBIND11_WARNING_DISABLE_MSVC(4505) +// C4505: 'PySlice_GetIndicesEx': unreferenced local function has been removed (PyPy only) +# if defined(_DEBUG) && !defined(Py_DEBUG) +// Workaround for a VS 2022 issue. +// NOTE: This workaround knowingly violates the Python.h include order requirement: +// https://docs.python.org/3/c-api/intro.html#include-files +// See https://github.com/pybind/pybind11/pull/3497 for full context. +# include +# if _MSVC_STL_VERSION >= 143 +# include +# endif +# define PYBIND11_DEBUG_MARKER +# undef _DEBUG +# endif +#endif + // https://en.cppreference.com/w/c/chrono/localtime #if defined(__STDC_LIB_EXT1__) && !defined(__STDC_WANT_LIB_EXT1__) # define __STDC_WANT_LIB_EXT1__ @@ -243,14 +271,46 @@ # endif #endif +#include +#if PY_VERSION_HEX < 0x03070000 +# error "PYTHON < 3.7 IS UNSUPPORTED. pybind11 v2.12 was the last to support Python 3.6." +#endif +#include +#include + +/* Python #defines overrides on all sorts of core functions, which + tends to weak havok in C++ codebases that expect these to work + like regular functions (potentially with several overloads) */ +#if defined(isalnum) +# undef isalnum +# undef isalpha +# undef islower +# undef isspace +# undef isupper +# undef tolower +# undef toupper +#endif + +#if defined(copysign) +# undef copysign +#endif + #if defined(PYBIND11_NUMPY_1_ONLY) # define PYBIND11_INTERNAL_NUMPY_1_ONLY_DETECTED #endif -#if (defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)) && !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) +#if defined(PYPY_VERSION) && !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) # define PYBIND11_SIMPLE_GIL_MANAGEMENT #endif +#if defined(_MSC_VER) +# if defined(PYBIND11_DEBUG_MARKER) +# define _DEBUG +# undef PYBIND11_DEBUG_MARKER +# endif +PYBIND11_WARNING_POP +#endif + #include #include #include @@ -269,17 +329,6 @@ # endif #endif -// For libc++, the exceptions should be exported, -// otherwise, the exception translation would be incorrect. -// IMPORTANT: This code block must stay BELOW the #include above (see PR #5390). -#if !defined(PYBIND11_EXPORT_EXCEPTION) -# if defined(_LIBCPP_EXCEPTION) -# define PYBIND11_EXPORT_EXCEPTION PYBIND11_EXPORT -# else -# define PYBIND11_EXPORT_EXCEPTION -# endif -#endif - // Must be after including or one of the other headers specified by the standard #if defined(__cpp_lib_char8_t) && __cpp_lib_char8_t >= 201811L # define PYBIND11_HAS_U8STRING @@ -338,20 +387,6 @@ #define PYBIND11_CONCAT(first, second) first##second #define PYBIND11_ENSURE_INTERNALS_READY pybind11::detail::get_internals(); -#if !defined(GRAALVM_PYTHON) -# define PYBIND11_PYCFUNCTION_GET_DOC(func) ((func)->m_ml->ml_doc) -# define PYBIND11_PYCFUNCTION_SET_DOC(func, doc) \ - do { \ - (func)->m_ml->ml_doc = (doc); \ - } while (0) -#else -# define PYBIND11_PYCFUNCTION_GET_DOC(func) (GraalPyCFunction_GetDoc((PyObject *) (func))) -# define PYBIND11_PYCFUNCTION_SET_DOC(func, doc) \ - do { \ - GraalPyCFunction_SetDoc((PyObject *) (func), (doc)); \ - } while (0) -#endif - #define PYBIND11_CHECK_PYTHON_VERSION \ { \ const char *compiled_ver \ @@ -605,8 +640,6 @@ struct instance { bool simple_instance_registered : 1; /// If true, get_internals().patients has an entry for this object bool has_patients : 1; - /// If true, this Python object needs to be kept alive for the lifetime of the C++ value. - bool is_alias : 1; /// Initializes all of the above type/values/holders data (but not the instance values /// themselves) @@ -629,14 +662,6 @@ struct instance { static_assert(std::is_standard_layout::value, "Internal error: `pybind11::detail::instance` is not standard layout!"); -// Some older compilers (e.g. gcc 9.4.0) require -// static_assert(always_false::value, "..."); -// instead of -// static_assert(false, "..."); -// to trigger the static_assert() in a template only if it is actually instantiated. -template -struct always_false : std::false_type {}; - /// from __cpp_future__ import (convenient aliases from C++14/17) #if defined(PYBIND11_CPP14) using std::conditional_t; @@ -1103,14 +1128,14 @@ struct overload_cast_impl { } template - constexpr auto operator()(Return (Class::*pmf)(Args...), std::false_type = {}) const noexcept - -> decltype(pmf) { + constexpr auto operator()(Return (Class::*pmf)(Args...), + std::false_type = {}) const noexcept -> decltype(pmf) { return pmf; } template - constexpr auto operator()(Return (Class::*pmf)(Args...) const, std::true_type) const noexcept - -> decltype(pmf) { + constexpr auto operator()(Return (Class::*pmf)(Args...) const, + std::true_type) const noexcept -> decltype(pmf) { return pmf; } }; @@ -1258,10 +1283,5 @@ constexpr # define PYBIND11_DETAILED_ERROR_MESSAGES #endif -// CPython 3.11+ provides Py_TPFLAGS_MANAGED_DICT, but PyPy3.11 does not, see PR #5508. -#if PY_VERSION_HEX < 0x030B0000 || defined(PYPY_VERSION) -# define PYBIND11_BACKWARD_COMPATIBILITY_TP_DICTOFFSET -#endif - PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/pybind11/include/pybind11/detail/descr.h b/pybind11/include/pybind11/detail/descr.h index a5f17f869..7d546311e 100644 --- a/pybind11/include/pybind11/detail/descr.h +++ b/pybind11/include/pybind11/detail/descr.h @@ -99,13 +99,6 @@ constexpr descr<1, Type> const_name() { return {'%'}; } -// Use a different name based on whether the parameter is used as input or output -template -constexpr descr io_name(char const (&text1)[N1], char const (&text2)[N2]) { - return const_name("@") + const_name(text1) + const_name("@") + const_name(text2) - + const_name("@"); -} - // If "_" is defined as a macro, py::detail::_ cannot be provided. // It is therefore best to use py::detail::const_name universally. // This block is for backward compatibility only. @@ -163,8 +156,9 @@ constexpr auto concat(const descr &d, const Args &...args) { } #else template -constexpr auto concat(const descr &d, const Args &...args) - -> decltype(std::declval>() + concat(args...)) { +constexpr auto concat(const descr &d, + const Args &...args) -> decltype(std::declval>() + + concat(args...)) { return d + const_name(", ") + concat(args...); } #endif @@ -174,15 +168,5 @@ constexpr descr type_descr(const descr &descr) { return const_name("{") + descr + const_name("}"); } -template -constexpr descr arg_descr(const descr &descr) { - return const_name("@^") + descr + const_name("@!"); -} - -template -constexpr descr return_descr(const descr &descr) { - return const_name("@$") + descr + const_name("@!"); -} - PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/pybind11/include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h b/pybind11/include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h deleted file mode 100644 index 7c00fe98c..000000000 --- a/pybind11/include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2021 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 - -PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) -PYBIND11_NAMESPACE_BEGIN(detail) - -template -struct dynamic_raw_ptr_cast_is_possible : std::false_type {}; - -template -struct dynamic_raw_ptr_cast_is_possible< - To, - From, - detail::enable_if_t::value && std::is_polymorphic::value>> - : std::true_type {}; - -template ::value, int> = 0> -To *dynamic_raw_ptr_cast_if_possible(From * /*ptr*/) { - return nullptr; -} - -template ::value, int> = 0> -To *dynamic_raw_ptr_cast_if_possible(From *ptr) { - return dynamic_cast(ptr); -} - -PYBIND11_NAMESPACE_END(detail) -PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/pybind11/include/pybind11/detail/exception_translation.h b/pybind11/include/pybind11/detail/exception_translation.h index 22ae8a1c9..2764180bb 100644 --- a/pybind11/include/pybind11/detail/exception_translation.h +++ b/pybind11/include/pybind11/detail/exception_translation.h @@ -50,17 +50,17 @@ inline void try_translate_exceptions() { - delegate translation to the next translator by throwing a new type of exception. */ - bool handled = with_exception_translators( - [&](std::forward_list &exception_translators, - std::forward_list &local_exception_translators) { - if (detail::apply_exception_translators(local_exception_translators)) { - return true; - } - if (detail::apply_exception_translators(exception_translators)) { - return true; - } - return false; - }); + bool handled = with_internals([&](internals &internals) { + auto &local_exception_translators = get_local_internals().registered_exception_translators; + if (detail::apply_exception_translators(local_exception_translators)) { + return true; + } + auto &exception_translators = internals.registered_exception_translators; + if (detail::apply_exception_translators(exception_translators)) { + return true; + } + return false; + }); if (!handled) { set_error(PyExc_SystemError, "Exception escaped from default exception translator!"); diff --git a/pybind11/include/pybind11/detail/init.h b/pybind11/include/pybind11/detail/init.h index ed95afe58..79cc930c8 100644 --- a/pybind11/include/pybind11/detail/init.h +++ b/pybind11/include/pybind11/detail/init.h @@ -10,7 +10,6 @@ #pragma once #include "class.h" -#include "using_smart_holder.h" PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) @@ -156,7 +155,7 @@ void construct(value_and_holder &v_h, Alias *alias_ptr, bool) { // holder. This also handles types like std::shared_ptr and std::unique_ptr where T is a // derived type (through those holder's implicit conversion from derived class holder // constructors). -template >::value, int> = 0> +template void construct(value_and_holder &v_h, Holder holder, bool need_alias) { PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias); auto *ptr = holder_helper>::get(holder); @@ -198,74 +197,6 @@ void construct(value_and_holder &v_h, Alias &&result, bool) { v_h.value_ptr() = new Alias(std::move(result)); } -template -smart_holder init_smart_holder_from_unique_ptr(std::unique_ptr &&unq_ptr, - bool void_cast_raw_ptr) { - void *void_ptr = void_cast_raw_ptr ? static_cast(unq_ptr.get()) : nullptr; - return smart_holder::from_unique_ptr(std::move(unq_ptr), void_ptr); -} - -template >, - detail::enable_if_t>::value, int> = 0> -void construct(value_and_holder &v_h, std::unique_ptr, D> &&unq_ptr, bool need_alias) { - PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias); - auto *ptr = unq_ptr.get(); - no_nullptr(ptr); - if (Class::has_alias && need_alias && !is_alias(ptr)) { - throw type_error("pybind11::init(): construction failed: returned std::unique_ptr pointee " - "is not an alias instance"); - } - // Here and below: if the new object is a trampoline, the shared_from_this mechanism needs - // to be prevented from accessing the smart_holder vptr, because it does not keep the - // trampoline Python object alive. For types that don't inherit from enable_shared_from_this - // it does not matter if void_cast_raw_ptr is true or false, therefore it's not necessary - // to also inspect the type. - auto smhldr = init_smart_holder_from_unique_ptr( - std::move(unq_ptr), /*void_cast_raw_ptr*/ Class::has_alias && is_alias(ptr)); - v_h.value_ptr() = ptr; - v_h.type->init_instance(v_h.inst, &smhldr); -} - -template >, - detail::enable_if_t>::value, int> = 0> -void construct(value_and_holder &v_h, - std::unique_ptr, D> &&unq_ptr, - bool /*need_alias*/) { - auto *ptr = unq_ptr.get(); - no_nullptr(ptr); - auto smhldr - = init_smart_holder_from_unique_ptr(std::move(unq_ptr), /*void_cast_raw_ptr*/ true); - v_h.value_ptr() = ptr; - v_h.type->init_instance(v_h.inst, &smhldr); -} - -template >::value, int> = 0> -void construct(value_and_holder &v_h, std::shared_ptr> &&shd_ptr, bool need_alias) { - PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias); - auto *ptr = shd_ptr.get(); - no_nullptr(ptr); - if (Class::has_alias && need_alias && !is_alias(ptr)) { - throw type_error("pybind11::init(): construction failed: returned std::shared_ptr pointee " - "is not an alias instance"); - } - auto smhldr = smart_holder::from_shared_ptr(shd_ptr); - v_h.value_ptr() = ptr; - v_h.type->init_instance(v_h.inst, &smhldr); -} - -template >::value, int> = 0> -void construct(value_and_holder &v_h, - std::shared_ptr> &&shd_ptr, - bool /*need_alias*/) { - auto *ptr = shd_ptr.get(); - no_nullptr(ptr); - auto smhldr = smart_holder::from_shared_ptr(shd_ptr); - v_h.value_ptr() = ptr; - v_h.type->init_instance(v_h.inst, &smhldr); -} - // Implementing class for py::init<...>() template struct constructor { @@ -479,7 +410,7 @@ struct pickle_factory { template void execute(Class &cl, const Extra &...extra) && { - cl.def("__getstate__", std::move(get), pos_only()); + cl.def("__getstate__", std::move(get)); #if defined(PYBIND11_CPP14) cl.def( diff --git a/pybind11/include/pybind11/detail/internals.h b/pybind11/include/pybind11/detail/internals.h index 841c8fe15..232bc32d8 100644 --- a/pybind11/include/pybind11/detail/internals.h +++ b/pybind11/include/pybind11/detail/internals.h @@ -15,7 +15,6 @@ # include #endif -#include #include #include @@ -37,12 +36,18 @@ /// further ABI-incompatible changes may be made before the ABI is officially /// changed to the new version. #ifndef PYBIND11_INTERNALS_VERSION -# define PYBIND11_INTERNALS_VERSION 7 +# if PY_VERSION_HEX >= 0x030C0000 || defined(_MSC_VER) +// Version bump for Python 3.12+, before first 3.12 beta release. +// Version bump for MSVC piggy-backed on PR #4779. See comments there. +# define PYBIND11_INTERNALS_VERSION 5 +# else +# define PYBIND11_INTERNALS_VERSION 4 +# endif #endif -#if PYBIND11_INTERNALS_VERSION < 7 -# error "PYBIND11_INTERNALS_VERSION 7 is the minimum for all platforms for pybind11v3." -#endif +// This requirement is mainly to reduce the support burden (see PR #4570). +static_assert(PY_VERSION_HEX < 0x030C0000 || PYBIND11_INTERNALS_VERSION >= 5, + "pybind11 ABI version 5 is the minimum for Python 3.12+"); PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) @@ -61,29 +66,40 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass); // Thread Specific Storage (TSS) API. // Avoid unnecessary allocation of `Py_tss_t`, since we cannot use // `Py_LIMITED_API` anyway. -#define PYBIND11_TLS_KEY_REF Py_tss_t & -#if defined(__clang__) -# define PYBIND11_TLS_KEY_INIT(var) \ - _Pragma("clang diagnostic push") /**/ \ - _Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \ - Py_tss_t var \ - = Py_tss_NEEDS_INIT; \ - _Pragma("clang diagnostic pop") -#elif defined(__GNUC__) && !defined(__INTEL_COMPILER) -# define PYBIND11_TLS_KEY_INIT(var) \ - _Pragma("GCC diagnostic push") /**/ \ - _Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \ - Py_tss_t var \ - = Py_tss_NEEDS_INIT; \ - _Pragma("GCC diagnostic pop") +#if PYBIND11_INTERNALS_VERSION > 4 +# define PYBIND11_TLS_KEY_REF Py_tss_t & +# if defined(__clang__) +# define PYBIND11_TLS_KEY_INIT(var) \ + _Pragma("clang diagnostic push") /**/ \ + _Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \ + Py_tss_t var \ + = Py_tss_NEEDS_INIT; \ + _Pragma("clang diagnostic pop") +# elif defined(__GNUC__) && !defined(__INTEL_COMPILER) +# define PYBIND11_TLS_KEY_INIT(var) \ + _Pragma("GCC diagnostic push") /**/ \ + _Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \ + Py_tss_t var \ + = Py_tss_NEEDS_INIT; \ + _Pragma("GCC diagnostic pop") +# else +# define PYBIND11_TLS_KEY_INIT(var) Py_tss_t var = Py_tss_NEEDS_INIT; +# endif +# define PYBIND11_TLS_KEY_CREATE(var) (PyThread_tss_create(&(var)) == 0) +# define PYBIND11_TLS_GET_VALUE(key) PyThread_tss_get(&(key)) +# define PYBIND11_TLS_REPLACE_VALUE(key, value) PyThread_tss_set(&(key), (value)) +# define PYBIND11_TLS_DELETE_VALUE(key) PyThread_tss_set(&(key), nullptr) +# define PYBIND11_TLS_FREE(key) PyThread_tss_delete(&(key)) #else -# define PYBIND11_TLS_KEY_INIT(var) Py_tss_t var = Py_tss_NEEDS_INIT; +# define PYBIND11_TLS_KEY_REF Py_tss_t * +# define PYBIND11_TLS_KEY_INIT(var) Py_tss_t *var = nullptr; +# define PYBIND11_TLS_KEY_CREATE(var) \ + (((var) = PyThread_tss_alloc()) != nullptr && (PyThread_tss_create((var)) == 0)) +# define PYBIND11_TLS_GET_VALUE(key) PyThread_tss_get((key)) +# define PYBIND11_TLS_REPLACE_VALUE(key, value) PyThread_tss_set((key), (value)) +# define PYBIND11_TLS_DELETE_VALUE(key) PyThread_tss_set((key), nullptr) +# define PYBIND11_TLS_FREE(key) PyThread_tss_free(key) #endif -#define PYBIND11_TLS_KEY_CREATE(var) (PyThread_tss_create(&(var)) == 0) -#define PYBIND11_TLS_GET_VALUE(key) PyThread_tss_get(&(key)) -#define PYBIND11_TLS_REPLACE_VALUE(key, value) PyThread_tss_set(&(key), (value)) -#define PYBIND11_TLS_DELETE_VALUE(key) PyThread_tss_set(&(key), nullptr) -#define PYBIND11_TLS_FREE(key) PyThread_tss_delete(&(key)) // Python loads modules by default with dlopen with the RTLD_LOCAL flag; under libc++ and possibly // other STLs, this means `typeid(A)` from one module won't equal `typeid(A)` from another module @@ -91,7 +107,8 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass); // libstdc++, this doesn't happen: equality and the type_index hash are based on the type name, // which works. If not under a known-good stl, provide our own name-based hash and equality // functions that use the type name. -#if !defined(_LIBCPP_VERSION) +#if (PYBIND11_INTERNALS_VERSION <= 4 && defined(__GLIBCXX__)) \ + || (PYBIND11_INTERNALS_VERSION >= 5 && !defined(_LIBCPP_VERSION)) inline bool same_type(const std::type_info &lhs, const std::type_info &rhs) { return lhs == rhs; } using type_hash = std::hash; using type_equal_to = std::equal_to; @@ -160,7 +177,6 @@ static_assert(sizeof(instance_map_shard) % 64 == 0, struct internals { #ifdef Py_GIL_DISABLED pymutex mutex; - pymutex exception_translator_mutex; #endif // std::type_index -> pybind11's type information type_map registered_types_cpp; @@ -179,26 +195,35 @@ struct internals { std::forward_list registered_exception_translators; std::unordered_map shared_data; // Custom data to be shared across // extensions - std::forward_list static_strings; // Stores the std::strings backing - // detail::c_str() +#if PYBIND11_INTERNALS_VERSION == 4 + std::vector unused_loader_patient_stack_remove_at_v5; +#endif + std::forward_list static_strings; // Stores the std::strings backing + // detail::c_str() PyTypeObject *static_property_type; PyTypeObject *default_metaclass; PyObject *instance_base; // Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined: PYBIND11_TLS_KEY_INIT(tstate) +#if PYBIND11_INTERNALS_VERSION > 4 PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key) +#endif // PYBIND11_INTERNALS_VERSION > 4 // Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined: PyInterpreterState *istate = nullptr; +#if PYBIND11_INTERNALS_VERSION > 4 // Note that we have to use a std::string to allocate memory to ensure a unique address // We want unique addresses since we use pointer equality to compare function records std::string function_record_capsule_name = internals_function_record_capsule_name; +#endif internals() = default; internals(const internals &other) = delete; internals &operator=(const internals &other) = delete; ~internals() { +#if PYBIND11_INTERNALS_VERSION > 4 PYBIND11_TLS_FREE(loader_life_support_tls_key); +#endif // PYBIND11_INTERNALS_VERSION > 4 // This destructor is called *after* Py_Finalize() in finalize_interpreter(). // That *SHOULD BE* fine. The following details what happens when PyThread_tss_free is @@ -211,17 +236,6 @@ struct internals { } }; -// For backwards compatibility (i.e. #ifdef guards): -#define PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT - -enum class holder_enum_t : uint8_t { - undefined, - std_unique_ptr, // Default, lacking interop with std::shared_ptr. - std_shared_ptr, // Lacking interop with std::unique_ptr. - smart_holder, // Full std::unique_ptr / std::shared_ptr interop. - custom_holder, -}; - /// Additional type information which does not fit into the PyTypeObject. /// Changes to this struct also require bumping `PYBIND11_INTERNALS_VERSION`. struct type_info { @@ -237,7 +251,6 @@ struct type_info { buffer_info *(*get_buffer)(PyObject *, void *) = nullptr; void *get_buffer_data = nullptr; void *(*module_local_load)(PyObject *, const type_info *) = nullptr; - holder_enum_t holder_enum_v = holder_enum_t::undefined; /* A simple type never occurs as a (direct or indirect) parent * of a class that makes use of multiple inheritance. * A type can be simple even if it has non-simple ancestors as long as it has no descendants. @@ -245,17 +258,80 @@ struct type_info { bool simple_type : 1; /* True if there is no multiple inheritance in this type's inheritance tree */ bool simple_ancestors : 1; + /* for base vs derived holder_type checks */ + bool default_holder : 1; /* true if this is a type registered with py::module_local */ bool module_local : 1; }; +/// On MSVC, debug and release builds are not ABI-compatible! +#if defined(_MSC_VER) && defined(_DEBUG) +# define PYBIND11_BUILD_TYPE "_debug" +#else +# define PYBIND11_BUILD_TYPE "" +#endif + +/// Let's assume that different compilers are ABI-incompatible. +/// A user can manually set this string if they know their +/// compiler is compatible. +#ifndef PYBIND11_COMPILER_TYPE +# if defined(_MSC_VER) +# define PYBIND11_COMPILER_TYPE "_msvc" +# elif defined(__INTEL_COMPILER) +# define PYBIND11_COMPILER_TYPE "_icc" +# elif defined(__clang__) +# define PYBIND11_COMPILER_TYPE "_clang" +# elif defined(__PGI) +# define PYBIND11_COMPILER_TYPE "_pgi" +# elif defined(__MINGW32__) +# define PYBIND11_COMPILER_TYPE "_mingw" +# elif defined(__CYGWIN__) +# define PYBIND11_COMPILER_TYPE "_gcc_cygwin" +# elif defined(__GNUC__) +# define PYBIND11_COMPILER_TYPE "_gcc" +# else +# define PYBIND11_COMPILER_TYPE "_unknown" +# endif +#endif + +/// Also standard libs +#ifndef PYBIND11_STDLIB +# if defined(_LIBCPP_VERSION) +# define PYBIND11_STDLIB "_libcpp" +# elif defined(__GLIBCXX__) || defined(__GLIBCPP__) +# define PYBIND11_STDLIB "_libstdcpp" +# else +# define PYBIND11_STDLIB "" +# endif +#endif + +/// On Linux/OSX, changes in __GXX_ABI_VERSION__ indicate ABI incompatibility. +/// On MSVC, changes in _MSC_VER may indicate ABI incompatibility (#2898). +#ifndef PYBIND11_BUILD_ABI +# if defined(__GXX_ABI_VERSION) +# define PYBIND11_BUILD_ABI "_cxxabi" PYBIND11_TOSTRING(__GXX_ABI_VERSION) +# elif defined(_MSC_VER) +# define PYBIND11_BUILD_ABI "_mscver" PYBIND11_TOSTRING(_MSC_VER) +# else +# define PYBIND11_BUILD_ABI "" +# endif +#endif + +#ifndef PYBIND11_INTERNALS_KIND +# define PYBIND11_INTERNALS_KIND "" +#endif + +#define PYBIND11_PLATFORM_ABI_ID \ + PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI \ + PYBIND11_BUILD_TYPE + #define PYBIND11_INTERNALS_ID \ "__pybind11_internals_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \ - PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE PYBIND11_PLATFORM_ABI_ID "__" + PYBIND11_PLATFORM_ABI_ID "__" #define PYBIND11_MODULE_LOCAL_ID \ "__pybind11_module_local_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \ - PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE PYBIND11_PLATFORM_ABI_ID "__" + PYBIND11_PLATFORM_ABI_ID "__" /// Each module locally stores a pointer to the `internals` data. The data /// itself is shared among modules with the same `PYBIND11_INTERNALS_ID`. @@ -373,7 +449,7 @@ inline void translate_local_exception(std::exception_ptr p) { inline object get_python_state_dict() { object state_dict; -#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON) +#if PYBIND11_INTERNALS_VERSION <= 4 || PY_VERSION_HEX < 0x03080000 || defined(PYPY_VERSION) state_dict = reinterpret_borrow(PyEval_GetBuiltins()); #else # if PY_VERSION_HEX < 0x03090000 @@ -471,12 +547,13 @@ PYBIND11_NOINLINE internals &get_internals() { } PYBIND11_TLS_REPLACE_VALUE(internals_ptr->tstate, tstate); +#if PYBIND11_INTERNALS_VERSION > 4 // NOLINTNEXTLINE(bugprone-assignment-in-if-condition) if (!PYBIND11_TLS_KEY_CREATE(internals_ptr->loader_life_support_tls_key)) { pybind11_fail("get_internals: could not successfully initialize the " "loader_life_support TSS key!"); } - +#endif internals_ptr->istate = tstate->interp; state_dict[PYBIND11_INTERNALS_ID] = capsule(reinterpret_cast(internals_pp)); internals_ptr->registered_exception_translators.push_front(&translate_exception); @@ -506,6 +583,40 @@ PYBIND11_NOINLINE internals &get_internals() { struct local_internals { type_map registered_types_cpp; std::forward_list registered_exception_translators; +#if PYBIND11_INTERNALS_VERSION == 4 + + // For ABI compatibility, we can't store the loader_life_support TLS key in + // the `internals` struct directly. Instead, we store it in `shared_data` and + // cache a copy in `local_internals`. If we allocated a separate TLS key for + // each instance of `local_internals`, we could end up allocating hundreds of + // TLS keys if hundreds of different pybind11 modules are loaded (which is a + // plausible number). + PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key) + + // Holds the shared TLS key for the loader_life_support stack. + struct shared_loader_life_support_data { + PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key) + shared_loader_life_support_data() { + // NOLINTNEXTLINE(bugprone-assignment-in-if-condition) + if (!PYBIND11_TLS_KEY_CREATE(loader_life_support_tls_key)) { + pybind11_fail("local_internals: could not successfully initialize the " + "loader_life_support TLS key!"); + } + } + // We can't help but leak the TLS key, because Python never unloads extension modules. + }; + + local_internals() { + auto &internals = get_internals(); + // Get or create the `loader_life_support_stack_key`. + auto &ptr = internals.shared_data["_life_support"]; + if (!ptr) { + ptr = new shared_loader_life_support_data; + } + loader_life_support_tls_key + = static_cast(ptr)->loader_life_support_tls_key; + } +#endif // PYBIND11_INTERNALS_VERSION == 4 }; /// Works like `get_internals`, but for things which are locally registered. @@ -532,19 +643,6 @@ inline auto with_internals(const F &cb) -> decltype(cb(get_internals())) { return cb(internals); } -template -inline auto with_exception_translators(const F &cb) - -> decltype(cb(get_internals().registered_exception_translators, - get_local_internals().registered_exception_translators)) { - auto &internals = get_internals(); -#ifdef Py_GIL_DISABLED - std::unique_lock lock((internals).exception_translator_mutex); -#endif - auto &local_internals = get_local_internals(); - return cb(internals.registered_exception_translators, - local_internals.registered_exception_translators); -} - inline std::uint64_t mix64(std::uint64_t z) { // David Stafford's variant 13 of the MurmurHash3 finalizer popularized // by the SplitMix PRNG. @@ -555,8 +653,8 @@ inline std::uint64_t mix64(std::uint64_t z) { } template -inline auto with_instance_map(const void *ptr, const F &cb) - -> decltype(cb(std::declval())) { +inline auto with_instance_map(const void *ptr, + const F &cb) -> decltype(cb(std::declval())) { auto &internals = get_internals(); #ifdef Py_GIL_DISABLED @@ -611,8 +709,7 @@ const char *c_str(Args &&...args) { } inline const char *get_function_record_capsule_name() { - // On GraalPy, pointer equality of the names is currently not guaranteed -#if !defined(GRAALVM_PYTHON) +#if PYBIND11_INTERNALS_VERSION > 4 return get_internals().function_record_capsule_name.c_str(); #else return nullptr; diff --git a/pybind11/include/pybind11/detail/struct_smart_holder.h b/pybind11/include/pybind11/detail/struct_smart_holder.h deleted file mode 100644 index 980fc3699..000000000 --- a/pybind11/include/pybind11/detail/struct_smart_holder.h +++ /dev/null @@ -1,349 +0,0 @@ -// Copyright (c) 2020-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. - -/* Proof-of-Concept for smart pointer interoperability. - -High-level aspects: - -* Support all `unique_ptr`, `shared_ptr` interops that are feasible. - -* Cleanly and clearly report all interops that are infeasible. - -* Meant to fit into a `PyObject`, as a holder for C++ objects. - -* Support a system design that makes it impossible to trigger - C++ Undefined Behavior, especially from Python. - -* Support a system design with clean runtime inheritance casting. From this - it follows that the `smart_holder` needs to be type-erased (`void*`). - -* Handling of RTTI for the type-erased held pointer is NOT implemented here. - It is the responsibility of the caller to ensure that `static_cast` - is well-formed when calling `as_*` member functions. Inheritance casting - needs to be handled in a different layer (similar to the code organization - in boost/python/object/inheritance.hpp). - -Details: - -* The "root holder" chosen here is a `shared_ptr` (named `vptr` in this - implementation). This choice is practically inevitable because `shared_ptr` - has only very limited support for inspecting and accessing its deleter. - -* If created from a raw pointer, or a `unique_ptr` without a custom deleter, - `vptr` always uses a custom deleter, to support `unique_ptr`-like disowning. - The custom deleters could be extended to included life-time management for - external objects (e.g. `PyObject`). - -* If created from an external `shared_ptr`, or a `unique_ptr` with a custom - deleter, including life-time management for external objects is infeasible. - -* By choice, the smart_holder is movable but not copyable, to keep the design - simple, and to guard against accidental copying overhead. - -* The `void_cast_raw_ptr` option is needed to make the `smart_holder` `vptr` - member invisible to the `shared_from_this` mechanism, in case the lifetime - of a `PyObject` is tied to the pointee. -*/ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -// pybindit = Python Bindings Innovation Track. -// Currently not in pybind11 namespace to signal that this POC does not depend -// on any existing pybind11 functionality. -namespace pybindit { -namespace memory { - -static constexpr bool type_has_shared_from_this(...) { return false; } - -template -static constexpr bool type_has_shared_from_this(const std::enable_shared_from_this *) { - return true; -} - -struct guarded_delete { - std::weak_ptr released_ptr; // Trick to keep the smart_holder memory footprint small. - std::function del_fun; // Rare case. - void (*del_ptr)(void *); // Common case. - bool use_del_fun; - bool armed_flag; - guarded_delete(std::function &&del_fun, bool armed_flag) - : del_fun{std::move(del_fun)}, del_ptr{nullptr}, use_del_fun{true}, - armed_flag{armed_flag} {} - guarded_delete(void (*del_ptr)(void *), bool armed_flag) - : del_ptr{del_ptr}, use_del_fun{false}, armed_flag{armed_flag} {} - void operator()(void *raw_ptr) const { - if (armed_flag) { - if (use_del_fun) { - del_fun(raw_ptr); - } else { - del_ptr(raw_ptr); - } - } - } -}; - -template ::value, int>::type = 0> -inline void builtin_delete_if_destructible(void *raw_ptr) { - std::default_delete{}(static_cast(raw_ptr)); -} - -template ::value, int>::type = 0> -inline void builtin_delete_if_destructible(void *) { - // This noop operator is needed to avoid a compilation error (for `delete raw_ptr;`), but - // throwing an exception from a destructor will std::terminate the process. Therefore the - // runtime check for lifetime-management correctness is implemented elsewhere (in - // ensure_pointee_is_destructible()). -} - -template -guarded_delete make_guarded_builtin_delete(bool armed_flag) { - return guarded_delete(builtin_delete_if_destructible, armed_flag); -} - -template -struct custom_deleter { - D deleter; - explicit custom_deleter(D &&deleter) : deleter{std::forward(deleter)} {} - void operator()(void *raw_ptr) { deleter(static_cast(raw_ptr)); } -}; - -template -guarded_delete make_guarded_custom_deleter(D &&uqp_del, bool armed_flag) { - return guarded_delete( - std::function(custom_deleter(std::forward(uqp_del))), armed_flag); -} - -template -inline bool is_std_default_delete(const std::type_info &rtti_deleter) { - return rtti_deleter == typeid(std::default_delete) - || rtti_deleter == typeid(std::default_delete); -} - -struct smart_holder { - const std::type_info *rtti_uqp_del = nullptr; - std::shared_ptr vptr; - bool vptr_is_using_noop_deleter : 1; - bool vptr_is_using_builtin_delete : 1; - bool vptr_is_external_shared_ptr : 1; - bool is_populated : 1; - bool is_disowned : 1; - - // Design choice: smart_holder is movable but not copyable. - smart_holder(smart_holder &&) = default; - smart_holder(const smart_holder &) = delete; - smart_holder &operator=(smart_holder &&) = delete; - smart_holder &operator=(const smart_holder &) = delete; - - smart_holder() - : vptr_is_using_noop_deleter{false}, vptr_is_using_builtin_delete{false}, - vptr_is_external_shared_ptr{false}, is_populated{false}, is_disowned{false} {} - - bool has_pointee() const { return vptr != nullptr; } - - template - static void ensure_pointee_is_destructible(const char *context) { - if (!std::is_destructible::value) { - throw std::invalid_argument(std::string("Pointee is not destructible (") + context - + ")."); - } - } - - void ensure_is_populated(const char *context) const { - if (!is_populated) { - throw std::runtime_error(std::string("Unpopulated holder (") + context + ")."); - } - } - void ensure_is_not_disowned(const char *context) const { - if (is_disowned) { - throw std::runtime_error(std::string("Holder was disowned already (") + context - + ")."); - } - } - - void ensure_vptr_is_using_builtin_delete(const char *context) const { - if (vptr_is_external_shared_ptr) { - throw std::invalid_argument(std::string("Cannot disown external shared_ptr (") - + context + ")."); - } - if (vptr_is_using_noop_deleter) { - throw std::invalid_argument(std::string("Cannot disown non-owning holder (") + context - + ")."); - } - if (!vptr_is_using_builtin_delete) { - throw std::invalid_argument(std::string("Cannot disown custom deleter (") + context - + ")."); - } - } - - template - void ensure_compatible_rtti_uqp_del(const char *context) const { - const std::type_info *rtti_requested = &typeid(D); - if (!rtti_uqp_del) { - if (!is_std_default_delete(*rtti_requested)) { - throw std::invalid_argument(std::string("Missing unique_ptr deleter (") + context - + ")."); - } - ensure_vptr_is_using_builtin_delete(context); - } else if (!(*rtti_requested == *rtti_uqp_del) - && !(vptr_is_using_builtin_delete - && is_std_default_delete(*rtti_requested))) { - throw std::invalid_argument(std::string("Incompatible unique_ptr deleter (") + context - + ")."); - } - } - - void ensure_has_pointee(const char *context) const { - if (!has_pointee()) { - throw std::invalid_argument(std::string("Disowned holder (") + context + ")."); - } - } - - void ensure_use_count_1(const char *context) const { - if (vptr == nullptr) { - throw std::invalid_argument(std::string("Cannot disown nullptr (") + context + ")."); - } - // In multithreaded environments accessing use_count can lead to - // race conditions, but in the context of Python it is a bug (elsewhere) - // if the Global Interpreter Lock (GIL) is not being held when this code - // is reached. - // PYBIND11:REMINDER: This may need to be protected by a mutex in free-threaded Python. - if (vptr.use_count() != 1) { - throw std::invalid_argument(std::string("Cannot disown use_count != 1 (") + context - + ")."); - } - } - - void reset_vptr_deleter_armed_flag(bool armed_flag) const { - auto *vptr_del_ptr = std::get_deleter(vptr); - if (vptr_del_ptr == nullptr) { - throw std::runtime_error( - "smart_holder::reset_vptr_deleter_armed_flag() called in an invalid context."); - } - vptr_del_ptr->armed_flag = armed_flag; - } - - // Caller is responsible for precondition: ensure_compatible_rtti_uqp_del() must succeed. - template - std::unique_ptr extract_deleter(const char *context) const { - const auto *gd = std::get_deleter(vptr); - if (gd && gd->use_del_fun) { - const auto &custom_deleter_ptr = gd->del_fun.template target>(); - if (custom_deleter_ptr == nullptr) { - throw std::runtime_error( - std::string("smart_holder::extract_deleter() precondition failure (") + context - + ")."); - } - static_assert(std::is_copy_constructible::value, - "Required for compatibility with smart_holder functionality."); - return std::unique_ptr(new D(custom_deleter_ptr->deleter)); - } - return nullptr; - } - - static smart_holder from_raw_ptr_unowned(void *raw_ptr) { - smart_holder hld; - hld.vptr.reset(raw_ptr, [](void *) {}); - hld.vptr_is_using_noop_deleter = true; - hld.is_populated = true; - return hld; - } - - template - T *as_raw_ptr_unowned() const { - return static_cast(vptr.get()); - } - - template - static smart_holder from_raw_ptr_take_ownership(T *raw_ptr, bool void_cast_raw_ptr = false) { - ensure_pointee_is_destructible("from_raw_ptr_take_ownership"); - smart_holder hld; - auto gd = make_guarded_builtin_delete(true); - if (void_cast_raw_ptr) { - hld.vptr.reset(static_cast(raw_ptr), std::move(gd)); - } else { - hld.vptr.reset(raw_ptr, std::move(gd)); - } - hld.vptr_is_using_builtin_delete = true; - hld.is_populated = true; - return hld; - } - - // Caller is responsible for ensuring the complex preconditions - // (see `smart_holder_type_caster_support::load_helper`). - void disown() { - reset_vptr_deleter_armed_flag(false); - is_disowned = true; - } - - // Caller is responsible for ensuring the complex preconditions - // (see `smart_holder_type_caster_support::load_helper`). - void reclaim_disowned() { - reset_vptr_deleter_armed_flag(true); - is_disowned = false; - } - - // Caller is responsible for ensuring the complex preconditions - // (see `smart_holder_type_caster_support::load_helper`). - void release_disowned() { vptr.reset(); } - - void ensure_can_release_ownership(const char *context = "ensure_can_release_ownership") const { - ensure_is_not_disowned(context); - ensure_vptr_is_using_builtin_delete(context); - ensure_use_count_1(context); - } - - // Caller is responsible for ensuring the complex preconditions - // (see `smart_holder_type_caster_support::load_helper`). - void release_ownership() { - reset_vptr_deleter_armed_flag(false); - release_disowned(); - } - - template - static smart_holder from_unique_ptr(std::unique_ptr &&unq_ptr, - void *void_ptr = nullptr) { - smart_holder hld; - hld.rtti_uqp_del = &typeid(D); - hld.vptr_is_using_builtin_delete = is_std_default_delete(*hld.rtti_uqp_del); - guarded_delete gd{nullptr, false}; - if (hld.vptr_is_using_builtin_delete) { - gd = make_guarded_builtin_delete(true); - } else { - gd = make_guarded_custom_deleter(std::move(unq_ptr.get_deleter()), true); - } - if (void_ptr != nullptr) { - hld.vptr.reset(void_ptr, std::move(gd)); - } else { - hld.vptr.reset(unq_ptr.get(), std::move(gd)); - } - (void) unq_ptr.release(); - hld.is_populated = true; - return hld; - } - - template - static smart_holder from_shared_ptr(std::shared_ptr shd_ptr) { - smart_holder hld; - hld.vptr = std::static_pointer_cast(shd_ptr); - hld.vptr_is_external_shared_ptr = true; - hld.is_populated = true; - return hld; - } - - template - std::shared_ptr as_shared_ptr() const { - return std::static_pointer_cast(vptr); - } -}; - -} // namespace memory -} // namespace pybindit diff --git a/pybind11/include/pybind11/detail/type_caster_base.h b/pybind11/include/pybind11/detail/type_caster_base.h index 9618b2181..e40e44ba6 100644 --- a/pybind11/include/pybind11/detail/type_caster_base.h +++ b/pybind11/include/pybind11/detail/type_caster_base.h @@ -9,17 +9,13 @@ #pragma once -#include #include -#include #include "common.h" #include "cpp_conduit.h" #include "descr.h" -#include "dynamic_raw_ptr_cast_if_possible.h" #include "internals.h" #include "typeid.h" -#include "using_smart_holder.h" #include "value_and_holder.h" #include @@ -47,7 +43,11 @@ private: // Store stack pointer in thread-local storage. static PYBIND11_TLS_KEY_REF get_stack_tls_key() { +#if PYBIND11_INTERNALS_VERSION == 4 + return get_local_internals().loader_life_support_tls_key; +#else return get_internals().loader_life_support_tls_key; +#endif } static loader_life_support *get_stack_top() { return static_cast(PYBIND11_TLS_GET_VALUE(get_stack_tls_key())); @@ -117,6 +117,7 @@ PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector(t->tp_bases)) { check.push_back((PyTypeObject *) parent.ptr()); } + auto const &type_dict = get_internals().registered_types_py; for (size_t i = 0; i < check.size(); i++) { auto *type = check[i]; @@ -175,7 +176,13 @@ PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector &all_type_info(PyTypeObject *type) { - return all_type_info_get_cache(type).first->second; + auto ins = all_type_info_get_cache(type); + if (ins.second) { + // New cache entry: populate it + all_type_info_populate(type, ins.first->second); + } + + return ins.first->second; } /** @@ -241,49 +248,6 @@ PYBIND11_NOINLINE handle get_type_handle(const std::type_info &tp, bool throw_if return handle(type_info ? ((PyObject *) type_info->type) : nullptr); } -inline bool try_incref(PyObject *obj) { - // Tries to increment the reference count of an object if it's not zero. - // TODO: Use PyUnstable_TryIncref when available. - // See https://github.com/python/cpython/issues/128844 -#ifdef Py_GIL_DISABLED - // See - // https://github.com/python/cpython/blob/d05140f9f77d7dfc753dd1e5ac3a5962aaa03eff/Include/internal/pycore_object.h#L761 - uint32_t local = _Py_atomic_load_uint32_relaxed(&obj->ob_ref_local); - local += 1; - if (local == 0) { - // immortal - return true; - } - if (_Py_IsOwnedByCurrentThread(obj)) { - _Py_atomic_store_uint32_relaxed(&obj->ob_ref_local, local); -# ifdef Py_REF_DEBUG - _Py_INCREF_IncRefTotal(); -# endif - return true; - } - Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared); - for (;;) { - // If the shared refcount is zero and the object is either merged - // or may not have weak references, then we cannot incref it. - if (shared == 0 || shared == _Py_REF_MERGED) { - return false; - } - - if (_Py_atomic_compare_exchange_ssize( - &obj->ob_ref_shared, &shared, shared + (1 << _Py_REF_SHARED_SHIFT))) { -# ifdef Py_REF_DEBUG - _Py_INCREF_IncRefTotal(); -# endif - return true; - } - } -#else - assert(Py_REFCNT(obj) > 0); - Py_INCREF(obj); - return true; -#endif -} - // Searches the inheritance graph for a registered Python instance, using all_type_info(). PYBIND11_NOINLINE handle find_registered_python_instance(void *src, const detail::type_info *tinfo) { @@ -292,10 +256,7 @@ PYBIND11_NOINLINE handle find_registered_python_instance(void *src, for (auto it_i = it_instances.first; it_i != it_instances.second; ++it_i) { for (auto *instance_type : detail::all_type_info(Py_TYPE(it_i->second))) { if (instance_type && same_type(*instance_type->cpptype, *tinfo->cpptype)) { - auto *wrapper = reinterpret_cast(it_i->second); - if (try_incref(wrapper)) { - return handle(wrapper); - } + return handle((PyObject *) it_i->second).inc_ref(); } } } @@ -498,7 +459,7 @@ PYBIND11_NOINLINE handle get_object_handle(const void *ptr, const detail::type_i } inline PyThreadState *get_thread_state_unchecked() { -#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON) +#if defined(PYPY_VERSION) return PyThreadState_GET(); #elif PY_VERSION_HEX < 0x030D0000 return _PyThreadState_UncheckedGet(); @@ -511,361 +472,6 @@ inline PyThreadState *get_thread_state_unchecked() { void keep_alive_impl(handle nurse, handle patient); inline PyObject *make_new_instance(PyTypeObject *type); -// PYBIND11:REMINDER: Needs refactoring of existing pybind11 code. -inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo); - -PYBIND11_NAMESPACE_BEGIN(smart_holder_type_caster_support) - -struct value_and_holder_helper { - value_and_holder loaded_v_h; - - bool have_holder() const { - return loaded_v_h.vh != nullptr && loaded_v_h.holder_constructed(); - } - - smart_holder &holder() const { return loaded_v_h.holder(); } - - void throw_if_uninitialized_or_disowned_holder(const char *typeid_name) const { - static const std::string missing_value_msg = "Missing value for wrapped C++ type `"; - if (!holder().is_populated) { - throw value_error(missing_value_msg + clean_type_id(typeid_name) - + "`: Python instance is uninitialized."); - } - if (!holder().has_pointee()) { - throw value_error(missing_value_msg + clean_type_id(typeid_name) - + "`: Python instance was disowned."); - } - } - - void throw_if_uninitialized_or_disowned_holder(const std::type_info &type_info) const { - throw_if_uninitialized_or_disowned_holder(type_info.name()); - } - - // have_holder() must be true or this function will fail. - void throw_if_instance_is_currently_owned_by_shared_ptr() const { - auto *vptr_gd_ptr = std::get_deleter(holder().vptr); - if (vptr_gd_ptr != nullptr && !vptr_gd_ptr->released_ptr.expired()) { - throw value_error("Python instance is currently owned by a std::shared_ptr."); - } - } - - void *get_void_ptr_or_nullptr() const { - if (have_holder()) { - auto &hld = holder(); - if (hld.is_populated && hld.has_pointee()) { - return hld.template as_raw_ptr_unowned(); - } - } - return nullptr; - } -}; - -template -handle smart_holder_from_unique_ptr(std::unique_ptr &&src, - return_value_policy policy, - handle parent, - const std::pair &st) { - if (policy == return_value_policy::copy) { - throw cast_error("return_value_policy::copy is invalid for unique_ptr."); - } - if (!src) { - return none().release(); - } - void *src_raw_void_ptr = const_cast(st.first); - assert(st.second != nullptr); - const detail::type_info *tinfo = st.second; - if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) { - auto *self_life_support - = dynamic_raw_ptr_cast_if_possible(src.get()); - if (self_life_support != nullptr) { - value_and_holder &v_h = self_life_support->v_h; - if (v_h.inst != nullptr && v_h.vh != nullptr) { - auto &holder = v_h.holder(); - if (!holder.is_disowned) { - pybind11_fail("smart_holder_from_unique_ptr: unexpected " - "smart_holder.is_disowned failure."); - } - // Critical transfer-of-ownership section. This must stay together. - self_life_support->deactivate_life_support(); - holder.reclaim_disowned(); - (void) src.release(); - // Critical section end. - return existing_inst; - } - } - throw cast_error("Invalid unique_ptr: another instance owns this pointer already."); - } - - auto inst = reinterpret_steal(make_new_instance(tinfo->type)); - auto *inst_raw_ptr = reinterpret_cast(inst.ptr()); - inst_raw_ptr->owned = true; - void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr(); - valueptr = src_raw_void_ptr; - - if (static_cast(src.get()) == src_raw_void_ptr) { - // This is a multiple-inheritance situation that is incompatible with the current - // shared_from_this handling (see PR #3023). Is there a better solution? - src_raw_void_ptr = nullptr; - } - auto smhldr = smart_holder::from_unique_ptr(std::move(src), src_raw_void_ptr); - tinfo->init_instance(inst_raw_ptr, static_cast(&smhldr)); - - if (policy == return_value_policy::reference_internal) { - keep_alive_impl(inst, parent); - } - - return inst.release(); -} - -template -handle smart_holder_from_unique_ptr(std::unique_ptr &&src, - return_value_policy policy, - handle parent, - const std::pair &st) { - return smart_holder_from_unique_ptr( - std::unique_ptr(const_cast(src.release()), - std::move(src.get_deleter())), // Const2Mutbl - policy, - parent, - st); -} - -template -handle smart_holder_from_shared_ptr(const std::shared_ptr &src, - return_value_policy policy, - handle parent, - const std::pair &st) { - switch (policy) { - case return_value_policy::automatic: - case return_value_policy::automatic_reference: - break; - case return_value_policy::take_ownership: - throw cast_error("Invalid return_value_policy for shared_ptr (take_ownership)."); - case return_value_policy::copy: - case return_value_policy::move: - break; - case return_value_policy::reference: - throw cast_error("Invalid return_value_policy for shared_ptr (reference)."); - case return_value_policy::reference_internal: - break; - } - if (!src) { - return none().release(); - } - - auto src_raw_ptr = src.get(); - assert(st.second != nullptr); - void *src_raw_void_ptr = static_cast(src_raw_ptr); - const detail::type_info *tinfo = st.second; - if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) { - // PYBIND11:REMINDER: MISSING: Enforcement of consistency with existing smart_holder. - // PYBIND11:REMINDER: MISSING: keep_alive. - return existing_inst; - } - - auto inst = reinterpret_steal(make_new_instance(tinfo->type)); - auto *inst_raw_ptr = reinterpret_cast(inst.ptr()); - inst_raw_ptr->owned = true; - void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr(); - valueptr = src_raw_void_ptr; - - auto smhldr - = smart_holder::from_shared_ptr(std::shared_ptr(src, const_cast(st.first))); - tinfo->init_instance(inst_raw_ptr, static_cast(&smhldr)); - - if (policy == return_value_policy::reference_internal) { - keep_alive_impl(inst, parent); - } - - return inst.release(); -} - -template -handle smart_holder_from_shared_ptr(const std::shared_ptr &src, - return_value_policy policy, - handle parent, - const std::pair &st) { - return smart_holder_from_shared_ptr(std::const_pointer_cast(src), // Const2Mutbl - policy, - parent, - st); -} - -struct shared_ptr_parent_life_support { - PyObject *parent; - explicit shared_ptr_parent_life_support(PyObject *parent) : parent{parent} { - Py_INCREF(parent); - } - // NOLINTNEXTLINE(readability-make-member-function-const) - void operator()(void *) { - gil_scoped_acquire gil; - Py_DECREF(parent); - } -}; - -struct shared_ptr_trampoline_self_life_support { - PyObject *self; - explicit shared_ptr_trampoline_self_life_support(instance *inst) - : self{reinterpret_cast(inst)} { - gil_scoped_acquire gil; - Py_INCREF(self); - } - // NOLINTNEXTLINE(readability-make-member-function-const) - void operator()(void *) { - gil_scoped_acquire gil; - Py_DECREF(self); - } -}; - -template ::value, int>::type = 0> -inline std::unique_ptr unique_with_deleter(T *raw_ptr, std::unique_ptr &&deleter) { - if (deleter == nullptr) { - return std::unique_ptr(raw_ptr); - } - return std::unique_ptr(raw_ptr, std::move(*deleter)); -} - -template ::value, int>::type = 0> -inline std::unique_ptr unique_with_deleter(T *raw_ptr, std::unique_ptr &&deleter) { - if (deleter == nullptr) { - pybind11_fail("smart_holder_type_casters: deleter is not default constructible and no" - " instance available to return."); - } - return std::unique_ptr(raw_ptr, std::move(*deleter)); -} - -template -struct load_helper : value_and_holder_helper { - bool was_populated = false; - bool python_instance_is_alias = false; - - void maybe_set_python_instance_is_alias(handle src) { - if (was_populated) { - python_instance_is_alias = reinterpret_cast(src.ptr())->is_alias; - } - } - - static std::shared_ptr make_shared_ptr_with_responsible_parent(T *raw_ptr, handle parent) { - return std::shared_ptr(raw_ptr, shared_ptr_parent_life_support(parent.ptr())); - } - - std::shared_ptr load_as_shared_ptr(void *void_raw_ptr, - handle responsible_parent = nullptr) const { - if (!have_holder()) { - return nullptr; - } - throw_if_uninitialized_or_disowned_holder(typeid(T)); - smart_holder &hld = holder(); - hld.ensure_is_not_disowned("load_as_shared_ptr"); - if (hld.vptr_is_using_noop_deleter) { - if (responsible_parent) { - return make_shared_ptr_with_responsible_parent(static_cast(void_raw_ptr), - responsible_parent); - } - throw std::runtime_error("Non-owning holder (load_as_shared_ptr)."); - } - auto *type_raw_ptr = static_cast(void_raw_ptr); - if (python_instance_is_alias) { - auto *vptr_gd_ptr = std::get_deleter(hld.vptr); - if (vptr_gd_ptr != nullptr) { - std::shared_ptr released_ptr = vptr_gd_ptr->released_ptr.lock(); - if (released_ptr) { - return std::shared_ptr(released_ptr, type_raw_ptr); - } - std::shared_ptr to_be_released( - type_raw_ptr, shared_ptr_trampoline_self_life_support(loaded_v_h.inst)); - vptr_gd_ptr->released_ptr = to_be_released; - return to_be_released; - } - auto *sptsls_ptr = std::get_deleter(hld.vptr); - if (sptsls_ptr != nullptr) { - // This code is reachable only if there are multiple registered_instances for the - // same pointee. - if (reinterpret_cast(loaded_v_h.inst) == sptsls_ptr->self) { - pybind11_fail("smart_holder_type_caster_support load_as_shared_ptr failure: " - "loaded_v_h.inst == sptsls_ptr->self"); - } - } - if (sptsls_ptr != nullptr - || !pybindit::memory::type_has_shared_from_this(type_raw_ptr)) { - return std::shared_ptr( - type_raw_ptr, shared_ptr_trampoline_self_life_support(loaded_v_h.inst)); - } - if (hld.vptr_is_external_shared_ptr) { - pybind11_fail("smart_holder_type_casters load_as_shared_ptr failure: not " - "implemented: trampoline-self-life-support for external shared_ptr " - "to type inheriting from std::enable_shared_from_this."); - } - pybind11_fail( - "smart_holder_type_casters: load_as_shared_ptr failure: internal inconsistency."); - } - std::shared_ptr void_shd_ptr = hld.template as_shared_ptr(); - return std::shared_ptr(void_shd_ptr, type_raw_ptr); - } - - template - std::unique_ptr load_as_unique_ptr(void *raw_void_ptr, - const char *context = "load_as_unique_ptr") { - if (!have_holder()) { - return unique_with_deleter(nullptr, std::unique_ptr()); - } - throw_if_uninitialized_or_disowned_holder(typeid(T)); - throw_if_instance_is_currently_owned_by_shared_ptr(); - holder().ensure_is_not_disowned(context); - holder().template ensure_compatible_rtti_uqp_del(context); - holder().ensure_use_count_1(context); - - T *raw_type_ptr = static_cast(raw_void_ptr); - - auto *self_life_support - = dynamic_raw_ptr_cast_if_possible(raw_type_ptr); - if (self_life_support == nullptr && python_instance_is_alias) { - throw value_error("Alias class (also known as trampoline) does not inherit from " - "py::trampoline_self_life_support, therefore the ownership of this " - "instance cannot safely be transferred to C++."); - } - - std::unique_ptr extracted_deleter = holder().template extract_deleter(context); - - // Critical transfer-of-ownership section. This must stay together. - if (self_life_support != nullptr) { - holder().disown(); - } else { - holder().release_ownership(); - } - auto result = unique_with_deleter(raw_type_ptr, std::move(extracted_deleter)); - if (self_life_support != nullptr) { - self_life_support->activate_life_support(loaded_v_h); - } else { - void *value_void_ptr = loaded_v_h.value_ptr(); - loaded_v_h.value_ptr() = nullptr; - deregister_instance(loaded_v_h.inst, value_void_ptr, loaded_v_h.type); - } - // Critical section end. - - return result; - } - - // This assumes load_as_shared_ptr succeeded(), and the returned shared_ptr is still alive. - // The returned unique_ptr is meant to never expire (the behavior is undefined otherwise). - template - std::unique_ptr - load_as_const_unique_ptr(T *raw_type_ptr, const char *context = "load_as_const_unique_ptr") { - if (!have_holder()) { - return unique_with_deleter(nullptr, std::unique_ptr()); - } - holder().template ensure_compatible_rtti_uqp_del(context); - return unique_with_deleter( - raw_type_ptr, std::move(holder().template extract_deleter(context))); - } -}; - -PYBIND11_NAMESPACE_END(smart_holder_type_caster_support) - class type_caster_generic { public: PYBIND11_NOINLINE explicit type_caster_generic(const std::type_info &type_info) @@ -970,15 +576,6 @@ public: // Base methods for generic caster; there are overridden in copyable_holder_caster void load_value(value_and_holder &&v_h) { - if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { - smart_holder_type_caster_support::value_and_holder_helper v_h_helper; - v_h_helper.loaded_v_h = v_h; - if (v_h_helper.have_holder()) { - v_h_helper.throw_if_uninitialized_or_disowned_holder(cpptype->name()); - value = v_h_helper.holder().template as_raw_ptr_unowned(); - return; - } - } auto *&vptr = v_h.value_ptr(); // Lazy allocation for unallocated values: if (vptr == nullptr) { @@ -1564,14 +1161,14 @@ protected: does not have a private operator new implementation. A comma operator is used in the decltype argument to apply SFINAE to the public copy/move constructors.*/ template ::value>> - static auto make_copy_constructor(const T *) - -> decltype(new T(std::declval()), Constructor{}) { + static auto make_copy_constructor(const T *) -> decltype(new T(std::declval()), + Constructor{}) { return [](const void *arg) -> void * { return new T(*reinterpret_cast(arg)); }; } template ::value>> - static auto make_move_constructor(const T *) - -> decltype(new T(std::declval()), Constructor{}) { + static auto make_move_constructor(const T *) -> decltype(new T(std::declval()), + Constructor{}) { return [](const void *arg) -> void * { return new T(std::move(*const_cast(reinterpret_cast(arg)))); }; diff --git a/pybind11/include/pybind11/detail/using_smart_holder.h b/pybind11/include/pybind11/detail/using_smart_holder.h deleted file mode 100644 index 57f99b95f..000000000 --- a/pybind11/include/pybind11/detail/using_smart_holder.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 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 "struct_smart_holder.h" - -#include - -PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) - -using pybindit::memory::smart_holder; - -PYBIND11_NAMESPACE_BEGIN(detail) - -template -using is_smart_holder = std::is_same; - -PYBIND11_NAMESPACE_END(detail) -PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/pybind11/include/pybind11/detail/value_and_holder.h b/pybind11/include/pybind11/detail/value_and_holder.h index 64c55cc59..ca37d70ad 100644 --- a/pybind11/include/pybind11/detail/value_and_holder.h +++ b/pybind11/include/pybind11/detail/value_and_holder.h @@ -7,7 +7,6 @@ #include "common.h" #include -#include #include PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) diff --git a/pybind11/include/pybind11/eigen/matrix.h b/pybind11/include/pybind11/eigen/matrix.h index ca599c954..5cf1f0a2a 100644 --- a/pybind11/include/pybind11/eigen/matrix.h +++ b/pybind11/include/pybind11/eigen/matrix.h @@ -225,22 +225,19 @@ struct EigenProps { = !show_c_contiguous && show_order && requires_col_major; static constexpr auto descriptor - = const_name("typing.Annotated[") - + io_name("numpy.typing.ArrayLike, ", "numpy.typing.NDArray[") - + npy_format_descriptor::name + io_name("", "]") + const_name(", \"[") + = const_name("numpy.ndarray[") + npy_format_descriptor::name + const_name("[") + const_name(const_name<(size_t) rows>(), const_name("m")) + const_name(", ") - + const_name(const_name<(size_t) cols>(), const_name("n")) - + const_name("]\"") + + const_name(const_name<(size_t) cols>(), const_name("n")) + const_name("]") + + // For a reference type (e.g. Ref) we have other constraints that might need to // be satisfied: writeable=True (for a mutable reference), and, depending on the map's // stride options, possibly f_contiguous or c_contiguous. We include them in the // descriptor output to provide some hint as to why a TypeError is occurring (otherwise - // it can be confusing to see that a function accepts a - // 'typing.Annotated[numpy.typing.NDArray[numpy.float64], "[3,2]"]' and an error message - // that you *gave* a numpy.ndarray of the right type and dimensions. - + const_name(", \"flags.writeable\"", "") - + const_name(", \"flags.c_contiguous\"", "") - + const_name(", \"flags.f_contiguous\"", "") + const_name("]"); + // it can be confusing to see that a function accepts a 'numpy.ndarray[float64[3,2]]' and + // an error message that you *gave* a numpy.ndarray of the right type and dimensions. + const_name(", flags.writeable", "") + + const_name(", flags.c_contiguous", "") + + const_name(", flags.f_contiguous", "") + const_name("]"); }; // Casts an Eigen type to numpy array. If given a base, the numpy array references the src data, @@ -319,11 +316,8 @@ struct type_caster::value>> { return false; } - PYBIND11_WARNING_PUSH - PYBIND11_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // See PR #5516 // Allocate the new type, then build a numpy reference into it value = Type(fits.rows, fits.cols); - PYBIND11_WARNING_POP auto ref = reinterpret_steal(eigen_ref_array(value)); if (dims == 1) { ref = ref.squeeze(); @@ -444,9 +438,7 @@ public: } } - // return_descr forces the use of NDArray instead of ArrayLike in args - // since Ref<...> args can only accept arrays. - static constexpr auto name = return_descr(props::descriptor); + static constexpr auto name = props::descriptor; // Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return // types but not bound arguments). We still provide them (with an explicitly delete) so that diff --git a/pybind11/include/pybind11/eigen/tensor.h b/pybind11/include/pybind11/eigen/tensor.h index 50e8b50b1..0a9d7c252 100644 --- a/pybind11/include/pybind11/eigen/tensor.h +++ b/pybind11/include/pybind11/eigen/tensor.h @@ -124,16 +124,13 @@ struct eigen_tensor_helper< template struct get_tensor_descriptor { static constexpr auto details - = const_name(", \"flags.writeable\"", "") + const_name - < static_cast(Type::Layout) - == static_cast(Eigen::RowMajor) - > (", \"flags.c_contiguous\"", ", \"flags.f_contiguous\""); + = const_name(", flags.writeable", "") + + const_name(Type::Layout) == static_cast(Eigen::RowMajor)>( + ", flags.c_contiguous", ", flags.f_contiguous"); static constexpr auto value - = const_name("typing.Annotated[") - + io_name("numpy.typing.ArrayLike, ", "numpy.typing.NDArray[") - + npy_format_descriptor::name + io_name("", "]") - + const_name(", \"[") + eigen_tensor_helper>::dimensions_descriptor - + const_name("]\"") + const_name(details, const_name("")) + const_name("]"); + = const_name("numpy.ndarray[") + npy_format_descriptor::name + + const_name("[") + eigen_tensor_helper>::dimensions_descriptor + + const_name("]") + const_name(details, const_name("")) + const_name("]"); }; // When EIGEN_AVOID_STL_ARRAY is defined, Eigen::DSizes does not have the begin() member @@ -505,10 +502,7 @@ protected: std::unique_ptr value; public: - // return_descr forces the use of NDArray instead of ArrayLike since refs can only reference - // arrays - static constexpr auto name - = return_descr(get_tensor_descriptor::value); + static constexpr auto name = get_tensor_descriptor::value; explicit operator MapType *() { return value.get(); } explicit operator MapType &() { return *value; } explicit operator MapType &&() && { return std::move(*value); } diff --git a/pybind11/include/pybind11/embed.h b/pybind11/include/pybind11/embed.h index 0af777033..9d29eb824 100644 --- a/pybind11/include/pybind11/embed.h +++ b/pybind11/include/pybind11/embed.h @@ -104,13 +104,23 @@ inline void initialize_interpreter_pre_pyconfig(bool init_signal_handlers, detail::precheck_interpreter(); Py_InitializeEx(init_signal_handlers ? 1 : 0); + // Before it was special-cased in python 3.8, passing an empty or null argv + // caused a segfault, so we have to reimplement the special case ourselves. + bool special_case = (argv == nullptr || argc <= 0); + + const char *const empty_argv[]{"\0"}; + const char *const *safe_argv = special_case ? empty_argv : argv; + if (special_case) { + argc = 1; + } + auto argv_size = static_cast(argc); // SetArgv* on python 3 takes wchar_t, so we have to convert. std::unique_ptr widened_argv(new wchar_t *[argv_size]); std::vector> widened_argv_entries; widened_argv_entries.reserve(argv_size); for (size_t ii = 0; ii < argv_size; ++ii) { - widened_argv_entries.emplace_back(detail::widen_chars(argv[ii])); + widened_argv_entries.emplace_back(detail::widen_chars(safe_argv[ii])); if (!widened_argv_entries.back()) { // A null here indicates a character-encoding failure or the python // interpreter out of memory. Give up. diff --git a/pybind11/include/pybind11/eval.h b/pybind11/include/pybind11/eval.h index 3ed1b5a4a..bd5f981f5 100644 --- a/pybind11/include/pybind11/eval.h +++ b/pybind11/include/pybind11/eval.h @@ -19,7 +19,7 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(detail) inline void ensure_builtins_in_globals(object &global) { -#if defined(PYPY_VERSION) +#if defined(PYPY_VERSION) || PY_VERSION_HEX < 0x03080000 // Running exec and eval adds `builtins` module under `__builtins__` key to // globals if not yet present. Python 3.8 made PyRun_String behave // similarly. Let's also do that for older versions, for consistency. This @@ -94,18 +94,18 @@ void exec(const char (&s)[N], object global = globals(), object local = object() eval(s, std::move(global), std::move(local)); } -#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON) +#if defined(PYPY_VERSION) template object eval_file(str, object, object) { - pybind11_fail("eval_file not supported in this interpreter. Use eval"); + pybind11_fail("eval_file not supported in PyPy3. Use eval"); } template object eval_file(str, object) { - pybind11_fail("eval_file not supported in this interpreter. Use eval"); + pybind11_fail("eval_file not supported in PyPy3. Use eval"); } template object eval_file(str) { - pybind11_fail("eval_file not supported in this interpreter. Use eval"); + pybind11_fail("eval_file not supported in PyPy3. Use eval"); } #else template diff --git a/pybind11/include/pybind11/gil.h b/pybind11/include/pybind11/gil.h index 888810493..6b0edaee4 100644 --- a/pybind11/include/pybind11/gil.h +++ b/pybind11/include/pybind11/gil.h @@ -147,7 +147,9 @@ public: // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer) tstate = PyEval_SaveThread(); if (disassoc) { - auto key = internals.tstate; // NOLINT(readability-qualified-auto) + // Python >= 3.7 can remove this, it's an int before 3.7 + // NOLINTNEXTLINE(readability-qualified-auto) + auto key = internals.tstate; PYBIND11_TLS_DELETE_VALUE(key); } } @@ -171,7 +173,9 @@ public: PyEval_RestoreThread(tstate); } if (disassoc) { - auto key = detail::get_internals().tstate; // NOLINT(readability-qualified-auto) + // Python >= 3.7 can remove this, it's an int before 3.7 + // NOLINTNEXTLINE(readability-qualified-auto) + auto key = detail::get_internals().tstate; PYBIND11_TLS_REPLACE_VALUE(key, tstate); } } diff --git a/pybind11/include/pybind11/gil_safe_call_once.h b/pybind11/include/pybind11/gil_safe_call_once.h index 44e68f029..5f9e1b03c 100644 --- a/pybind11/include/pybind11/gil_safe_call_once.h +++ b/pybind11/include/pybind11/gil_safe_call_once.h @@ -46,8 +46,6 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) // get processed only when it is the main thread's turn again and it is running // normal Python code. However, this will be unnoticeable for quick call-once // functions, which is usually the case. -// -// For in-depth background, see docs/advanced/deadlock.md template class gil_safe_call_once_and_store { public: diff --git a/pybind11/include/pybind11/numpy.h b/pybind11/include/pybind11/numpy.h index 3a370fe5a..09894cf74 100644 --- a/pybind11/include/pybind11/numpy.h +++ b/pybind11/include/pybind11/numpy.h @@ -175,6 +175,7 @@ inline numpy_internals &get_numpy_internals() { PYBIND11_NOINLINE module_ import_numpy_core_submodule(const char *submodule_name) { module_ numpy = module_::import("numpy"); str version_string = numpy.attr("__version__"); + module_ numpy_lib = module_::import("numpy.lib"); object numpy_version = numpy_lib.attr("NumpyVersion")(version_string); int major_version = numpy_version.attr("major").cast(); @@ -211,7 +212,6 @@ constexpr int platform_lookup(int I, Ints... Is) { } struct npy_api { - // If you change this code, please review `normalized_dtype_num` below. enum constants { NPY_ARRAY_C_CONTIGUOUS_ = 0x0001, NPY_ARRAY_F_CONTIGUOUS_ = 0x0002, @@ -384,74 +384,6 @@ private: } }; -// This table normalizes typenums by mapping NPY_INT_, NPY_LONG, ... to NPY_INT32_, NPY_INT64, ... -// This is needed to correctly handle situations where multiple typenums map to the same type, -// e.g. NPY_LONG_ may be equivalent to NPY_INT_ or NPY_LONGLONG_ despite having a different -// typenum. The normalized typenum should always match the values used in npy_format_descriptor. -// If you change this code, please review `enum constants` above. -static constexpr int normalized_dtype_num[npy_api::NPY_VOID_ + 1] = { - // NPY_BOOL_ => - npy_api::NPY_BOOL_, - // NPY_BYTE_ => - npy_api::NPY_BYTE_, - // NPY_UBYTE_ => - npy_api::NPY_UBYTE_, - // NPY_SHORT_ => - npy_api::NPY_INT16_, - // NPY_USHORT_ => - npy_api::NPY_UINT16_, - // NPY_INT_ => - sizeof(int) == sizeof(std::int16_t) ? npy_api::NPY_INT16_ - : sizeof(int) == sizeof(std::int32_t) ? npy_api::NPY_INT32_ - : sizeof(int) == sizeof(std::int64_t) ? npy_api::NPY_INT64_ - : npy_api::NPY_INT_, - // NPY_UINT_ => - sizeof(unsigned int) == sizeof(std::uint16_t) ? npy_api::NPY_UINT16_ - : sizeof(unsigned int) == sizeof(std::uint32_t) ? npy_api::NPY_UINT32_ - : sizeof(unsigned int) == sizeof(std::uint64_t) ? npy_api::NPY_UINT64_ - : npy_api::NPY_UINT_, - // NPY_LONG_ => - sizeof(long) == sizeof(std::int16_t) ? npy_api::NPY_INT16_ - : sizeof(long) == sizeof(std::int32_t) ? npy_api::NPY_INT32_ - : sizeof(long) == sizeof(std::int64_t) ? npy_api::NPY_INT64_ - : npy_api::NPY_LONG_, - // NPY_ULONG_ => - sizeof(unsigned long) == sizeof(std::uint16_t) ? npy_api::NPY_UINT16_ - : sizeof(unsigned long) == sizeof(std::uint32_t) ? npy_api::NPY_UINT32_ - : sizeof(unsigned long) == sizeof(std::uint64_t) ? npy_api::NPY_UINT64_ - : npy_api::NPY_ULONG_, - // NPY_LONGLONG_ => - sizeof(long long) == sizeof(std::int16_t) ? npy_api::NPY_INT16_ - : sizeof(long long) == sizeof(std::int32_t) ? npy_api::NPY_INT32_ - : sizeof(long long) == sizeof(std::int64_t) ? npy_api::NPY_INT64_ - : npy_api::NPY_LONGLONG_, - // NPY_ULONGLONG_ => - sizeof(unsigned long long) == sizeof(std::uint16_t) ? npy_api::NPY_UINT16_ - : sizeof(unsigned long long) == sizeof(std::uint32_t) ? npy_api::NPY_UINT32_ - : sizeof(unsigned long long) == sizeof(std::uint64_t) ? npy_api::NPY_UINT64_ - : npy_api::NPY_ULONGLONG_, - // NPY_FLOAT_ => - npy_api::NPY_FLOAT_, - // NPY_DOUBLE_ => - npy_api::NPY_DOUBLE_, - // NPY_LONGDOUBLE_ => - npy_api::NPY_LONGDOUBLE_, - // NPY_CFLOAT_ => - npy_api::NPY_CFLOAT_, - // NPY_CDOUBLE_ => - npy_api::NPY_CDOUBLE_, - // NPY_CLONGDOUBLE_ => - npy_api::NPY_CLONGDOUBLE_, - // NPY_OBJECT_ => - npy_api::NPY_OBJECT_, - // NPY_STRING_ => - npy_api::NPY_STRING_, - // NPY_UNICODE_ => - npy_api::NPY_UNICODE_, - // NPY_VOID_ => - npy_api::NPY_VOID_, -}; - inline PyArray_Proxy *array_proxy(void *ptr) { return reinterpret_cast(ptr); } inline const PyArray_Proxy *array_proxy(const void *ptr) { @@ -752,13 +684,6 @@ public: return detail::npy_format_descriptor::type>::dtype(); } - /// Return the type number associated with a C++ type. - /// This is the constexpr equivalent of `dtype::of().num()`. - template - static constexpr int num_of() { - return detail::npy_format_descriptor::type>::value; - } - /// Size of the data type in bytes. #ifdef PYBIND11_NUMPY_1_ONLY ssize_t itemsize() const { return detail::array_descriptor_proxy(m_ptr)->elsize; } @@ -800,9 +725,7 @@ public: return detail::array_descriptor_proxy(m_ptr)->type; } - /// Type number of dtype. Note that different values may be returned for equivalent types, - /// e.g. even though ``long`` may be equivalent to ``int`` or ``long long``, they still have - /// different type numbers. Consider using `normalized_num` to avoid this. + /// type number of dtype. int num() const { // Note: The signature, `dtype::num` follows the naming of NumPy's public // Python API (i.e., ``dtype.num``), rather than its internal @@ -810,17 +733,6 @@ public: return detail::array_descriptor_proxy(m_ptr)->type_num; } - /// Type number of dtype, normalized to match the return value of `num_of` for equivalent - /// types. This function can be used to write switch statements that correctly handle - /// equivalent types with different type numbers. - int normalized_num() const { - int value = num(); - if (value >= 0 && value <= detail::npy_api::NPY_VOID_) { - return detail::normalized_dtype_num[value]; - } - return value; - } - /// Single character for byteorder char byteorder() const { return detail::array_descriptor_proxy(m_ptr)->byteorder; } @@ -1516,11 +1428,7 @@ public: }; template -struct npy_format_descriptor< - T, - enable_if_t::value - || ((std::is_same::value || std::is_same::value) - && sizeof(T) == sizeof(PyObject *))>> { +struct npy_format_descriptor::value>> { static constexpr auto name = const_name("object"); static constexpr int value = npy_api::NPY_OBJECT_; @@ -2182,8 +2090,7 @@ vectorize_helper vectorize_extractor(const Func &f, Retur template struct handle_type_name> { static constexpr auto name - = io_name("typing.Annotated[numpy.typing.ArrayLike, ", "numpy.typing.NDArray[") - + npy_format_descriptor::name + const_name("]"); + = const_name("numpy.ndarray[") + npy_format_descriptor::name + const_name("]"); }; PYBIND11_NAMESPACE_END(detail) diff --git a/pybind11/include/pybind11/pybind11.h b/pybind11/include/pybind11/pybind11.h index 4dee2c55f..5219c0ff8 100644 --- a/pybind11/include/pybind11/pybind11.h +++ b/pybind11/include/pybind11/pybind11.h @@ -10,10 +10,8 @@ #pragma once #include "detail/class.h" -#include "detail/dynamic_raw_ptr_cast_if_possible.h" #include "detail/exception_translation.h" #include "detail/init.h" -#include "detail/using_smart_holder.h" #include "attr.h" #include "gil.h" #include "gil_safe_call_once.h" @@ -24,17 +22,10 @@ #include #include #include -#include #include #include #include -// See PR #5448. This warning suppression is needed for the PYBIND11_OVERRIDE macro family. -// NOTE that this is NOT embedded in a push/pop pair because that is very difficult to achieve. -#if defined(__clang_major__) && __clang_major__ < 14 -PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments") -#endif - #if defined(__cpp_lib_launder) && !(defined(_MSC_VER) && (_MSC_VER < 1914)) # define PYBIND11_STD_LAUNDER std::launder # define PYBIND11_HAS_STD_LAUNDER 1 @@ -310,20 +301,9 @@ protected: constexpr bool has_kw_only_args = any_of...>::value, has_pos_only_args = any_of...>::value, has_arg_annotations = any_of...>::value; - constexpr bool has_is_method = any_of...>::value; - // The implicit `self` argument is not present and not counted in method definitions. - constexpr bool has_args = cast_in::args_pos >= 0; - constexpr bool is_method_with_self_arg_only = has_is_method && !has_args; static_assert(has_arg_annotations || !has_kw_only_args, "py::kw_only requires the use of argument annotations"); - static_assert(((/* Need `py::arg("arg_name")` annotation in function/method. */ - has_arg_annotations) - || (/* Allow methods with no arguments `def method(self, /): ...`. - * A method has at least one argument `self`. There can be no - * `py::arg` annotation. E.g. `class.def("method", py::pos_only())`. - */ - is_method_with_self_arg_only)) - || !has_pos_only_args, + static_assert(has_arg_annotations || !has_pos_only_args, "py::pos_only requires the use of argument annotations (for docstrings " "and aligning the annotations to the argument)"); @@ -443,13 +423,6 @@ protected: std::string signature; size_t type_index = 0, arg_index = 0; bool is_starred = false; - // `is_return_value.top()` is true if we are currently inside the return type of the - // signature. Using `@^`/`@$` we can force types to be arg/return types while `@!` pops - // back to the previous state. - std::stack is_return_value({false}); - // The following characters have special meaning in the signature parsing. Literals - // containing these are escaped with `!`. - std::string special_chars("!@%{}-"); for (const auto *pc = text; *pc != '\0'; ++pc) { const auto c = *pc; @@ -503,57 +476,7 @@ protected: } else { signature += detail::quote_cpp_type_name(detail::clean_type_id(t->name())); } - } else if (c == '!' && special_chars.find(*(pc + 1)) != std::string::npos) { - // typing::Literal escapes special characters with ! - signature += *++pc; - } else if (c == '@') { - // `@^ ... @!` and `@$ ... @!` are used to force arg/return value type (see - // typing::Callable/detail::arg_descr/detail::return_descr) - if (*(pc + 1) == '^') { - is_return_value.emplace(false); - ++pc; - continue; - } - if (*(pc + 1) == '$') { - is_return_value.emplace(true); - ++pc; - continue; - } - if (*(pc + 1) == '!') { - is_return_value.pop(); - ++pc; - continue; - } - // Handle types that differ depending on whether they appear - // in an argument or a return value position (see io_name). - // For named arguments (py::arg()) with noconvert set, return value type is used. - ++pc; - if (!is_return_value.top() - && !(arg_index < rec->args.size() && !rec->args[arg_index].convert)) { - while (*pc != '\0' && *pc != '@') { - signature += *pc++; - } - if (*pc == '@') { - ++pc; - } - while (*pc != '\0' && *pc != '@') { - ++pc; - } - } else { - while (*pc != '\0' && *pc != '@') { - ++pc; - } - if (*pc == '@') { - ++pc; - } - while (*pc != '\0' && *pc != '@') { - signature += *pc++; - } - } } else { - if (c == '-' && *(pc + 1) == '>') { - is_return_value.emplace(true); - } signature += c; } } @@ -650,7 +573,8 @@ protected: // chain. chain_start = rec; rec->next = chain; - auto rec_capsule = reinterpret_borrow(PyCFunction_GET_SELF(m_ptr)); + auto rec_capsule + = reinterpret_borrow(((PyCFunctionObject *) m_ptr)->m_self); rec_capsule.set_pointer(unique_rec.release()); guarded_strdup.release(); } else { @@ -710,11 +634,12 @@ protected: } } + /* Install docstring */ auto *func = (PyCFunctionObject *) m_ptr; + std::free(const_cast(func->m_ml->ml_doc)); // Install docstring if it's non-empty (when at least one option is enabled) - auto *doc = signatures.empty() ? nullptr : PYBIND11_COMPAT_STRDUP(signatures.c_str()); - std::free(const_cast(PYBIND11_PYCFUNCTION_GET_DOC(func))); - PYBIND11_PYCFUNCTION_SET_DOC(func, doc); + func->m_ml->ml_doc + = signatures.empty() ? nullptr : PYBIND11_COMPAT_STRDUP(signatures.c_str()); if (rec->is_method) { m_ptr = PYBIND11_INSTANCE_METHOD_NEW(m_ptr, rec->scope.ptr()); @@ -1382,7 +1307,7 @@ PYBIND11_NAMESPACE_BEGIN(detail) template <> struct handle_type_name { - static constexpr auto name = const_name("types.ModuleType"); + static constexpr auto name = const_name("module"); }; PYBIND11_NAMESPACE_END(detail) @@ -1446,8 +1371,8 @@ protected: tinfo->dealloc = rec.dealloc; tinfo->simple_type = true; tinfo->simple_ancestors = true; + tinfo->default_holder = rec.default_holder; tinfo->module_local = rec.module_local; - tinfo->holder_enum_v = rec.holder_enum_v; with_internals([&](internals &internals) { auto tindex = std::type_index(*rec.type); @@ -1457,17 +1382,7 @@ protected: } else { internals.registered_types_cpp[tindex] = tinfo; } - - PYBIND11_WARNING_PUSH -#if defined(__GNUC__) && __GNUC__ == 12 - // When using GCC 12 these warnings are disabled as they trigger - // false positive warnings. Discussed here: - // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=115824. - PYBIND11_WARNING_DISABLE_GCC("-Warray-bounds") - PYBIND11_WARNING_DISABLE_GCC("-Wstringop-overread") -#endif internals.registered_types_py[(PyTypeObject *) m_ptr] = {tinfo}; - PYBIND11_WARNING_POP }); if (rec.bases.size() > 1 || rec.multiple_inheritance) { @@ -1620,239 +1535,6 @@ auto method_adaptor(Return (Class::*pmf)(Args...) const) -> Return (Derived::*)( return pmf; } -PYBIND11_NAMESPACE_BEGIN(detail) - -// Helper for the property_cpp_function static member functions below. -// The only purpose of these functions is to support .def_readonly & .def_readwrite. -// In this context, the PM template parameter is certain to be a Pointer to a Member. -// The main purpose of must_be_member_function_pointer is to make this obvious, and to guard -// against accidents. As a side-effect, it also explains why the syntactical overhead for -// perfect forwarding is not needed. -template -using must_be_member_function_pointer = enable_if_t::value, int>; - -// Note that property_cpp_function is intentionally in the main pybind11 namespace, -// because user-defined specializations could be useful. - -// Classic (non-smart_holder) implementations for .def_readonly and .def_readwrite -// getter and setter functions. -// WARNING: This classic implementation can lead to dangling pointers for raw pointer members. -// See test_ptr() in tests/test_class_sh_property.py -// However, this implementation works as-is (and safely) for smart_holder std::shared_ptr members. -template -struct property_cpp_function_classic { - template = 0> - static cpp_function readonly(PM pm, const handle &hdl) { - return cpp_function([pm](const T &c) -> const D & { return c.*pm; }, is_method(hdl)); - } - - template = 0> - static cpp_function read(PM pm, const handle &hdl) { - return readonly(pm, hdl); - } - - template = 0> - static cpp_function write(PM pm, const handle &hdl) { - return cpp_function([pm](T &c, const D &value) { c.*pm = value; }, is_method(hdl)); - } -}; - -PYBIND11_NAMESPACE_END(detail) - -template -struct property_cpp_function : detail::property_cpp_function_classic {}; - -PYBIND11_NAMESPACE_BEGIN(detail) - -template -struct both_t_and_d_use_type_caster_base : std::false_type {}; - -// `T` is assumed to be equivalent to `intrinsic_t`. -// `D` is may or may not be equivalent to `intrinsic_t`. -template -struct both_t_and_d_use_type_caster_base< - T, - D, - enable_if_t, type_caster>, - std::is_base_of>, make_caster>>::value>> - : std::true_type {}; - -// Specialization for raw pointer members, using smart_holder if that is the class_ holder, -// or falling back to the classic implementation if not. -// WARNING: Like the classic implementation, this implementation can lead to dangling pointers. -// See test_ptr() in tests/test_class_sh_property.py -// However, the read functions return a shared_ptr to the member, emulating the PyCLIF approach: -// https://github.com/google/clif/blob/c371a6d4b28d25d53a16e6d2a6d97305fb1be25a/clif/python/instance.h#L233 -// This prevents disowning of the Python object owning the raw pointer member. -template -struct property_cpp_function_sh_raw_ptr_member { - using drp = typename std::remove_pointer::type; - - template = 0> - static cpp_function readonly(PM pm, const handle &hdl) { - type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); - if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { - return cpp_function( - [pm](handle c_hdl) -> std::shared_ptr { - std::shared_ptr c_sp - = type_caster>::shared_ptr_with_responsible_parent( - c_hdl); - D ptr = (*c_sp).*pm; - return std::shared_ptr(c_sp, ptr); - }, - is_method(hdl)); - } - return property_cpp_function_classic::readonly(pm, hdl); - } - - template = 0> - static cpp_function read(PM pm, const handle &hdl) { - return readonly(pm, hdl); - } - - template = 0> - static cpp_function write(PM pm, const handle &hdl) { - type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); - if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { - return cpp_function([pm](T &c, D value) { c.*pm = std::forward(std::move(value)); }, - is_method(hdl)); - } - return property_cpp_function_classic::write(pm, hdl); - } -}; - -// Specialization for members held by-value, using smart_holder if that is the class_ holder, -// or falling back to the classic implementation if not. -// The read functions return a shared_ptr to the member, emulating the PyCLIF approach: -// https://github.com/google/clif/blob/c371a6d4b28d25d53a16e6d2a6d97305fb1be25a/clif/python/instance.h#L233 -// This prevents disowning of the Python object owning the member. -template -struct property_cpp_function_sh_member_held_by_value { - template = 0> - static cpp_function readonly(PM pm, const handle &hdl) { - type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); - if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { - return cpp_function( - [pm](handle c_hdl) -> std::shared_ptr::type> { - std::shared_ptr c_sp - = type_caster>::shared_ptr_with_responsible_parent( - c_hdl); - return std::shared_ptr::type>(c_sp, - &(c_sp.get()->*pm)); - }, - is_method(hdl)); - } - return property_cpp_function_classic::readonly(pm, hdl); - } - - template = 0> - static cpp_function read(PM pm, const handle &hdl) { - type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); - if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { - return cpp_function( - [pm](handle c_hdl) -> std::shared_ptr { - std::shared_ptr c_sp - = type_caster>::shared_ptr_with_responsible_parent( - c_hdl); - return std::shared_ptr(c_sp, &(c_sp.get()->*pm)); - }, - is_method(hdl)); - } - return property_cpp_function_classic::read(pm, hdl); - } - - template = 0> - static cpp_function write(PM pm, const handle &hdl) { - type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); - if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { - return cpp_function([pm](T &c, const D &value) { c.*pm = value; }, is_method(hdl)); - } - return property_cpp_function_classic::write(pm, hdl); - } -}; - -// Specialization for std::unique_ptr members, using smart_holder if that is the class_ holder, -// or falling back to the classic implementation if not. -// read disowns the member unique_ptr. -// write disowns the passed Python object. -// readonly is disabled (static_assert) because there is no safe & intuitive way to make the member -// accessible as a Python object without disowning the member unique_ptr. A .def_readonly disowning -// the unique_ptr member is deemed highly prone to misunderstandings. -template -struct property_cpp_function_sh_unique_ptr_member { - template = 0> - static cpp_function readonly(PM, const handle &) { - static_assert(!is_instantiation::value, - "def_readonly cannot be used for std::unique_ptr members."); - return cpp_function{}; // Unreachable. - } - - template = 0> - static cpp_function read(PM pm, const handle &hdl) { - type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); - if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { - return cpp_function( - [pm](handle c_hdl) -> D { - std::shared_ptr c_sp - = type_caster>::shared_ptr_with_responsible_parent( - c_hdl); - return D{std::move(c_sp.get()->*pm)}; - }, - is_method(hdl)); - } - return property_cpp_function_classic::read(pm, hdl); - } - - template = 0> - static cpp_function write(PM pm, const handle &hdl) { - return cpp_function([pm](T &c, D &&value) { c.*pm = std::move(value); }, is_method(hdl)); - } -}; - -PYBIND11_NAMESPACE_END(detail) - -template -struct property_cpp_function< - T, - D, - detail::enable_if_t, - detail::both_t_and_d_use_type_caster_base>::value>> - : detail::property_cpp_function_sh_raw_ptr_member {}; - -template -struct property_cpp_function, - std::is_array, - detail::is_instantiation, - detail::is_instantiation>, - detail::both_t_and_d_use_type_caster_base>::value>> - : detail::property_cpp_function_sh_member_held_by_value {}; - -template -struct property_cpp_function< - T, - D, - detail::enable_if_t, - detail::both_t_and_d_use_type_caster_base>::value>> - : detail::property_cpp_function_sh_unique_ptr_member {}; - -#ifdef PYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE -// NOTE: THIS IS MEANT FOR STRESS-TESTING OR TRIAGING ONLY! -// Running the pybind11 unit tests with smart_holder as the default holder is to ensure -// that `py::smart_holder` / `py::classh` is backward-compatible with all pre-existing -// functionality. -// Be careful not to link translation units compiled with different default holders, because -// this will cause ODR violations (https://en.wikipedia.org/wiki/One_Definition_Rule). -template -using default_holder_type = smart_holder; -#else -template -using default_holder_type = std::unique_ptr; -#endif - template class class_ : public detail::generic_type { template @@ -1869,7 +1551,7 @@ public: using type = type_; using type_alias = detail::exactly_one_t; constexpr static bool has_alias = !std::is_void::value; - using holder_type = detail::exactly_one_t, options...>; + using holder_type = detail::exactly_one_t, options...>; static_assert(detail::all_of...>::value, "Unknown/invalid class_ template parameters provided"); @@ -1900,16 +1582,8 @@ public: record.type_align = alignof(conditional_t &); record.holder_size = sizeof(holder_type); record.init_instance = init_instance; - - if (detail::is_instantiation::value) { - record.holder_enum_v = detail::holder_enum_t::std_unique_ptr; - } else if (detail::is_instantiation::value) { - record.holder_enum_v = detail::holder_enum_t::std_shared_ptr; - } else if (std::is_same::value) { - record.holder_enum_v = detail::holder_enum_t::smart_holder; - } else { - record.holder_enum_v = detail::holder_enum_t::custom_holder; - } + record.dealloc = dealloc; + record.default_holder = detail::is_instantiation::value; set_operator_new(&record); @@ -1919,12 +1593,6 @@ public: /* Process optional arguments, if any */ process_attributes::init(extra..., &record); - if (record.release_gil_before_calling_cpp_dtor) { - record.dealloc = dealloc_release_gil_before_calling_cpp_dtor; - } else { - record.dealloc = dealloc_without_manipulating_gil; - } - generic_type::initialize(record); if (has_alias) { @@ -2048,11 +1716,9 @@ public: class_ &def_readwrite(const char *name, D C::*pm, const Extra &...extra) { static_assert(std::is_same::value || std::is_base_of::value, "def_readwrite() requires a class member (or base class member)"); - def_property(name, - property_cpp_function::read(pm, *this), - property_cpp_function::write(pm, *this), - return_value_policy::reference_internal, - extra...); + cpp_function fget([pm](const type &c) -> const D & { return c.*pm; }, is_method(*this)), + fset([pm](type &c, const D &value) { c.*pm = value; }, is_method(*this)); + def_property(name, fget, fset, return_value_policy::reference_internal, extra...); return *this; } @@ -2060,10 +1726,8 @@ public: class_ &def_readonly(const char *name, const D C::*pm, const Extra &...extra) { static_assert(std::is_same::value || std::is_base_of::value, "def_readonly() requires a class member (or base class member)"); - def_property_readonly(name, - property_cpp_function::readonly(pm, *this), - return_value_policy::reference_internal, - extra...); + cpp_function fget([pm](const type &c) -> const D & { return c.*pm; }, is_method(*this)); + def_property_readonly(name, fget, return_value_policy::reference_internal, extra...); return *this; } @@ -2240,8 +1904,6 @@ private: /// instance. Should be called as soon as the `type` value_ptr is set for an instance. Takes /// an optional pointer to an existing holder to use; if not specified and the instance is /// `.owned`, a new holder will be constructed to manage the value pointer. - template ::value, int> = 0> static void init_instance(detail::instance *inst, const void *holder_ptr) { auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(type))); if (!v_h.instance_registered()) { @@ -2251,73 +1913,15 @@ private: init_holder(inst, v_h, (const holder_type *) holder_ptr, v_h.value_ptr()); } - template - static bool try_initialization_using_shared_from_this(holder_type *, WrappedType *, ...) { - return false; - } - - // Adopting existing approach used by type_caster_base, although it leads to somewhat fuzzy - // ownership semantics: if we detected via shared_from_this that a shared_ptr exists already, - // it is reused, irrespective of the return_value_policy in effect. - // "SomeBaseOfWrappedType" is needed because std::enable_shared_from_this is not necessarily a - // direct base of WrappedType. - template - static bool try_initialization_using_shared_from_this( - holder_type *uninitialized_location, - WrappedType *value_ptr_w_t, - const std::enable_shared_from_this *) { - auto shd_ptr = std::dynamic_pointer_cast( - detail::try_get_shared_from_this(value_ptr_w_t)); - if (!shd_ptr) { - return false; - } - // Note: inst->owned ignored. - new (uninitialized_location) holder_type(holder_type::from_shared_ptr(shd_ptr)); - return true; - } - - template ::value, int> = 0> - static void init_instance(detail::instance *inst, const void *holder_const_void_ptr) { - // Need for const_cast is a consequence of the type_info::init_instance type: - // void (*init_instance)(instance *, const void *); - auto *holder_void_ptr = const_cast(holder_const_void_ptr); - - auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(type))); - if (!v_h.instance_registered()) { - register_instance(inst, v_h.value_ptr(), v_h.type); - v_h.set_instance_registered(); - } - auto *uninitialized_location = std::addressof(v_h.holder()); - auto *value_ptr_w_t = v_h.value_ptr(); - // Try downcast from `type` to `type_alias`: - inst->is_alias - = detail::dynamic_raw_ptr_cast_if_possible(value_ptr_w_t) != nullptr; - if (holder_void_ptr) { - // Note: inst->owned ignored. - auto *holder_ptr = static_cast(holder_void_ptr); - new (uninitialized_location) holder_type(std::move(*holder_ptr)); - } else if (!try_initialization_using_shared_from_this( - uninitialized_location, value_ptr_w_t, value_ptr_w_t)) { - if (inst->owned) { - new (uninitialized_location) holder_type(holder_type::from_raw_ptr_take_ownership( - value_ptr_w_t, /*void_cast_raw_ptr*/ inst->is_alias)); - } else { - new (uninitialized_location) - holder_type(holder_type::from_raw_ptr_unowned(value_ptr_w_t)); - } - } - v_h.set_holder_constructed(); - } - - // Deallocates an instance; via holder, if constructed; otherwise via operator delete. - // NOTE: The Python error indicator needs to cleared BEFORE this function is called. - // This is because we could be deallocating while cleaning up after a Python exception. - // If the error indicator is not cleared but the C++ destructor code makes Python C API - // calls, those calls are likely to generate a new exception, and pybind11 will then - // throw `error_already_set` from the C++ destructor. This is forbidden and will - // trigger std::terminate(). - static void dealloc_impl(detail::value_and_holder &v_h) { + /// Deallocates an instance; via holder, if constructed; otherwise via operator delete. + static void dealloc(detail::value_and_holder &v_h) { + // We could be deallocating because we are cleaning up after a Python exception. + // If so, the Python error indicator will be set. We need to clear that before + // running the destructor, in case the destructor code calls more Python. + // If we don't, the Python API will exit with an exception, and pybind11 will + // throw error_already_set from the C++ destructor which is forbidden and triggers + // std::terminate(). + error_scope scope; if (v_h.holder_constructed()) { v_h.holder().~holder_type(); v_h.set_holder_constructed(false); @@ -2328,32 +1932,6 @@ private: v_h.value_ptr() = nullptr; } - static void dealloc_without_manipulating_gil(detail::value_and_holder &v_h) { - error_scope scope; - dealloc_impl(v_h); - } - - static void dealloc_release_gil_before_calling_cpp_dtor(detail::value_and_holder &v_h) { - error_scope scope; - // Intentionally not using `gil_scoped_release` because the non-simple - // version unconditionally calls `get_internals()`. - // `Py_BEGIN_ALLOW_THREADS`, `Py_END_ALLOW_THREADS` cannot be used - // because those macros include `{` and `}`. - PyThreadState *py_ts = PyEval_SaveThread(); - try { - dealloc_impl(v_h); - } catch (...) { - // This code path is expected to be unreachable unless there is a - // bug in pybind11 itself. - // An alternative would be to mark this function, or - // `dealloc_impl()`, with `nothrow`, but that would be a subtle - // behavior change and could make debugging more difficult. - PyEval_RestoreThread(py_ts); - throw; - } - PyEval_RestoreThread(py_ts); - } - static detail::function_record *get_function_record(handle h) { h = detail::get_function(h); if (!h) { @@ -2375,11 +1953,6 @@ private: } }; -// Supports easier switching between py::class_ and py::class_: -// users can simply replace the `_` in `class_` with `h` or vice versa. -template -using classh = class_; - /// Binds an existing constructor taking arguments Args... template detail::initimpl::constructor init() { @@ -2441,11 +2014,9 @@ struct enum_base { .format(std::move(type_name), enum_name(arg), int_(arg)); }, name("__repr__"), - is_method(m_base), - pos_only()); + is_method(m_base)); - m_base.attr("name") - = property(cpp_function(&enum_name, name("name"), is_method(m_base), pos_only())); + m_base.attr("name") = property(cpp_function(&enum_name, name("name"), is_method(m_base))); m_base.attr("__str__") = cpp_function( [](handle arg) -> str { @@ -2453,8 +2024,7 @@ struct enum_base { return pybind11::str("{}.{}").format(std::move(type_name), enum_name(arg)); }, name("__str__"), - is_method(m_base), - pos_only()); + is_method(m_base)); if (options::show_enum_members_docstring()) { m_base.attr("__doc__") = static_property( @@ -2509,8 +2079,7 @@ struct enum_base { }, \ name(op), \ is_method(m_base), \ - arg("other"), \ - pos_only()) + arg("other")) #define PYBIND11_ENUM_OP_CONV(op, expr) \ m_base.attr(op) = cpp_function( \ @@ -2520,8 +2089,7 @@ struct enum_base { }, \ name(op), \ is_method(m_base), \ - arg("other"), \ - pos_only()) + arg("other")) #define PYBIND11_ENUM_OP_CONV_LHS(op, expr) \ m_base.attr(op) = cpp_function( \ @@ -2531,8 +2099,7 @@ struct enum_base { }, \ name(op), \ is_method(m_base), \ - arg("other"), \ - pos_only()) + arg("other")) if (is_convertible) { PYBIND11_ENUM_OP_CONV_LHS("__eq__", !b.is_none() && a.equal(b)); @@ -2552,8 +2119,7 @@ struct enum_base { m_base.attr("__invert__") = cpp_function([](const object &arg) { return ~(int_(arg)); }, name("__invert__"), - is_method(m_base), - pos_only()); + is_method(m_base)); } } else { PYBIND11_ENUM_OP_STRICT("__eq__", int_(a).equal(int_(b)), return false); @@ -2573,15 +2139,11 @@ struct enum_base { #undef PYBIND11_ENUM_OP_CONV #undef PYBIND11_ENUM_OP_STRICT - m_base.attr("__getstate__") = cpp_function([](const object &arg) { return int_(arg); }, - name("__getstate__"), - is_method(m_base), - pos_only()); + m_base.attr("__getstate__") = cpp_function( + [](const object &arg) { return int_(arg); }, name("__getstate__"), is_method(m_base)); - m_base.attr("__hash__") = cpp_function([](const object &arg) { return int_(arg); }, - name("__hash__"), - is_method(m_base), - pos_only()); + m_base.attr("__hash__") = cpp_function( + [](const object &arg) { return int_(arg); }, name("__hash__"), is_method(m_base)); } PYBIND11_NOINLINE void value(char const *name_, object value, const char *doc = nullptr) { @@ -2673,9 +2235,9 @@ public: m_base.init(is_arithmetic, is_convertible); def(init([](Scalar i) { return static_cast(i); }), arg("value")); - def_property_readonly("value", [](Type value) { return (Scalar) value; }, pos_only()); - def("__int__", [](Type value) { return (Scalar) value; }, pos_only()); - def("__index__", [](Type value) { return (Scalar) value; }, pos_only()); + def_property_readonly("value", [](Type value) { return (Scalar) value; }); + def("__int__", [](Type value) { return (Scalar) value; }); + def("__index__", [](Type value) { return (Scalar) value; }); attr("__setstate__") = cpp_function( [](detail::value_and_holder &v_h, Scalar arg) { detail::initimpl::setstate( @@ -2684,8 +2246,7 @@ public: detail::is_new_style_constructor(), pybind11::name("__setstate__"), is_method(*this), - arg("state"), - pos_only()); + arg("state")); } /// Export enumeration entries into the parent scope @@ -2757,20 +2318,13 @@ keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret) { inline std::pair all_type_info_get_cache(PyTypeObject *type) { auto res = with_internals([type](internals &internals) { - auto ins = internals - .registered_types_py + return internals + .registered_types_py #ifdef __cpp_lib_unordered_map_try_emplace - .try_emplace(type); + .try_emplace(type); #else - .emplace(type, std::vector()); + .emplace(type, std::vector()); #endif - if (ins.second) { - // For free-threading mode, this call must be under - // the with_internals() mutex lock, to avoid that other threads - // continue running with the empty ins.first->second. - all_type_info_populate(type, ins.first->second); - } - return ins; }); if (res.second) { // New cache entry created; set up a weak reference to automatically remove it if the type @@ -2871,8 +2425,7 @@ iterator make_iterator_impl(Iterator first, Sentinel last, Extra &&...extra) { if (!detail::get_type_info(typeid(state), false)) { class_(handle(), "iterator", pybind11::module_local()) - .def( - "__iter__", [](state &s) -> state & { return s; }, pos_only()) + .def("__iter__", [](state &s) -> state & { return s; }) .def( "__next__", [](state &s) -> ValueType { @@ -2889,7 +2442,6 @@ iterator make_iterator_impl(Iterator first, Sentinel last, Extra &&...extra) { // NOLINTNEXTLINE(readability-const-return-type) // PR #3263 }, std::forward(extra)..., - pos_only(), Policy); } @@ -3024,12 +2576,10 @@ void implicitly_convertible() { } inline void register_exception_translator(ExceptionTranslator &&translator) { - detail::with_exception_translators( - [&](std::forward_list &exception_translators, - std::forward_list &local_exception_translators) { - (void) local_exception_translators; - exception_translators.push_front(std::forward(translator)); - }); + detail::with_internals([&](detail::internals &internals) { + internals.registered_exception_translators.push_front( + std::forward(translator)); + }); } /** @@ -3039,12 +2589,11 @@ inline void register_exception_translator(ExceptionTranslator &&translator) { * the exception. */ inline void register_local_exception_translator(ExceptionTranslator &&translator) { - detail::with_exception_translators( - [&](std::forward_list &exception_translators, - std::forward_list &local_exception_translators) { - (void) exception_translators; - local_exception_translators.push_front(std::forward(translator)); - }); + detail::with_internals([&](detail::internals &internals) { + (void) internals; + detail::get_local_internals().registered_exception_translators.push_front( + std::forward(translator)); + }); } /** @@ -3218,8 +2767,8 @@ get_type_override(const void *this_ptr, const type_info *this_type, const char * } /* Don't call dispatch code if invoked from overridden function. - Unfortunately this doesn't work on PyPy and GraalPy. */ -#if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON) + Unfortunately this doesn't work on PyPy. */ +#if !defined(PYPY_VERSION) # if PY_VERSION_HEX >= 0x03090000 PyFrameObject *frame = PyThreadState_GetFrame(PyThreadState_Get()); if (frame != nullptr) { diff --git a/pybind11/include/pybind11/pytypes.h b/pybind11/include/pybind11/pytypes.h index 92e0a81f4..1e76d7bc1 100644 --- a/pybind11/include/pybind11/pytypes.h +++ b/pybind11/include/pybind11/pytypes.h @@ -113,17 +113,6 @@ public: /// See above (the only difference is that the key is provided as a string literal) str_attr_accessor attr(const char *key) const; - /** \rst - Similar to the above attr functions with the difference that the templated Type - is used to set the `__annotations__` dict value to the corresponding key. Worth noting - that attr_with_type_hint is implemented in cast.h. - \endrst */ - template - obj_attr_accessor attr_with_type_hint(handle key) const; - /// See above (the only difference is that the key is provided as a string literal) - template - str_attr_accessor attr_with_type_hint(const char *key) const; - /** \rst Matches * unpacking in Python, e.g. to unpack arguments out of a ``tuple`` or ``list`` for a function call. Applying another * to the result yields @@ -193,9 +182,6 @@ public: /// Get or set the object's docstring, i.e. ``obj.__doc__``. str_attr_accessor doc() const; - /// Get or set the object's annotations, i.e. ``obj.__annotations__``. - object annotations() const; - /// Return the object's current reference count ssize_t ref_count() const { #ifdef PYPY_VERSION @@ -657,7 +643,7 @@ struct error_fetch_and_normalize { bool have_trace = false; if (m_trace) { -#if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON) +#if !defined(PYPY_VERSION) auto *tb = reinterpret_cast(m_trace.ptr()); // Get the deepest trace possible. @@ -1370,7 +1356,7 @@ inline bool PyUnicode_Check_Permissive(PyObject *o) { # define PYBIND11_STR_CHECK_FUN PyUnicode_Check #endif -inline bool PyStaticMethod_Check(PyObject *o) { return Py_TYPE(o) == &PyStaticMethod_Type; } +inline bool PyStaticMethod_Check(PyObject *o) { return o->ob_type == &PyStaticMethod_Type; } class kwargs_proxy : public handle { public: @@ -1484,17 +1470,11 @@ public: PYBIND11_OBJECT_DEFAULT(iterator, object, PyIter_Check) iterator &operator++() { - init(); advance(); return *this; } iterator operator++(int) { - // Note: We must call init() first so that rv.value is - // the same as this->value just before calling advance(). - // Otherwise, dereferencing the returned iterator may call - // advance() again and return the 3rd item instead of the 1st. - init(); auto rv = *this; advance(); return rv; @@ -1502,12 +1482,15 @@ public: // NOLINTNEXTLINE(readability-const-return-type) // PR #3263 reference operator*() const { - init(); + if (m_ptr && !value.ptr()) { + auto &self = const_cast(*this); + self.advance(); + } return value; } pointer operator->() const { - init(); + operator*(); return &value; } @@ -1530,13 +1513,6 @@ public: friend bool operator!=(const iterator &a, const iterator &b) { return a->ptr() != b->ptr(); } private: - void init() const { - if (m_ptr && !value.ptr()) { - auto &self = const_cast(*this); - self.advance(); - } - } - void advance() { value = reinterpret_steal(PyIter_Next(m_ptr)); if (value.ptr() == nullptr && PyErr_Occurred()) { @@ -2240,18 +2216,6 @@ class kwargs : public dict { PYBIND11_OBJECT_DEFAULT(kwargs, dict, PyDict_Check) }; -// Subclasses of args and kwargs to support type hinting -// as defined in PEP 484. See #5357 for more info. -template -class Args : public args { - using args::args; -}; - -template -class KWArgs : public kwargs { - using kwargs::kwargs; -}; - class anyset : public object { public: PYBIND11_OBJECT(anyset, object, PyAnySet_Check) @@ -2572,19 +2536,6 @@ str_attr_accessor object_api::doc() const { return attr("__doc__"); } -template -object object_api::annotations() const { -#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION <= 9 - // https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older - if (!hasattr(derived(), "__annotations__")) { - setattr(derived(), "__annotations__", dict()); - } - return attr("__annotations__"); -#else - return getattr(derived(), "__annotations__", dict()); -#endif -} - template handle object_api::get_type() const { return type::handle_of(derived()); diff --git a/pybind11/include/pybind11/stl.h b/pybind11/include/pybind11/stl.h index 6a148e740..71bc5902e 100644 --- a/pybind11/include/pybind11/stl.h +++ b/pybind11/include/pybind11/stl.h @@ -11,14 +11,10 @@ #include "pybind11.h" #include "detail/common.h" -#include "detail/descr.h" -#include "detail/type_caster_base.h" #include -#include #include #include -#include #include #include #include @@ -39,89 +35,6 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(detail) -// -// Begin: Equivalent of -// https://github.com/google/clif/blob/ae4eee1de07cdf115c0c9bf9fec9ff28efce6f6c/clif/python/runtime.cc#L388-L438 -/* -The three `PyObjectTypeIsConvertibleTo*()` functions below are -the result of converging the behaviors of pybind11 and PyCLIF -(http://github.com/google/clif). - -Originally PyCLIF was extremely far on the permissive side of the spectrum, -while pybind11 was very far on the strict side. Originally PyCLIF accepted any -Python iterable as input for a C++ `vector`/`set`/`map` argument, as long as -the elements were convertible. The obvious (in hindsight) problem was that -any empty Python iterable could be passed to any of these C++ types, e.g. `{}` -was accepted for C++ `vector`/`set` arguments, or `[]` for C++ `map` arguments. - -The functions below strike a practical permissive-vs-strict compromise, -informed by tens of thousands of use cases in the wild. A main objective is -to prevent accidents and improve readability: - -- Python literals must match the C++ types. - -- For C++ `set`: The potentially reducing conversion from a Python sequence - (e.g. Python `list` or `tuple`) to a C++ `set` must be explicit, by going - through a Python `set`. - -- However, a Python `set` can still be passed to a C++ `vector`. The rationale - is that this conversion is not reducing. Implicit conversions of this kind - are also fairly commonly used, therefore enforcing explicit conversions - would have an unfavorable cost : benefit ratio; more sloppily speaking, - such an enforcement would be more annoying than helpful. -*/ - -inline bool PyObjectIsInstanceWithOneOfTpNames(PyObject *obj, - std::initializer_list tp_names) { - if (PyType_Check(obj)) { - return false; - } - const char *obj_tp_name = Py_TYPE(obj)->tp_name; - for (const auto *tp_name : tp_names) { - if (std::strcmp(obj_tp_name, tp_name) == 0) { - return true; - } - } - return false; -} - -inline bool PyObjectTypeIsConvertibleToStdVector(PyObject *obj) { - if (PySequence_Check(obj) != 0) { - return !PyUnicode_Check(obj) && !PyBytes_Check(obj); - } - return (PyGen_Check(obj) != 0) || (PyAnySet_Check(obj) != 0) - || PyObjectIsInstanceWithOneOfTpNames( - obj, {"dict_keys", "dict_values", "dict_items", "map", "zip"}); -} - -inline bool PyObjectTypeIsConvertibleToStdSet(PyObject *obj) { - return (PyAnySet_Check(obj) != 0) || PyObjectIsInstanceWithOneOfTpNames(obj, {"dict_keys"}); -} - -inline bool PyObjectTypeIsConvertibleToStdMap(PyObject *obj) { - if (PyDict_Check(obj)) { - return true; - } - // Implicit requirement in the conditions below: - // A type with `.__getitem__()` & `.items()` methods must implement these - // to be compatible with https://docs.python.org/3/c-api/mapping.html - if (PyMapping_Check(obj) == 0) { - return false; - } - PyObject *items = PyObject_GetAttrString(obj, "items"); - if (items == nullptr) { - PyErr_Clear(); - return false; - } - bool is_convertible = (PyCallable_Check(items) != 0); - Py_DECREF(items); - return is_convertible; -} - -// -// End: Equivalent of clif/python/runtime.cc -// - /// Extracts an const lvalue reference or rvalue reference for U based on the type of T (e.g. for /// forwarding a container element). Typically used indirect via forwarded_type(), below. template @@ -153,10 +66,17 @@ private: } void reserve_maybe(const anyset &, void *) {} - bool convert_iterable(const iterable &itbl, bool convert) { - for (const auto &it : itbl) { +public: + bool load(handle src, bool convert) { + if (!isinstance(src)) { + return false; + } + auto s = reinterpret_borrow(src); + value.clear(); + reserve_maybe(s, &value); + for (auto entry : s) { key_conv conv; - if (!conv.load(it, convert)) { + if (!conv.load(entry, convert)) { return false; } value.insert(cast_op(std::move(conv))); @@ -164,29 +84,6 @@ private: return true; } - bool convert_anyset(anyset s, bool convert) { - value.clear(); - reserve_maybe(s, &value); - return convert_iterable(s, convert); - } - -public: - bool load(handle src, bool convert) { - if (!PyObjectTypeIsConvertibleToStdSet(src.ptr())) { - return false; - } - if (isinstance(src)) { - value.clear(); - return convert_anyset(reinterpret_borrow(src), convert); - } - if (!convert) { - return false; - } - assert(isinstance(src)); - value.clear(); - return convert_iterable(reinterpret_borrow(src), convert); - } - template static handle cast(T &&src, return_value_policy policy, handle parent) { if (!std::is_lvalue_reference::value) { @@ -218,10 +115,15 @@ private: } void reserve_maybe(const dict &, void *) {} - bool convert_elements(const dict &d, bool convert) { +public: + bool load(handle src, bool convert) { + if (!isinstance(src)) { + return false; + } + auto d = reinterpret_borrow(src); value.clear(); reserve_maybe(d, &value); - for (const auto &it : d) { + for (auto it : d) { key_conv kconv; value_conv vconv; if (!kconv.load(it.first.ptr(), convert) || !vconv.load(it.second.ptr(), convert)) { @@ -232,25 +134,6 @@ private: return true; } -public: - bool load(handle src, bool convert) { - if (!PyObjectTypeIsConvertibleToStdMap(src.ptr())) { - return false; - } - if (isinstance(src)) { - return convert_elements(reinterpret_borrow(src), convert); - } - if (!convert) { - return false; - } - auto items = reinterpret_steal(PyMapping_Items(src.ptr())); - if (!items) { - throw error_already_set(); - } - assert(isinstance(items)); - return convert_elements(dict(reinterpret_borrow(items)), convert); - } - template static handle cast(T &&src, return_value_policy policy, handle parent) { dict d; @@ -283,35 +166,13 @@ struct list_caster { using value_conv = make_caster; bool load(handle src, bool convert) { - if (!PyObjectTypeIsConvertibleToStdVector(src.ptr())) { + if (!isinstance(src) || isinstance(src) || isinstance(src)) { return false; } - if (isinstance(src)) { - return convert_elements(src, convert); - } - if (!convert) { - return false; - } - // Designed to be behavior-equivalent to passing tuple(src) from Python: - // The conversion to a tuple will first exhaust the generator object, to ensure that - // the generator is not left in an unpredictable (to the caller) partially-consumed - // state. - assert(isinstance(src)); - return convert_elements(tuple(reinterpret_borrow(src)), convert); - } - -private: - template ::value, int> = 0> - void reserve_maybe(const sequence &s, Type *) { - value.reserve(s.size()); - } - void reserve_maybe(const sequence &, void *) {} - - bool convert_elements(handle seq, bool convert) { - auto s = reinterpret_borrow(seq); + auto s = reinterpret_borrow(src); value.clear(); reserve_maybe(s, &value); - for (const auto &it : seq) { + for (const auto &it : s) { value_conv conv; if (!conv.load(it, convert)) { return false; @@ -321,6 +182,13 @@ private: return true; } +private: + template ::value, int> = 0> + void reserve_maybe(const sequence &s, Type *) { + value.reserve(s.size()); + } + void reserve_maybe(const sequence &, void *) {} + public: template static handle cast(T &&src, return_value_policy policy, handle parent) { @@ -352,87 +220,43 @@ struct type_caster> : list_caster struct type_caster> : list_caster, Type> {}; -template -ArrayType vector_to_array_impl(V &&v, index_sequence) { - return {{std::move(v[I])...}}; -} - -// Based on https://en.cppreference.com/w/cpp/container/array/to_array -template -ArrayType vector_to_array(V &&v) { - return vector_to_array_impl(std::forward(v), make_index_sequence{}); -} - template struct array_caster { using value_conv = make_caster; private: - std::unique_ptr value; + template + bool require_size(enable_if_t size) { + if (value.size() != size) { + value.resize(size); + } + return true; + } + template + bool require_size(enable_if_t size) { + return size == Size; + } - template = 0> - bool convert_elements(handle seq, bool convert) { - auto l = reinterpret_borrow(seq); - value.reset(new ArrayType{}); - // Using `resize` to preserve the behavior exactly as it was before PR #5305 - // For the `resize` to work, `Value` must be default constructible. - // For `std::valarray`, this is a requirement: - // https://en.cppreference.com/w/cpp/named_req/NumericType - value->resize(l.size()); +public: + bool load(handle src, bool convert) { + if (!isinstance(src)) { + return false; + } + auto l = reinterpret_borrow(src); + if (!require_size(l.size())) { + return false; + } size_t ctr = 0; for (const auto &it : l) { value_conv conv; if (!conv.load(it, convert)) { return false; } - (*value)[ctr++] = cast_op(std::move(conv)); + value[ctr++] = cast_op(std::move(conv)); } return true; } - template = 0> - bool convert_elements(handle seq, bool convert) { - auto l = reinterpret_borrow(seq); - if (l.size() != Size) { - return false; - } - // The `temp` storage is needed to support `Value` types that are not - // default-constructible. - // Deliberate choice: no template specializations, for simplicity, and - // because the compile time overhead for the specializations is deemed - // more significant than the runtime overhead for the `temp` storage. - std::vector temp; - temp.reserve(l.size()); - for (auto it : l) { - value_conv conv; - if (!conv.load(it, convert)) { - return false; - } - temp.emplace_back(cast_op(std::move(conv))); - } - value.reset(new ArrayType(vector_to_array(std::move(temp)))); - return true; - } - -public: - bool load(handle src, bool convert) { - if (!PyObjectTypeIsConvertibleToStdVector(src.ptr())) { - return false; - } - if (isinstance(src)) { - return convert_elements(src, convert); - } - if (!convert) { - return false; - } - // Designed to be behavior-equivalent to passing tuple(src) from Python: - // The conversion to a tuple will first exhaust the generator object, to ensure that - // the generator is not left in an unpredictable (to the caller) partially-consumed - // state. - assert(isinstance(src)); - return convert_elements(tuple(reinterpret_borrow(src)), convert); - } - template static handle cast(T &&src, return_value_policy policy, handle parent) { list l(src.size()); @@ -448,36 +272,12 @@ public: return l.release(); } - // Code copied from PYBIND11_TYPE_CASTER macro. - // Intentionally preserving the behavior exactly as it was before PR #5305 - template >::value, int> = 0> - static handle cast(T_ *src, return_value_policy policy, handle parent) { - if (!src) { - return none().release(); - } - if (policy == return_value_policy::take_ownership) { - auto h = cast(std::move(*src), policy, parent); - delete src; // WARNING: Assumes `src` was allocated with `new`. - return h; - } - return cast(*src, policy, parent); - } - - // NOLINTNEXTLINE(google-explicit-constructor) - operator ArrayType *() { return &(*value); } - // NOLINTNEXTLINE(google-explicit-constructor) - operator ArrayType &() { return *value; } - // NOLINTNEXTLINE(google-explicit-constructor) - operator ArrayType &&() && { return std::move(*value); } - - template - using cast_op_type = movable_cast_op_type; - - static constexpr auto name - = const_name(const_name(""), const_name("Annotated[")) + const_name("list[") - + value_conv::name + const_name("]") - + const_name( - const_name(""), const_name(", FixedSize(") + const_name() + const_name(")]")); + PYBIND11_TYPE_CASTER(ArrayType, + const_name(const_name(""), const_name("Annotated[")) + + const_name("list[") + value_conv::name + const_name("]") + + const_name(const_name(""), + const_name(", FixedSize(") + + const_name() + const_name(")]"))); }; template diff --git a/pybind11/include/pybind11/stl/filesystem.h b/pybind11/include/pybind11/stl/filesystem.h index fb8164e0d..c16a9ae5c 100644 --- a/pybind11/include/pybind11/stl/filesystem.h +++ b/pybind11/include/pybind11/stl/filesystem.h @@ -106,7 +106,7 @@ public: return true; } - PYBIND11_TYPE_CASTER(T, io_name("Union[os.PathLike, str, bytes]", "pathlib.Path")); + PYBIND11_TYPE_CASTER(T, const_name("os.PathLike")); }; #endif // PYBIND11_HAS_FILESYSTEM || defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM) diff --git a/pybind11/include/pybind11/stl_bind.h b/pybind11/include/pybind11/stl_bind.h index 3eb1e53f4..fcb48dea3 100644 --- a/pybind11/include/pybind11/stl_bind.h +++ b/pybind11/include/pybind11/stl_bind.h @@ -487,7 +487,7 @@ PYBIND11_NAMESPACE_END(detail) // // std::vector // -template , typename... Args> +template , typename... Args> class_ bind_vector(handle scope, std::string const &name, Args &&...args) { using Class_ = class_; @@ -694,43 +694,9 @@ struct ItemsViewImpl : public detail::items_view { Map ↦ }; -inline str format_message_key_error_key_object(handle py_key) { - str message = "pybind11::bind_map key"; - if (!py_key) { - return message; - } - try { - message = str(py_key); - } catch (const std::exception &) { - try { - message = repr(py_key); - } catch (const std::exception &) { - return message; - } - } - const ssize_t cut_length = 100; - if (len(message) > 2 * cut_length + 3) { - return str(message[slice(0, cut_length, 1)]) + str("✄✄✄") - + str(message[slice(-cut_length, static_cast(len(message)), 1)]); - } - return message; -} - -template -str format_message_key_error(const KeyType &key) { - object py_key; - try { - py_key = cast(key); - } catch (const std::exception &) { - do { // Trick to avoid "empty catch" warning/error. - } while (false); - } - return format_message_key_error_key_object(py_key); -} - PYBIND11_NAMESPACE_END(detail) -template , typename... Args> +template , typename... Args> class_ bind_map(handle scope, const std::string &name, Args &&...args) { using KeyType = typename Map::key_type; using MappedType = typename Map::mapped_type; @@ -819,8 +785,7 @@ class_ bind_map(handle scope, const std::string &name, Args && [](Map &m, const KeyType &k) -> MappedType & { auto it = m.find(k); if (it == m.end()) { - set_error(PyExc_KeyError, detail::format_message_key_error(k)); - throw error_already_set(); + throw key_error(); } return it->second; }, @@ -843,8 +808,7 @@ class_ bind_map(handle scope, const std::string &name, Args && cl.def("__delitem__", [](Map &m, const KeyType &k) { auto it = m.find(k); if (it == m.end()) { - set_error(PyExc_KeyError, detail::format_message_key_error(k)); - throw error_already_set(); + throw key_error(); } m.erase(it); }); diff --git a/pybind11/include/pybind11/trampoline_self_life_support.h b/pybind11/include/pybind11/trampoline_self_life_support.h deleted file mode 100644 index 484045bb1..000000000 --- a/pybind11/include/pybind11/trampoline_self_life_support.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2021 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 "detail/common.h" -#include "detail/using_smart_holder.h" -#include "detail/value_and_holder.h" - -PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) - -PYBIND11_NAMESPACE_BEGIN(detail) -// PYBIND11:REMINDER: Needs refactoring of existing pybind11 code. -inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo); -PYBIND11_NAMESPACE_END(detail) - -// The original core idea for this struct goes back to PyCLIF: -// https://github.com/google/clif/blob/07f95d7e69dca2fcf7022978a55ef3acff506c19/clif/python/runtime.cc#L37 -// URL provided here mainly to give proper credit. -struct trampoline_self_life_support { - detail::value_and_holder v_h; - - trampoline_self_life_support() = default; - - void activate_life_support(const detail::value_and_holder &v_h_) { - Py_INCREF((PyObject *) v_h_.inst); - v_h = v_h_; - } - - void deactivate_life_support() { - Py_DECREF((PyObject *) v_h.inst); - v_h = detail::value_and_holder(); - } - - ~trampoline_self_life_support() { - if (v_h.inst != nullptr && v_h.vh != nullptr) { - void *value_void_ptr = v_h.value_ptr(); - if (value_void_ptr != nullptr) { - PyGILState_STATE threadstate = PyGILState_Ensure(); - v_h.value_ptr() = nullptr; - v_h.holder().release_disowned(); - detail::deregister_instance(v_h.inst, value_void_ptr, v_h.type); - Py_DECREF((PyObject *) v_h.inst); // Must be after deregister. - PyGILState_Release(threadstate); - } - } - } - - // For the next two, the default implementations generate undefined behavior (ASAN failures - // manually verified). The reason is that v_h needs to be kept default-initialized. - trampoline_self_life_support(const trampoline_self_life_support &) {} - trampoline_self_life_support(trampoline_self_life_support &&) noexcept {} - - // These should never be needed (please provide test cases if you think they are). - trampoline_self_life_support &operator=(const trampoline_self_life_support &) = delete; - trampoline_self_life_support &operator=(trampoline_self_life_support &&) = delete; -}; - -PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/pybind11/include/pybind11/typing.h b/pybind11/include/pybind11/typing.h index c5f342aed..84aaf9f70 100644 --- a/pybind11/include/pybind11/typing.h +++ b/pybind11/include/pybind11/typing.h @@ -16,13 +16,6 @@ #include -#if defined(__cpp_nontype_template_args) && __cpp_nontype_template_args >= 201911L -# define PYBIND11_TYPING_H_HAS_STRING_LITERAL -# include -# include -# include -#endif - PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(typing) @@ -89,18 +82,6 @@ class Optional : public object { using object::object; }; -template -class Final : public object { - PYBIND11_OBJECT_DEFAULT(Final, object, PyObject_Type) - using object::object; -}; - -template -class ClassVar : public object { - PYBIND11_OBJECT_DEFAULT(ClassVar, object, PyObject_Type) - using object::object; -}; - template class TypeGuard : public bool_ { using bool_::bool_; @@ -119,7 +100,8 @@ class Never : public none { using none::none; }; -#if defined(PYBIND11_TYPING_H_HAS_STRING_LITERAL) +#if defined(__cpp_nontype_template_args) && __cpp_nontype_template_args >= 201911L +# define PYBIND11_TYPING_H_HAS_STRING_LITERAL template struct StringLiteral { constexpr StringLiteral(const char (&str)[N]) { std::copy_n(str, N, name); } @@ -194,19 +176,16 @@ template struct handle_type_name> { using retval_type = conditional_t::value, void_type, Return>; static constexpr auto name - = const_name("Callable[[") - + ::pybind11::detail::concat(::pybind11::detail::arg_descr(make_caster::name)...) - + const_name("], ") + ::pybind11::detail::return_descr(make_caster::name) - + const_name("]"); + = const_name("Callable[[") + ::pybind11::detail::concat(make_caster::name...) + + const_name("], ") + make_caster::name + const_name("]"); }; template struct handle_type_name> { // PEP 484 specifies this syntax for defining only return types of callables using retval_type = conditional_t::value, void_type, Return>; - static constexpr auto name = const_name("Callable[..., ") - + ::pybind11::detail::return_descr(make_caster::name) - + const_name("]"); + static constexpr auto name + = const_name("Callable[..., ") + make_caster::name + const_name("]"); }; template @@ -226,16 +205,6 @@ struct handle_type_name> { static constexpr auto name = const_name("Optional[") + make_caster::name + const_name("]"); }; -template -struct handle_type_name> { - static constexpr auto name = const_name("Final[") + make_caster::name + const_name("]"); -}; - -template -struct handle_type_name> { - static constexpr auto name = const_name("ClassVar[") + make_caster::name + const_name("]"); -}; - template struct handle_type_name> { static constexpr auto name = const_name("TypeGuard[") + make_caster::name + const_name("]"); @@ -257,35 +226,15 @@ struct handle_type_name { }; #if defined(PYBIND11_TYPING_H_HAS_STRING_LITERAL) -template -consteval auto sanitize_string_literal() { - constexpr std::string_view v(StrLit.name); - constexpr std::string_view special_chars("!@%{}-"); - constexpr auto num_special_chars = std::accumulate( - special_chars.begin(), special_chars.end(), (size_t) 0, [&v](auto acc, const char &c) { - return std::move(acc) + std::ranges::count(v, c); - }); - char result[v.size() + num_special_chars + 1]; - size_t i = 0; - for (auto c : StrLit.name) { - if (special_chars.find(c) != std::string_view::npos) { - result[i++] = '!'; - } - result[i++] = c; - } - return typing::StringLiteral(result); -} - template struct handle_type_name> { - static constexpr auto name - = const_name("Literal[") - + pybind11::detail::concat(const_name(sanitize_string_literal().name)...) - + const_name("]"); + static constexpr auto name = const_name("Literal[") + + pybind11::detail::concat(const_name(Literals.name)...) + + const_name("]"); }; template struct handle_type_name> { - static constexpr auto name = const_name(sanitize_string_literal().name); + static constexpr auto name = const_name(StrLit.name); }; #endif diff --git a/pybind11/include/pybind11/warnings.h b/pybind11/include/pybind11/warnings.h deleted file mode 100644 index 263b2990e..000000000 --- a/pybind11/include/pybind11/warnings.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - pybind11/warnings.h: Python warnings wrappers. - - Copyright (c) 2024 Jan Iwaszkiewicz - - 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 "pybind11.h" -#include "detail/common.h" - -PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) - -PYBIND11_NAMESPACE_BEGIN(detail) - -inline bool PyWarning_Check(PyObject *obj) { - int result = PyObject_IsSubclass(obj, PyExc_Warning); - if (result == 1) { - return true; - } - if (result == -1) { - raise_from(PyExc_SystemError, - "pybind11::detail::PyWarning_Check(): PyObject_IsSubclass() call failed."); - throw error_already_set(); - } - return false; -} - -PYBIND11_NAMESPACE_END(detail) - -PYBIND11_NAMESPACE_BEGIN(warnings) - -inline object -new_warning_type(handle scope, const char *name, handle base = PyExc_RuntimeWarning) { - if (!detail::PyWarning_Check(base.ptr())) { - pybind11_fail("pybind11::warnings::new_warning_type(): cannot create custom warning, base " - "must be a subclass of " - "PyExc_Warning!"); - } - if (hasattr(scope, name)) { - pybind11_fail("pybind11::warnings::new_warning_type(): an attribute with name \"" - + std::string(name) + "\" exists already."); - } - std::string full_name = scope.attr("__name__").cast() + std::string(".") + name; - handle h(PyErr_NewException(full_name.c_str(), base.ptr(), nullptr)); - if (!h) { - raise_from(PyExc_SystemError, - "pybind11::warnings::new_warning_type(): PyErr_NewException() call failed."); - throw error_already_set(); - } - auto obj = reinterpret_steal(h); - scope.attr(name) = obj; - return obj; -} - -// Similar to Python `warnings.warn()` -inline void -warn(const char *message, handle category = PyExc_RuntimeWarning, int stack_level = 2) { - if (!detail::PyWarning_Check(category.ptr())) { - pybind11_fail( - "pybind11::warnings::warn(): cannot raise warning, category must be a subclass of " - "PyExc_Warning!"); - } - - if (PyErr_WarnEx(category.ptr(), message, stack_level) == -1) { - throw error_already_set(); - } -} - -PYBIND11_NAMESPACE_END(warnings) - -PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/pybind11/pybind11/__init__.py b/pybind11/pybind11/__init__.py index e9d033c4b..b14660cae 100644 --- a/pybind11/pybind11/__init__.py +++ b/pybind11/pybind11/__init__.py @@ -2,8 +2,8 @@ from __future__ import annotations import sys -if sys.version_info < (3, 8): # noqa: UP036 - msg = "pybind11 does not support Python < 3.8. v2.13 was the last release supporting Python 3.7." +if sys.version_info < (3, 7): # noqa: UP036 + msg = "pybind11 does not support Python < 3.7. v2.12 was the last release supporting Python 3.6." raise ImportError(msg) diff --git a/pybind11/pybind11/__main__.py b/pybind11/pybind11/__main__.py index 28be9f165..0abc7e211 100644 --- a/pybind11/pybind11/__main__.py +++ b/pybind11/pybind11/__main__.py @@ -71,11 +71,6 @@ def main() -> None: action="store_true", help="Print the pkgconfig directory, ideal for setting $PKG_CONFIG_PATH.", ) - parser.add_argument( - "--extension-suffix", - action="store_true", - help="Print the extension for a Python module", - ) args = parser.parse_args() if not sys.argv[1:]: parser.print_help() @@ -85,8 +80,6 @@ def main() -> None: print(quote(get_cmake_dir())) if args.pkgconfigdir: print(quote(get_pkgconfig_dir())) - if args.extension_suffix: - print(sysconfig.get_config_var("EXT_SUFFIX")) if __name__ == "__main__": diff --git a/pybind11/pybind11/_version.py b/pybind11/pybind11/_version.py index 2dfe67616..c298836bc 100644 --- a/pybind11/pybind11/_version.py +++ b/pybind11/pybind11/_version.py @@ -8,5 +8,5 @@ def _to_int(s: str) -> int | str: return s -__version__ = "3.0.0.dev1" +__version__ = "2.13.6" version_info = tuple(_to_int(s) for s in __version__.split(".")) diff --git a/pybind11/pybind11/setup_helpers.py b/pybind11/pybind11/setup_helpers.py index f24291818..ced506f8c 100644 --- a/pybind11/pybind11/setup_helpers.py +++ b/pybind11/pybind11/setup_helpers.py @@ -249,7 +249,7 @@ def has_flag(compiler: Any, flag: str) -> bool: cpp_flag_cache = None -@lru_cache +@lru_cache() def auto_cpp_level(compiler: Any) -> str | int: """ Return the max supported C++ std level (17, 14, or 11). Returns latest on Windows. diff --git a/pybind11/pyproject.toml b/pybind11/pyproject.toml index 13dd04a51..71e7f5617 100644 --- a/pybind11/pyproject.toml +++ b/pybind11/pyproject.toml @@ -30,7 +30,7 @@ ignore_missing_imports = true [tool.pylint] -master.py-version = "3.8" +master.py-version = "3.7" reports.output-format = "colorized" messages_control.disable = [ "design", @@ -45,7 +45,7 @@ messages_control.disable = [ ] [tool.ruff] -target-version = "py38" +target-version = "py37" src = ["src"] [tool.ruff.lint] @@ -71,6 +71,7 @@ ignore = [ "PLR", # Design related pylint "E501", # Line too long (Black is enough) "PT011", # Too broad with raises in pytest + "PT004", # Fixture that doesn't return needs underscore (no, it is fine) "SIM118", # iter(x) is not always the same as iter(x.keys()) ] unfixable = ["T20"] diff --git a/pybind11/setup.cfg b/pybind11/setup.cfg index bb5b744aa..2beca780f 100644 --- a/pybind11/setup.cfg +++ b/pybind11/setup.cfg @@ -14,6 +14,7 @@ classifiers = Topic :: Utilities Programming Language :: C++ Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 @@ -38,5 +39,5 @@ project_urls = Chat = https://gitter.im/pybind/Lobby [options] -python_requires = >=3.8 +python_requires = >=3.7 zip_safe = False diff --git a/pybind11/setup.py b/pybind11/setup.py index c65bbc627..96563c1a5 100644 --- a/pybind11/setup.py +++ b/pybind11/setup.py @@ -144,10 +144,6 @@ with remove_output("pybind11/include", "pybind11/share"): stderr=sys.stderr, ) - # pkgconf-pypi needs pybind11/share/pkgconfig to be importable - Path("pybind11/share/__init__.py").touch() - Path("pybind11/share/pkgconfig/__init__.py").touch() - txt = get_and_replace(setup_py, version=version, extra_cmd=extra_cmd) code = compile(txt, setup_py, "exec") exec(code, {"SDist": SDist}) diff --git a/pybind11/tests/CMakeLists.txt b/pybind11/tests/CMakeLists.txt index e2fab9c13..81ec65821 100644 --- a/pybind11/tests/CMakeLists.txt +++ b/pybind11/tests/CMakeLists.txt @@ -5,7 +5,16 @@ # All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. -cmake_minimum_required(VERSION 3.15...3.30) +cmake_minimum_required(VERSION 3.5) + +# The `cmake_minimum_required(VERSION 3.5...3.29)` syntax does not work with +# some versions of VS that have a patched CMake 3.11. This forces us to emulate +# the behavior using the following workaround: +if(${CMAKE_VERSION} VERSION_LESS 3.29) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.29) +endif() # Filter out items; print an optional message if any items filtered. This ignores extensions. # @@ -67,8 +76,8 @@ project(pybind11_tests CXX) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../tools") option(PYBIND11_WERROR "Report all warnings as errors" OFF) -option(DOWNLOAD_EIGEN "Download EIGEN" OFF) -option(PYBIND11_CUDA_TESTS "Enable building CUDA tests" OFF) +option(DOWNLOAD_EIGEN "Download EIGEN (requires CMake 3.11+)" OFF) +option(PYBIND11_CUDA_TESTS "Enable building CUDA tests (requires CMake 3.12+)" OFF) set(PYBIND11_TEST_OVERRIDE "" CACHE STRING "Tests from ;-separated list of *.cpp files will be built instead of all tests") @@ -115,24 +124,6 @@ set(PYBIND11_TEST_FILES test_callbacks test_chrono test_class - test_class_release_gil_before_calling_cpp_dtor - test_class_sh_basic - test_class_sh_disowning - test_class_sh_disowning_mi - test_class_sh_factory_constructors - test_class_sh_inheritance - test_class_sh_mi_thunks - test_class_sh_property - test_class_sh_property_non_owning - test_class_sh_shared_ptr_copy_move - test_class_sh_trampoline_basic - test_class_sh_trampoline_self_life_support - test_class_sh_trampoline_shared_from_this - test_class_sh_trampoline_shared_ptr_cpp_arg - test_class_sh_trampoline_unique_ptr - test_class_sh_unique_ptr_custom_deleter - test_class_sh_unique_ptr_member - test_class_sh_virtual_py_cpp_mix test_const_name test_constants_and_functions test_copy_move @@ -140,7 +131,6 @@ set(PYBIND11_TEST_FILES test_custom_type_casters test_custom_type_setup test_docstring_options - test_docs_advanced_cast_custom test_eigen_matrix test_eigen_tensor test_enum @@ -174,8 +164,7 @@ set(PYBIND11_TEST_FILES test_unnamed_namespace_a test_unnamed_namespace_b test_vector_unique_ptr_member - test_virtual_functions - test_warnings) + test_virtual_functions) # Invoking cmake with something like: # cmake -DPYBIND11_TEST_OVERRIDE="test_callbacks.cpp;test_pickling.cpp" .. @@ -263,21 +252,25 @@ endif() if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) # Try loading via newer Eigen's Eigen3Config first (bypassing tools/FindEigen3.cmake). # Eigen 3.3.1+ exports a cmake 3.0+ target for handling dependency requirements, but also + # produces a fatal error if loaded from a pre-3.0 cmake. if(DOWNLOAD_EIGEN) - if(CMAKE_VERSION VERSION_LESS 3.18) - set(_opts) - else() - set(_opts SOURCE_SUBDIR no-cmake-build) + if(CMAKE_VERSION VERSION_LESS 3.11) + message(FATAL_ERROR "CMake 3.11+ required when using DOWNLOAD_EIGEN") endif() + include(FetchContent) FetchContent_Declare( eigen GIT_REPOSITORY "${PYBIND11_EIGEN_REPO}" - GIT_TAG "${PYBIND11_EIGEN_VERSION_HASH}" - ${_opts}) - FetchContent_MakeAvailable(eigen) - if(NOT CMAKE_VERSION VERSION_LESS 3.18) - set(EIGEN3_INCLUDE_DIR "${eigen_SOURCE_DIR}") + GIT_TAG "${PYBIND11_EIGEN_VERSION_HASH}") + + FetchContent_GetProperties(eigen) + if(NOT eigen_POPULATED) + message( + STATUS + "Downloading Eigen ${PYBIND11_EIGEN_VERSION_STRING} (${PYBIND11_EIGEN_VERSION_HASH}) from ${PYBIND11_EIGEN_REPO}" + ) + FetchContent_Populate(eigen) endif() set(EIGEN3_INCLUDE_DIR ${eigen_SOURCE_DIR}) @@ -325,7 +318,8 @@ if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I}) endif() - message(STATUS "Building tests WITHOUT Eigen, use -DDOWNLOAD_EIGEN=ON to download") + message( + STATUS "Building tests WITHOUT Eigen, use -DDOWNLOAD_EIGEN=ON on CMake 3.11+ to download") endif() endif() @@ -510,19 +504,15 @@ foreach(target ${test_targets}) if(SKBUILD) install(TARGETS ${target} LIBRARY DESTINATION .) endif() - - if("${target}" STREQUAL "exo_planet_c_api") - if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Intel|Clang|NVHPC)") - target_compile_options(${target} PRIVATE -fno-exceptions) - endif() - endif() endforeach() # Provide nice organisation in IDEs -source_group( - TREE "${CMAKE_CURRENT_SOURCE_DIR}/../include" - PREFIX "Header Files" - FILES ${PYBIND11_HEADERS}) +if(NOT CMAKE_VERSION VERSION_LESS 3.8) + source_group( + TREE "${CMAKE_CURRENT_SOURCE_DIR}/../include" + PREFIX "Header Files" + FILES ${PYBIND11_HEADERS}) +endif() # Make sure pytest is found or produce a warning pybind11_find_import(pytest VERSION 3.1) @@ -606,9 +596,6 @@ add_custom_command( ${CMAKE_CURRENT_BINARY_DIR}/sosize-$.txt) if(NOT PYBIND11_CUDA_TESTS) - # Test pure C++ code (not depending on Python). Provides the `test_pure_cpp` target. - add_subdirectory(pure_cpp) - # Test embedding the interpreter. Provides the `cpptest` target. add_subdirectory(test_embed) diff --git a/pybind11/tests/conftest.py b/pybind11/tests/conftest.py index a018a3f79..7de6c2ace 100644 --- a/pybind11/tests/conftest.py +++ b/pybind11/tests/conftest.py @@ -28,8 +28,8 @@ except Exception: @pytest.fixture(scope="session", autouse=True) def use_multiprocessing_forkserver_on_linux(): - if sys.platform != "linux" or sys.implementation.name == "graalpy": - # The default on Windows, macOS and GraalPy is "spawn": If it's not broken, don't fix it. + if sys.platform != "linux": + # The default on Windows and macOS is "spawn": If it's not broken, don't fix it. return # Full background: https://github.com/pybind/pybind11/issues/4105#issuecomment-1301004592 @@ -136,7 +136,7 @@ class Capture: return Output(self.err) -@pytest.fixture +@pytest.fixture() def capture(capsys): """Extended `capsys` with context manager and custom equality operators""" return Capture(capsys) @@ -172,7 +172,7 @@ def _sanitize_docstring(thing): return _sanitize_general(s) -@pytest.fixture +@pytest.fixture() def doc(): """Sanitize docstrings and add custom failure explanation""" return SanitizedString(_sanitize_docstring) @@ -184,7 +184,7 @@ def _sanitize_message(thing): return _hexadecimal.sub("0", s) -@pytest.fixture +@pytest.fixture() def msg(): """Sanitize messages and add custom failure explanation""" return SanitizedString(_sanitize_message) @@ -198,11 +198,10 @@ def pytest_assertrepr_compare(op, left, right): # noqa: ARG001 def gc_collect(): - """Run the garbage collector three times (needed when running + """Run the garbage collector twice (needed when running reference counting tests with PyPy)""" gc.collect() gc.collect() - gc.collect() def pytest_configure(): @@ -212,9 +211,9 @@ def pytest_configure(): def pytest_report_header(config): del config # Unused. - assert pybind11_tests.compiler_info is not None, ( - "Please update pybind11_tests.cpp if this assert fails." - ) + assert ( + pybind11_tests.compiler_info is not None + ), "Please update pybind11_tests.cpp if this assert fails." return ( "C++ Info:" f" {pybind11_tests.compiler_info}" diff --git a/pybind11/tests/constructor_stats.h b/pybind11/tests/constructor_stats.h index 352b1b6ca..9a5754fed 100644 --- a/pybind11/tests/constructor_stats.h +++ b/pybind11/tests/constructor_stats.h @@ -312,16 +312,8 @@ void print_created(T *inst, Values &&...values) { } template void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values - /* - * On GraalPy, destructors can trigger anywhere and this can cause random - * failures in unrelated tests. - */ -#if !defined(GRAALVM_PYTHON) print_constr_details(inst, "destroyed", values...); track_destroyed(inst); -#else - py::detail::silence_unused_warnings(inst, values...); -#endif } template void print_values(T *inst, Values &&...values) { diff --git a/pybind11/tests/custom_exceptions.py b/pybind11/tests/custom_exceptions.py deleted file mode 100644 index b1a092d76..000000000 --- a/pybind11/tests/custom_exceptions.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import annotations - - -class PythonMyException7(Exception): - def __init__(self, message): - self.message = message - super().__init__(message) - - def __str__(self): - return "[PythonMyException7]: " + self.message.a diff --git a/pybind11/tests/env.py b/pybind11/tests/env.py index b513e455d..9f5347f2e 100644 --- a/pybind11/tests/env.py +++ b/pybind11/tests/env.py @@ -12,7 +12,6 @@ WIN = sys.platform.startswith("win32") or sys.platform.startswith("cygwin") CPYTHON = platform.python_implementation() == "CPython" PYPY = platform.python_implementation() == "PyPy" -GRAALPY = sys.implementation.name == "graalpy" PY_GIL_DISABLED = bool(sysconfig.get_config_var("Py_GIL_DISABLED")) diff --git a/pybind11/tests/exo_planet_c_api.cpp b/pybind11/tests/exo_planet_c_api.cpp index 151010717..3bde0b27b 100644 --- a/pybind11/tests/exo_planet_c_api.cpp +++ b/pybind11/tests/exo_planet_c_api.cpp @@ -1,33 +1,59 @@ // Copyright (c) 2024 The pybind Community. -// In production situations it is totally fine to build with -// C++ Exception Handling enabled. However, here we want to ensure that -// C++ Exception Handling is not required. -#if defined(_MSC_VER) || defined(__EMSCRIPTEN__) -// Too much trouble making the required cmake changes (see PR #5375). -#else -# ifdef __cpp_exceptions -// https://isocpp.org/std/standing-documents/sd-6-sg10-feature-test-recommendations#__cpp_exceptions -# error This test is meant to be built with C++ Exception Handling disabled, but __cpp_exceptions is defined. -# endif -# ifdef __EXCEPTIONS -// https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html -# error This test is meant to be built with C++ Exception Handling disabled, but __EXCEPTIONS is defined. -# endif -#endif - // THIS MUST STAY AT THE TOP! -#include // VERY light-weight dependency. +#include // EXCLUSIVELY for PYBIND11_PLATFORM_ABI_ID +// Potential future direction to maximize reusability: +// (e.g. for use from SWIG, Cython, PyCLIF, nanobind): +// #include +// This would only depend on: +// 1. A C++ compiler, WITHOUT requiring -fexceptions. +// 2. Python.h #include "test_cpp_conduit_traveler_types.h" #include +#include namespace { +void *get_cpp_conduit_void_ptr(PyObject *py_obj, const std::type_info *cpp_type_info) { + PyObject *cpp_type_info_capsule + = PyCapsule_New(const_cast(static_cast(cpp_type_info)), + typeid(std::type_info).name(), + nullptr); + if (cpp_type_info_capsule == nullptr) { + return nullptr; + } + PyObject *cpp_conduit = PyObject_CallMethod(py_obj, + "_pybind11_conduit_v1_", + "yOy", + PYBIND11_PLATFORM_ABI_ID, + cpp_type_info_capsule, + "raw_pointer_ephemeral"); + Py_DECREF(cpp_type_info_capsule); + if (cpp_conduit == nullptr) { + return nullptr; + } + void *void_ptr = PyCapsule_GetPointer(cpp_conduit, cpp_type_info->name()); + Py_DECREF(cpp_conduit); + if (PyErr_Occurred()) { + return nullptr; + } + return void_ptr; +} + +template +T *get_cpp_conduit_type_ptr(PyObject *py_obj) { + void *void_ptr = get_cpp_conduit_void_ptr(py_obj, &typeid(T)); + if (void_ptr == nullptr) { + return nullptr; + } + return static_cast(void_ptr); +} + extern "C" PyObject *wrapGetLuggage(PyObject * /*self*/, PyObject *traveler) { - const auto *cpp_traveler = pybind11_conduit_v1::get_type_pointer_ephemeral< - pybind11_tests::test_cpp_conduit::Traveler>(traveler); + const auto *cpp_traveler + = get_cpp_conduit_type_ptr(traveler); if (cpp_traveler == nullptr) { return nullptr; } @@ -35,8 +61,9 @@ extern "C" PyObject *wrapGetLuggage(PyObject * /*self*/, PyObject *traveler) { } extern "C" PyObject *wrapGetPoints(PyObject * /*self*/, PyObject *premium_traveler) { - const auto *cpp_premium_traveler = pybind11_conduit_v1::get_type_pointer_ephemeral< - pybind11_tests::test_cpp_conduit::PremiumTraveler>(premium_traveler); + const auto *cpp_premium_traveler + = get_cpp_conduit_type_ptr( + premium_traveler); if (cpp_premium_traveler == nullptr) { return nullptr; } diff --git a/pybind11/tests/extra_python_package/test_files.py b/pybind11/tests/extra_python_package/test_files.py index 5d7299c2a..5752a32ab 100644 --- a/pybind11/tests/extra_python_package/test_files.py +++ b/pybind11/tests/extra_python_package/test_files.py @@ -9,6 +9,7 @@ import tarfile import zipfile # These tests must be run explicitly +# They require CMake 3.15+ (--install) DIR = os.path.abspath(os.path.dirname(__file__)) MAIN_DIR = os.path.dirname(os.path.dirname(DIR)) @@ -45,17 +46,8 @@ main_headers = { "include/pybind11/pytypes.h", "include/pybind11/stl.h", "include/pybind11/stl_bind.h", - "include/pybind11/trampoline_self_life_support.h", "include/pybind11/type_caster_pyobject_ptr.h", "include/pybind11/typing.h", - "include/pybind11/warnings.h", -} - -conduit_headers = { - "include/pybind11/conduit/README.txt", - "include/pybind11/conduit/pybind11_conduit_v1.h", - "include/pybind11/conduit/pybind11_platform_abi_id.h", - "include/pybind11/conduit/wrap_include_python_h.h", } detail_headers = { @@ -63,13 +55,10 @@ detail_headers = { "include/pybind11/detail/common.h", "include/pybind11/detail/cpp_conduit.h", "include/pybind11/detail/descr.h", - "include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h", "include/pybind11/detail/init.h", "include/pybind11/detail/internals.h", - "include/pybind11/detail/struct_smart_holder.h", "include/pybind11/detail/type_caster_base.h", "include/pybind11/detail/typeid.h", - "include/pybind11/detail/using_smart_holder.h", "include/pybind11/detail/value_and_holder.h", "include/pybind11/detail/exception_translation.h", } @@ -84,6 +73,16 @@ stl_headers = { "include/pybind11/stl/filesystem.h", } +eigen_headers = { + "include/pybind11/eigen/common.h", + "include/pybind11/eigen/matrix.h", + "include/pybind11/eigen/tensor.h", +} + +stl_headers = { + "include/pybind11/stl/filesystem.h", +} + cmake_files = { "share/cmake/pybind11/FindPythonLibsNew.cmake", "share/cmake/pybind11/pybind11Common.cmake", @@ -106,11 +105,9 @@ py_files = { "commands.py", "py.typed", "setup_helpers.py", - "share/__init__.py", - "share/pkgconfig/__init__.py", } -headers = main_headers | conduit_headers | detail_headers | eigen_headers | stl_headers +headers = main_headers | detail_headers | eigen_headers | stl_headers src_files = headers | cmake_files | pkgconfig_files all_files = src_files | py_files @@ -119,7 +116,6 @@ sdist_files = { "pybind11", "pybind11/include", "pybind11/include/pybind11", - "pybind11/include/pybind11/conduit", "pybind11/include/pybind11/detail", "pybind11/include/pybind11/eigen", "pybind11/include/pybind11/stl", diff --git a/pybind11/tests/pure_cpp/CMakeLists.txt b/pybind11/tests/pure_cpp/CMakeLists.txt deleted file mode 100644 index 17be74b7f..000000000 --- a/pybind11/tests/pure_cpp/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -find_package(Catch 2.13.2) - -if(CATCH_FOUND) - message(STATUS "Building pure C++ tests (not depending on Python) using Catch v${CATCH_VERSION}") -else() - message(STATUS "Catch not detected. Interpreter tests will be skipped. Install Catch headers" - " manually or use `cmake -DDOWNLOAD_CATCH=ON` to fetch them automatically.") - return() -endif() - -add_executable(smart_holder_poc_test smart_holder_poc_test.cpp) -pybind11_enable_warnings(smart_holder_poc_test) -target_link_libraries(smart_holder_poc_test PRIVATE pybind11::headers Catch2::Catch2) - -add_custom_target( - test_pure_cpp - COMMAND "$" - WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") - -add_dependencies(check test_pure_cpp) diff --git a/pybind11/tests/pure_cpp/smart_holder_poc.h b/pybind11/tests/pure_cpp/smart_holder_poc.h deleted file mode 100644 index 320311b7d..000000000 --- a/pybind11/tests/pure_cpp/smart_holder_poc.h +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) 2020-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 "pybind11/detail/struct_smart_holder.h" - -namespace pybindit { -namespace memory { -namespace smart_holder_poc { // Proof-of-Concept implementations. - -template -T &as_lvalue_ref(const smart_holder &hld) { - static const char *context = "as_lvalue_ref"; - hld.ensure_is_populated(context); - hld.ensure_has_pointee(context); - return *hld.as_raw_ptr_unowned(); -} - -template -T &&as_rvalue_ref(const smart_holder &hld) { - static const char *context = "as_rvalue_ref"; - hld.ensure_is_populated(context); - hld.ensure_has_pointee(context); - return std::move(*hld.as_raw_ptr_unowned()); -} - -template -T *as_raw_ptr_release_ownership(smart_holder &hld, - const char *context = "as_raw_ptr_release_ownership") { - hld.ensure_can_release_ownership(context); - T *raw_ptr = hld.as_raw_ptr_unowned(); - hld.release_ownership(); - return raw_ptr; -} - -template > -std::unique_ptr as_unique_ptr(smart_holder &hld) { - static const char *context = "as_unique_ptr"; - hld.ensure_compatible_rtti_uqp_del(context); - hld.ensure_use_count_1(context); - T *raw_ptr = hld.as_raw_ptr_unowned(); - hld.release_ownership(); - // KNOWN DEFECT (see PR #4850): Does not copy the deleter. - return std::unique_ptr(raw_ptr); -} - -} // namespace smart_holder_poc -} // namespace memory -} // namespace pybindit diff --git a/pybind11/tests/pure_cpp/smart_holder_poc_test.cpp b/pybind11/tests/pure_cpp/smart_holder_poc_test.cpp deleted file mode 100644 index 24ab643ee..000000000 --- a/pybind11/tests/pure_cpp/smart_holder_poc_test.cpp +++ /dev/null @@ -1,415 +0,0 @@ -#include "smart_holder_poc.h" - -#include -#include -#include -#include - -// Catch uses _ internally, which breaks gettext style defines -#ifdef _ -# undef _ -#endif - -#define CATCH_CONFIG_MAIN -#include "catch.hpp" - -using pybindit::memory::smart_holder; -namespace poc = pybindit::memory::smart_holder_poc; - -namespace helpers { - -struct movable_int { - int valu; - explicit movable_int(int v) : valu{v} {} - movable_int(movable_int &&other) noexcept : valu(other.valu) { other.valu = 91; } -}; - -template -struct functor_builtin_delete { - void operator()(T *ptr) { delete ptr; } -#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 8) \ - || (defined(__clang_major__) && __clang_major__ == 3 && __clang_minor__ == 6) - // Workaround for these errors: - // gcc 4.8.5: too many initializers for 'helpers::functor_builtin_delete' - // clang 3.6: excess elements in struct initializer - functor_builtin_delete() = default; - functor_builtin_delete(const functor_builtin_delete &) {} - functor_builtin_delete(functor_builtin_delete &&) {} -#endif -}; - -template -struct functor_other_delete : functor_builtin_delete {}; - -struct indestructible_int { - int valu; - explicit indestructible_int(int v) : valu{v} {} - -private: - ~indestructible_int() = default; -}; - -struct base { - virtual int get() { return 10; } - virtual ~base() = default; -}; - -struct derived : public base { - int get() override { return 100; } -}; - -} // namespace helpers - -TEST_CASE("from_raw_ptr_unowned+as_raw_ptr_unowned", "[S]") { - static int value = 19; - auto hld = smart_holder::from_raw_ptr_unowned(&value); - REQUIRE(*hld.as_raw_ptr_unowned() == 19); -} - -TEST_CASE("from_raw_ptr_unowned+as_lvalue_ref", "[S]") { - static int value = 19; - auto hld = smart_holder::from_raw_ptr_unowned(&value); - REQUIRE(poc::as_lvalue_ref(hld) == 19); -} - -TEST_CASE("from_raw_ptr_unowned+as_rvalue_ref", "[S]") { - helpers::movable_int orig(19); - { - auto hld = smart_holder::from_raw_ptr_unowned(&orig); - helpers::movable_int othr(poc::as_rvalue_ref(hld)); - REQUIRE(othr.valu == 19); - REQUIRE(orig.valu == 91); - } -} - -TEST_CASE("from_raw_ptr_unowned+as_raw_ptr_release_ownership", "[E]") { - static int value = 19; - auto hld = smart_holder::from_raw_ptr_unowned(&value); - REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership(hld), - "Cannot disown non-owning holder (as_raw_ptr_release_ownership)."); -} - -TEST_CASE("from_raw_ptr_unowned+as_unique_ptr", "[E]") { - static int value = 19; - auto hld = smart_holder::from_raw_ptr_unowned(&value); - REQUIRE_THROWS_WITH(poc::as_unique_ptr(hld), - "Cannot disown non-owning holder (as_unique_ptr)."); -} - -TEST_CASE("from_raw_ptr_unowned+as_unique_ptr_with_deleter", "[E]") { - static int value = 19; - auto hld = smart_holder::from_raw_ptr_unowned(&value); - REQUIRE_THROWS_WITH((poc::as_unique_ptr>(hld)), - "Missing unique_ptr deleter (as_unique_ptr)."); -} - -TEST_CASE("from_raw_ptr_unowned+as_shared_ptr", "[S]") { - static int value = 19; - auto hld = smart_holder::from_raw_ptr_unowned(&value); - REQUIRE(*hld.as_shared_ptr() == 19); -} - -TEST_CASE("from_raw_ptr_take_ownership+as_lvalue_ref", "[S]") { - auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); - REQUIRE(hld.has_pointee()); - REQUIRE(poc::as_lvalue_ref(hld) == 19); -} - -TEST_CASE("from_raw_ptr_take_ownership+as_raw_ptr_release_ownership1", "[S]") { - auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); - auto new_owner = std::unique_ptr(poc::as_raw_ptr_release_ownership(hld)); - REQUIRE(!hld.has_pointee()); - REQUIRE(*new_owner == 19); -} - -TEST_CASE("from_raw_ptr_take_ownership+as_raw_ptr_release_ownership2", "[E]") { - auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); - auto shd_ptr = hld.as_shared_ptr(); - REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership(hld), - "Cannot disown use_count != 1 (as_raw_ptr_release_ownership)."); -} - -TEST_CASE("from_raw_ptr_take_ownership+as_unique_ptr1", "[S]") { - auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); - std::unique_ptr new_owner = poc::as_unique_ptr(hld); - REQUIRE(!hld.has_pointee()); - REQUIRE(*new_owner == 19); -} - -TEST_CASE("from_raw_ptr_take_ownership+as_unique_ptr2", "[E]") { - auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); - auto shd_ptr = hld.as_shared_ptr(); - REQUIRE_THROWS_WITH(poc::as_unique_ptr(hld), - "Cannot disown use_count != 1 (as_unique_ptr)."); -} - -TEST_CASE("from_raw_ptr_take_ownership+as_unique_ptr_with_deleter", "[E]") { - auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); - REQUIRE_THROWS_WITH((poc::as_unique_ptr>(hld)), - "Missing unique_ptr deleter (as_unique_ptr)."); -} - -TEST_CASE("from_raw_ptr_take_ownership+as_shared_ptr", "[S]") { - auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); - std::shared_ptr new_owner = hld.as_shared_ptr(); - REQUIRE(hld.has_pointee()); - REQUIRE(*new_owner == 19); -} - -TEST_CASE("from_raw_ptr_take_ownership+disown+reclaim_disowned", "[S]") { - auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); - std::unique_ptr new_owner(hld.as_raw_ptr_unowned()); - hld.disown(); - REQUIRE(poc::as_lvalue_ref(hld) == 19); - REQUIRE(*new_owner == 19); - hld.reclaim_disowned(); // Manually veriified: without this, clang++ -fsanitize=address reports - // "detected memory leaks". - // NOLINTNEXTLINE(bugprone-unused-return-value) - (void) new_owner.release(); // Manually verified: without this, clang++ -fsanitize=address - // reports "attempting double-free". - REQUIRE(poc::as_lvalue_ref(hld) == 19); - REQUIRE(new_owner.get() == nullptr); -} - -TEST_CASE("from_raw_ptr_take_ownership+disown+release_disowned", "[S]") { - auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); - std::unique_ptr new_owner(hld.as_raw_ptr_unowned()); - hld.disown(); - REQUIRE(poc::as_lvalue_ref(hld) == 19); - REQUIRE(*new_owner == 19); - hld.release_disowned(); - REQUIRE(!hld.has_pointee()); -} - -TEST_CASE("from_raw_ptr_take_ownership+disown+ensure_is_not_disowned", "[E]") { - const char *context = "test_case"; - auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); - hld.ensure_is_not_disowned(context); // Does not throw. - std::unique_ptr new_owner(hld.as_raw_ptr_unowned()); - hld.disown(); - REQUIRE_THROWS_WITH(hld.ensure_is_not_disowned(context), - "Holder was disowned already (test_case)."); -} - -TEST_CASE("from_unique_ptr+as_lvalue_ref", "[S]") { - std::unique_ptr orig_owner(new int(19)); - auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); - REQUIRE(orig_owner.get() == nullptr); - REQUIRE(poc::as_lvalue_ref(hld) == 19); -} - -TEST_CASE("from_unique_ptr+as_raw_ptr_release_ownership1", "[S]") { - std::unique_ptr orig_owner(new int(19)); - auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); - REQUIRE(orig_owner.get() == nullptr); - auto new_owner = std::unique_ptr(poc::as_raw_ptr_release_ownership(hld)); - REQUIRE(!hld.has_pointee()); - REQUIRE(*new_owner == 19); -} - -TEST_CASE("from_unique_ptr+as_raw_ptr_release_ownership2", "[E]") { - std::unique_ptr orig_owner(new int(19)); - auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); - REQUIRE(orig_owner.get() == nullptr); - auto shd_ptr = hld.as_shared_ptr(); - REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership(hld), - "Cannot disown use_count != 1 (as_raw_ptr_release_ownership)."); -} - -TEST_CASE("from_unique_ptr+as_unique_ptr1", "[S]") { - std::unique_ptr orig_owner(new int(19)); - auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); - REQUIRE(orig_owner.get() == nullptr); - std::unique_ptr new_owner = poc::as_unique_ptr(hld); - REQUIRE(!hld.has_pointee()); - REQUIRE(*new_owner == 19); -} - -TEST_CASE("from_unique_ptr+as_unique_ptr2", "[E]") { - std::unique_ptr orig_owner(new int(19)); - auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); - REQUIRE(orig_owner.get() == nullptr); - auto shd_ptr = hld.as_shared_ptr(); - REQUIRE_THROWS_WITH(poc::as_unique_ptr(hld), - "Cannot disown use_count != 1 (as_unique_ptr)."); -} - -TEST_CASE("from_unique_ptr+as_unique_ptr_with_deleter", "[E]") { - std::unique_ptr orig_owner(new int(19)); - auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); - REQUIRE(orig_owner.get() == nullptr); - REQUIRE_THROWS_WITH((poc::as_unique_ptr>(hld)), - "Incompatible unique_ptr deleter (as_unique_ptr)."); -} - -TEST_CASE("from_unique_ptr+as_shared_ptr", "[S]") { - std::unique_ptr orig_owner(new int(19)); - auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); - REQUIRE(orig_owner.get() == nullptr); - std::shared_ptr new_owner = hld.as_shared_ptr(); - REQUIRE(hld.has_pointee()); - REQUIRE(*new_owner == 19); -} - -TEST_CASE("from_unique_ptr_derived+as_unique_ptr_base", "[S]") { - std::unique_ptr orig_owner(new helpers::derived()); - auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); - REQUIRE(orig_owner.get() == nullptr); - std::unique_ptr new_owner = poc::as_unique_ptr(hld); - REQUIRE(!hld.has_pointee()); - REQUIRE(new_owner->get() == 100); -} - -TEST_CASE("from_unique_ptr_derived+as_unique_ptr_base2", "[E]") { - std::unique_ptr> orig_owner( - new helpers::derived()); - auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); - REQUIRE(orig_owner.get() == nullptr); - REQUIRE_THROWS_WITH( - (poc::as_unique_ptr>(hld)), - "Incompatible unique_ptr deleter (as_unique_ptr)."); -} - -TEST_CASE("from_unique_ptr_with_deleter+as_lvalue_ref", "[S]") { - std::unique_ptr> orig_owner(new int(19)); - auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); - REQUIRE(orig_owner.get() == nullptr); - REQUIRE(poc::as_lvalue_ref(hld) == 19); -} - -TEST_CASE("from_unique_ptr_with_std_function_deleter+as_lvalue_ref", "[S]") { - std::unique_ptr> orig_owner( - new int(19), [](const int *raw_ptr) { delete raw_ptr; }); - auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); - REQUIRE(orig_owner.get() == nullptr); - REQUIRE(poc::as_lvalue_ref(hld) == 19); -} - -TEST_CASE("from_unique_ptr_with_deleter+as_raw_ptr_release_ownership", "[E]") { - std::unique_ptr> orig_owner(new int(19)); - auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); - REQUIRE(orig_owner.get() == nullptr); - REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership(hld), - "Cannot disown custom deleter (as_raw_ptr_release_ownership)."); -} - -TEST_CASE("from_unique_ptr_with_deleter+as_unique_ptr", "[E]") { - std::unique_ptr> orig_owner(new int(19)); - auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); - REQUIRE(orig_owner.get() == nullptr); - REQUIRE_THROWS_WITH(poc::as_unique_ptr(hld), - "Incompatible unique_ptr deleter (as_unique_ptr)."); -} - -TEST_CASE("from_unique_ptr_with_deleter+as_unique_ptr_with_deleter1", "[S]") { - std::unique_ptr> orig_owner(new int(19)); - auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); - REQUIRE(orig_owner.get() == nullptr); - std::unique_ptr> new_owner - = poc::as_unique_ptr>(hld); - REQUIRE(!hld.has_pointee()); - REQUIRE(*new_owner == 19); -} - -TEST_CASE("from_unique_ptr_with_deleter+as_unique_ptr_with_deleter2", "[E]") { - std::unique_ptr> orig_owner(new int(19)); - auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); - REQUIRE(orig_owner.get() == nullptr); - REQUIRE_THROWS_WITH((poc::as_unique_ptr>(hld)), - "Incompatible unique_ptr deleter (as_unique_ptr)."); -} - -TEST_CASE("from_unique_ptr_with_deleter+as_shared_ptr", "[S]") { - std::unique_ptr> orig_owner(new int(19)); - auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); - REQUIRE(orig_owner.get() == nullptr); - std::shared_ptr new_owner = hld.as_shared_ptr(); - REQUIRE(hld.has_pointee()); - REQUIRE(*new_owner == 19); -} - -TEST_CASE("from_shared_ptr+as_lvalue_ref", "[S]") { - std::shared_ptr orig_owner(new int(19)); - auto hld = smart_holder::from_shared_ptr(orig_owner); - REQUIRE(poc::as_lvalue_ref(hld) == 19); -} - -TEST_CASE("from_shared_ptr+as_raw_ptr_release_ownership", "[E]") { - std::shared_ptr orig_owner(new int(19)); - auto hld = smart_holder::from_shared_ptr(orig_owner); - REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership(hld), - "Cannot disown external shared_ptr (as_raw_ptr_release_ownership)."); -} - -TEST_CASE("from_shared_ptr+as_unique_ptr", "[E]") { - std::shared_ptr orig_owner(new int(19)); - auto hld = smart_holder::from_shared_ptr(orig_owner); - REQUIRE_THROWS_WITH(poc::as_unique_ptr(hld), - "Cannot disown external shared_ptr (as_unique_ptr)."); -} - -TEST_CASE("from_shared_ptr+as_unique_ptr_with_deleter", "[E]") { - std::shared_ptr orig_owner(new int(19)); - auto hld = smart_holder::from_shared_ptr(orig_owner); - REQUIRE_THROWS_WITH((poc::as_unique_ptr>(hld)), - "Missing unique_ptr deleter (as_unique_ptr)."); -} - -TEST_CASE("from_shared_ptr+as_shared_ptr", "[S]") { - std::shared_ptr orig_owner(new int(19)); - auto hld = smart_holder::from_shared_ptr(orig_owner); - REQUIRE(*hld.as_shared_ptr() == 19); -} - -TEST_CASE("error_unpopulated_holder", "[E]") { - smart_holder hld; - REQUIRE_THROWS_WITH(poc::as_lvalue_ref(hld), "Unpopulated holder (as_lvalue_ref)."); -} - -TEST_CASE("error_disowned_holder", "[E]") { - auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); - poc::as_unique_ptr(hld); - REQUIRE_THROWS_WITH(poc::as_lvalue_ref(hld), "Disowned holder (as_lvalue_ref)."); -} - -TEST_CASE("error_cannot_disown_nullptr", "[E]") { - auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); - poc::as_unique_ptr(hld); - REQUIRE_THROWS_WITH(poc::as_unique_ptr(hld), "Cannot disown nullptr (as_unique_ptr)."); -} - -TEST_CASE("indestructible_int-from_raw_ptr_unowned+as_raw_ptr_unowned", "[S]") { - using zombie = helpers::indestructible_int; - // Using placement new instead of plain new, to not trigger leak sanitizer errors. - static std::aligned_storage::type memory_block[1]; - auto *value = new (memory_block) zombie(19); - auto hld = smart_holder::from_raw_ptr_unowned(value); - REQUIRE(hld.as_raw_ptr_unowned()->valu == 19); -} - -TEST_CASE("indestructible_int-from_raw_ptr_take_ownership", "[E]") { - helpers::indestructible_int *value = nullptr; - REQUIRE_THROWS_WITH(smart_holder::from_raw_ptr_take_ownership(value), - "Pointee is not destructible (from_raw_ptr_take_ownership)."); -} - -TEST_CASE("from_raw_ptr_take_ownership+as_shared_ptr-outliving_smart_holder", "[S]") { - // Exercises guarded_builtin_delete flag_ptr validity past destruction of smart_holder. - std::shared_ptr longer_living; - { - auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); - longer_living = hld.as_shared_ptr(); - } - REQUIRE(*longer_living == 19); -} - -TEST_CASE("from_unique_ptr_with_deleter+as_shared_ptr-outliving_smart_holder", "[S]") { - // Exercises guarded_custom_deleter flag_ptr validity past destruction of smart_holder. - std::shared_ptr longer_living; - { - std::unique_ptr> orig_owner(new int(19)); - auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); - longer_living = hld.as_shared_ptr(); - } - REQUIRE(*longer_living == 19); -} diff --git a/pybind11/tests/pybind11_tests.cpp b/pybind11/tests/pybind11_tests.cpp index 818d53a54..3d2d84e77 100644 --- a/pybind11/tests/pybind11_tests.cpp +++ b/pybind11/tests/pybind11_tests.cpp @@ -128,9 +128,4 @@ PYBIND11_MODULE(pybind11_tests, m, py::mod_gil_not_used()) { for (const auto &initializer : initializers()) { initializer(m); } - - py::class_(m, "TestContext") - .def(py::init<>(&TestContext::createNewContextForInit)) - .def("__enter__", &TestContext::contextEnter) - .def("__exit__", &TestContext::contextExit); } diff --git a/pybind11/tests/pybind11_tests.h b/pybind11/tests/pybind11_tests.h index 0eb0398df..7be58feb6 100644 --- a/pybind11/tests/pybind11_tests.h +++ b/pybind11/tests/pybind11_tests.h @@ -96,24 +96,3 @@ void ignoreOldStyleInitWarnings(F &&body) { )", py::dict(py::arg("body") = py::cpp_function(body))); } - -// See PR #5419 for background. -class TestContext { -public: - TestContext() = delete; - TestContext(const TestContext &) = delete; - TestContext(TestContext &&) = delete; - static TestContext *createNewContextForInit() { return new TestContext("new-context"); } - - pybind11::object contextEnter() { - py::object contextObj = py::cast(*this); - return contextObj; - } - void contextExit(const pybind11::object & /*excType*/, - const pybind11::object & /*excVal*/, - const pybind11::object & /*excTb*/) {} - -private: - explicit TestContext(const std::string &context) : context(context) {} - std::string context; -}; diff --git a/pybind11/tests/pyproject.toml b/pybind11/tests/pyproject.toml index 469c145df..044bf15c0 100644 --- a/pybind11/tests/pyproject.toml +++ b/pybind11/tests/pyproject.toml @@ -10,6 +10,10 @@ 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 diff --git a/pybind11/tests/requirements.txt b/pybind11/tests/requirements.txt index 688ff6fe8..337897bd2 100644 --- a/pybind11/tests/requirements.txt +++ b/pybind11/tests/requirements.txt @@ -1,13 +1,13 @@ --only-binary=:all: -build~=1.0; python_version>="3.8" +build~=1.0; python_version>="3.7" +numpy~=1.20.0; python_version=="3.7" and platform_python_implementation=="PyPy" numpy~=1.23.0; python_version=="3.8" and platform_python_implementation=="PyPy" numpy~=1.25.0; python_version=="3.9" and platform_python_implementation=='PyPy' -numpy~=1.26.0; platform_python_implementation=="GraalVM" and sys_platform=="linux" -numpy~=1.21.5; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version>="3.8" and python_version<"3.10" -numpy~=1.22.2; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version=="3.10" -numpy~=1.26.0; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version>="3.11" and python_version<"3.13" +numpy~=1.21.5; platform_python_implementation!="PyPy" and python_version>="3.7" and python_version<"3.10" +numpy~=1.22.2; platform_python_implementation!="PyPy" and python_version=="3.10" +numpy~=1.26.0; platform_python_implementation!="PyPy" and python_version>="3.11" and python_version<"3.13" pytest~=7.0 pytest-timeout -scipy~=1.5.4; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version<"3.10" -scipy~=1.8.0; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version=="3.10" and sys_platform!='win32' -scipy~=1.11.1; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version>="3.11" and python_version<"3.13" and sys_platform!='win32' +scipy~=1.5.4; platform_python_implementation!="PyPy" and python_version<"3.10" +scipy~=1.8.0; platform_python_implementation!="PyPy" and python_version=="3.10" and sys_platform!='win32' +scipy~=1.11.1; platform_python_implementation!="PyPy" and python_version>="3.11" and python_version<"3.13" and sys_platform!='win32' diff --git a/pybind11/tests/test_buffers.cpp b/pybind11/tests/test_buffers.cpp index ac4489f70..a6c527c10 100644 --- a/pybind11/tests/test_buffers.cpp +++ b/pybind11/tests/test_buffers.cpp @@ -167,125 +167,6 @@ TEST_SUBMODULE(buffers, m) { sizeof(float)}); }); - // A matrix that uses Fortran storage order. - class FortranMatrix : public Matrix { - public: - FortranMatrix(py::ssize_t rows, py::ssize_t cols) : Matrix(cols, rows) { - print_created(this, - std::to_string(rows) + "x" + std::to_string(cols) + " Fortran matrix"); - } - - float operator()(py::ssize_t i, py::ssize_t j) const { return Matrix::operator()(j, i); } - - float &operator()(py::ssize_t i, py::ssize_t j) { return Matrix::operator()(j, i); } - - using Matrix::data; - - py::ssize_t rows() const { return Matrix::cols(); } - py::ssize_t cols() const { return Matrix::rows(); } - }; - py::class_(m, "FortranMatrix", py::buffer_protocol()) - .def(py::init()) - - .def("rows", &FortranMatrix::rows) - .def("cols", &FortranMatrix::cols) - - /// Bare bones interface - .def("__getitem__", - [](const FortranMatrix &m, std::pair i) { - if (i.first >= m.rows() || i.second >= m.cols()) { - throw py::index_error(); - } - return m(i.first, i.second); - }) - .def("__setitem__", - [](FortranMatrix &m, std::pair i, float v) { - if (i.first >= m.rows() || i.second >= m.cols()) { - throw py::index_error(); - } - m(i.first, i.second) = v; - }) - /// Provide buffer access - .def_buffer([](FortranMatrix &m) -> py::buffer_info { - return py::buffer_info(m.data(), /* Pointer to buffer */ - {m.rows(), m.cols()}, /* Buffer dimensions */ - /* Strides (in bytes) for each index */ - {sizeof(float), sizeof(float) * size_t(m.rows())}); - }); - - // A matrix that uses a discontiguous underlying memory block. - class DiscontiguousMatrix : public Matrix { - public: - DiscontiguousMatrix(py::ssize_t rows, - py::ssize_t cols, - py::ssize_t row_factor, - py::ssize_t col_factor) - : Matrix(rows * row_factor, cols * col_factor), m_row_factor(row_factor), - m_col_factor(col_factor) { - print_created(this, - std::to_string(rows) + "(*" + std::to_string(row_factor) + ")x" - + std::to_string(cols) + "(*" + std::to_string(col_factor) - + ") matrix"); - } - - ~DiscontiguousMatrix() { - print_destroyed(this, - std::to_string(rows() / m_row_factor) + "(*" - + std::to_string(m_row_factor) + ")x" - + std::to_string(cols() / m_col_factor) + "(*" - + std::to_string(m_col_factor) + ") matrix"); - } - - float operator()(py::ssize_t i, py::ssize_t j) const { - return Matrix::operator()(i * m_row_factor, j * m_col_factor); - } - - float &operator()(py::ssize_t i, py::ssize_t j) { - return Matrix::operator()(i * m_row_factor, j * m_col_factor); - } - - using Matrix::data; - - py::ssize_t rows() const { return Matrix::rows() / m_row_factor; } - py::ssize_t cols() const { return Matrix::cols() / m_col_factor; } - py::ssize_t row_factor() const { return m_row_factor; } - py::ssize_t col_factor() const { return m_col_factor; } - - private: - py::ssize_t m_row_factor; - py::ssize_t m_col_factor; - }; - py::class_(m, "DiscontiguousMatrix", py::buffer_protocol()) - .def(py::init()) - - .def("rows", &DiscontiguousMatrix::rows) - .def("cols", &DiscontiguousMatrix::cols) - - /// Bare bones interface - .def("__getitem__", - [](const DiscontiguousMatrix &m, std::pair i) { - if (i.first >= m.rows() || i.second >= m.cols()) { - throw py::index_error(); - } - return m(i.first, i.second); - }) - .def("__setitem__", - [](DiscontiguousMatrix &m, std::pair i, float v) { - if (i.first >= m.rows() || i.second >= m.cols()) { - throw py::index_error(); - } - m(i.first, i.second) = v; - }) - /// Provide buffer access - .def_buffer([](DiscontiguousMatrix &m) -> py::buffer_info { - return py::buffer_info(m.data(), /* Pointer to buffer */ - {m.rows(), m.cols()}, /* Buffer dimensions */ - /* Strides (in bytes) for each index */ - {size_t(m.col_factor()) * sizeof(float) * size_t(m.cols()) - * size_t(m.row_factor()), - size_t(m.col_factor()) * sizeof(float)}); - }); - class BrokenMatrix : public Matrix { public: BrokenMatrix(py::ssize_t rows, py::ssize_t cols) : Matrix(rows, cols) {} @@ -387,56 +268,4 @@ TEST_SUBMODULE(buffers, m) { }); m.def("get_buffer_info", [](const py::buffer &buffer) { return buffer.request(); }); - - // Expose Py_buffer for testing. - m.attr("PyBUF_FORMAT") = PyBUF_FORMAT; - m.attr("PyBUF_SIMPLE") = PyBUF_SIMPLE; - m.attr("PyBUF_ND") = PyBUF_ND; - m.attr("PyBUF_STRIDES") = PyBUF_STRIDES; - m.attr("PyBUF_INDIRECT") = PyBUF_INDIRECT; - m.attr("PyBUF_C_CONTIGUOUS") = PyBUF_C_CONTIGUOUS; - m.attr("PyBUF_F_CONTIGUOUS") = PyBUF_F_CONTIGUOUS; - m.attr("PyBUF_ANY_CONTIGUOUS") = PyBUF_ANY_CONTIGUOUS; - - m.def("get_py_buffer", [](const py::object &object, int flags) { - Py_buffer buffer; - memset(&buffer, 0, sizeof(Py_buffer)); - if (PyObject_GetBuffer(object.ptr(), &buffer, flags) == -1) { - throw py::error_already_set(); - } - - auto SimpleNamespace = py::module_::import("types").attr("SimpleNamespace"); - py::object result = SimpleNamespace("len"_a = buffer.len, - "readonly"_a = buffer.readonly, - "itemsize"_a = buffer.itemsize, - "format"_a = buffer.format, - "ndim"_a = buffer.ndim, - "shape"_a = py::none(), - "strides"_a = py::none(), - "suboffsets"_a = py::none()); - if (buffer.shape != nullptr) { - py::list l; - for (auto i = 0; i < buffer.ndim; i++) { - l.append(buffer.shape[i]); - } - py::setattr(result, "shape", l); - } - if (buffer.strides != nullptr) { - py::list l; - for (auto i = 0; i < buffer.ndim; i++) { - l.append(buffer.strides[i]); - } - py::setattr(result, "strides", l); - } - if (buffer.suboffsets != nullptr) { - py::list l; - for (auto i = 0; i < buffer.ndim; i++) { - l.append(buffer.suboffsets[i]); - } - py::setattr(result, "suboffsets", l); - } - - PyBuffer_Release(&buffer); - return result; - }); } diff --git a/pybind11/tests/test_buffers.py b/pybind11/tests/test_buffers.py index 2612edb27..535f33c2d 100644 --- a/pybind11/tests/test_buffers.py +++ b/pybind11/tests/test_buffers.py @@ -82,8 +82,6 @@ def test_from_python(): for j in range(m4.cols()): assert m3[i, j] == m4[i, j] - if env.GRAALPY: - pytest.skip("ConstructorStats is incompatible with GraalPy.") cstats = ConstructorStats.get(m.Matrix) assert cstats.alive() == 1 del m3, m4 @@ -120,8 +118,6 @@ def test_to_python(): mat2[2, 3] = 5 assert mat2[2, 3] == 5 - if env.GRAALPY: - pytest.skip("ConstructorStats is incompatible with GraalPy.") cstats = ConstructorStats.get(m.Matrix) assert cstats.alive() == 1 del mat @@ -239,163 +235,3 @@ def test_buffer_exception(): memoryview(m.BrokenMatrix(1, 1)) assert isinstance(excinfo.value.__cause__, RuntimeError) assert "for context" in str(excinfo.value.__cause__) - - -@pytest.mark.parametrize("type", ["pybind11", "numpy"]) -def test_c_contiguous_to_pybuffer(type): - if type == "pybind11": - mat = m.Matrix(5, 4) - elif type == "numpy": - mat = np.empty((5, 4), dtype=np.float32) - else: - raise ValueError(f"Unknown parametrization {type}") - - info = m.get_py_buffer(mat, m.PyBUF_SIMPLE) - assert info.format is None - assert info.itemsize == ctypes.sizeof(ctypes.c_float) - assert info.len == 5 * 4 * info.itemsize - assert info.ndim == 0 # See discussion on PR #5407. - assert info.shape is None - assert info.strides is None - assert info.suboffsets is None - assert not info.readonly - info = m.get_py_buffer(mat, m.PyBUF_SIMPLE | m.PyBUF_FORMAT) - assert info.format == "f" - assert info.itemsize == ctypes.sizeof(ctypes.c_float) - assert info.len == 5 * 4 * info.itemsize - assert info.ndim == 0 # See discussion on PR #5407. - assert info.shape is None - assert info.strides is None - assert info.suboffsets is None - assert not info.readonly - info = m.get_py_buffer(mat, m.PyBUF_ND) - assert info.itemsize == ctypes.sizeof(ctypes.c_float) - assert info.len == 5 * 4 * info.itemsize - assert info.ndim == 2 - assert info.shape == [5, 4] - assert info.strides is None - assert info.suboffsets is None - assert not info.readonly - info = m.get_py_buffer(mat, m.PyBUF_STRIDES) - assert info.itemsize == ctypes.sizeof(ctypes.c_float) - assert info.len == 5 * 4 * info.itemsize - assert info.ndim == 2 - assert info.shape == [5, 4] - assert info.strides == [4 * info.itemsize, info.itemsize] - assert info.suboffsets is None - assert not info.readonly - info = m.get_py_buffer(mat, m.PyBUF_INDIRECT) - assert info.itemsize == ctypes.sizeof(ctypes.c_float) - assert info.len == 5 * 4 * info.itemsize - assert info.ndim == 2 - assert info.shape == [5, 4] - assert info.strides == [4 * info.itemsize, info.itemsize] - assert info.suboffsets is None # Should be filled in here, but we don't use it. - assert not info.readonly - - -@pytest.mark.parametrize("type", ["pybind11", "numpy"]) -def test_fortran_contiguous_to_pybuffer(type): - if type == "pybind11": - mat = m.FortranMatrix(5, 4) - elif type == "numpy": - mat = np.empty((5, 4), dtype=np.float32, order="F") - else: - raise ValueError(f"Unknown parametrization {type}") - - # A Fortran-shaped buffer can only be accessed at PyBUF_STRIDES level or higher. - info = m.get_py_buffer(mat, m.PyBUF_STRIDES) - assert info.itemsize == ctypes.sizeof(ctypes.c_float) - assert info.len == 5 * 4 * info.itemsize - assert info.ndim == 2 - assert info.shape == [5, 4] - assert info.strides == [info.itemsize, 5 * info.itemsize] - assert info.suboffsets is None - assert not info.readonly - info = m.get_py_buffer(mat, m.PyBUF_INDIRECT) - assert info.itemsize == ctypes.sizeof(ctypes.c_float) - assert info.len == 5 * 4 * info.itemsize - assert info.ndim == 2 - assert info.shape == [5, 4] - assert info.strides == [info.itemsize, 5 * info.itemsize] - assert info.suboffsets is None # Should be filled in here, but we don't use it. - assert not info.readonly - - -@pytest.mark.parametrize("type", ["pybind11", "numpy"]) -def test_discontiguous_to_pybuffer(type): - if type == "pybind11": - mat = m.DiscontiguousMatrix(5, 4, 2, 3) - elif type == "numpy": - mat = np.empty((5 * 2, 4 * 3), dtype=np.float32)[::2, ::3] - else: - raise ValueError(f"Unknown parametrization {type}") - - info = m.get_py_buffer(mat, m.PyBUF_STRIDES) - assert info.itemsize == ctypes.sizeof(ctypes.c_float) - assert info.len == 5 * 4 * info.itemsize - assert info.ndim == 2 - assert info.shape == [5, 4] - assert info.strides == [2 * 4 * 3 * info.itemsize, 3 * info.itemsize] - assert info.suboffsets is None - assert not info.readonly - - -@pytest.mark.parametrize("type", ["pybind11", "numpy"]) -def test_to_pybuffer_contiguity(type): - def check_strides(mat): - # The full block is memset to 0, so fill it with non-zero in real spots. - expected = np.arange(1, 5 * 4 + 1).reshape((5, 4)) - for i in range(5): - for j in range(4): - mat[i, j] = expected[i, j] - # If all strides are correct, the exposed buffer should match the input. - np.testing.assert_array_equal(np.array(mat), expected) - - if type == "pybind11": - cmat = m.Matrix(5, 4) # C contiguous. - fmat = m.FortranMatrix(5, 4) # Fortran contiguous. - dmat = m.DiscontiguousMatrix(5, 4, 2, 3) # Not contiguous. - expected_exception = BufferError - elif type == "numpy": - cmat = np.empty((5, 4), dtype=np.float32) # C contiguous. - fmat = np.empty((5, 4), dtype=np.float32, order="F") # Fortran contiguous. - dmat = np.empty((5 * 2, 4 * 3), dtype=np.float32)[::2, ::3] # Not contiguous. - # NumPy incorrectly raises ValueError; when the minimum NumPy requirement is - # above the version that fixes https://github.com/numpy/numpy/issues/3634 then - # BufferError can be used everywhere. - expected_exception = (BufferError, ValueError) - else: - raise ValueError(f"Unknown parametrization {type}") - - check_strides(cmat) - # Should work in C-contiguous mode, but not Fortran order. - m.get_py_buffer(cmat, m.PyBUF_C_CONTIGUOUS) - m.get_py_buffer(cmat, m.PyBUF_ANY_CONTIGUOUS) - with pytest.raises(expected_exception): - m.get_py_buffer(cmat, m.PyBUF_F_CONTIGUOUS) - - check_strides(fmat) - # These flags imply C-contiguity, so won't work. - with pytest.raises(expected_exception): - m.get_py_buffer(fmat, m.PyBUF_SIMPLE) - with pytest.raises(expected_exception): - m.get_py_buffer(fmat, m.PyBUF_ND) - # Should work in Fortran-contiguous mode, but not C order. - with pytest.raises(expected_exception): - m.get_py_buffer(fmat, m.PyBUF_C_CONTIGUOUS) - m.get_py_buffer(fmat, m.PyBUF_ANY_CONTIGUOUS) - m.get_py_buffer(fmat, m.PyBUF_F_CONTIGUOUS) - - check_strides(dmat) - # Should never work. - with pytest.raises(expected_exception): - m.get_py_buffer(dmat, m.PyBUF_SIMPLE) - with pytest.raises(expected_exception): - m.get_py_buffer(dmat, m.PyBUF_ND) - with pytest.raises(expected_exception): - m.get_py_buffer(dmat, m.PyBUF_C_CONTIGUOUS) - with pytest.raises(expected_exception): - m.get_py_buffer(dmat, m.PyBUF_ANY_CONTIGUOUS) - with pytest.raises(expected_exception): - m.get_py_buffer(dmat, m.PyBUF_F_CONTIGUOUS) diff --git a/pybind11/tests/test_builtin_casters.py b/pybind11/tests/test_builtin_casters.py index 240be85e7..b37aacff1 100644 --- a/pybind11/tests/test_builtin_casters.py +++ b/pybind11/tests/test_builtin_casters.py @@ -297,7 +297,7 @@ def test_int_convert(): cant_convert(3.14159) # TODO: Avoid DeprecationWarning in `PyLong_AsLong` (and similar) # TODO: PyPy 3.8 does not behave like CPython 3.8 here yet (7.3.7) - if sys.version_info < (3, 10) and env.CPYTHON: + if (3, 8) <= sys.version_info < (3, 10) and env.CPYTHON: with env.deprecated_call(): assert convert(Int()) == 42 else: diff --git a/pybind11/tests/test_call_policies.cpp b/pybind11/tests/test_call_policies.cpp index 9140f7e9f..92924cb45 100644 --- a/pybind11/tests/test_call_policies.cpp +++ b/pybind11/tests/test_call_policies.cpp @@ -95,8 +95,8 @@ TEST_SUBMODULE(call_policies, m) { }, py::call_guard()); -#if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON) - // `py::call_guard()` should work in PyPy/GraalPy as well, +#if !defined(PYPY_VERSION) + // `py::call_guard()` should work in PyPy as well, // but it's unclear how to test it without `PyGILState_GetThisThreadState`. auto report_gil_status = []() { auto is_gil_held = false; diff --git a/pybind11/tests/test_call_policies.py b/pybind11/tests/test_call_policies.py index 11aab9fd9..91670deb3 100644 --- a/pybind11/tests/test_call_policies.py +++ b/pybind11/tests/test_call_policies.py @@ -8,7 +8,6 @@ from pybind11_tests import call_policies as m @pytest.mark.xfail("env.PYPY", reason="sometimes comes out 1 off on PyPy", strict=False) -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_keep_alive_argument(capture): n_inst = ConstructorStats.detail_reg_inst() with capture: @@ -61,7 +60,6 @@ def test_keep_alive_argument(capture): assert str(excinfo.value) == "Could not activate keep_alive!" -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_keep_alive_return_value(capture): n_inst = ConstructorStats.detail_reg_inst() with capture: @@ -120,7 +118,6 @@ def test_keep_alive_return_value(capture): # https://foss.heptapod.net/pypy/pypy/-/issues/2447 @pytest.mark.xfail("env.PYPY", reason="_PyObject_GetDictPtr is unimplemented") -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_alive_gc(capture): n_inst = ConstructorStats.detail_reg_inst() p = m.ParentGC() @@ -140,7 +137,6 @@ def test_alive_gc(capture): ) -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_alive_gc_derived(capture): class Derived(m.Parent): pass @@ -163,7 +159,6 @@ def test_alive_gc_derived(capture): ) -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_alive_gc_multi_derived(capture): class Derived(m.Parent, m.Child): def __init__(self): @@ -190,7 +185,6 @@ def test_alive_gc_multi_derived(capture): ) -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_return_none(capture): n_inst = ConstructorStats.detail_reg_inst() with capture: @@ -218,7 +212,6 @@ def test_return_none(capture): assert capture == "Releasing parent." -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_keep_alive_constructor(capture): n_inst = ConstructorStats.detail_reg_inst() diff --git a/pybind11/tests/test_callbacks.cpp b/pybind11/tests/test_callbacks.cpp index 20dbaa6b2..e303e7656 100644 --- a/pybind11/tests/test_callbacks.cpp +++ b/pybind11/tests/test_callbacks.cpp @@ -269,7 +269,12 @@ TEST_SUBMODULE(callbacks, m) { rec_capsule.set_name(rec_capsule_name); m.add_object("custom_function", PyCFunction_New(custom_def, rec_capsule.ptr())); + // This test requires a new ABI version to pass +#if PYBIND11_INTERNALS_VERSION > 4 // rec_capsule with nullptr name py::capsule rec_capsule2(std::malloc(1), [](void *data) { std::free(data); }); m.add_object("custom_function2", PyCFunction_New(custom_def, rec_capsule2.ptr())); +#else + m.add_object("custom_function2", py::none()); +#endif } diff --git a/pybind11/tests/test_callbacks.py b/pybind11/tests/test_callbacks.py index 7919ad0ae..db6d8dece 100644 --- a/pybind11/tests/test_callbacks.py +++ b/pybind11/tests/test_callbacks.py @@ -90,7 +90,6 @@ def test_keyword_args_and_generalized_unpacking(): ) -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_lambda_closure_cleanup(): m.test_lambda_closure_cleanup() cstats = m.payload_cstats() @@ -99,7 +98,6 @@ def test_lambda_closure_cleanup(): assert cstats.move_constructions >= 1 -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_cpp_callable_cleanup(): alive_counts = m.test_cpp_callable_cleanup() assert alive_counts == [0, 1, 2, 1, 2, 1, 0] @@ -217,7 +215,9 @@ def test_custom_func(): assert m.roundtrip(m.custom_function)(4) == 36 -@pytest.mark.skipif("env.GRAALPY", reason="TODO debug segfault") +@pytest.mark.skipif( + m.custom_function2 is None, reason="Current PYBIND11_INTERNALS_VERSION too low" +) def test_custom_func2(): assert m.custom_function2(3) == 27 assert m.roundtrip(m.custom_function2)(3) == 27 diff --git a/pybind11/tests/test_class.cpp b/pybind11/tests/test_class.cpp index 28dc31333..9001d86b1 100644 --- a/pybind11/tests/test_class.cpp +++ b/pybind11/tests/test_class.cpp @@ -52,24 +52,8 @@ void bind_empty0(py::module_ &m) { } } // namespace pr4220_tripped_over_this - -namespace pr5396_forward_declared_class { -class ForwardClass; -class Args : public py::args {}; -} // namespace pr5396_forward_declared_class - } // namespace test_class -static_assert(py::detail::is_same_or_base_of::value, ""); -static_assert( - py::detail::is_same_or_base_of::value, - ""); -static_assert(!py::detail::is_same_or_base_of< - py::args, - test_class::pr5396_forward_declared_class::ForwardClass>::value, - ""); - TEST_SUBMODULE(class_, m) { m.def("obj_class_name", [](py::handle obj) { return py::detail::obj_class_name(obj.ptr()); }); @@ -105,12 +89,6 @@ TEST_SUBMODULE(class_, m) { .def_static("__new__", [](const py::object &) { return NoConstructorNew::new_instance(); }); - // test_pass_unique_ptr - struct ToBeHeldByUniquePtr {}; - py::class_>(m, "ToBeHeldByUniquePtr") - .def(py::init<>()); - m.def("pass_unique_ptr", [](std::unique_ptr &&) {}); - // test_inheritance class Pet { public: @@ -233,12 +211,11 @@ TEST_SUBMODULE(class_, m) { m.def("mismatched_holder_1", []() { auto mod = py::module_::import("__main__"); py::class_>(mod, "MismatchBase1"); - py::class_, MismatchBase1>( - mod, "MismatchDerived1"); + py::class_(mod, "MismatchDerived1"); }); m.def("mismatched_holder_2", []() { auto mod = py::module_::import("__main__"); - py::class_>(mod, "MismatchBase2"); + py::class_(mod, "MismatchBase2"); py::class_, MismatchBase2>( mod, "MismatchDerived2"); }); @@ -632,10 +609,8 @@ CHECK_NOALIAS(8); CHECK_HOLDER(1, unique); CHECK_HOLDER(2, unique); CHECK_HOLDER(3, unique); -#ifndef PYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE CHECK_HOLDER(4, unique); CHECK_HOLDER(5, unique); -#endif CHECK_HOLDER(6, shared); CHECK_HOLDER(7, shared); CHECK_HOLDER(8, shared); diff --git a/pybind11/tests/test_class.py b/pybind11/tests/test_class.py index 2e11feb7b..9b2b1d834 100644 --- a/pybind11/tests/test_class.py +++ b/pybind11/tests/test_class.py @@ -1,6 +1,5 @@ from __future__ import annotations -import sys from unittest import mock import pytest @@ -28,9 +27,6 @@ def test_instance(msg): instance = m.NoConstructor.new_instance() - if env.GRAALPY: - pytest.skip("ConstructorStats is incompatible with GraalPy.") - cstats = ConstructorStats.get(m.NoConstructor) assert cstats.alive() == 1 del instance @@ -39,26 +35,12 @@ def test_instance(msg): def test_instance_new(): instance = m.NoConstructorNew() # .__new__(m.NoConstructor.__class__) - - if env.GRAALPY: - pytest.skip("ConstructorStats is incompatible with GraalPy.") - cstats = ConstructorStats.get(m.NoConstructorNew) assert cstats.alive() == 1 del instance assert cstats.alive() == 0 -def test_pass_unique_ptr(): - obj = m.ToBeHeldByUniquePtr() - with pytest.raises(RuntimeError) as execinfo: - m.pass_unique_ptr(obj) - assert str(execinfo.value).startswith( - "Passing `std::unique_ptr` from Python to C++ requires `py::class_` (with T = " - ) - assert "ToBeHeldByUniquePtr" in str(execinfo.value) - - def test_type(): assert m.check_type(1) == m.DerivedClass1 with pytest.raises(RuntimeError) as execinfo: @@ -379,7 +361,7 @@ def test_brace_initialization(): assert b.vec == [123, 456] -@pytest.mark.xfail("env.PYPY or env.GRAALPY") +@pytest.mark.xfail("env.PYPY") def test_class_refcount(): """Instances must correctly increase/decrease the reference count of their types (#1029)""" from sys import getrefcount @@ -519,31 +501,3 @@ def test_pr4220_tripped_over_this(): m.Empty0().get_msg() == "This is really only meant to exercise successful compilation." ) - - -@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") -def test_all_type_info_multithreaded(): - # See PR #5419 for background. - import threading - - from pybind11_tests import TestContext - - class Context(TestContext): - pass - - num_runs = 10 - num_threads = 4 - barrier = threading.Barrier(num_threads) - - def func(): - barrier.wait() - with Context(): - pass - - for _ in range(num_runs): - threads = [threading.Thread(target=func) for _ in range(num_threads)] - for thread in threads: - thread.start() - - for thread in threads: - thread.join() diff --git a/pybind11/tests/test_class_release_gil_before_calling_cpp_dtor.cpp b/pybind11/tests/test_class_release_gil_before_calling_cpp_dtor.cpp deleted file mode 100644 index a4869a846..000000000 --- a/pybind11/tests/test_class_release_gil_before_calling_cpp_dtor.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include - -#include "pybind11_tests.h" - -#include -#include - -namespace pybind11_tests { -namespace class_release_gil_before_calling_cpp_dtor { - -using RegistryType = std::unordered_map; - -static RegistryType &PyGILState_Check_Results() { - static RegistryType singleton; // Local static variables have thread-safe initialization. - return singleton; -} - -template // Using int as a trick to easily generate a series of types. -struct ProbeType { -private: - std::string unique_key; - -public: - explicit ProbeType(const std::string &unique_key) : unique_key{unique_key} {} - - ~ProbeType() { - RegistryType ® = PyGILState_Check_Results(); - assert(reg.count(unique_key) == 0); - reg[unique_key] = PyGILState_Check(); - } -}; - -} // namespace class_release_gil_before_calling_cpp_dtor -} // namespace pybind11_tests - -TEST_SUBMODULE(class_release_gil_before_calling_cpp_dtor, m) { - using namespace pybind11_tests::class_release_gil_before_calling_cpp_dtor; - - py::class_>(m, "ProbeType0").def(py::init()); - - py::class_>(m, "ProbeType1", py::release_gil_before_calling_cpp_dtor()) - .def(py::init()); - - m.def("PopPyGILState_Check_Result", [](const std::string &unique_key) -> std::string { - RegistryType ® = PyGILState_Check_Results(); - if (reg.count(unique_key) == 0) { - return "MISSING"; - } - int res = reg[unique_key]; - reg.erase(unique_key); - return std::to_string(res); - }); -} diff --git a/pybind11/tests/test_class_release_gil_before_calling_cpp_dtor.py b/pybind11/tests/test_class_release_gil_before_calling_cpp_dtor.py deleted file mode 100644 index 0b1f246bb..000000000 --- a/pybind11/tests/test_class_release_gil_before_calling_cpp_dtor.py +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -import gc - -import pytest - -from pybind11_tests import class_release_gil_before_calling_cpp_dtor as m - - -@pytest.mark.parametrize( - ("probe_type", "unique_key", "expected_result"), - [ - (m.ProbeType0, "without_manipulating_gil", "1"), - (m.ProbeType1, "release_gil_before_calling_cpp_dtor", "0"), - ], -) -def test_gil_state_check_results(probe_type, unique_key, expected_result): - probe_type(unique_key) - gc.collect() - result = m.PopPyGILState_Check_Result(unique_key) - assert result == expected_result diff --git a/pybind11/tests/test_class_sh_basic.cpp b/pybind11/tests/test_class_sh_basic.cpp deleted file mode 100644 index b89372b19..000000000 --- a/pybind11/tests/test_class_sh_basic.cpp +++ /dev/null @@ -1,247 +0,0 @@ -#include "pybind11_tests.h" - -#include -#include -#include - -namespace pybind11_tests { -namespace class_sh_basic { - -struct atyp { // Short for "any type". - std::string mtxt; - atyp() : mtxt("DefaultConstructor") {} - explicit atyp(const std::string &mtxt_) : mtxt(mtxt_) {} - atyp(const atyp &other) { mtxt = other.mtxt + "_CpCtor"; } - atyp(atyp &&other) noexcept { mtxt = other.mtxt + "_MvCtor"; } -}; - -struct uconsumer { // unique_ptr consumer - std::unique_ptr held; - bool valid() const { return static_cast(held); } - - void pass_valu(std::unique_ptr obj) { held = std::move(obj); } - void pass_rref(std::unique_ptr &&obj) { held = std::move(obj); } - std::unique_ptr rtrn_valu() { return std::move(held); } - std::unique_ptr &rtrn_lref() { return held; } - const std::unique_ptr &rtrn_cref() const { return held; } -}; - -/// Custom deleter that is default constructible. -struct custom_deleter { - std::string trace_txt; - - custom_deleter() = default; - explicit custom_deleter(const std::string &trace_txt_) : trace_txt(trace_txt_) {} - - custom_deleter(const custom_deleter &other) { trace_txt = other.trace_txt + "_CpCtor"; } - - custom_deleter &operator=(const custom_deleter &rhs) { - trace_txt = rhs.trace_txt + "_CpLhs"; - return *this; - } - - custom_deleter(custom_deleter &&other) noexcept { - trace_txt = other.trace_txt + "_MvCtorTo"; - other.trace_txt += "_MvCtorFrom"; - } - - custom_deleter &operator=(custom_deleter &&rhs) noexcept { - trace_txt = rhs.trace_txt + "_MvLhs"; - rhs.trace_txt += "_MvRhs"; - return *this; - } - - void operator()(atyp *p) const { std::default_delete()(p); } - void operator()(const atyp *p) const { std::default_delete()(p); } -}; -static_assert(std::is_default_constructible::value, ""); - -/// Custom deleter that is not default constructible. -struct custom_deleter_nd : custom_deleter { - custom_deleter_nd() = delete; - explicit custom_deleter_nd(const std::string &trace_txt_) : custom_deleter(trace_txt_) {} -}; -static_assert(!std::is_default_constructible::value, ""); - -// clang-format off - -atyp rtrn_valu() { atyp obj{"rtrn_valu"}; return obj; } -atyp&& rtrn_rref() { static atyp obj; obj.mtxt = "rtrn_rref"; return std::move(obj); } -atyp const& rtrn_cref() { static atyp obj; obj.mtxt = "rtrn_cref"; return obj; } -atyp& rtrn_mref() { static atyp obj; obj.mtxt = "rtrn_mref"; return obj; } -atyp const* rtrn_cptr() { return new atyp{"rtrn_cptr"}; } -atyp* rtrn_mptr() { return new atyp{"rtrn_mptr"}; } - -std::string pass_valu(atyp obj) { return "pass_valu:" + obj.mtxt; } // NOLINT -std::string pass_cref(atyp const& obj) { return "pass_cref:" + obj.mtxt; } -std::string pass_mref(atyp& obj) { return "pass_mref:" + obj.mtxt; } -std::string pass_cptr(atyp const* obj) { return "pass_cptr:" + obj->mtxt; } -std::string pass_mptr(atyp* obj) { return "pass_mptr:" + obj->mtxt; } - -std::shared_ptr rtrn_shmp() { return std::make_shared("rtrn_shmp"); } -std::shared_ptr rtrn_shcp() { return std::shared_ptr(new atyp{"rtrn_shcp"}); } - -std::string pass_shmp(std::shared_ptr obj) { return "pass_shmp:" + obj->mtxt; } // NOLINT -std::string pass_shcp(std::shared_ptr obj) { return "pass_shcp:" + obj->mtxt; } // NOLINT - -std::unique_ptr rtrn_uqmp() { return std::unique_ptr(new atyp{"rtrn_uqmp"}); } -std::unique_ptr rtrn_uqcp() { return std::unique_ptr(new atyp{"rtrn_uqcp"}); } - -std::string pass_uqmp(std::unique_ptr obj) { return "pass_uqmp:" + obj->mtxt; } -std::string pass_uqcp(std::unique_ptr obj) { return "pass_uqcp:" + obj->mtxt; } - -struct sddm : std::default_delete {}; -struct sddc : std::default_delete {}; - -std::unique_ptr rtrn_udmp() { return std::unique_ptr(new atyp{"rtrn_udmp"}); } -std::unique_ptr rtrn_udcp() { return std::unique_ptr(new atyp{"rtrn_udcp"}); } - -std::string pass_udmp(std::unique_ptr obj) { return "pass_udmp:" + obj->mtxt; } -std::string pass_udcp(std::unique_ptr obj) { return "pass_udcp:" + obj->mtxt; } - -std::unique_ptr rtrn_udmp_del() { return std::unique_ptr(new atyp{"rtrn_udmp_del"}, custom_deleter{"udmp_deleter"}); } -std::unique_ptr rtrn_udcp_del() { return std::unique_ptr(new atyp{"rtrn_udcp_del"}, custom_deleter{"udcp_deleter"}); } - -std::string pass_udmp_del(std::unique_ptr obj) { return "pass_udmp_del:" + obj->mtxt + "," + obj.get_deleter().trace_txt; } -std::string pass_udcp_del(std::unique_ptr obj) { return "pass_udcp_del:" + obj->mtxt + "," + obj.get_deleter().trace_txt; } - -std::unique_ptr rtrn_udmp_del_nd() { return std::unique_ptr(new atyp{"rtrn_udmp_del_nd"}, custom_deleter_nd{"udmp_deleter_nd"}); } -std::unique_ptr rtrn_udcp_del_nd() { return std::unique_ptr(new atyp{"rtrn_udcp_del_nd"}, custom_deleter_nd{"udcp_deleter_nd"}); } - -std::string pass_udmp_del_nd(std::unique_ptr obj) { return "pass_udmp_del_nd:" + obj->mtxt + "," + obj.get_deleter().trace_txt; } -std::string pass_udcp_del_nd(std::unique_ptr obj) { return "pass_udcp_del_nd:" + obj->mtxt + "," + obj.get_deleter().trace_txt; } - -// clang-format on - -// Helpers for testing. -std::string get_mtxt(atyp const &obj) { return obj.mtxt; } -std::ptrdiff_t get_ptr(atyp const &obj) { return reinterpret_cast(&obj); } - -std::unique_ptr unique_ptr_roundtrip(std::unique_ptr obj) { return obj; } - -std::string pass_unique_ptr_cref(const std::unique_ptr &obj) { return obj->mtxt; } - -const std::unique_ptr &rtrn_unique_ptr_cref(const std::string &mtxt) { - static std::unique_ptr obj{new atyp{"static_ctor_arg"}}; - if (!mtxt.empty()) { - obj->mtxt = mtxt; - } - return obj; -} - -const std::unique_ptr &unique_ptr_cref_roundtrip(const std::unique_ptr &obj) { - return obj; -} - -struct SharedPtrStash { - std::vector> stash; - void Add(const std::shared_ptr &obj) { stash.push_back(obj); } -}; - -class LocalUnusualOpRef : UnusualOpRef {}; // To avoid clashing with `py::class_`. -py::object CastUnusualOpRefConstRef(const LocalUnusualOpRef &cref) { return py::cast(cref); } -py::object CastUnusualOpRefMovable(LocalUnusualOpRef &&mvbl) { return py::cast(std::move(mvbl)); } - -TEST_SUBMODULE(class_sh_basic, m) { - namespace py = pybind11; - - py::classh(m, "atyp").def(py::init<>()).def(py::init([](const std::string &mtxt) { - atyp obj; - obj.mtxt = mtxt; - return obj; - })); - - m.def("rtrn_valu", rtrn_valu); - m.def("rtrn_rref", rtrn_rref); - m.def("rtrn_cref", rtrn_cref); - m.def("rtrn_mref", rtrn_mref); - m.def("rtrn_cptr", rtrn_cptr); - m.def("rtrn_mptr", rtrn_mptr); - - m.def("pass_valu", pass_valu); - m.def("pass_cref", pass_cref); - m.def("pass_mref", pass_mref); - m.def("pass_cptr", pass_cptr); - m.def("pass_mptr", pass_mptr); - - m.def("rtrn_shmp", rtrn_shmp); - m.def("rtrn_shcp", rtrn_shcp); - - m.def("pass_shmp", pass_shmp); - m.def("pass_shcp", pass_shcp); - - m.def("rtrn_uqmp", rtrn_uqmp); - m.def("rtrn_uqcp", rtrn_uqcp); - - m.def("pass_uqmp", pass_uqmp); - m.def("pass_uqcp", pass_uqcp); - - m.def("rtrn_udmp", rtrn_udmp); - m.def("rtrn_udcp", rtrn_udcp); - - m.def("pass_udmp", pass_udmp); - m.def("pass_udcp", pass_udcp); - - m.def("rtrn_udmp_del", rtrn_udmp_del); - m.def("rtrn_udcp_del", rtrn_udcp_del); - - m.def("pass_udmp_del", pass_udmp_del); - m.def("pass_udcp_del", pass_udcp_del); - - m.def("rtrn_udmp_del_nd", rtrn_udmp_del_nd); - m.def("rtrn_udcp_del_nd", rtrn_udcp_del_nd); - - m.def("pass_udmp_del_nd", pass_udmp_del_nd); - m.def("pass_udcp_del_nd", pass_udcp_del_nd); - - py::classh(m, "uconsumer") - .def(py::init<>()) - .def("valid", &uconsumer::valid) - .def("pass_valu", &uconsumer::pass_valu) - .def("pass_rref", &uconsumer::pass_rref) - .def("rtrn_valu", &uconsumer::rtrn_valu) - .def("rtrn_lref", &uconsumer::rtrn_lref) - .def("rtrn_cref", &uconsumer::rtrn_cref); - - // Helpers for testing. - // These require selected functions above to work first, as indicated: - m.def("get_mtxt", get_mtxt); // pass_cref - m.def("get_ptr", get_ptr); // pass_cref - - m.def("unique_ptr_roundtrip", unique_ptr_roundtrip); // pass_uqmp, rtrn_uqmp - - m.def("pass_unique_ptr_cref", pass_unique_ptr_cref); - m.def("rtrn_unique_ptr_cref", rtrn_unique_ptr_cref); - m.def("unique_ptr_cref_roundtrip", unique_ptr_cref_roundtrip); - - py::classh(m, "SharedPtrStash") - .def(py::init<>()) - .def("Add", &SharedPtrStash::Add, py::arg("obj")); - - m.def("py_type_handle_of_atyp", []() { - return py::type::handle_of(); // Exercises static_cast in this function. - }); - - // Checks for type names used as arguments - m.def("args_shared_ptr", [](std::shared_ptr p) { return p; }); - m.def("args_shared_ptr_const", [](std::shared_ptr p) { return p; }); - m.def("args_unique_ptr", [](std::unique_ptr p) { return p; }); - m.def("args_unique_ptr_const", [](std::unique_ptr p) { return p; }); - - // Make sure unique_ptr type caster accept automatic_reference return value policy. - m.def( - "rtrn_uq_automatic_reference", - []() { return std::unique_ptr(new atyp("rtrn_uq_automatic_reference")); }, - pybind11::return_value_policy::automatic_reference); - - m.def("pass_shared_ptr_ptr", [](std::shared_ptr *) {}); - - py::classh(m, "LocalUnusualOpRef"); - m.def("CallCastUnusualOpRefConstRef", - []() { return CastUnusualOpRefConstRef(LocalUnusualOpRef()); }); - m.def("CallCastUnusualOpRefMovable", - []() { return CastUnusualOpRefMovable(LocalUnusualOpRef()); }); -} - -} // namespace class_sh_basic -} // namespace pybind11_tests diff --git a/pybind11/tests/test_class_sh_basic.py b/pybind11/tests/test_class_sh_basic.py deleted file mode 100644 index f07253a5d..000000000 --- a/pybind11/tests/test_class_sh_basic.py +++ /dev/null @@ -1,246 +0,0 @@ -# Importing re before pytest after observing a PyPy CI flake when importing pytest first. -from __future__ import annotations - -import re - -import pytest - -from pybind11_tests import class_sh_basic as m - - -def test_atyp_constructors(): - obj = m.atyp() - assert obj.__class__.__name__ == "atyp" - obj = m.atyp("") - assert obj.__class__.__name__ == "atyp" - obj = m.atyp("txtm") - assert obj.__class__.__name__ == "atyp" - - -@pytest.mark.parametrize( - ("rtrn_f", "expected"), - [ - (m.rtrn_valu, "rtrn_valu(_MvCtor)*_MvCtor"), - (m.rtrn_rref, "rtrn_rref(_MvCtor)*_MvCtor"), - (m.rtrn_cref, "rtrn_cref(_MvCtor)*_CpCtor"), - (m.rtrn_mref, "rtrn_mref(_MvCtor)*_CpCtor"), - (m.rtrn_cptr, "rtrn_cptr"), - (m.rtrn_mptr, "rtrn_mptr"), - (m.rtrn_shmp, "rtrn_shmp"), - (m.rtrn_shcp, "rtrn_shcp"), - (m.rtrn_uqmp, "rtrn_uqmp"), - (m.rtrn_uqcp, "rtrn_uqcp"), - (m.rtrn_udmp, "rtrn_udmp"), - (m.rtrn_udcp, "rtrn_udcp"), - ], -) -def test_cast(rtrn_f, expected): - assert re.match(expected, m.get_mtxt(rtrn_f())) - - -@pytest.mark.parametrize( - ("pass_f", "mtxt", "expected"), - [ - (m.pass_valu, "Valu", "pass_valu:Valu(_MvCtor)*_CpCtor"), - (m.pass_cref, "Cref", "pass_cref:Cref(_MvCtor)*_MvCtor"), - (m.pass_mref, "Mref", "pass_mref:Mref(_MvCtor)*_MvCtor"), - (m.pass_cptr, "Cptr", "pass_cptr:Cptr(_MvCtor)*_MvCtor"), - (m.pass_mptr, "Mptr", "pass_mptr:Mptr(_MvCtor)*_MvCtor"), - (m.pass_shmp, "Shmp", "pass_shmp:Shmp(_MvCtor)*_MvCtor"), - (m.pass_shcp, "Shcp", "pass_shcp:Shcp(_MvCtor)*_MvCtor"), - (m.pass_uqmp, "Uqmp", "pass_uqmp:Uqmp(_MvCtor)*_MvCtor"), - (m.pass_uqcp, "Uqcp", "pass_uqcp:Uqcp(_MvCtor)*_MvCtor"), - ], -) -def test_load_with_mtxt(pass_f, mtxt, expected): - assert re.match(expected, pass_f(m.atyp(mtxt))) - - -@pytest.mark.parametrize( - ("pass_f", "rtrn_f", "expected"), - [ - (m.pass_udmp, m.rtrn_udmp, "pass_udmp:rtrn_udmp"), - (m.pass_udcp, m.rtrn_udcp, "pass_udcp:rtrn_udcp"), - ], -) -def test_load_with_rtrn_f(pass_f, rtrn_f, expected): - assert pass_f(rtrn_f()) == expected - - -@pytest.mark.parametrize( - ("pass_f", "rtrn_f", "regex_expected"), - [ - ( - m.pass_udmp_del, - m.rtrn_udmp_del, - "pass_udmp_del:rtrn_udmp_del,udmp_deleter(_MvCtorTo)*_MvCtorTo", - ), - ( - m.pass_udcp_del, - m.rtrn_udcp_del, - "pass_udcp_del:rtrn_udcp_del,udcp_deleter(_MvCtorTo)*_MvCtorTo", - ), - ( - m.pass_udmp_del_nd, - m.rtrn_udmp_del_nd, - "pass_udmp_del_nd:rtrn_udmp_del_nd,udmp_deleter_nd(_MvCtorTo)*_MvCtorTo", - ), - ( - m.pass_udcp_del_nd, - m.rtrn_udcp_del_nd, - "pass_udcp_del_nd:rtrn_udcp_del_nd,udcp_deleter_nd(_MvCtorTo)*_MvCtorTo", - ), - ], -) -def test_deleter_roundtrip(pass_f, rtrn_f, regex_expected): - assert re.match(regex_expected, pass_f(rtrn_f())) - - -@pytest.mark.parametrize( - ("pass_f", "rtrn_f", "expected"), - [ - (m.pass_uqmp, m.rtrn_uqmp, "pass_uqmp:rtrn_uqmp"), - (m.pass_uqcp, m.rtrn_uqcp, "pass_uqcp:rtrn_uqcp"), - (m.pass_udmp, m.rtrn_udmp, "pass_udmp:rtrn_udmp"), - (m.pass_udcp, m.rtrn_udcp, "pass_udcp:rtrn_udcp"), - ], -) -def test_pass_unique_ptr_disowns(pass_f, rtrn_f, expected): - obj = rtrn_f() - assert pass_f(obj) == expected - with pytest.raises(ValueError) as exc_info: - pass_f(obj) - assert str(exc_info.value) == ( - "Missing value for wrapped C++ type" - + " `pybind11_tests::class_sh_basic::atyp`:" - + " Python instance was disowned." - ) - - -@pytest.mark.parametrize( - ("pass_f", "rtrn_f"), - [ - (m.pass_uqmp, m.rtrn_uqmp), - (m.pass_uqcp, m.rtrn_uqcp), - (m.pass_udmp, m.rtrn_udmp), - (m.pass_udcp, m.rtrn_udcp), - ], -) -def test_cannot_disown_use_count_ne_1(pass_f, rtrn_f): - obj = rtrn_f() - stash = m.SharedPtrStash() - stash.Add(obj) - with pytest.raises(ValueError) as exc_info: - pass_f(obj) - assert str(exc_info.value) == ("Cannot disown use_count != 1 (load_as_unique_ptr).") - - -def test_unique_ptr_roundtrip(num_round_trips=1000): - # Multiple roundtrips to stress-test instance registration/deregistration. - recycled = m.atyp("passenger") - for _ in range(num_round_trips): - id_orig = id(recycled) - recycled = m.unique_ptr_roundtrip(recycled) - assert re.match("passenger(_MvCtor)*_MvCtor", m.get_mtxt(recycled)) - id_rtrn = id(recycled) - # Ensure the returned object is a different Python instance. - assert id_rtrn != id_orig - id_orig = id_rtrn - - -def test_pass_unique_ptr_cref(): - obj = m.atyp("ctor_arg") - assert re.match("ctor_arg(_MvCtor)*_MvCtor", m.get_mtxt(obj)) - assert re.match("ctor_arg(_MvCtor)*_MvCtor", m.pass_unique_ptr_cref(obj)) - assert re.match("ctor_arg(_MvCtor)*_MvCtor", m.get_mtxt(obj)) - - -def test_rtrn_unique_ptr_cref(): - obj0 = m.rtrn_unique_ptr_cref("") - assert m.get_mtxt(obj0) == "static_ctor_arg" - obj1 = m.rtrn_unique_ptr_cref("passed_mtxt_1") - assert m.get_mtxt(obj1) == "passed_mtxt_1" - assert m.get_mtxt(obj0) == "passed_mtxt_1" - assert obj0 is obj1 - - -def test_unique_ptr_cref_roundtrip(num_round_trips=1000): - # Multiple roundtrips to stress-test implementation. - orig = m.atyp("passenger") - mtxt_orig = m.get_mtxt(orig) - recycled = orig - for _ in range(num_round_trips): - recycled = m.unique_ptr_cref_roundtrip(recycled) - assert recycled is orig - assert m.get_mtxt(recycled) == mtxt_orig - - -@pytest.mark.parametrize( - ("pass_f", "rtrn_f", "moved_out", "moved_in"), - [ - (m.uconsumer.pass_valu, m.uconsumer.rtrn_valu, True, True), - (m.uconsumer.pass_rref, m.uconsumer.rtrn_valu, True, True), - (m.uconsumer.pass_valu, m.uconsumer.rtrn_lref, True, False), - (m.uconsumer.pass_valu, m.uconsumer.rtrn_cref, True, False), - ], -) -def test_unique_ptr_consumer_roundtrip(pass_f, rtrn_f, moved_out, moved_in): - c = m.uconsumer() - assert not c.valid() - recycled = m.atyp("passenger") - mtxt_orig = m.get_mtxt(recycled) - assert re.match("passenger_(MvCtor){1,2}", mtxt_orig) - - pass_f(c, recycled) - if moved_out: - with pytest.raises(ValueError) as excinfo: - m.get_mtxt(recycled) - assert "Python instance was disowned" in str(excinfo.value) - - recycled = rtrn_f(c) - assert c.valid() != moved_in - assert m.get_mtxt(recycled) == mtxt_orig - - -def test_py_type_handle_of_atyp(): - obj = m.py_type_handle_of_atyp() - assert obj.__class__.__name__ == "pybind11_type" - - -def test_function_signatures(doc): - assert ( - doc(m.args_shared_ptr) - == "args_shared_ptr(arg0: m.class_sh_basic.atyp) -> m.class_sh_basic.atyp" - ) - assert ( - doc(m.args_shared_ptr_const) - == "args_shared_ptr_const(arg0: m.class_sh_basic.atyp) -> m.class_sh_basic.atyp" - ) - assert ( - doc(m.args_unique_ptr) - == "args_unique_ptr(arg0: m.class_sh_basic.atyp) -> m.class_sh_basic.atyp" - ) - assert ( - doc(m.args_unique_ptr_const) - == "args_unique_ptr_const(arg0: m.class_sh_basic.atyp) -> m.class_sh_basic.atyp" - ) - - -def test_unique_ptr_return_value_policy_automatic_reference(): - assert m.get_mtxt(m.rtrn_uq_automatic_reference()) == "rtrn_uq_automatic_reference" - - -def test_pass_shared_ptr_ptr(): - obj = m.atyp() - with pytest.raises(RuntimeError) as excinfo: - m.pass_shared_ptr_ptr(obj) - assert str(excinfo.value) == ( - "Passing `std::shared_ptr *` from Python to C++ is not supported" - " (inherently unsafe)." - ) - - -def test_unusual_op_ref(): - # Merely to test that this still exists and built successfully. - assert m.CallCastUnusualOpRefConstRef().__class__.__name__ == "LocalUnusualOpRef" - assert m.CallCastUnusualOpRefMovable().__class__.__name__ == "LocalUnusualOpRef" diff --git a/pybind11/tests/test_class_sh_disowning.cpp b/pybind11/tests/test_class_sh_disowning.cpp deleted file mode 100644 index 490e6d59f..000000000 --- a/pybind11/tests/test_class_sh_disowning.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "pybind11_tests.h" - -#include - -namespace pybind11_tests { -namespace class_sh_disowning { - -template // Using int as a trick to easily generate a series of types. -struct Atype { - int val = 0; - explicit Atype(int val_) : val{val_} {} - int get() const { return val * 10 + SerNo; } -}; - -int same_twice(std::unique_ptr> at1a, std::unique_ptr> at1b) { - return at1a->get() * 100 + at1b->get() * 10; -} - -int mixed(std::unique_ptr> at1, std::unique_ptr> at2) { - return at1->get() * 200 + at2->get() * 20; -} - -int overloaded(std::unique_ptr> at1, int i) { return at1->get() * 30 + i; } -int overloaded(std::unique_ptr> at2, int i) { return at2->get() * 40 + i; } - -} // namespace class_sh_disowning -} // namespace pybind11_tests - -TEST_SUBMODULE(class_sh_disowning, m) { - using namespace pybind11_tests::class_sh_disowning; - - py::classh>(m, "Atype1").def(py::init()).def("get", &Atype<1>::get); - py::classh>(m, "Atype2").def(py::init()).def("get", &Atype<2>::get); - - m.def("same_twice", same_twice); - - m.def("mixed", mixed); - - m.def("overloaded", (int (*)(std::unique_ptr>, int)) &overloaded); - m.def("overloaded", (int (*)(std::unique_ptr>, int)) &overloaded); -} diff --git a/pybind11/tests/test_class_sh_disowning.py b/pybind11/tests/test_class_sh_disowning.py deleted file mode 100644 index b9e648999..000000000 --- a/pybind11/tests/test_class_sh_disowning.py +++ /dev/null @@ -1,78 +0,0 @@ -from __future__ import annotations - -import pytest - -from pybind11_tests import class_sh_disowning as m - - -def is_disowned(obj): - try: - obj.get() - except ValueError: - return True - return False - - -def test_same_twice(): - while True: - obj1a = m.Atype1(57) - obj1b = m.Atype1(62) - assert m.same_twice(obj1a, obj1b) == (57 * 10 + 1) * 100 + (62 * 10 + 1) * 10 - assert is_disowned(obj1a) - assert is_disowned(obj1b) - obj1c = m.Atype1(0) - with pytest.raises(ValueError): - # Disowning works for one argument, but not both. - m.same_twice(obj1c, obj1c) - assert is_disowned(obj1c) - return # Comment out for manual leak checking (use `top` command). - - -def test_mixed(): - first_pass = True - while True: - obj1a = m.Atype1(90) - obj2a = m.Atype2(25) - assert m.mixed(obj1a, obj2a) == (90 * 10 + 1) * 200 + (25 * 10 + 2) * 20 - assert is_disowned(obj1a) - assert is_disowned(obj2a) - - # The C++ order of evaluation of function arguments is (unfortunately) unspecified: - # https://en.cppreference.com/w/cpp/language/eval_order - # Read on. - obj1b = m.Atype1(0) - with pytest.raises(ValueError): - # If the 1st argument is evaluated first, obj1b is disowned before the conversion for - # the already disowned obj2a fails as expected. - m.mixed(obj1b, obj2a) - obj2b = m.Atype2(0) - with pytest.raises(ValueError): - # If the 2nd argument is evaluated first, obj2b is disowned before the conversion for - # the already disowned obj1a fails as expected. - m.mixed(obj1a, obj2b) - - # Either obj1b or obj2b was disowned in the expected failed m.mixed() calls above, but not - # both. - is_disowned_results = (is_disowned(obj1b), is_disowned(obj2b)) - assert is_disowned_results.count(True) == 1 - if first_pass: - first_pass = False - ix = is_disowned_results.index(True) + 1 - print(f"\nC++ function argument {ix} is evaluated first.") - - return # Comment out for manual leak checking (use `top` command). - - -def test_overloaded(): - while True: - obj1 = m.Atype1(81) - obj2 = m.Atype2(60) - with pytest.raises(TypeError): - m.overloaded(obj1, "NotInt") - assert obj1.get() == 81 * 10 + 1 # Not disowned. - assert m.overloaded(obj1, 3) == (81 * 10 + 1) * 30 + 3 - with pytest.raises(TypeError): - m.overloaded(obj2, "NotInt") - assert obj2.get() == 60 * 10 + 2 # Not disowned. - assert m.overloaded(obj2, 2) == (60 * 10 + 2) * 40 + 2 - return # Comment out for manual leak checking (use `top` command). diff --git a/pybind11/tests/test_class_sh_disowning_mi.cpp b/pybind11/tests/test_class_sh_disowning_mi.cpp deleted file mode 100644 index d0ffd45ec..000000000 --- a/pybind11/tests/test_class_sh_disowning_mi.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "pybind11_tests.h" - -#include - -namespace pybind11_tests { -namespace class_sh_disowning_mi { - -// Diamond inheritance (copied from test_multiple_inheritance.cpp). -struct B { - int val_b = 10; - B() = default; - B(const B &) = default; - virtual ~B() = default; -}; - -struct C0 : public virtual B { - int val_c0 = 20; -}; - -struct C1 : public virtual B { - int val_c1 = 21; -}; - -struct D : public C0, public C1 { - int val_d = 30; -}; - -void disown_b(std::unique_ptr) {} - -// test_multiple_inheritance_python -struct Base1 { - explicit Base1(int i) : i(i) {} - int foo() const { return i; } - int i; -}; - -struct Base2 { - explicit Base2(int j) : j(j) {} - int bar() const { return j; } - int j; -}; - -int disown_base1(std::unique_ptr b1) { return b1->i * 2000 + 1; } -int disown_base2(std::unique_ptr b2) { return b2->j * 2000 + 2; } - -} // namespace class_sh_disowning_mi -} // namespace pybind11_tests - -TEST_SUBMODULE(class_sh_disowning_mi, m) { - using namespace pybind11_tests::class_sh_disowning_mi; - - py::classh(m, "B") - .def(py::init<>()) - .def_readonly("val_b", &D::val_b) - .def("b", [](B *self) { return self; }) - .def("get", [](const B &self) { return self.val_b; }); - - py::classh(m, "C0") - .def(py::init<>()) - .def_readonly("val_c0", &D::val_c0) - .def("c0", [](C0 *self) { return self; }) - .def("get", [](const C0 &self) { return self.val_b * 100 + self.val_c0; }); - - py::classh(m, "C1") - .def(py::init<>()) - .def_readonly("val_c1", &D::val_c1) - .def("c1", [](C1 *self) { return self; }) - .def("get", [](const C1 &self) { return self.val_b * 100 + self.val_c1; }); - - py::classh(m, "D") - .def(py::init<>()) - .def_readonly("val_d", &D::val_d) - .def("d", [](D *self) { return self; }) - .def("get", [](const D &self) { - return self.val_b * 1000000 + self.val_c0 * 10000 + self.val_c1 * 100 + self.val_d; - }); - - m.def("disown_b", disown_b); - - // test_multiple_inheritance_python - py::classh(m, "Base1").def(py::init()).def("foo", &Base1::foo); - py::classh(m, "Base2").def(py::init()).def("bar", &Base2::bar); - m.def("disown_base1", disown_base1); - m.def("disown_base2", disown_base2); -} diff --git a/pybind11/tests/test_class_sh_disowning_mi.py b/pybind11/tests/test_class_sh_disowning_mi.py deleted file mode 100644 index 4a4beecce..000000000 --- a/pybind11/tests/test_class_sh_disowning_mi.py +++ /dev/null @@ -1,246 +0,0 @@ -from __future__ import annotations - -import pytest - -import env # noqa: F401 -from pybind11_tests import class_sh_disowning_mi as m - - -def test_diamond_inheritance(): - # Very similar to test_multiple_inheritance.py:test_diamond_inheritance. - d = m.D() - assert d is d.d() - assert d is d.c0() - assert d is d.c1() - assert d is d.b() - assert d is d.c0().b() - assert d is d.c1().b() - assert d is d.c0().c1().b().c0().b() - - -def is_disowned(callable_method): - try: - callable_method() - except ValueError as e: - assert "Python instance was disowned" in str(e) # noqa: PT017 - return True - return False - - -def test_disown_b(): - b = m.B() - assert b.get() == 10 - m.disown_b(b) - assert is_disowned(b.get) - - -@pytest.mark.parametrize("var_to_disown", ["c0", "b"]) -def test_disown_c0(var_to_disown): - c0 = m.C0() - assert c0.get() == 1020 - b = c0.b() - m.disown_b(locals()[var_to_disown]) - assert is_disowned(c0.get) - assert is_disowned(b.get) - - -@pytest.mark.parametrize("var_to_disown", ["c1", "b"]) -def test_disown_c1(var_to_disown): - c1 = m.C1() - assert c1.get() == 1021 - b = c1.b() - m.disown_b(locals()[var_to_disown]) - assert is_disowned(c1.get) - assert is_disowned(b.get) - - -@pytest.mark.parametrize("var_to_disown", ["d", "c1", "c0", "b"]) -def test_disown_d(var_to_disown): - d = m.D() - assert d.get() == 10202130 - b = d.b() - c0 = d.c0() - c1 = d.c1() - m.disown_b(locals()[var_to_disown]) - assert is_disowned(d.get) - assert is_disowned(c1.get) - assert is_disowned(c0.get) - assert is_disowned(b.get) - - -# Based on test_multiple_inheritance.py:test_multiple_inheritance_python. -class MI1(m.Base1, m.Base2): - def __init__(self, i, j): - m.Base1.__init__(self, i) - m.Base2.__init__(self, j) - - -class B1: - def v(self): - return 1 - - -class MI2(B1, m.Base1, m.Base2): - def __init__(self, i, j): - B1.__init__(self) - m.Base1.__init__(self, i) - m.Base2.__init__(self, j) - - -class MI3(MI2): - def __init__(self, i, j): - MI2.__init__(self, i, j) - - -class MI4(MI3, m.Base2): - def __init__(self, i, j): - MI3.__init__(self, i, j) - # This should be ignored (Base2 is already initialized via MI2): - m.Base2.__init__(self, i + 100) - - -class MI5(m.Base2, B1, m.Base1): - def __init__(self, i, j): - B1.__init__(self) - m.Base1.__init__(self, i) - m.Base2.__init__(self, j) - - -class MI6(m.Base2, B1): - def __init__(self, i): - m.Base2.__init__(self, i) - B1.__init__(self) - - -class B2(B1): - def v(self): - return 2 - - -class B3: - def v(self): - return 3 - - -class B4(B3, B2): - def v(self): - return 4 - - -class MI7(B4, MI6): - def __init__(self, i): - B4.__init__(self) - MI6.__init__(self, i) - - -class MI8(MI6, B3): - def __init__(self, i): - MI6.__init__(self, i) - B3.__init__(self) - - -class MI8b(B3, MI6): - def __init__(self, i): - B3.__init__(self) - MI6.__init__(self, i) - - -@pytest.mark.xfail("env.PYPY") -def test_multiple_inheritance_python(): - # Based on test_multiple_inheritance.py:test_multiple_inheritance_python. - # Exercises values_and_holders with 2 value_and_holder instances. - - mi1 = MI1(1, 2) - assert mi1.foo() == 1 - assert mi1.bar() == 2 - - mi2 = MI2(3, 4) - assert mi2.v() == 1 - assert mi2.foo() == 3 - assert mi2.bar() == 4 - - mi3 = MI3(5, 6) - assert mi3.v() == 1 - assert mi3.foo() == 5 - assert mi3.bar() == 6 - - mi4 = MI4(7, 8) - assert mi4.v() == 1 - assert mi4.foo() == 7 - assert mi4.bar() == 8 - - mi5 = MI5(10, 11) - assert mi5.v() == 1 - assert mi5.foo() == 10 - assert mi5.bar() == 11 - - mi6 = MI6(12) - assert mi6.v() == 1 - assert mi6.bar() == 12 - - mi7 = MI7(13) - assert mi7.v() == 4 - assert mi7.bar() == 13 - - mi8 = MI8(14) - assert mi8.v() == 1 - assert mi8.bar() == 14 - - mi8b = MI8b(15) - assert mi8b.v() == 3 - assert mi8b.bar() == 15 - - -DISOWN_CLS_I_J_V_LIST = [ - (MI1, 1, 2, None), - (MI2, 3, 4, 1), - (MI3, 5, 6, 1), - (MI4, 7, 8, 1), - (MI5, 10, 11, 1), -] - - -@pytest.mark.xfail("env.PYPY", strict=False) -@pytest.mark.parametrize(("cls", "i", "j", "v"), DISOWN_CLS_I_J_V_LIST) -def test_disown_base1_first(cls, i, j, v): - obj = cls(i, j) - assert obj.foo() == i - assert m.disown_base1(obj) == 2000 * i + 1 - assert is_disowned(obj.foo) - assert obj.bar() == j - assert m.disown_base2(obj) == 2000 * j + 2 - assert is_disowned(obj.bar) - if v is not None: - assert obj.v() == v - - -@pytest.mark.xfail("env.PYPY", strict=False) -@pytest.mark.parametrize(("cls", "i", "j", "v"), DISOWN_CLS_I_J_V_LIST) -def test_disown_base2_first(cls, i, j, v): - obj = cls(i, j) - assert obj.bar() == j - assert m.disown_base2(obj) == 2000 * j + 2 - assert is_disowned(obj.bar) - assert obj.foo() == i - assert m.disown_base1(obj) == 2000 * i + 1 - assert is_disowned(obj.foo) - if v is not None: - assert obj.v() == v - - -@pytest.mark.xfail("env.PYPY", strict=False) -@pytest.mark.parametrize( - ("cls", "j", "v"), - [ - (MI6, 12, 1), - (MI7, 13, 4), - (MI8, 14, 1), - (MI8b, 15, 3), - ], -) -def test_disown_base2(cls, j, v): - obj = cls(j) - assert obj.bar() == j - assert m.disown_base2(obj) == 2000 * j + 2 - assert is_disowned(obj.bar) - assert obj.v() == v diff --git a/pybind11/tests/test_class_sh_factory_constructors.cpp b/pybind11/tests/test_class_sh_factory_constructors.cpp deleted file mode 100644 index b3a8daea5..000000000 --- a/pybind11/tests/test_class_sh_factory_constructors.cpp +++ /dev/null @@ -1,164 +0,0 @@ -#include "pybind11_tests.h" - -#include -#include - -namespace pybind11_tests { -namespace class_sh_factory_constructors { - -template // Using int as a trick to easily generate a series of types. -struct atyp { // Short for "any type". - std::string mtxt; -}; - -template -std::string get_mtxt(const T &obj) { - return obj.mtxt; -} - -using atyp_valu = atyp<0x0>; -using atyp_rref = atyp<0x1>; -using atyp_cref = atyp<0x2>; -using atyp_mref = atyp<0x3>; -using atyp_cptr = atyp<0x4>; -using atyp_mptr = atyp<0x5>; -using atyp_shmp = atyp<0x6>; -using atyp_shcp = atyp<0x7>; -using atyp_uqmp = atyp<0x8>; -using atyp_uqcp = atyp<0x9>; -using atyp_udmp = atyp<0xA>; -using atyp_udcp = atyp<0xB>; - -// clang-format off - -atyp_valu rtrn_valu() { atyp_valu obj{"Valu"}; return obj; } -atyp_rref&& rtrn_rref() { static atyp_rref obj; obj.mtxt = "Rref"; return std::move(obj); } -atyp_cref const& rtrn_cref() { static atyp_cref obj; obj.mtxt = "Cref"; return obj; } -atyp_mref& rtrn_mref() { static atyp_mref obj; obj.mtxt = "Mref"; return obj; } -atyp_cptr const* rtrn_cptr() { return new atyp_cptr{"Cptr"}; } -atyp_mptr* rtrn_mptr() { return new atyp_mptr{"Mptr"}; } - -std::shared_ptr rtrn_shmp() { return std::make_shared(atyp_shmp{"Shmp"}); } -std::shared_ptr rtrn_shcp() { return std::shared_ptr(new atyp_shcp{"Shcp"}); } - -std::unique_ptr rtrn_uqmp() { return std::unique_ptr(new atyp_uqmp{"Uqmp"}); } -std::unique_ptr rtrn_uqcp() { return std::unique_ptr(new atyp_uqcp{"Uqcp"}); } - -struct sddm : std::default_delete {}; -struct sddc : std::default_delete {}; - -std::unique_ptr rtrn_udmp() { return std::unique_ptr(new atyp_udmp{"Udmp"}); } -std::unique_ptr rtrn_udcp() { return std::unique_ptr(new atyp_udcp{"Udcp"}); } - -// clang-format on - -// Minimalistic approach to achieve full coverage of construct() overloads for constructing -// smart_holder from unique_ptr and shared_ptr returns. -struct with_alias { - int val = 0; - virtual ~with_alias() = default; - // Some compilers complain about implicitly defined versions of some of the following: - with_alias() = default; - with_alias(const with_alias &) = default; - with_alias(with_alias &&) = default; - with_alias &operator=(const with_alias &) = default; - with_alias &operator=(with_alias &&) = default; -}; -struct with_alias_alias : with_alias {}; -struct sddwaa : std::default_delete {}; - -} // namespace class_sh_factory_constructors -} // namespace pybind11_tests - -TEST_SUBMODULE(class_sh_factory_constructors, m) { - using namespace pybind11_tests::class_sh_factory_constructors; - - py::classh(m, "atyp_valu") - .def(py::init(&rtrn_valu)) - .def("get_mtxt", get_mtxt); - - py::classh(m, "atyp_rref") - .def(py::init(&rtrn_rref)) - .def("get_mtxt", get_mtxt); - - py::classh(m, "atyp_cref") - // class_: ... must return a compatible ... - // classh: ... cannot pass object of non-trivial type ... - // .def(py::init(&rtrn_cref)) - .def("get_mtxt", get_mtxt); - - py::classh(m, "atyp_mref") - // class_: ... must return a compatible ... - // classh: ... cannot pass object of non-trivial type ... - // .def(py::init(&rtrn_mref)) - .def("get_mtxt", get_mtxt); - - py::classh(m, "atyp_cptr") - // class_: ... must return a compatible ... - // classh: ... must return a compatible ... - // .def(py::init(&rtrn_cptr)) - .def("get_mtxt", get_mtxt); - - py::classh(m, "atyp_mptr") - .def(py::init(&rtrn_mptr)) - .def("get_mtxt", get_mtxt); - - py::classh(m, "atyp_shmp") - .def(py::init(&rtrn_shmp)) - .def("get_mtxt", get_mtxt); - - py::classh(m, "atyp_shcp") - // py::class_>(m, "atyp_shcp") - // class_: ... must return a compatible ... - // classh: ... cannot pass object of non-trivial type ... - // .def(py::init(&rtrn_shcp)) - .def("get_mtxt", get_mtxt); - - py::classh(m, "atyp_uqmp") - .def(py::init(&rtrn_uqmp)) - .def("get_mtxt", get_mtxt); - - py::classh(m, "atyp_uqcp") - // class_: ... cannot pass object of non-trivial type ... - // classh: ... cannot pass object of non-trivial type ... - // .def(py::init(&rtrn_uqcp)) - .def("get_mtxt", get_mtxt); - - py::classh(m, "atyp_udmp") - .def(py::init(&rtrn_udmp)) - .def("get_mtxt", get_mtxt); - - py::classh(m, "atyp_udcp") - // py::class_>(m, "atyp_udcp") - // class_: ... must return a compatible ... - // classh: ... cannot pass object of non-trivial type ... - // .def(py::init(&rtrn_udcp)) - .def("get_mtxt", get_mtxt); - - py::classh(m, "with_alias") - .def_readonly("val", &with_alias::val) - .def(py::init([](int i) { - auto p = std::unique_ptr(new with_alias_alias); - p->val = i * 100; - return p; - })) - .def(py::init([](int i, int j) { - auto p = std::unique_ptr(new with_alias_alias); - p->val = i * 100 + j * 10; - return p; - })) - .def(py::init([](int i, int j, int k) { - auto p = std::make_shared(); - p->val = i * 100 + j * 10 + k; - return p; - })) - .def(py::init( - [](int, int, int, int) { return std::unique_ptr(new with_alias); }, - [](int, int, int, int) { - return std::unique_ptr(new with_alias); // Invalid alias factory. - })) - .def(py::init([](int, int, int, int, int) { return std::make_shared(); }, - [](int, int, int, int, int) { - return std::make_shared(); // Invalid alias factory. - })); -} diff --git a/pybind11/tests/test_class_sh_factory_constructors.py b/pybind11/tests/test_class_sh_factory_constructors.py deleted file mode 100644 index 5d45db6fd..000000000 --- a/pybind11/tests/test_class_sh_factory_constructors.py +++ /dev/null @@ -1,53 +0,0 @@ -from __future__ import annotations - -import pytest - -from pybind11_tests import class_sh_factory_constructors as m - - -def test_atyp_factories(): - assert m.atyp_valu().get_mtxt() == "Valu" - assert m.atyp_rref().get_mtxt() == "Rref" - # sert m.atyp_cref().get_mtxt() == "Cref" - # sert m.atyp_mref().get_mtxt() == "Mref" - # sert m.atyp_cptr().get_mtxt() == "Cptr" - assert m.atyp_mptr().get_mtxt() == "Mptr" - assert m.atyp_shmp().get_mtxt() == "Shmp" - # sert m.atyp_shcp().get_mtxt() == "Shcp" - assert m.atyp_uqmp().get_mtxt() == "Uqmp" - # sert m.atyp_uqcp().get_mtxt() == "Uqcp" - assert m.atyp_udmp().get_mtxt() == "Udmp" - # sert m.atyp_udcp().get_mtxt() == "Udcp" - - -@pytest.mark.parametrize( - ("init_args", "expected"), - [ - ((3,), 300), - ((5, 7), 570), - ((9, 11, 13), 1023), - ], -) -def test_with_alias_success(init_args, expected): - assert m.with_alias(*init_args).val == expected - - -@pytest.mark.parametrize( - ("num_init_args", "smart_ptr"), - [ - (4, "std::unique_ptr"), - (5, "std::shared_ptr"), - ], -) -def test_with_alias_invalid(num_init_args, smart_ptr): - class PyDrvdWithAlias(m.with_alias): - pass - - with pytest.raises(TypeError) as excinfo: - PyDrvdWithAlias(*((0,) * num_init_args)) - assert ( - str(excinfo.value) - == "pybind11::init(): construction failed: returned " - + smart_ptr - + " pointee is not an alias instance" - ) diff --git a/pybind11/tests/test_class_sh_inheritance.cpp b/pybind11/tests/test_class_sh_inheritance.cpp deleted file mode 100644 index 8bdd0a7f8..000000000 --- a/pybind11/tests/test_class_sh_inheritance.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "pybind11_tests.h" - -#include - -namespace pybind11_tests { -namespace class_sh_inheritance { - -template -struct base_template { - base_template() : base_id(Id) {} - virtual ~base_template() = default; - virtual int id() const { return base_id; } - int base_id; - - // Some compilers complain about implicitly defined versions of some of the following: - base_template(const base_template &) = default; - base_template(base_template &&) noexcept = default; - base_template &operator=(const base_template &) = default; - base_template &operator=(base_template &&) noexcept = default; -}; - -using base = base_template<100>; - -struct drvd : base { - int id() const override { return 2 * base_id; } -}; - -// clang-format off -inline drvd *rtrn_mptr_drvd() { return new drvd; } -inline base *rtrn_mptr_drvd_up_cast() { return new drvd; } - -inline int pass_cptr_base(base const *b) { return b->id() + 11; } -inline int pass_cptr_drvd(drvd const *d) { return d->id() + 12; } - -inline std::shared_ptr rtrn_shmp_drvd() { return std::make_shared(); } -inline std::shared_ptr rtrn_shmp_drvd_up_cast() { return std::make_shared(); } - -inline int pass_shcp_base(const std::shared_ptr& b) { return b->id() + 21; } -inline int pass_shcp_drvd(const std::shared_ptr& d) { return d->id() + 22; } -// clang-format on - -using base1 = base_template<110>; -using base2 = base_template<120>; - -// Not reusing base here because it would interfere with the single-inheritance test. -struct drvd2 : base1, base2 { - int id() const override { return 3 * base1::base_id + 4 * base2::base_id; } -}; - -// clang-format off -inline drvd2 *rtrn_mptr_drvd2() { return new drvd2; } -inline base1 *rtrn_mptr_drvd2_up_cast1() { return new drvd2; } -inline base2 *rtrn_mptr_drvd2_up_cast2() { return new drvd2; } - -inline int pass_cptr_base1(base1 const *b) { return b->id() + 21; } -inline int pass_cptr_base2(base2 const *b) { return b->id() + 22; } -inline int pass_cptr_drvd2(drvd2 const *d) { return d->id() + 23; } -// clang-format on - -TEST_SUBMODULE(class_sh_inheritance, m) { - py::classh(m, "base"); - py::classh(m, "drvd"); - - auto rvto = py::return_value_policy::take_ownership; - - m.def("rtrn_mptr_drvd", rtrn_mptr_drvd, rvto); - m.def("rtrn_mptr_drvd_up_cast", rtrn_mptr_drvd_up_cast, rvto); - m.def("pass_cptr_base", pass_cptr_base); - m.def("pass_cptr_drvd", pass_cptr_drvd); - - m.def("rtrn_shmp_drvd", rtrn_shmp_drvd); - m.def("rtrn_shmp_drvd_up_cast", rtrn_shmp_drvd_up_cast); - m.def("pass_shcp_base", pass_shcp_base); - m.def("pass_shcp_drvd", pass_shcp_drvd); - - // __init__ needed for Python inheritance. - py::classh(m, "base1").def(py::init<>()); - py::classh(m, "base2").def(py::init<>()); - py::classh(m, "drvd2"); - - m.def("rtrn_mptr_drvd2", rtrn_mptr_drvd2, rvto); - m.def("rtrn_mptr_drvd2_up_cast1", rtrn_mptr_drvd2_up_cast1, rvto); - m.def("rtrn_mptr_drvd2_up_cast2", rtrn_mptr_drvd2_up_cast2, rvto); - m.def("pass_cptr_base1", pass_cptr_base1); - m.def("pass_cptr_base2", pass_cptr_base2); - m.def("pass_cptr_drvd2", pass_cptr_drvd2); -} - -} // namespace class_sh_inheritance -} // namespace pybind11_tests diff --git a/pybind11/tests/test_class_sh_inheritance.py b/pybind11/tests/test_class_sh_inheritance.py deleted file mode 100644 index cd9d6f47e..000000000 --- a/pybind11/tests/test_class_sh_inheritance.py +++ /dev/null @@ -1,63 +0,0 @@ -from __future__ import annotations - -from pybind11_tests import class_sh_inheritance as m - - -def test_rtrn_mptr_drvd_pass_cptr_base(): - d = m.rtrn_mptr_drvd() - i = m.pass_cptr_base(d) # load_impl Case 2a - assert i == 2 * 100 + 11 - - -def test_rtrn_shmp_drvd_pass_shcp_base(): - d = m.rtrn_shmp_drvd() - i = m.pass_shcp_base(d) # load_impl Case 2a - assert i == 2 * 100 + 21 - - -def test_rtrn_mptr_drvd_up_cast_pass_cptr_drvd(): - b = m.rtrn_mptr_drvd_up_cast() - # the base return is down-cast immediately. - assert b.__class__.__name__ == "drvd" - i = m.pass_cptr_drvd(b) - assert i == 2 * 100 + 12 - - -def test_rtrn_shmp_drvd_up_cast_pass_shcp_drvd(): - b = m.rtrn_shmp_drvd_up_cast() - # the base return is down-cast immediately. - assert b.__class__.__name__ == "drvd" - i = m.pass_shcp_drvd(b) - assert i == 2 * 100 + 22 - - -def test_rtrn_mptr_drvd2_pass_cptr_bases(): - d = m.rtrn_mptr_drvd2() - i1 = m.pass_cptr_base1(d) # load_impl Case 2c - assert i1 == 3 * 110 + 4 * 120 + 21 - i2 = m.pass_cptr_base2(d) - assert i2 == 3 * 110 + 4 * 120 + 22 - - -def test_rtrn_mptr_drvd2_up_casts_pass_cptr_drvd2(): - b1 = m.rtrn_mptr_drvd2_up_cast1() - assert b1.__class__.__name__ == "drvd2" - i1 = m.pass_cptr_drvd2(b1) - assert i1 == 3 * 110 + 4 * 120 + 23 - b2 = m.rtrn_mptr_drvd2_up_cast2() - assert b2.__class__.__name__ == "drvd2" - i2 = m.pass_cptr_drvd2(b2) - assert i2 == 3 * 110 + 4 * 120 + 23 - - -def test_python_drvd2(): - class Drvd2(m.base1, m.base2): - def __init__(self): - m.base1.__init__(self) - m.base2.__init__(self) - - d = Drvd2() - i1 = m.pass_cptr_base1(d) # load_impl Case 2b - assert i1 == 110 + 21 - i2 = m.pass_cptr_base2(d) - assert i2 == 120 + 22 diff --git a/pybind11/tests/test_class_sh_mi_thunks.cpp b/pybind11/tests/test_class_sh_mi_thunks.cpp deleted file mode 100644 index d8548ec5c..000000000 --- a/pybind11/tests/test_class_sh_mi_thunks.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include "pybind11_tests.h" - -#include -#include -#include - -namespace test_class_sh_mi_thunks { - -// For general background: https://shaharmike.com/cpp/vtable-part2/ -// C++ vtables - Part 2 - Multiple Inheritance -// ... the compiler creates a 'thunk' method that corrects `this` ... - -struct Base0 { - virtual ~Base0() = default; - Base0() = default; - Base0(const Base0 &) = delete; -}; - -struct Base1 { - virtual ~Base1() = default; - // Using `vector` here because it is known to make this test very sensitive to bugs. - std::vector vec = {1, 2, 3, 4, 5}; - Base1() = default; - Base1(const Base1 &) = delete; -}; - -struct Derived : Base1, Base0 { - ~Derived() override = default; - Derived() = default; - Derived(const Derived &) = delete; -}; - -} // namespace test_class_sh_mi_thunks - -TEST_SUBMODULE(class_sh_mi_thunks, m) { - using namespace test_class_sh_mi_thunks; - - m.def("ptrdiff_drvd_base0", []() { - auto drvd = std::unique_ptr(new Derived); - auto *base0 = dynamic_cast(drvd.get()); - return std::ptrdiff_t(reinterpret_cast(drvd.get()) - - reinterpret_cast(base0)); - }); - - py::classh(m, "Base0"); - py::classh(m, "Base1"); - py::classh(m, "Derived"); - - m.def( - "get_drvd_as_base0_raw_ptr", - []() { - auto *drvd = new Derived; - auto *base0 = dynamic_cast(drvd); - return base0; - }, - py::return_value_policy::take_ownership); - - m.def("get_drvd_as_base0_shared_ptr", []() { - auto drvd = std::make_shared(); - auto base0 = std::dynamic_pointer_cast(drvd); - return base0; - }); - - m.def("get_drvd_as_base0_unique_ptr", []() { - auto drvd = std::unique_ptr(new Derived); - auto base0 = std::unique_ptr(std::move(drvd)); - return base0; - }); - - m.def("vec_size_base0_raw_ptr", [](const Base0 *obj) { - const auto *obj_der = dynamic_cast(obj); - if (obj_der == nullptr) { - return std::size_t(0); - } - return obj_der->vec.size(); - }); - - m.def("vec_size_base0_shared_ptr", [](const std::shared_ptr &obj) -> std::size_t { - const auto obj_der = std::dynamic_pointer_cast(obj); - if (!obj_der) { - return std::size_t(0); - } - return obj_der->vec.size(); - }); - - m.def("vec_size_base0_unique_ptr", [](std::unique_ptr obj) -> std::size_t { - const auto *obj_der = dynamic_cast(obj.get()); - if (obj_der == nullptr) { - return std::size_t(0); - } - return obj_der->vec.size(); - }); -} diff --git a/pybind11/tests/test_class_sh_mi_thunks.py b/pybind11/tests/test_class_sh_mi_thunks.py deleted file mode 100644 index 32bf47554..000000000 --- a/pybind11/tests/test_class_sh_mi_thunks.py +++ /dev/null @@ -1,53 +0,0 @@ -from __future__ import annotations - -import pytest - -from pybind11_tests import class_sh_mi_thunks as m - - -def test_ptrdiff_drvd_base0(): - ptrdiff = m.ptrdiff_drvd_base0() - # A failure here does not (necessarily) mean that there is a bug, but that - # test_class_sh_mi_thunks is not exercising what it is supposed to. - # If this ever fails on some platforms: use pytest.skip() - # If this ever fails on all platforms: don't know, seems extremely unlikely. - assert ptrdiff != 0 - - -@pytest.mark.parametrize( - "vec_size_fn", - [ - m.vec_size_base0_raw_ptr, - m.vec_size_base0_shared_ptr, - ], -) -@pytest.mark.parametrize( - "get_fn", - [ - m.get_drvd_as_base0_raw_ptr, - m.get_drvd_as_base0_shared_ptr, - m.get_drvd_as_base0_unique_ptr, - ], -) -def test_get_vec_size_raw_shared(get_fn, vec_size_fn): - obj = get_fn() - assert vec_size_fn(obj) == 5 - - -@pytest.mark.parametrize( - "get_fn", [m.get_drvd_as_base0_raw_ptr, m.get_drvd_as_base0_unique_ptr] -) -def test_get_vec_size_unique(get_fn): - obj = get_fn() - assert m.vec_size_base0_unique_ptr(obj) == 5 - with pytest.raises(ValueError, match="Python instance was disowned"): - m.vec_size_base0_unique_ptr(obj) - - -def test_get_shared_vec_size_unique(): - obj = m.get_drvd_as_base0_shared_ptr() - with pytest.raises(ValueError) as exc_info: - m.vec_size_base0_unique_ptr(obj) - assert ( - str(exc_info.value) == "Cannot disown external shared_ptr (load_as_unique_ptr)." - ) diff --git a/pybind11/tests/test_class_sh_property.cpp b/pybind11/tests/test_class_sh_property.cpp deleted file mode 100644 index 8863ad7d7..000000000 --- a/pybind11/tests/test_class_sh_property.cpp +++ /dev/null @@ -1,94 +0,0 @@ -// The compact 4-character naming matches that in test_class_sh_basic.cpp -// Variable names are intentionally terse, to not distract from the more important C++ type names: -// valu(e), ref(erence), ptr or p (pointer), r = rvalue, m = mutable, c = const, -// sh = shared_ptr, uq = unique_ptr. - -#include "pybind11_tests.h" - -#include - -namespace test_class_sh_property { - -struct ClassicField { - int num = -88; -}; - -struct ClassicOuter { - ClassicField *m_mptr = nullptr; - const ClassicField *m_cptr = nullptr; -}; - -struct Field { - int num = -99; -}; - -struct Outer { - Field m_valu; - Field *m_mptr = nullptr; - const Field *m_cptr = nullptr; - std::unique_ptr m_uqmp; - std::unique_ptr m_uqcp; - std::shared_ptr m_shmp; - std::shared_ptr m_shcp; -}; - -inline void DisownOuter(std::unique_ptr) {} - -struct WithCharArrayMember { - WithCharArrayMember() { std::memcpy(char6_member, "Char6", 6); } - char char6_member[6]; -}; - -struct WithConstCharPtrMember { - const char *const_char_ptr_member = "ConstChar*"; -}; - -} // namespace test_class_sh_property - -TEST_SUBMODULE(class_sh_property, m) { - using namespace test_class_sh_property; - - py::class_>(m, "ClassicField") - .def(py::init<>()) - .def_readwrite("num", &ClassicField::num); - - py::class_>(m, "ClassicOuter") - .def(py::init<>()) - .def_readonly("m_mptr_readonly", &ClassicOuter::m_mptr) - .def_readwrite("m_mptr_readwrite", &ClassicOuter::m_mptr) - .def_readwrite("m_cptr_readonly", &ClassicOuter::m_cptr) - .def_readwrite("m_cptr_readwrite", &ClassicOuter::m_cptr); - - py::classh(m, "Field").def(py::init<>()).def_readwrite("num", &Field::num); - - py::classh(m, "Outer") - .def(py::init<>()) - - .def_readonly("m_valu_readonly", &Outer::m_valu) - .def_readwrite("m_valu_readwrite", &Outer::m_valu) - - .def_readonly("m_mptr_readonly", &Outer::m_mptr) - .def_readwrite("m_mptr_readwrite", &Outer::m_mptr) - .def_readonly("m_cptr_readonly", &Outer::m_cptr) - .def_readwrite("m_cptr_readwrite", &Outer::m_cptr) - - // .def_readonly("m_uqmp_readonly", &Outer::m_uqmp) // Custom compilation Error. - .def_readwrite("m_uqmp_readwrite", &Outer::m_uqmp) - // .def_readonly("m_uqcp_readonly", &Outer::m_uqcp) // Custom compilation Error. - .def_readwrite("m_uqcp_readwrite", &Outer::m_uqcp) - - .def_readwrite("m_shmp_readonly", &Outer::m_shmp) - .def_readwrite("m_shmp_readwrite", &Outer::m_shmp) - .def_readwrite("m_shcp_readonly", &Outer::m_shcp) - .def_readwrite("m_shcp_readwrite", &Outer::m_shcp); - - m.def("DisownOuter", DisownOuter); - - py::classh(m, "WithCharArrayMember") - .def(py::init<>()) - .def_readonly("char6_member", &WithCharArrayMember::char6_member); - - py::classh(m, "WithConstCharPtrMember") - .def(py::init<>()) - .def_readonly("const_char_ptr_member", &WithConstCharPtrMember::const_char_ptr_member); -} diff --git a/pybind11/tests/test_class_sh_property.py b/pybind11/tests/test_class_sh_property.py deleted file mode 100644 index 0250a7f78..000000000 --- a/pybind11/tests/test_class_sh_property.py +++ /dev/null @@ -1,166 +0,0 @@ -# The compact 4-character naming scheme (e.g. mptr, cptr, shcp) is explained at the top of -# test_class_sh_property.cpp. -from __future__ import annotations - -import pytest - -import env # noqa: F401 -from pybind11_tests import class_sh_property as m - - -@pytest.mark.skipif( - "env.PYPY or env.GRAALPY", reason="gc after `del field` is apparently deferred" -) -@pytest.mark.parametrize("m_attr", ["m_valu_readonly", "m_valu_readwrite"]) -def test_valu_getter(m_attr): - # Reduced from PyCLIF test: - # https://github.com/google/clif/blob/c371a6d4b28d25d53a16e6d2a6d97305fb1be25a/clif/testing/python/nested_fields_test.py#L56 - outer = m.Outer() - field = getattr(outer, m_attr) - assert field.num == -99 - with pytest.raises(ValueError) as excinfo: - m.DisownOuter(outer) - assert str(excinfo.value) == "Cannot disown use_count != 1 (load_as_unique_ptr)." - del field - m.DisownOuter(outer) - with pytest.raises(ValueError, match="Python instance was disowned") as excinfo: - getattr(outer, m_attr) - - -def test_valu_setter(): - outer = m.Outer() - assert outer.m_valu_readonly.num == -99 - assert outer.m_valu_readwrite.num == -99 - field = m.Field() - field.num = 35 - outer.m_valu_readwrite = field - assert outer.m_valu_readonly.num == 35 - assert outer.m_valu_readwrite.num == 35 - - -@pytest.mark.parametrize("m_attr", ["m_shmp", "m_shcp"]) -def test_shp(m_attr): - m_attr_readonly = m_attr + "_readonly" - m_attr_readwrite = m_attr + "_readwrite" - outer = m.Outer() - assert getattr(outer, m_attr_readonly) is None - assert getattr(outer, m_attr_readwrite) is None - field = m.Field() - field.num = 43 - setattr(outer, m_attr_readwrite, field) - assert getattr(outer, m_attr_readonly).num == 43 - assert getattr(outer, m_attr_readwrite).num == 43 - getattr(outer, m_attr_readonly).num = 57 - getattr(outer, m_attr_readwrite).num = 57 - assert field.num == 57 - del field - assert getattr(outer, m_attr_readonly).num == 57 - assert getattr(outer, m_attr_readwrite).num == 57 - - -@pytest.mark.parametrize( - ("field_type", "num_default", "outer_type"), - [ - (m.ClassicField, -88, m.ClassicOuter), - (m.Field, -99, m.Outer), - ], -) -@pytest.mark.parametrize("m_attr", ["m_mptr", "m_cptr"]) -@pytest.mark.parametrize("r_kind", ["_readonly", "_readwrite"]) -def test_ptr(field_type, num_default, outer_type, m_attr, r_kind): - m_attr_r_kind = m_attr + r_kind - outer = outer_type() - assert getattr(outer, m_attr_r_kind) is None - field = field_type() - assert field.num == num_default - setattr(outer, m_attr + "_readwrite", field) - assert getattr(outer, m_attr_r_kind).num == num_default - field.num = 76 - assert getattr(outer, m_attr_r_kind).num == 76 - # Change to -88 or -99 to demonstrate Undefined Behavior (dangling pointer). - if num_default == 88 and m_attr == "m_mptr": - del field - assert getattr(outer, m_attr_r_kind).num == 76 - - -@pytest.mark.parametrize("m_attr_readwrite", ["m_uqmp_readwrite", "m_uqcp_readwrite"]) -def test_uqp(m_attr_readwrite): - outer = m.Outer() - assert getattr(outer, m_attr_readwrite) is None - field_orig = m.Field() - field_orig.num = 39 - setattr(outer, m_attr_readwrite, field_orig) - with pytest.raises(ValueError, match="Python instance was disowned"): - _ = field_orig.num - field_retr1 = getattr(outer, m_attr_readwrite) - assert getattr(outer, m_attr_readwrite) is None - assert field_retr1.num == 39 - field_retr1.num = 93 - setattr(outer, m_attr_readwrite, field_retr1) - with pytest.raises(ValueError): - _ = field_retr1.num - field_retr2 = getattr(outer, m_attr_readwrite) - assert field_retr2.num == 93 - - -# Proof-of-concept (POC) for safe & intuitive Python access to unique_ptr members. -# The C++ member unique_ptr is disowned to a temporary Python object for accessing -# an attribute of the member. After the attribute was accessed, the Python object -# is disowned back to the C++ member unique_ptr. -# Productizing this POC is left for a future separate PR, as needed. -class unique_ptr_field_proxy_poc: - def __init__(self, obj, field_name): - object.__setattr__(self, "__obj", obj) - object.__setattr__(self, "__field_name", field_name) - - def __getattr__(self, *args, **kwargs): - return _proxy_dereference(self, getattr, *args, **kwargs) - - def __setattr__(self, *args, **kwargs): - return _proxy_dereference(self, setattr, *args, **kwargs) - - def __delattr__(self, *args, **kwargs): - return _proxy_dereference(self, delattr, *args, **kwargs) - - -def _proxy_dereference(proxy, xxxattr, *args, **kwargs): - obj = object.__getattribute__(proxy, "__obj") - field_name = object.__getattribute__(proxy, "__field_name") - field = getattr(obj, field_name) # Disowns the C++ unique_ptr member. - assert field is not None - try: - return xxxattr(field, *args, **kwargs) - finally: - setattr(obj, field_name, field) # Disowns the temporary Python object (field). - - -@pytest.mark.parametrize("m_attr", ["m_uqmp", "m_uqcp"]) -def test_unique_ptr_field_proxy_poc(m_attr): - m_attr_readwrite = m_attr + "_readwrite" - outer = m.Outer() - field_orig = m.Field() - field_orig.num = 45 - setattr(outer, m_attr_readwrite, field_orig) - field_proxy = unique_ptr_field_proxy_poc(outer, m_attr_readwrite) - assert field_proxy.num == 45 - assert field_proxy.num == 45 - with pytest.raises(AttributeError): - _ = field_proxy.xyz - assert field_proxy.num == 45 - field_proxy.num = 82 - assert field_proxy.num == 82 - field_proxy = unique_ptr_field_proxy_poc(outer, m_attr_readwrite) - assert field_proxy.num == 82 - with pytest.raises(AttributeError): - del field_proxy.num - assert field_proxy.num == 82 - - -def test_readonly_char6_member(): - obj = m.WithCharArrayMember() - assert obj.char6_member == "Char6" - - -def test_readonly_const_char_ptr_member(): - obj = m.WithConstCharPtrMember() - assert obj.const_char_ptr_member == "ConstChar*" diff --git a/pybind11/tests/test_class_sh_property_non_owning.cpp b/pybind11/tests/test_class_sh_property_non_owning.cpp deleted file mode 100644 index 45fe7c7be..000000000 --- a/pybind11/tests/test_class_sh_property_non_owning.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "pybind11_tests.h" - -#include -#include - -namespace test_class_sh_property_non_owning { - -struct CoreField { - explicit CoreField(int int_value = -99) : int_value{int_value} {} - int int_value; -}; - -struct DataField { - DataField(int i_value, int i_shared, int i_unique) - : core_fld_value{i_value}, core_fld_shared_ptr{new CoreField{i_shared}}, - core_fld_raw_ptr{core_fld_shared_ptr.get()}, - core_fld_unique_ptr{new CoreField{i_unique}} {} - CoreField core_fld_value; - std::shared_ptr core_fld_shared_ptr; - CoreField *core_fld_raw_ptr; - std::unique_ptr core_fld_unique_ptr; -}; - -struct DataFieldsHolder { -private: - std::vector vec; - -public: - explicit DataFieldsHolder(std::size_t vec_size) { - for (std::size_t i = 0; i < vec_size; i++) { - int i11 = static_cast(i) * 11; - vec.emplace_back(13 + i11, 14 + i11, 15 + i11); - } - } - - DataField *vec_at(std::size_t index) { - if (index >= vec.size()) { - return nullptr; - } - return &vec[index]; - } -}; - -} // namespace test_class_sh_property_non_owning - -using namespace test_class_sh_property_non_owning; - -TEST_SUBMODULE(class_sh_property_non_owning, m) { - py::classh(m, "CoreField").def_readwrite("int_value", &CoreField::int_value); - - py::classh(m, "DataField") - .def_readonly("core_fld_value_ro", &DataField::core_fld_value) - .def_readwrite("core_fld_value_rw", &DataField::core_fld_value) - .def_readonly("core_fld_shared_ptr_ro", &DataField::core_fld_shared_ptr) - .def_readwrite("core_fld_shared_ptr_rw", &DataField::core_fld_shared_ptr) - .def_readonly("core_fld_raw_ptr_ro", &DataField::core_fld_raw_ptr) - .def_readwrite("core_fld_raw_ptr_rw", &DataField::core_fld_raw_ptr) - .def_readwrite("core_fld_unique_ptr_rw", &DataField::core_fld_unique_ptr); - - py::classh(m, "DataFieldsHolder") - .def(py::init()) - .def("vec_at", &DataFieldsHolder::vec_at, py::return_value_policy::reference_internal); -} diff --git a/pybind11/tests/test_class_sh_property_non_owning.py b/pybind11/tests/test_class_sh_property_non_owning.py deleted file mode 100644 index 33a9d4503..000000000 --- a/pybind11/tests/test_class_sh_property_non_owning.py +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -import pytest - -from pybind11_tests import class_sh_property_non_owning as m - - -@pytest.mark.parametrize("persistent_holder", [True, False]) -@pytest.mark.parametrize( - ("core_fld", "expected"), - [ - ("core_fld_value_ro", (13, 24)), - ("core_fld_value_rw", (13, 24)), - ("core_fld_shared_ptr_ro", (14, 25)), - ("core_fld_shared_ptr_rw", (14, 25)), - ("core_fld_raw_ptr_ro", (14, 25)), - ("core_fld_raw_ptr_rw", (14, 25)), - ("core_fld_unique_ptr_rw", (15, 26)), - ], -) -def test_core_fld_common(core_fld, expected, persistent_holder): - if persistent_holder: - h = m.DataFieldsHolder(2) - for i, exp in enumerate(expected): - c = getattr(h.vec_at(i), core_fld) - assert c.int_value == exp - else: - for i, exp in enumerate(expected): - c = getattr(m.DataFieldsHolder(2).vec_at(i), core_fld) - assert c.int_value == exp diff --git a/pybind11/tests/test_class_sh_shared_ptr_copy_move.cpp b/pybind11/tests/test_class_sh_shared_ptr_copy_move.cpp deleted file mode 100644 index 889425a0b..000000000 --- a/pybind11/tests/test_class_sh_shared_ptr_copy_move.cpp +++ /dev/null @@ -1,103 +0,0 @@ -#include "pybind11_tests.h" - -#include -#include -#include - -namespace pybind11_tests { -namespace { - -const std::string fooNames[] = {"ShPtr_", "SmHld_"}; - -template -struct Foo { - std::string history; - explicit Foo(const std::string &history_) : history(history_) {} - Foo(const Foo &other) : history(other.history + "_CpCtor") {} - Foo(Foo &&other) noexcept : history(other.history + "_MvCtor") {} - Foo &operator=(const Foo &other) { - history = other.history + "_OpEqLv"; - return *this; - } - Foo &operator=(Foo &&other) noexcept { - history = other.history + "_OpEqRv"; - return *this; - } - std::string get_history() const { return "Foo" + fooNames[SerNo] + history; } -}; - -using FooShPtr = Foo<0>; -using FooSmHld = Foo<1>; - -struct Outer { - std::shared_ptr ShPtr; - std::shared_ptr SmHld; - Outer() - : ShPtr(std::make_shared("Outer")), SmHld(std::make_shared("Outer")) {} - std::shared_ptr getShPtr() const { return ShPtr; } - std::shared_ptr getSmHld() const { return SmHld; } -}; - -} // namespace - -TEST_SUBMODULE(class_sh_shared_ptr_copy_move, m) { - namespace py = pybind11; - - py::class_>(m, "FooShPtr") - .def("get_history", &FooShPtr::get_history); - py::classh(m, "FooSmHld").def("get_history", &FooSmHld::get_history); - - auto outer = py::class_(m, "Outer").def(py::init()); -#define MAKE_PROP(PropTyp) \ - MAKE_PROP_FOO(ShPtr, PropTyp) \ - MAKE_PROP_FOO(SmHld, PropTyp) - -#define MAKE_PROP_FOO(FooTyp, PropTyp) \ - .def_##PropTyp(#FooTyp "_" #PropTyp "_default", &Outer::FooTyp) \ - .def_##PropTyp( \ - #FooTyp "_" #PropTyp "_copy", &Outer::FooTyp, py::return_value_policy::copy) \ - .def_##PropTyp( \ - #FooTyp "_" #PropTyp "_move", &Outer::FooTyp, py::return_value_policy::move) - outer MAKE_PROP(readonly) MAKE_PROP(readwrite); -#undef MAKE_PROP_FOO - -#define MAKE_PROP_FOO(FooTyp, PropTyp) \ - .def_##PropTyp(#FooTyp "_property_" #PropTyp "_default", &Outer::FooTyp) \ - .def_property_##PropTyp(#FooTyp "_property_" #PropTyp "_copy", \ - &Outer::get##FooTyp, \ - py::return_value_policy::copy) \ - .def_property_##PropTyp(#FooTyp "_property_" #PropTyp "_move", \ - &Outer::get##FooTyp, \ - py::return_value_policy::move) - outer MAKE_PROP(readonly); -#undef MAKE_PROP_FOO -#undef MAKE_PROP - - m.def("test_ShPtr_copy", []() { - auto o = std::make_shared("copy"); - auto l = py::list(); - l.append(o); - return l; - }); - m.def("test_SmHld_copy", []() { - auto o = std::make_shared("copy"); - auto l = py::list(); - l.append(o); - return l; - }); - - m.def("test_ShPtr_move", []() { - auto o = std::make_shared("move"); - auto l = py::list(); - l.append(std::move(o)); - return l; - }); - m.def("test_SmHld_move", []() { - auto o = std::make_shared("move"); - auto l = py::list(); - l.append(std::move(o)); - return l; - }); -} - -} // namespace pybind11_tests diff --git a/pybind11/tests/test_class_sh_shared_ptr_copy_move.py b/pybind11/tests/test_class_sh_shared_ptr_copy_move.py deleted file mode 100644 index 067bb47d2..000000000 --- a/pybind11/tests/test_class_sh_shared_ptr_copy_move.py +++ /dev/null @@ -1,41 +0,0 @@ -from __future__ import annotations - -from pybind11_tests import class_sh_shared_ptr_copy_move as m - - -def test_shptr_copy(): - txt = m.test_ShPtr_copy()[0].get_history() - assert txt == "FooShPtr_copy" - - -def test_smhld_copy(): - txt = m.test_SmHld_copy()[0].get_history() - assert txt == "FooSmHld_copy" - - -def test_shptr_move(): - txt = m.test_ShPtr_move()[0].get_history() - assert txt == "FooShPtr_move" - - -def test_smhld_move(): - txt = m.test_SmHld_move()[0].get_history() - assert txt == "FooSmHld_move" - - -def _check_property(foo_typ, prop_typ, policy): - o = m.Outer() - name = f"{foo_typ}_{prop_typ}_{policy}" - history = f"Foo{foo_typ}_Outer" - f = getattr(o, name) - assert f.get_history() == history - # and try again to check that o did not get changed - f = getattr(o, name) - assert f.get_history() == history - - -def test_properties(): - for prop_typ in ("readonly", "readwrite", "property_readonly"): - for foo_typ in ("ShPtr", "SmHld"): - for policy in ("default", "copy", "move"): - _check_property(foo_typ, prop_typ, policy) diff --git a/pybind11/tests/test_class_sh_trampoline_basic.cpp b/pybind11/tests/test_class_sh_trampoline_basic.cpp deleted file mode 100644 index 0f42dcc71..000000000 --- a/pybind11/tests/test_class_sh_trampoline_basic.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "pybind11_tests.h" - -#include - -namespace pybind11_tests { -namespace class_sh_trampoline_basic { - -template // Using int as a trick to easily generate a series of types. -struct Abase { - int val = 0; - virtual ~Abase() = default; - explicit Abase(int val_) : val{val_} {} - int Get() const { return val * 10 + 3; } - virtual int Add(int other_val) const = 0; - - // Some compilers complain about implicitly defined versions of some of the following: - Abase(const Abase &) = default; - Abase(Abase &&) noexcept = default; - Abase &operator=(const Abase &) = default; - Abase &operator=(Abase &&) noexcept = default; -}; - -template -struct AbaseAlias : Abase { - using Abase::Abase; - - int Add(int other_val) const override { - PYBIND11_OVERRIDE_PURE(int, /* Return type */ - Abase, /* Parent class */ - Add, /* Name of function in C++ (must match Python name) */ - other_val); - } -}; - -template <> -struct AbaseAlias<1> : Abase<1>, py::trampoline_self_life_support { - using Abase<1>::Abase; - - int Add(int other_val) const override { - PYBIND11_OVERRIDE_PURE(int, /* Return type */ - Abase<1>, /* Parent class */ - Add, /* Name of function in C++ (must match Python name) */ - other_val); - } -}; - -template -int AddInCppRawPtr(const Abase *obj, int other_val) { - return obj->Add(other_val) * 10 + 7; -} - -template -int AddInCppSharedPtr(std::shared_ptr> obj, int other_val) { - return obj->Add(other_val) * 100 + 11; -} - -template -int AddInCppUniquePtr(std::unique_ptr> obj, int other_val) { - return obj->Add(other_val) * 100 + 13; -} - -template -void wrap(py::module_ m, const char *py_class_name) { - py::classh, AbaseAlias>(m, py_class_name) - .def(py::init(), py::arg("val")) - .def("Get", &Abase::Get) - .def("Add", &Abase::Add, py::arg("other_val")); - - m.def("AddInCppRawPtr", AddInCppRawPtr, py::arg("obj"), py::arg("other_val")); - m.def("AddInCppSharedPtr", AddInCppSharedPtr, py::arg("obj"), py::arg("other_val")); - m.def("AddInCppUniquePtr", AddInCppUniquePtr, py::arg("obj"), py::arg("other_val")); -} - -} // namespace class_sh_trampoline_basic -} // namespace pybind11_tests - -using namespace pybind11_tests::class_sh_trampoline_basic; - -TEST_SUBMODULE(class_sh_trampoline_basic, m) { - wrap<0>(m, "Abase0"); - wrap<1>(m, "Abase1"); -} diff --git a/pybind11/tests/test_class_sh_trampoline_basic.py b/pybind11/tests/test_class_sh_trampoline_basic.py deleted file mode 100644 index eab82121f..000000000 --- a/pybind11/tests/test_class_sh_trampoline_basic.py +++ /dev/null @@ -1,59 +0,0 @@ -from __future__ import annotations - -import pytest - -from pybind11_tests import class_sh_trampoline_basic as m - - -class PyDrvd0(m.Abase0): - def __init__(self, val): - super().__init__(val) - - def Add(self, other_val): - return self.Get() * 100 + other_val - - -class PyDrvd1(m.Abase1): - def __init__(self, val): - super().__init__(val) - - def Add(self, other_val): - return self.Get() * 200 + other_val - - -def test_drvd0_add(): - drvd = PyDrvd0(74) - assert drvd.Add(38) == (74 * 10 + 3) * 100 + 38 - - -def test_drvd0_add_in_cpp_raw_ptr(): - drvd = PyDrvd0(52) - assert m.AddInCppRawPtr(drvd, 27) == ((52 * 10 + 3) * 100 + 27) * 10 + 7 - - -def test_drvd0_add_in_cpp_shared_ptr(): - while True: - drvd = PyDrvd0(36) - assert m.AddInCppSharedPtr(drvd, 56) == ((36 * 10 + 3) * 100 + 56) * 100 + 11 - return # Comment out for manual leak checking (use `top` command). - - -def test_drvd0_add_in_cpp_unique_ptr(): - while True: - drvd = PyDrvd0(0) - with pytest.raises(ValueError) as exc_info: - m.AddInCppUniquePtr(drvd, 0) - assert ( - str(exc_info.value) - == "Alias class (also known as trampoline) does not inherit from" - " py::trampoline_self_life_support, therefore the ownership of this" - " instance cannot safely be transferred to C++." - ) - return # Comment out for manual leak checking (use `top` command). - - -def test_drvd1_add_in_cpp_unique_ptr(): - while True: - drvd = PyDrvd1(25) - assert m.AddInCppUniquePtr(drvd, 83) == ((25 * 10 + 3) * 200 + 83) * 100 + 13 - return # Comment out for manual leak checking (use `top` command). diff --git a/pybind11/tests/test_class_sh_trampoline_self_life_support.cpp b/pybind11/tests/test_class_sh_trampoline_self_life_support.cpp deleted file mode 100644 index 22b728e28..000000000 --- a/pybind11/tests/test_class_sh_trampoline_self_life_support.cpp +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2021 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. - -#include "pybind11/trampoline_self_life_support.h" -#include "pybind11_tests.h" - -#include -#include -#include - -namespace pybind11_tests { -namespace class_sh_trampoline_self_life_support { - -struct Big5 { // Also known as "rule of five". - std::string history; - - explicit Big5(std::string history_start) : history{std::move(history_start)} {} - - Big5(const Big5 &other) { history = other.history + "_CpCtor"; } - - Big5(Big5 &&other) noexcept { history = other.history + "_MvCtor"; } - - Big5 &operator=(const Big5 &other) { - history = other.history + "_OpEqLv"; - return *this; - } - - Big5 &operator=(Big5 &&other) noexcept { - history = other.history + "_OpEqRv"; - return *this; - } - - virtual ~Big5() = default; - -protected: - Big5() : history{"DefaultConstructor"} {} -}; - -struct Big5Trampoline : Big5, py::trampoline_self_life_support { - using Big5::Big5; -}; - -} // namespace class_sh_trampoline_self_life_support -} // namespace pybind11_tests - -using namespace pybind11_tests::class_sh_trampoline_self_life_support; - -TEST_SUBMODULE(class_sh_trampoline_self_life_support, m) { - py::classh(m, "Big5") - .def(py::init()) - .def_readonly("history", &Big5::history); - - m.def("action", [](std::unique_ptr obj, int action_id) { - py::object o2 = py::none(); - // This is very unusual, but needed to directly exercise the trampoline_self_life_support - // CpCtor, MvCtor, operator= lvalue, operator= rvalue. - auto *obj_trampoline = dynamic_cast(obj.get()); - if (obj_trampoline != nullptr) { - switch (action_id) { - case 0: { // CpCtor - std::unique_ptr cp(new Big5Trampoline(*obj_trampoline)); - o2 = py::cast(std::move(cp)); - } break; - case 1: { // MvCtor - std::unique_ptr mv(new Big5Trampoline(std::move(*obj_trampoline))); - o2 = py::cast(std::move(mv)); - } break; - case 2: { // operator= lvalue - std::unique_ptr lv(new Big5Trampoline); - *lv = *obj_trampoline; // NOLINT clang-tidy cppcoreguidelines-slicing - o2 = py::cast(std::move(lv)); - } break; - case 3: { // operator= rvalue - std::unique_ptr rv(new Big5Trampoline); - *rv = std::move(*obj_trampoline); - o2 = py::cast(std::move(rv)); - } break; - default: - break; - } - } - py::object o1 = py::cast(std::move(obj)); - return py::make_tuple(o1, o2); - }); -} diff --git a/pybind11/tests/test_class_sh_trampoline_self_life_support.py b/pybind11/tests/test_class_sh_trampoline_self_life_support.py deleted file mode 100644 index d4af2ab99..000000000 --- a/pybind11/tests/test_class_sh_trampoline_self_life_support.py +++ /dev/null @@ -1,38 +0,0 @@ -from __future__ import annotations - -import pytest - -import pybind11_tests.class_sh_trampoline_self_life_support as m - - -class PyBig5(m.Big5): - pass - - -def test_m_big5(): - obj = m.Big5("Seed") - assert obj.history == "Seed" - o1, o2 = m.action(obj, 0) - assert o1 is not obj - assert o1.history == "Seed" - with pytest.raises(ValueError) as excinfo: - _ = obj.history - assert "Python instance was disowned" in str(excinfo.value) - assert o2 is None - - -@pytest.mark.parametrize( - ("action_id", "expected_history"), - [ - (0, "Seed_CpCtor"), - (1, "Seed_MvCtor"), - (2, "Seed_OpEqLv"), - (3, "Seed_OpEqRv"), - ], -) -def test_py_big5(action_id, expected_history): - obj = PyBig5("Seed") - assert obj.history == "Seed" - o1, o2 = m.action(obj, action_id) - assert o1 is obj - assert o2.history == expected_history diff --git a/pybind11/tests/test_class_sh_trampoline_shared_from_this.cpp b/pybind11/tests/test_class_sh_trampoline_shared_from_this.cpp deleted file mode 100644 index dc6bf1c72..000000000 --- a/pybind11/tests/test_class_sh_trampoline_shared_from_this.cpp +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) 2021 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. - -#include "pybind11_tests.h" - -#include -#include - -namespace pybind11_tests { -namespace class_sh_trampoline_shared_from_this { - -struct Sft : std::enable_shared_from_this { - std::string history; - explicit Sft(const std::string &history_seed) : history{history_seed} {} - virtual ~Sft() = default; - -#if defined(__clang__) - // "Group of 4" begin. - // This group is not meant to be used, but will leave a trace in the - // history in case something goes wrong. - // However, compilers other than clang have a variety of issues. It is not - // worth the trouble covering all platforms. - Sft(const Sft &other) : enable_shared_from_this(other) { history = other.history + "_CpCtor"; } - - Sft(Sft &&other) noexcept { history = other.history + "_MvCtor"; } - - Sft &operator=(const Sft &other) { - history = other.history + "_OpEqLv"; - return *this; - } - - Sft &operator=(Sft &&other) noexcept { - history = other.history + "_OpEqRv"; - return *this; - } - // "Group of 4" end. -#endif -}; - -struct SftSharedPtrStash { - int ser_no; - std::vector> stash; - explicit SftSharedPtrStash(int ser_no) : ser_no{ser_no} {} - void Clear() { stash.clear(); } - void Add(const std::shared_ptr &obj) { - if (!obj->history.empty()) { - obj->history += "_Stash" + std::to_string(ser_no) + "Add"; - } - stash.push_back(obj); - } - void AddSharedFromThis(Sft *obj) { - auto sft = obj->shared_from_this(); - if (!sft->history.empty()) { - sft->history += "_Stash" + std::to_string(ser_no) + "AddSharedFromThis"; - } - stash.push_back(sft); - } - std::string history(unsigned i) { - if (i < stash.size()) { - return stash[i]->history; - } - return "OutOfRange"; - } - long use_count(unsigned i) { - if (i < stash.size()) { - return stash[i].use_count(); - } - return -1; - } -}; - -struct SftTrampoline : Sft, py::trampoline_self_life_support { - using Sft::Sft; -}; - -long use_count(const std::shared_ptr &obj) { return obj.use_count(); } - -long pass_shared_ptr(const std::shared_ptr &obj) { - auto sft = obj->shared_from_this(); - if (!sft->history.empty()) { - sft->history += "_PassSharedPtr"; - } - return sft.use_count(); -} - -std::string pass_unique_ptr_cref(const std::unique_ptr &obj) { - return obj ? obj->history : ""; -} -void pass_unique_ptr_rref(std::unique_ptr &&) { - throw std::runtime_error("Expected to not be reached."); -} - -Sft *make_pure_cpp_sft_raw_ptr(const std::string &history_seed) { return new Sft{history_seed}; } - -std::unique_ptr make_pure_cpp_sft_unq_ptr(const std::string &history_seed) { - return std::unique_ptr(new Sft{history_seed}); -} - -std::shared_ptr make_pure_cpp_sft_shd_ptr(const std::string &history_seed) { - return std::make_shared(history_seed); -} - -std::shared_ptr pass_through_shd_ptr(const std::shared_ptr &obj) { return obj; } - -} // namespace class_sh_trampoline_shared_from_this -} // namespace pybind11_tests - -using namespace pybind11_tests::class_sh_trampoline_shared_from_this; - -TEST_SUBMODULE(class_sh_trampoline_shared_from_this, m) { - py::classh(m, "Sft") - .def(py::init()) - .def(py::init([](const std::string &history, int) { - return std::make_shared(history); - })) - .def_readonly("history", &Sft::history) - // This leads to multiple entries in registered_instances: - .def(py::init([](const std::shared_ptr &existing) { return existing; })); - - py::classh(m, "SftSharedPtrStash") - .def(py::init()) - .def("Clear", &SftSharedPtrStash::Clear) - .def("Add", &SftSharedPtrStash::Add) - .def("AddSharedFromThis", &SftSharedPtrStash::AddSharedFromThis) - .def("history", &SftSharedPtrStash::history) - .def("use_count", &SftSharedPtrStash::use_count); - - m.def("use_count", use_count); - m.def("pass_shared_ptr", pass_shared_ptr); - m.def("pass_unique_ptr_cref", pass_unique_ptr_cref); - m.def("pass_unique_ptr_rref", pass_unique_ptr_rref); - m.def("make_pure_cpp_sft_raw_ptr", make_pure_cpp_sft_raw_ptr); - m.def("make_pure_cpp_sft_unq_ptr", make_pure_cpp_sft_unq_ptr); - m.def("make_pure_cpp_sft_shd_ptr", make_pure_cpp_sft_shd_ptr); - m.def("pass_through_shd_ptr", pass_through_shd_ptr); -} diff --git a/pybind11/tests/test_class_sh_trampoline_shared_from_this.py b/pybind11/tests/test_class_sh_trampoline_shared_from_this.py deleted file mode 100644 index fbe31387a..000000000 --- a/pybind11/tests/test_class_sh_trampoline_shared_from_this.py +++ /dev/null @@ -1,247 +0,0 @@ -from __future__ import annotations - -import sys -import weakref - -import pytest - -import env -import pybind11_tests.class_sh_trampoline_shared_from_this as m - - -class PySft(m.Sft): - pass - - -def test_release_and_shared_from_this(): - # Exercises the most direct path from building a shared_from_this-visible - # shared_ptr to calling shared_from_this. - obj = PySft("PySft") - assert obj.history == "PySft" - assert m.use_count(obj) == 1 - assert m.pass_shared_ptr(obj) == 2 - assert obj.history == "PySft_PassSharedPtr" - assert m.use_count(obj) == 1 - assert m.pass_shared_ptr(obj) == 2 - assert obj.history == "PySft_PassSharedPtr_PassSharedPtr" - assert m.use_count(obj) == 1 - - -def test_release_and_shared_from_this_leak(): - obj = PySft("") - while True: - m.pass_shared_ptr(obj) - assert not obj.history - assert m.use_count(obj) == 1 - break # Comment out for manual leak checking (use `top` command). - - -def test_release_and_stash(): - # Exercises correct functioning of guarded_delete weak_ptr. - obj = PySft("PySft") - stash1 = m.SftSharedPtrStash(1) - stash1.Add(obj) - exp_hist = "PySft_Stash1Add" - assert obj.history == exp_hist - assert m.use_count(obj) == 2 - assert stash1.history(0) == exp_hist - assert stash1.use_count(0) == 1 - assert m.pass_shared_ptr(obj) == 3 - exp_hist += "_PassSharedPtr" - assert obj.history == exp_hist - assert m.use_count(obj) == 2 - assert stash1.history(0) == exp_hist - assert stash1.use_count(0) == 1 - stash2 = m.SftSharedPtrStash(2) - stash2.Add(obj) - exp_hist += "_Stash2Add" - assert obj.history == exp_hist - assert m.use_count(obj) == 3 - assert stash2.history(0) == exp_hist - assert stash2.use_count(0) == 2 - stash2.Add(obj) - exp_hist += "_Stash2Add" - assert obj.history == exp_hist - assert m.use_count(obj) == 4 - assert stash1.history(0) == exp_hist - assert stash1.use_count(0) == 3 - assert stash2.history(0) == exp_hist - assert stash2.use_count(0) == 3 - assert stash2.history(1) == exp_hist - assert stash2.use_count(1) == 3 - del obj - assert stash2.history(0) == exp_hist - assert stash2.use_count(0) == 3 - assert stash2.history(1) == exp_hist - assert stash2.use_count(1) == 3 - stash2.Clear() - assert stash1.history(0) == exp_hist - assert stash1.use_count(0) == 1 - - -def test_release_and_stash_leak(): - obj = PySft("") - while True: - stash1 = m.SftSharedPtrStash(1) - stash1.Add(obj) - assert not obj.history - assert m.use_count(obj) == 2 - assert stash1.use_count(0) == 1 - stash1.Add(obj) - assert not obj.history - assert m.use_count(obj) == 3 - assert stash1.use_count(0) == 2 - assert stash1.use_count(1) == 2 - break # Comment out for manual leak checking (use `top` command). - - -def test_release_and_stash_via_shared_from_this(): - # Exercises that the smart_holder vptr is invisible to the shared_from_this mechanism. - obj = PySft("PySft") - stash1 = m.SftSharedPtrStash(1) - with pytest.raises(RuntimeError) as exc_info: - stash1.AddSharedFromThis(obj) - assert str(exc_info.value) == "bad_weak_ptr" - stash1.Add(obj) - assert obj.history == "PySft_Stash1Add" - assert stash1.use_count(0) == 1 - stash1.AddSharedFromThis(obj) - assert obj.history == "PySft_Stash1Add_Stash1AddSharedFromThis" - assert stash1.use_count(0) == 2 - assert stash1.use_count(1) == 2 - - -def test_release_and_stash_via_shared_from_this_leak(): - obj = PySft("") - while True: - stash1 = m.SftSharedPtrStash(1) - with pytest.raises(RuntimeError) as exc_info: - stash1.AddSharedFromThis(obj) - assert str(exc_info.value) == "bad_weak_ptr" - stash1.Add(obj) - assert not obj.history - assert stash1.use_count(0) == 1 - stash1.AddSharedFromThis(obj) - assert not obj.history - assert stash1.use_count(0) == 2 - assert stash1.use_count(1) == 2 - break # Comment out for manual leak checking (use `top` command). - - -def test_pass_released_shared_ptr_as_unique_ptr(): - # Exercises that returning a unique_ptr fails while a shared_from_this - # visible shared_ptr exists. - obj = PySft("PySft") - stash1 = m.SftSharedPtrStash(1) - stash1.Add(obj) # Releases shared_ptr to C++. - assert m.pass_unique_ptr_cref(obj) == "PySft_Stash1Add" - assert obj.history == "PySft_Stash1Add" - with pytest.raises(ValueError) as exc_info: - m.pass_unique_ptr_rref(obj) - assert str(exc_info.value) == ( - "Python instance is currently owned by a std::shared_ptr." - ) - assert obj.history == "PySft_Stash1Add" - - -@pytest.mark.parametrize( - "make_f", - [ - m.make_pure_cpp_sft_raw_ptr, - m.make_pure_cpp_sft_unq_ptr, - m.make_pure_cpp_sft_shd_ptr, - ], -) -def test_pure_cpp_sft_raw_ptr(make_f): - # Exercises void_cast_raw_ptr logic for different situations. - obj = make_f("PureCppSft") - assert m.pass_shared_ptr(obj) == 3 - assert obj.history == "PureCppSft_PassSharedPtr" - obj = make_f("PureCppSft") - stash1 = m.SftSharedPtrStash(1) - stash1.AddSharedFromThis(obj) - assert obj.history == "PureCppSft_Stash1AddSharedFromThis" - - -def test_multiple_registered_instances_for_same_pointee(): - obj0 = PySft("PySft") - obj0.attachment_in_dict = "Obj0" - assert m.pass_through_shd_ptr(obj0) is obj0 - while True: - obj = m.Sft(obj0) - assert obj is not obj0 - obj_pt = m.pass_through_shd_ptr(obj) - # Unpredictable! Because registered_instances is as std::unordered_multimap. - assert obj_pt is obj0 or obj_pt is obj - # Multiple registered_instances for the same pointee can lead to unpredictable results: - if obj_pt is obj0: - assert obj_pt.attachment_in_dict == "Obj0" - else: - assert not hasattr(obj_pt, "attachment_in_dict") - assert obj0.history == "PySft" - break # Comment out for manual leak checking (use `top` command). - - -def test_multiple_registered_instances_for_same_pointee_leak(): - obj0 = PySft("") - while True: - stash1 = m.SftSharedPtrStash(1) - stash1.Add(m.Sft(obj0)) - assert stash1.use_count(0) == 1 - stash1.Add(m.Sft(obj0)) - assert stash1.use_count(0) == 1 - assert stash1.use_count(1) == 1 - assert not obj0.history - break # Comment out for manual leak checking (use `top` command). - - -def test_multiple_registered_instances_for_same_pointee_recursive(): - while True: - obj0 = PySft("PySft") - if not env.PYPY: - obj0_wr = weakref.ref(obj0) - obj = obj0 - # This loop creates a chain of instances linked by shared_ptrs. - for _ in range(10): - obj_next = m.Sft(obj) - assert obj_next is not obj - obj = obj_next - del obj_next - assert obj.history == "PySft" - del obj0 - if not env.PYPY and not env.GRAALPY: - assert obj0_wr() is not None - del obj # This releases the chain recursively. - if not env.PYPY and not env.GRAALPY: - assert obj0_wr() is None - break # Comment out for manual leak checking (use `top` command). - - -# As of 2021-07-10 the pybind11 GitHub Actions valgrind build uses Python 3.9. -WORKAROUND_ENABLING_ROLLBACK_OF_PR3068 = env.LINUX and sys.version_info == (3, 9) - - -def test_std_make_shared_factory(): - class PySftMakeShared(m.Sft): - def __init__(self, history): - super().__init__(history, 0) - - obj = PySftMakeShared("PySftMakeShared") - assert obj.history == "PySftMakeShared" - if WORKAROUND_ENABLING_ROLLBACK_OF_PR3068: - try: - m.pass_through_shd_ptr(obj) - except RuntimeError as e: - str_exc_info_value = str(e) - else: - str_exc_info_value = "RuntimeError NOT RAISED" - else: - with pytest.raises(RuntimeError) as exc_info: - m.pass_through_shd_ptr(obj) - str_exc_info_value = str(exc_info.value) - assert ( - str_exc_info_value - == "smart_holder_type_casters load_as_shared_ptr failure: not implemented:" - " trampoline-self-life-support for external shared_ptr to type inheriting" - " from std::enable_shared_from_this." - ) diff --git a/pybind11/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp b/pybind11/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp deleted file mode 100644 index 49e1ac885..000000000 --- a/pybind11/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) 2021 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. - -#include "pybind11_tests.h" - -#include - -namespace pybind11_tests { -namespace class_sh_trampoline_shared_ptr_cpp_arg { - -// For testing whether a python subclass of a C++ object dies when the -// last python reference is lost -struct SpBase { - // returns true if the base virtual function is called - virtual bool is_base_used() { return true; } - - // returns true if there's an associated python instance - bool has_python_instance() { - auto *tinfo = py::detail::get_type_info(typeid(SpBase)); - return (bool) py::detail::get_object_handle(this, tinfo); - } - - SpBase() = default; - SpBase(const SpBase &) = delete; - virtual ~SpBase() = default; -}; - -std::shared_ptr pass_through_shd_ptr(const std::shared_ptr &obj) { return obj; } - -struct PySpBase : SpBase { - using SpBase::SpBase; - bool is_base_used() override { PYBIND11_OVERRIDE(bool, SpBase, is_base_used); } -}; - -struct SpBaseTester { - std::shared_ptr get_object() const { return m_obj; } - void set_object(std::shared_ptr obj) { m_obj = std::move(obj); } - bool is_base_used() { return m_obj->is_base_used(); } - bool has_instance() { return (bool) m_obj; } - bool has_python_instance() { return m_obj && m_obj->has_python_instance(); } - void set_nonpython_instance() { m_obj = std::make_shared(); } - std::shared_ptr m_obj; -}; - -// For testing that a C++ class without an alias does not retain the python -// portion of the object -struct SpGoAway {}; - -struct SpGoAwayTester { - std::shared_ptr m_obj; -}; - -} // namespace class_sh_trampoline_shared_ptr_cpp_arg -} // namespace pybind11_tests - -using namespace pybind11_tests::class_sh_trampoline_shared_ptr_cpp_arg; - -TEST_SUBMODULE(class_sh_trampoline_shared_ptr_cpp_arg, m) { - // For testing whether a python subclass of a C++ object dies when the - // last python reference is lost - - py::classh(m, "SpBase") - .def(py::init<>()) - .def(py::init([](int) { return std::make_shared(); })) - .def("is_base_used", &SpBase::is_base_used) - .def("has_python_instance", &SpBase::has_python_instance); - - m.def("pass_through_shd_ptr", pass_through_shd_ptr); - m.def("pass_through_shd_ptr_release_gil", - pass_through_shd_ptr, - py::call_guard()); // PR #4196 - - py::classh(m, "SpBaseTester") - .def(py::init<>()) - .def("get_object", &SpBaseTester::get_object) - .def("set_object", &SpBaseTester::set_object) - .def("is_base_used", &SpBaseTester::is_base_used) - .def("has_instance", &SpBaseTester::has_instance) - .def("has_python_instance", &SpBaseTester::has_python_instance) - .def("set_nonpython_instance", &SpBaseTester::set_nonpython_instance) - .def_readwrite("obj", &SpBaseTester::m_obj); - - // For testing that a C++ class without an alias does not retain the python - // portion of the object - - py::classh(m, "SpGoAway").def(py::init<>()); - - py::classh(m, "SpGoAwayTester") - .def(py::init<>()) - .def_readwrite("obj", &SpGoAwayTester::m_obj); -} diff --git a/pybind11/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.py b/pybind11/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.py deleted file mode 100644 index a693621e3..000000000 --- a/pybind11/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.py +++ /dev/null @@ -1,154 +0,0 @@ -from __future__ import annotations - -import pytest - -import env # noqa: F401 -import pybind11_tests.class_sh_trampoline_shared_ptr_cpp_arg as m - - -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") -def test_shared_ptr_cpp_arg(): - import weakref - - class PyChild(m.SpBase): - def is_base_used(self): - return False - - tester = m.SpBaseTester() - - obj = PyChild() - objref = weakref.ref(obj) - - # Pass the last python reference to the C++ function - tester.set_object(obj) - del obj - pytest.gc_collect() - - # python reference is still around since C++ has it now - assert objref() is not None - assert tester.is_base_used() is False - assert tester.obj.is_base_used() is False - assert tester.get_object() is objref() - - -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") -def test_shared_ptr_cpp_prop(): - class PyChild(m.SpBase): - def is_base_used(self): - return False - - tester = m.SpBaseTester() - - # Set the last python reference as a property of the C++ object - tester.obj = PyChild() - pytest.gc_collect() - - # python reference is still around since C++ has it now - assert tester.is_base_used() is False - assert tester.has_python_instance() is True - assert tester.obj.is_base_used() is False - assert tester.obj.has_python_instance() is True - - -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") -def test_shared_ptr_arg_identity(): - import weakref - - tester = m.SpBaseTester() - - obj = m.SpBase() - objref = weakref.ref(obj) - - tester.set_object(obj) - del obj - pytest.gc_collect() - - # NOTE: the behavior below is DIFFERENT from PR #2839 - # python reference is gone because it is not an Alias instance - assert objref() is None - assert tester.has_python_instance() is False - - -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") -def test_shared_ptr_alias_nonpython(): - tester = m.SpBaseTester() - - # C++ creates the object, a python instance shouldn't exist - tester.set_nonpython_instance() - assert tester.is_base_used() is True - assert tester.has_instance() is True - assert tester.has_python_instance() is False - - # Now a python instance exists - cobj = tester.get_object() - assert cobj.has_python_instance() - assert tester.has_instance() is True - assert tester.has_python_instance() is True - - # Now it's gone - del cobj - pytest.gc_collect() - assert tester.has_instance() is True - assert tester.has_python_instance() is False - - # When we pass it as an arg to a new tester the python instance should - # disappear because it wasn't created with an alias - new_tester = m.SpBaseTester() - - cobj = tester.get_object() - assert cobj.has_python_instance() - - new_tester.set_object(cobj) - assert tester.has_python_instance() is True - assert new_tester.has_python_instance() is True - - del cobj - pytest.gc_collect() - - # Gone! - assert tester.has_instance() is True - assert tester.has_python_instance() is False - assert new_tester.has_instance() is True - assert new_tester.has_python_instance() is False - - -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") -def test_shared_ptr_goaway(): - import weakref - - tester = m.SpGoAwayTester() - - obj = m.SpGoAway() - objref = weakref.ref(obj) - - assert tester.obj is None - - tester.obj = obj - del obj - pytest.gc_collect() - - # python reference is no longer around - assert objref() is None - # C++ reference is still around - assert tester.obj is not None - - -def test_infinite(): - tester = m.SpBaseTester() - while True: - tester.set_object(m.SpBase()) - break # Comment out for manual leak checking (use `top` command). - - -@pytest.mark.parametrize( - "pass_through_func", [m.pass_through_shd_ptr, m.pass_through_shd_ptr_release_gil] -) -def test_std_make_shared_factory(pass_through_func): - class PyChild(m.SpBase): - def __init__(self): - super().__init__(0) - - obj = PyChild() - while True: - assert pass_through_func(obj) is obj - break # Comment out for manual leak checking (use `top` command). diff --git a/pybind11/tests/test_class_sh_trampoline_unique_ptr.cpp b/pybind11/tests/test_class_sh_trampoline_unique_ptr.cpp deleted file mode 100644 index debe3324e..000000000 --- a/pybind11/tests/test_class_sh_trampoline_unique_ptr.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2021 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. - -#include "pybind11/trampoline_self_life_support.h" -#include "pybind11_tests.h" - -#include - -namespace pybind11_tests { -namespace class_sh_trampoline_unique_ptr { - -class Class { -public: - virtual ~Class() = default; - - void setVal(std::uint64_t val) { val_ = val; } - std::uint64_t getVal() const { return val_; } - - virtual std::unique_ptr clone() const = 0; - virtual int foo() const = 0; - -protected: - Class() = default; - - // Some compilers complain about implicitly defined versions of some of the following: - Class(const Class &) = default; - -private: - std::uint64_t val_ = 0; -}; - -} // namespace class_sh_trampoline_unique_ptr -} // namespace pybind11_tests - -namespace pybind11_tests { -namespace class_sh_trampoline_unique_ptr { - -class PyClass : public Class, public py::trampoline_self_life_support { -public: - std::unique_ptr clone() const override { - PYBIND11_OVERRIDE_PURE(std::unique_ptr, Class, clone); - } - - int foo() const override { PYBIND11_OVERRIDE_PURE(int, Class, foo); } -}; - -} // namespace class_sh_trampoline_unique_ptr -} // namespace pybind11_tests - -TEST_SUBMODULE(class_sh_trampoline_unique_ptr, m) { - using namespace pybind11_tests::class_sh_trampoline_unique_ptr; - - py::classh(m, "Class") - .def(py::init<>()) - .def("set_val", &Class::setVal) - .def("get_val", &Class::getVal) - .def("clone", &Class::clone) - .def("foo", &Class::foo); - - m.def("clone", [](const Class &obj) { return obj.clone(); }); - m.def("clone_and_foo", [](const Class &obj) { return obj.clone()->foo(); }); -} diff --git a/pybind11/tests/test_class_sh_trampoline_unique_ptr.py b/pybind11/tests/test_class_sh_trampoline_unique_ptr.py deleted file mode 100644 index 7799df6d6..000000000 --- a/pybind11/tests/test_class_sh_trampoline_unique_ptr.py +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import annotations - -import pybind11_tests.class_sh_trampoline_unique_ptr as m - - -class MyClass(m.Class): - def foo(self): - return 10 + self.get_val() - - def clone(self): - cloned = MyClass() - cloned.set_val(self.get_val() + 3) - return cloned - - -def test_m_clone(): - obj = MyClass() - while True: - obj.set_val(5) - obj = m.clone(obj) - assert obj.get_val() == 5 + 3 - assert obj.foo() == 10 + 5 + 3 - return # Comment out for manual leak checking (use `top` command). - - -def test_m_clone_and_foo(): - obj = MyClass() - obj.set_val(7) - while True: - assert m.clone_and_foo(obj) == 10 + 7 + 3 - return # Comment out for manual leak checking (use `top` command). diff --git a/pybind11/tests/test_class_sh_unique_ptr_custom_deleter.cpp b/pybind11/tests/test_class_sh_unique_ptr_custom_deleter.cpp deleted file mode 100644 index adaa2e47d..000000000 --- a/pybind11/tests/test_class_sh_unique_ptr_custom_deleter.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "pybind11_tests.h" - -#include - -namespace pybind11_tests { -namespace class_sh_unique_ptr_custom_deleter { - -// Reduced from a PyCLIF use case in the wild by @wangxf123456. -class Pet { -public: - using Ptr = std::unique_ptr>; - - std::string name; - - static Ptr New(const std::string &name) { - return Ptr(new Pet(name), std::default_delete()); - } - -private: - explicit Pet(const std::string &name) : name(name) {} -}; - -TEST_SUBMODULE(class_sh_unique_ptr_custom_deleter, m) { - py::classh(m, "Pet").def_readwrite("name", &Pet::name); - - m.def("create", &Pet::New); -} - -} // namespace class_sh_unique_ptr_custom_deleter -} // namespace pybind11_tests diff --git a/pybind11/tests/test_class_sh_unique_ptr_custom_deleter.py b/pybind11/tests/test_class_sh_unique_ptr_custom_deleter.py deleted file mode 100644 index 34aa52068..000000000 --- a/pybind11/tests/test_class_sh_unique_ptr_custom_deleter.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -from pybind11_tests import class_sh_unique_ptr_custom_deleter as m - - -def test_create(): - pet = m.create("abc") - assert pet.name == "abc" diff --git a/pybind11/tests/test_class_sh_unique_ptr_member.cpp b/pybind11/tests/test_class_sh_unique_ptr_member.cpp deleted file mode 100644 index 50c505a65..000000000 --- a/pybind11/tests/test_class_sh_unique_ptr_member.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "pybind11_tests.h" - -#include - -namespace pybind11_tests { -namespace class_sh_unique_ptr_member { - -class pointee { // NOT copyable. -public: - pointee() = default; - - int get_int() const { return 213; } - - pointee(const pointee &) = delete; - pointee(pointee &&) = delete; - pointee &operator=(const pointee &) = delete; - pointee &operator=(pointee &&) = delete; -}; - -inline std::unique_ptr make_unique_pointee() { - return std::unique_ptr(new pointee); -} - -class ptr_owner { -public: - explicit ptr_owner(std::unique_ptr ptr) : ptr_(std::move(ptr)) {} - - bool is_owner() const { return bool(ptr_); } - - std::unique_ptr give_up_ownership_via_unique_ptr() { return std::move(ptr_); } - std::shared_ptr give_up_ownership_via_shared_ptr() { return std::move(ptr_); } - -private: - std::unique_ptr ptr_; -}; - -TEST_SUBMODULE(class_sh_unique_ptr_member, m) { - py::classh(m, "pointee").def(py::init<>()).def("get_int", &pointee::get_int); - - m.def("make_unique_pointee", make_unique_pointee); - - py::class_(m, "ptr_owner") - .def(py::init>(), py::arg("ptr")) - .def("is_owner", &ptr_owner::is_owner) - .def("give_up_ownership_via_unique_ptr", &ptr_owner::give_up_ownership_via_unique_ptr) - .def("give_up_ownership_via_shared_ptr", &ptr_owner::give_up_ownership_via_shared_ptr); -} - -} // namespace class_sh_unique_ptr_member -} // namespace pybind11_tests diff --git a/pybind11/tests/test_class_sh_unique_ptr_member.py b/pybind11/tests/test_class_sh_unique_ptr_member.py deleted file mode 100644 index a5d2ccd23..000000000 --- a/pybind11/tests/test_class_sh_unique_ptr_member.py +++ /dev/null @@ -1,26 +0,0 @@ -from __future__ import annotations - -import pytest - -from pybind11_tests import class_sh_unique_ptr_member as m - - -def test_make_unique_pointee(): - obj = m.make_unique_pointee() - assert obj.get_int() == 213 - - -@pytest.mark.parametrize( - "give_up_ownership_via", - ["give_up_ownership_via_unique_ptr", "give_up_ownership_via_shared_ptr"], -) -def test_pointee_and_ptr_owner(give_up_ownership_via): - obj = m.pointee() - assert obj.get_int() == 213 - owner = m.ptr_owner(obj) - with pytest.raises(ValueError, match="Python instance was disowned"): - obj.get_int() - assert owner.is_owner() - reclaimed = getattr(owner, give_up_ownership_via)() - assert not owner.is_owner() - assert reclaimed.get_int() == 213 diff --git a/pybind11/tests/test_class_sh_virtual_py_cpp_mix.cpp b/pybind11/tests/test_class_sh_virtual_py_cpp_mix.cpp deleted file mode 100644 index df8af19e4..000000000 --- a/pybind11/tests/test_class_sh_virtual_py_cpp_mix.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include "pybind11_tests.h" - -#include - -namespace pybind11_tests { -namespace class_sh_virtual_py_cpp_mix { - -class Base { -public: - virtual ~Base() = default; - virtual int get() const { return 101; } - - // Some compilers complain about implicitly defined versions of some of the following: - Base() = default; - Base(const Base &) = default; -}; - -class CppDerivedPlain : public Base { -public: - int get() const override { return 202; } -}; - -class CppDerived : public Base { -public: - int get() const override { return 212; } -}; - -int get_from_cpp_plainc_ptr(const Base *b) { return b->get() + 4000; } - -int get_from_cpp_unique_ptr(std::unique_ptr b) { return b->get() + 5000; } - -struct BaseVirtualOverrider : Base, py::trampoline_self_life_support { - using Base::Base; - - int get() const override { PYBIND11_OVERRIDE(int, Base, get); } -}; - -struct CppDerivedVirtualOverrider : CppDerived, py::trampoline_self_life_support { - using CppDerived::CppDerived; - - int get() const override { PYBIND11_OVERRIDE(int, CppDerived, get); } -}; - -} // namespace class_sh_virtual_py_cpp_mix -} // namespace pybind11_tests - -using namespace pybind11_tests::class_sh_virtual_py_cpp_mix; - -TEST_SUBMODULE(class_sh_virtual_py_cpp_mix, m) { - py::classh(m, "Base").def(py::init<>()).def("get", &Base::get); - - py::classh(m, "CppDerivedPlain").def(py::init<>()); - - py::classh(m, "CppDerived").def(py::init<>()); - - m.def("get_from_cpp_plainc_ptr", get_from_cpp_plainc_ptr, py::arg("b")); - m.def("get_from_cpp_unique_ptr", get_from_cpp_unique_ptr, py::arg("b")); -} diff --git a/pybind11/tests/test_class_sh_virtual_py_cpp_mix.py b/pybind11/tests/test_class_sh_virtual_py_cpp_mix.py deleted file mode 100644 index 33133eb88..000000000 --- a/pybind11/tests/test_class_sh_virtual_py_cpp_mix.py +++ /dev/null @@ -1,66 +0,0 @@ -from __future__ import annotations - -import pytest - -from pybind11_tests import class_sh_virtual_py_cpp_mix as m - - -class PyBase(m.Base): # Avoiding name PyDerived, for more systematic naming. - def __init__(self): - m.Base.__init__(self) - - def get(self): - return 323 - - -class PyCppDerived(m.CppDerived): - def __init__(self): - m.CppDerived.__init__(self) - - def get(self): - return 434 - - -@pytest.mark.parametrize( - ("ctor", "expected"), - [ - (m.Base, 101), - (PyBase, 323), - (m.CppDerivedPlain, 202), - (m.CppDerived, 212), - (PyCppDerived, 434), - ], -) -def test_base_get(ctor, expected): - obj = ctor() - assert obj.get() == expected - - -@pytest.mark.parametrize( - ("ctor", "expected"), - [ - (m.Base, 4101), - (PyBase, 4323), - (m.CppDerivedPlain, 4202), - (m.CppDerived, 4212), - (PyCppDerived, 4434), - ], -) -def test_get_from_cpp_plainc_ptr(ctor, expected): - obj = ctor() - assert m.get_from_cpp_plainc_ptr(obj) == expected - - -@pytest.mark.parametrize( - ("ctor", "expected"), - [ - (m.Base, 5101), - (PyBase, 5323), - (m.CppDerivedPlain, 5202), - (m.CppDerived, 5212), - (PyCppDerived, 5434), - ], -) -def test_get_from_cpp_unique_ptr(ctor, expected): - obj = ctor() - assert m.get_from_cpp_unique_ptr(obj) == expected diff --git a/pybind11/tests/test_cmake_build/CMakeLists.txt b/pybind11/tests/test_cmake_build/CMakeLists.txt index ec365c0f6..f28bde08e 100644 --- a/pybind11/tests/test_cmake_build/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/CMakeLists.txt @@ -55,10 +55,8 @@ possibly_uninitialized(PYTHON_MODULE_EXTENSION Python_INTERPRETER_ID) pybind11_add_build_test(subdirectory_function) pybind11_add_build_test(subdirectory_target) -if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" - OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy" - OR "${PYTHON_MODULE_EXTENSION}" MATCHES "graalpy") - message(STATUS "Skipping embed test on PyPy or GraalPy") +if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy") + message(STATUS "Skipping embed test on PyPy") else() pybind11_add_build_test(subdirectory_embed) endif() @@ -68,14 +66,10 @@ if(PYBIND11_INSTALL) mock_install ${CMAKE_COMMAND} "-DCMAKE_INSTALL_PREFIX=${pybind11_BINARY_DIR}/mock_install" -P "${pybind11_BINARY_DIR}/cmake_install.cmake") - if(NOT "${PYTHON_MODULE_EXTENSION}" MATCHES "graalpy") - pybind11_add_build_test(installed_function INSTALL) - endif() + pybind11_add_build_test(installed_function INSTALL) pybind11_add_build_test(installed_target INSTALL) - if(NOT - ("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" - OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy" - OR "${PYTHON_MODULE_EXTENSION}" MATCHES "graalpy")) + if(NOT ("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy" + )) pybind11_add_build_test(installed_embed INSTALL) endif() endif() diff --git a/pybind11/tests/test_cmake_build/installed_embed/CMakeLists.txt b/pybind11/tests/test_cmake_build/installed_embed/CMakeLists.txt index 5c6267c72..2be0aa659 100644 --- a/pybind11/tests/test_cmake_build/installed_embed/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/installed_embed/CMakeLists.txt @@ -1,4 +1,13 @@ -cmake_minimum_required(VERSION 3.15...3.30) +cmake_minimum_required(VERSION 3.5) + +# The `cmake_minimum_required(VERSION 3.5...3.29)` syntax does not work with +# some versions of VS that have a patched CMake 3.11. This forces us to emulate +# the behavior using the following workaround: +if(${CMAKE_VERSION} VERSION_LESS 3.29) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.29) +endif() project(test_installed_embed CXX) diff --git a/pybind11/tests/test_cmake_build/installed_function/CMakeLists.txt b/pybind11/tests/test_cmake_build/installed_function/CMakeLists.txt index 2945b3d2e..fa7795e1e 100644 --- a/pybind11/tests/test_cmake_build/installed_function/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/installed_function/CMakeLists.txt @@ -1,4 +1,14 @@ -cmake_minimum_required(VERSION 3.15...3.30) +cmake_minimum_required(VERSION 3.5) +project(test_installed_module CXX) + +# The `cmake_minimum_required(VERSION 3.5...3.29)` syntax does not work with +# some versions of VS that have a patched CMake 3.11. This forces us to emulate +# the behavior using the following workaround: +if(${CMAKE_VERSION} VERSION_LESS 3.29) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.29) +endif() project(test_installed_function CXX) diff --git a/pybind11/tests/test_cmake_build/installed_target/CMakeLists.txt b/pybind11/tests/test_cmake_build/installed_target/CMakeLists.txt index 344c8bc69..7e73f4243 100644 --- a/pybind11/tests/test_cmake_build/installed_target/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/installed_target/CMakeLists.txt @@ -1,4 +1,13 @@ -cmake_minimum_required(VERSION 3.15...3.30) +cmake_minimum_required(VERSION 3.5) + +# The `cmake_minimum_required(VERSION 3.5...3.29)` syntax does not work with +# some versions of VS that have a patched CMake 3.11. This forces us to emulate +# the behavior using the following workaround: +if(${CMAKE_VERSION} VERSION_LESS 3.29) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.29) +endif() project(test_installed_target CXX) diff --git a/pybind11/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt b/pybind11/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt index d0ae0798e..33a366472 100644 --- a/pybind11/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt @@ -1,4 +1,13 @@ -cmake_minimum_required(VERSION 3.15...3.30) +cmake_minimum_required(VERSION 3.5) + +# The `cmake_minimum_required(VERSION 3.5...3.29)` syntax does not work with +# some versions of VS that have a patched CMake 3.11. This forces us to emulate +# the behavior using the following workaround: +if(${CMAKE_VERSION} VERSION_LESS 3.29) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.29) +endif() project(test_subdirectory_embed CXX) diff --git a/pybind11/tests/test_cmake_build/subdirectory_function/CMakeLists.txt b/pybind11/tests/test_cmake_build/subdirectory_function/CMakeLists.txt index a521f33df..76418a71f 100644 --- a/pybind11/tests/test_cmake_build/subdirectory_function/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/subdirectory_function/CMakeLists.txt @@ -1,4 +1,13 @@ -cmake_minimum_required(VERSION 3.15...3.30) +cmake_minimum_required(VERSION 3.5) + +# The `cmake_minimum_required(VERSION 3.5...3.29)` syntax does not work with +# some versions of VS that have a patched CMake 3.11. This forces us to emulate +# the behavior using the following workaround: +if(${CMAKE_VERSION} VERSION_LESS 3.29) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.29) +endif() project(test_subdirectory_function CXX) diff --git a/pybind11/tests/test_cmake_build/subdirectory_target/CMakeLists.txt b/pybind11/tests/test_cmake_build/subdirectory_target/CMakeLists.txt index 4a5a7f2be..28e903187 100644 --- a/pybind11/tests/test_cmake_build/subdirectory_target/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/subdirectory_target/CMakeLists.txt @@ -1,4 +1,13 @@ -cmake_minimum_required(VERSION 3.15...3.30) +cmake_minimum_required(VERSION 3.5) + +# The `cmake_minimum_required(VERSION 3.5...3.29)` syntax does not work with +# some versions of VS that have a patched CMake 3.11. This forces us to emulate +# the behavior using the following workaround: +if(${CMAKE_VERSION} VERSION_LESS 3.29) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.29) +endif() project(test_subdirectory_target CXX) diff --git a/pybind11/tests/test_copy_move.py b/pybind11/tests/test_copy_move.py index 3a3f29341..ee046305f 100644 --- a/pybind11/tests/test_copy_move.py +++ b/pybind11/tests/test_copy_move.py @@ -2,7 +2,6 @@ from __future__ import annotations import pytest -import env # noqa: F401 from pybind11_tests import copy_move_policies as m @@ -18,7 +17,6 @@ def test_lacking_move_ctor(): assert "is neither movable nor copyable!" in str(excinfo.value) -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_move_and_copy_casts(): """Cast some values in C++ via custom type casters and count the number of moves/copies.""" @@ -46,7 +44,6 @@ def test_move_and_copy_casts(): assert c_m.alive() + c_mc.alive() + c_c.alive() == 0 -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_move_and_copy_loads(): """Call some functions that load arguments via custom type casters and count the number of moves/copies.""" @@ -80,7 +77,6 @@ def test_move_and_copy_loads(): @pytest.mark.skipif(not m.has_optional, reason="no ") -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_move_and_copy_load_optional(): """Tests move/copy loads of std::optional arguments""" diff --git a/pybind11/tests/test_cpp_conduit.py b/pybind11/tests/test_cpp_conduit.py index eb300587f..51fcf6936 100644 --- a/pybind11/tests/test_cpp_conduit.py +++ b/pybind11/tests/test_cpp_conduit.py @@ -7,7 +7,6 @@ import exo_planet_pybind11 import home_planet_very_lonely_traveler import pytest -import env from pybind11_tests import cpp_conduit as home_planet @@ -28,10 +27,7 @@ def test_call_cpp_conduit_success(): home_planet.cpp_type_info_capsule_Traveler, b"raw_pointer_ephemeral", ) - assert cap.__class__.__name__ == "PyCapsule" or ( - # Note: this will become unnecessary in the next GraalPy release - env.GRAALPY and cap.__class__.__name__ == "capsule" - ) + assert cap.__class__.__name__ == "PyCapsule" def test_call_cpp_conduit_platform_abi_id_mismatch(): diff --git a/pybind11/tests/test_custom_type_casters.py b/pybind11/tests/test_custom_type_casters.py index dcf3ca734..689b1e9cb 100644 --- a/pybind11/tests/test_custom_type_casters.py +++ b/pybind11/tests/test_custom_type_casters.py @@ -2,7 +2,6 @@ from __future__ import annotations import pytest -import env # noqa: F401 from pybind11_tests import custom_type_casters as m @@ -95,7 +94,6 @@ def test_noconvert_args(msg): ) -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_custom_caster_destruction(): """Tests that returning a pointer to a type that gets converted with a custom type caster gets destroyed when the function has py::return_value_policy::take_ownership policy applied. diff --git a/pybind11/tests/test_custom_type_setup.py b/pybind11/tests/test_custom_type_setup.py index bb2865cad..ca3340bd5 100644 --- a/pybind11/tests/test_custom_type_setup.py +++ b/pybind11/tests/test_custom_type_setup.py @@ -34,7 +34,7 @@ def gc_tester(): # PyPy does not seem to reliably garbage collect. -@pytest.mark.skipif("env.PYPY or env.GRAALPY") +@pytest.mark.skipif("env.PYPY") def test_self_cycle(gc_tester): obj = m.OwnsPythonObjects() obj.value = obj @@ -42,7 +42,7 @@ def test_self_cycle(gc_tester): # PyPy does not seem to reliably garbage collect. -@pytest.mark.skipif("env.PYPY or env.GRAALPY") +@pytest.mark.skipif("env.PYPY") def test_indirect_cycle(gc_tester): obj = m.OwnsPythonObjects() obj_list = [obj] diff --git a/pybind11/tests/test_docs_advanced_cast_custom.cpp b/pybind11/tests/test_docs_advanced_cast_custom.cpp deleted file mode 100644 index 0ec1b17ac..000000000 --- a/pybind11/tests/test_docs_advanced_cast_custom.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// ######################################################################### -// PLEASE UPDATE docs/advanced/cast/custom.rst IF ANY CHANGES ARE MADE HERE. -// ######################################################################### - -#include "pybind11_tests.h" - -namespace user_space { - -struct Point2D { - double x; - double y; -}; - -Point2D negate(const Point2D &point) { return Point2D{-point.x, -point.y}; } - -} // namespace user_space - -namespace pybind11 { -namespace detail { - -template <> -struct type_caster { - // This macro inserts a lot of boilerplate code and sets the type hint. - // `io_name` is used to specify different type hints for arguments and return values. - // The signature of our negate function would then look like: - // `negate(Sequence[float]) -> tuple[float, float]` - PYBIND11_TYPE_CASTER(user_space::Point2D, io_name("Sequence[float]", "tuple[float, float]")); - - // C++ -> Python: convert `Point2D` to `tuple[float, float]`. The second and third arguments - // are used to indicate the return value policy and parent object (for - // return_value_policy::reference_internal) and are often ignored by custom casters. - // The return value should reflect the type hint specified by the second argument of `io_name`. - static handle - cast(const user_space::Point2D &number, return_value_policy /*policy*/, handle /*parent*/) { - return py::make_tuple(number.x, number.y).release(); - } - - // Python -> C++: convert a `PyObject` into a `Point2D` and return false upon failure. The - // second argument indicates whether implicit conversions should be allowed. - // The accepted types should reflect the type hint specified by the first argument of - // `io_name`. - bool load(handle src, bool /*convert*/) { - // Check if handle is a Sequence - if (!py::isinstance(src)) { - return false; - } - auto seq = py::reinterpret_borrow(src); - // Check if exactly two values are in the Sequence - if (seq.size() != 2) { - return false; - } - // Check if each element is either a float or an int - for (auto item : seq) { - if (!py::isinstance(item) && !py::isinstance(item)) { - return false; - } - } - value.x = seq[0].cast(); - value.y = seq[1].cast(); - return true; - } -}; - -} // namespace detail -} // namespace pybind11 - -// Bind the negate function -TEST_SUBMODULE(docs_advanced_cast_custom, m) { m.def("negate", user_space::negate); } diff --git a/pybind11/tests/test_docs_advanced_cast_custom.py b/pybind11/tests/test_docs_advanced_cast_custom.py deleted file mode 100644 index 8018b8f57..000000000 --- a/pybind11/tests/test_docs_advanced_cast_custom.py +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, Sequence - -if TYPE_CHECKING: - from conftest import SanitizedString - -from pybind11_tests import docs_advanced_cast_custom as m - - -def assert_negate_function( - input_sequence: Sequence[float], - target: tuple[float, float], -) -> None: - output = m.negate(input_sequence) - assert isinstance(output, tuple) - assert len(output) == 2 - assert isinstance(output[0], float) - assert isinstance(output[1], float) - assert output == target - - -def test_negate(doc: SanitizedString) -> None: - assert doc(m.negate) == "negate(arg0: Sequence[float]) -> tuple[float, float]" - assert_negate_function([1.0, -1.0], (-1.0, 1.0)) - assert_negate_function((1.0, -1.0), (-1.0, 1.0)) - assert_negate_function([1, -1], (-1.0, 1.0)) - assert_negate_function((1, -1), (-1.0, 1.0)) - - -def test_docs() -> None: - ########################################################################### - # PLEASE UPDATE docs/advanced/cast/custom.rst IF ANY CHANGES ARE MADE HERE. - ########################################################################### - point1 = [1.0, -1.0] - point2 = m.negate(point1) - assert point2 == (-1.0, 1.0) diff --git a/pybind11/tests/test_eigen.cpp b/pybind11/tests/test_eigen.cpp deleted file mode 100644 index 591dacc62..000000000 --- a/pybind11/tests/test_eigen.cpp +++ /dev/null @@ -1,401 +0,0 @@ -/* - tests/eigen.cpp -- automatic conversion of Eigen types - - Copyright (c) 2016 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#include -#include - -#include "constructor_stats.h" -#include "pybind11_tests.h" - -#if defined(_MSC_VER) -# pragma warning(disable : 4996) // C4996: std::unary_negation is deprecated -#endif - -#include - -using MatrixXdR = Eigen::Matrix; - -// Sets/resets a testing reference matrix to have values of 10*r + c, where r and c are the -// (1-based) row/column number. -template -void reset_ref(M &x) { - for (int i = 0; i < x.rows(); i++) { - for (int j = 0; j < x.cols(); j++) { - x(i, j) = 11 + 10 * i + j; - } - } -} - -// Returns a static, column-major matrix -Eigen::MatrixXd &get_cm() { - static Eigen::MatrixXd *x; - if (!x) { - x = new Eigen::MatrixXd(3, 3); - reset_ref(*x); - } - return *x; -} -// Likewise, but row-major -MatrixXdR &get_rm() { - static MatrixXdR *x; - if (!x) { - x = new MatrixXdR(3, 3); - reset_ref(*x); - } - return *x; -} -// Resets the values of the static matrices returned by get_cm()/get_rm() -void reset_refs() { - reset_ref(get_cm()); - reset_ref(get_rm()); -} - -// Returns element 2,1 from a matrix (used to test copy/nocopy) -double get_elem(const Eigen::Ref &m) { return m(2, 1); }; - -// Returns a matrix with 10*r + 100*c added to each matrix element (to help test that the matrix -// reference is referencing rows/columns correctly). -template -Eigen::MatrixXd adjust_matrix(MatrixArgType m) { - Eigen::MatrixXd ret(m); - for (int c = 0; c < m.cols(); c++) { - for (int r = 0; r < m.rows(); r++) { - ret(r, c) += 10 * r + 100 * c; // NOLINT(clang-analyzer-core.uninitialized.Assign) - } - } - return ret; -} - -struct CustomOperatorNew { - CustomOperatorNew() = default; - - Eigen::Matrix4d a = Eigen::Matrix4d::Zero(); - Eigen::Matrix4d b = Eigen::Matrix4d::Identity(); - - EIGEN_MAKE_ALIGNED_OPERATOR_NEW; -}; - -TEST_SUBMODULE(eigen, m) { - using FixedMatrixR = Eigen::Matrix; - using FixedMatrixC = Eigen::Matrix; - using DenseMatrixR = Eigen::Matrix; - using DenseMatrixC = Eigen::Matrix; - using FourRowMatrixC = Eigen::Matrix; - using FourColMatrixC = Eigen::Matrix; - using FourRowMatrixR = Eigen::Matrix; - using FourColMatrixR = Eigen::Matrix; - using SparseMatrixR = Eigen::SparseMatrix; - using SparseMatrixC = Eigen::SparseMatrix; - - // various tests - m.def("double_col", [](const Eigen::VectorXf &x) -> Eigen::VectorXf { return 2.0f * x; }); - m.def("double_row", - [](const Eigen::RowVectorXf &x) -> Eigen::RowVectorXf { return 2.0f * x; }); - m.def("double_complex", - [](const Eigen::VectorXcf &x) -> Eigen::VectorXcf { return 2.0f * x; }); - m.def("double_threec", [](py::EigenDRef x) { x *= 2; }); - m.def("double_threer", [](py::EigenDRef x) { x *= 2; }); - m.def("double_mat_cm", [](const Eigen::MatrixXf &x) -> Eigen::MatrixXf { return 2.0f * x; }); - m.def("double_mat_rm", [](const DenseMatrixR &x) -> DenseMatrixR { return 2.0f * x; }); - - // test_eigen_ref_to_python - // Different ways of passing via Eigen::Ref; the first and second are the Eigen-recommended - m.def("cholesky1", - [](const Eigen::Ref &x) -> Eigen::MatrixXd { return x.llt().matrixL(); }); - m.def("cholesky2", [](const Eigen::Ref &x) -> Eigen::MatrixXd { - return x.llt().matrixL(); - }); - m.def("cholesky3", - [](const Eigen::Ref &x) -> Eigen::MatrixXd { return x.llt().matrixL(); }); - m.def("cholesky4", [](const Eigen::Ref &x) -> Eigen::MatrixXd { - return x.llt().matrixL(); - }); - - // test_eigen_ref_mutators - // Mutators: these add some value to the given element using Eigen, but Eigen should be mapping - // into the numpy array data and so the result should show up there. There are three versions: - // one that works on a contiguous-row matrix (numpy's default), one for a contiguous-column - // matrix, and one for any matrix. - auto add_rm = [](Eigen::Ref x, int r, int c, double v) { x(r, c) += v; }; - auto add_cm = [](Eigen::Ref x, int r, int c, double v) { x(r, c) += v; }; - - // Mutators (Eigen maps into numpy variables): - m.def("add_rm", add_rm); // Only takes row-contiguous - m.def("add_cm", add_cm); // Only takes column-contiguous - // Overloaded versions that will accept either row or column contiguous: - m.def("add1", add_rm); - m.def("add1", add_cm); - m.def("add2", add_cm); - m.def("add2", add_rm); - // This one accepts a matrix of any stride: - m.def("add_any", - [](py::EigenDRef x, int r, int c, double v) { x(r, c) += v; }); - - // Return mutable references (numpy maps into eigen variables) - m.def("get_cm_ref", []() { return Eigen::Ref(get_cm()); }); - m.def("get_rm_ref", []() { return Eigen::Ref(get_rm()); }); - // The same references, but non-mutable (numpy maps into eigen variables, but is !writeable) - m.def("get_cm_const_ref", []() { return Eigen::Ref(get_cm()); }); - m.def("get_rm_const_ref", []() { return Eigen::Ref(get_rm()); }); - - m.def("reset_refs", reset_refs); // Restores get_{cm,rm}_ref to original values - - // Increments and returns ref to (same) matrix - m.def( - "incr_matrix", - [](Eigen::Ref m, double v) { - m += Eigen::MatrixXd::Constant(m.rows(), m.cols(), v); - return m; - }, - py::return_value_policy::reference); - - // Same, but accepts a matrix of any strides - m.def( - "incr_matrix_any", - [](py::EigenDRef m, double v) { - m += Eigen::MatrixXd::Constant(m.rows(), m.cols(), v); - return m; - }, - py::return_value_policy::reference); - - // Returns an eigen slice of even rows - m.def( - "even_rows", - [](py::EigenDRef m) { - return py::EigenDMap( - m.data(), - (m.rows() + 1) / 2, - m.cols(), - py::EigenDStride(m.outerStride(), 2 * m.innerStride())); - }, - py::return_value_policy::reference); - - // Returns an eigen slice of even columns - m.def( - "even_cols", - [](py::EigenDRef m) { - return py::EigenDMap( - m.data(), - m.rows(), - (m.cols() + 1) / 2, - py::EigenDStride(2 * m.outerStride(), m.innerStride())); - }, - py::return_value_policy::reference); - - // Returns diagonals: a vector-like object with an inner stride != 1 - m.def("diagonal", [](const Eigen::Ref &x) { return x.diagonal(); }); - m.def("diagonal_1", - [](const Eigen::Ref &x) { return x.diagonal<1>(); }); - m.def("diagonal_n", - [](const Eigen::Ref &x, int index) { return x.diagonal(index); }); - - // Return a block of a matrix (gives non-standard strides) - m.def("block", - [](const Eigen::Ref &x, - int start_row, - int start_col, - int block_rows, - int block_cols) { return x.block(start_row, start_col, block_rows, block_cols); }); - - // test_eigen_return_references, test_eigen_keepalive - // return value referencing/copying tests: - class ReturnTester { - Eigen::MatrixXd mat = create(); - - public: - ReturnTester() { print_created(this); } - ~ReturnTester() { print_destroyed(this); } - static Eigen::MatrixXd create() { return Eigen::MatrixXd::Ones(10, 10); } - // NOLINTNEXTLINE(readability-const-return-type) - static const Eigen::MatrixXd createConst() { return Eigen::MatrixXd::Ones(10, 10); } - Eigen::MatrixXd &get() { return mat; } - Eigen::MatrixXd *getPtr() { return &mat; } - const Eigen::MatrixXd &view() { return mat; } - const Eigen::MatrixXd *viewPtr() { return &mat; } - Eigen::Ref ref() { return mat; } - Eigen::Ref refConst() { return mat; } - Eigen::Block block(int r, int c, int nrow, int ncol) { - return mat.block(r, c, nrow, ncol); - } - Eigen::Block blockConst(int r, int c, int nrow, int ncol) const { - return mat.block(r, c, nrow, ncol); - } - py::EigenDMap corners() { - return py::EigenDMap( - mat.data(), - py::EigenDStride(mat.outerStride() * (mat.outerSize() - 1), - mat.innerStride() * (mat.innerSize() - 1))); - } - py::EigenDMap cornersConst() const { - return py::EigenDMap( - mat.data(), - py::EigenDStride(mat.outerStride() * (mat.outerSize() - 1), - mat.innerStride() * (mat.innerSize() - 1))); - } - }; - using rvp = py::return_value_policy; - py::class_(m, "ReturnTester") - .def(py::init<>()) - .def_static("create", &ReturnTester::create) - .def_static("create_const", &ReturnTester::createConst) - .def("get", &ReturnTester::get, rvp::reference_internal) - .def("get_ptr", &ReturnTester::getPtr, rvp::reference_internal) - .def("view", &ReturnTester::view, rvp::reference_internal) - .def("view_ptr", &ReturnTester::view, rvp::reference_internal) - .def("copy_get", &ReturnTester::get) // Default rvp: copy - .def("copy_view", &ReturnTester::view) // " - .def("ref", &ReturnTester::ref) // Default for Ref is to reference - .def("ref_const", &ReturnTester::refConst) // Likewise, but const - .def("ref_safe", &ReturnTester::ref, rvp::reference_internal) - .def("ref_const_safe", &ReturnTester::refConst, rvp::reference_internal) - .def("copy_ref", &ReturnTester::ref, rvp::copy) - .def("copy_ref_const", &ReturnTester::refConst, rvp::copy) - .def("block", &ReturnTester::block) - .def("block_safe", &ReturnTester::block, rvp::reference_internal) - .def("block_const", &ReturnTester::blockConst, rvp::reference_internal) - .def("copy_block", &ReturnTester::block, rvp::copy) - .def("corners", &ReturnTester::corners, rvp::reference_internal) - .def("corners_const", &ReturnTester::cornersConst, rvp::reference_internal); - - // test_special_matrix_objects - // Returns a DiagonalMatrix with diagonal (1,2,3,...) - m.def("incr_diag", [](int k) { - Eigen::DiagonalMatrix m(k); - for (int i = 0; i < k; i++) { - m.diagonal()[i] = i + 1; - } - return m; - }); - - // Returns a SelfAdjointView referencing the lower triangle of m - m.def("symmetric_lower", - [](const Eigen::MatrixXi &m) { return m.selfadjointView(); }); - // Returns a SelfAdjointView referencing the lower triangle of m - m.def("symmetric_upper", - [](const Eigen::MatrixXi &m) { return m.selfadjointView(); }); - - // Test matrix for various functions below. - Eigen::MatrixXf mat(5, 6); - mat << 0, 3, 0, 0, 0, 11, 22, 0, 0, 0, 17, 11, 7, 5, 0, 1, 0, 11, 0, 0, 0, 0, 0, 11, 0, 0, 14, - 0, 8, 11; - - // test_fixed, and various other tests - m.def("fixed_r", [mat]() -> FixedMatrixR { return FixedMatrixR(mat); }); - // Our Eigen does a hack which respects constness through the numpy writeable flag. - // Therefore, the const return actually affects this type despite being an rvalue. - // NOLINTNEXTLINE(readability-const-return-type) - m.def("fixed_r_const", [mat]() -> const FixedMatrixR { return FixedMatrixR(mat); }); - m.def("fixed_c", [mat]() -> FixedMatrixC { return FixedMatrixC(mat); }); - m.def("fixed_copy_r", [](const FixedMatrixR &m) -> FixedMatrixR { return m; }); - m.def("fixed_copy_c", [](const FixedMatrixC &m) -> FixedMatrixC { return m; }); - // test_mutator_descriptors - m.def("fixed_mutator_r", [](const Eigen::Ref &) {}); - m.def("fixed_mutator_c", [](const Eigen::Ref &) {}); - m.def("fixed_mutator_a", [](const py::EigenDRef &) {}); - // test_dense - m.def("dense_r", [mat]() -> DenseMatrixR { return DenseMatrixR(mat); }); - m.def("dense_c", [mat]() -> DenseMatrixC { return DenseMatrixC(mat); }); - m.def("dense_copy_r", [](const DenseMatrixR &m) -> DenseMatrixR { return m; }); - m.def("dense_copy_c", [](const DenseMatrixC &m) -> DenseMatrixC { return m; }); - // test_sparse, test_sparse_signature - m.def("sparse_r", [mat]() -> SparseMatrixR { - // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) - return Eigen::SparseView(mat); - }); - m.def("sparse_c", - [mat]() -> SparseMatrixC { return Eigen::SparseView(mat); }); - m.def("sparse_copy_r", [](const SparseMatrixR &m) -> SparseMatrixR { return m; }); - m.def("sparse_copy_c", [](const SparseMatrixC &m) -> SparseMatrixC { return m; }); - // test_partially_fixed - m.def("partial_copy_four_rm_r", [](const FourRowMatrixR &m) -> FourRowMatrixR { return m; }); - m.def("partial_copy_four_rm_c", [](const FourColMatrixR &m) -> FourColMatrixR { return m; }); - m.def("partial_copy_four_cm_r", [](const FourRowMatrixC &m) -> FourRowMatrixC { return m; }); - m.def("partial_copy_four_cm_c", [](const FourColMatrixC &m) -> FourColMatrixC { return m; }); - - // test_cpp_casting - // Test that we can cast a numpy object to a Eigen::MatrixXd explicitly - m.def("cpp_copy", [](py::handle m) { return m.cast()(1, 0); }); - m.def("cpp_ref_c", [](py::handle m) { return m.cast>()(1, 0); }); - m.def("cpp_ref_r", [](py::handle m) { return m.cast>()(1, 0); }); - m.def("cpp_ref_any", - [](py::handle m) { return m.cast>()(1, 0); }); - - // [workaround(intel)] ICC 20/21 breaks with py::arg().stuff, using py::arg{}.stuff works. - - // test_nocopy_wrapper - // Test that we can prevent copying into an argument that would normally copy: First a version - // that would allow copying (if types or strides don't match) for comparison: - m.def("get_elem", &get_elem); - // Now this alternative that calls the tells pybind to fail rather than copy: - m.def( - "get_elem_nocopy", - [](const Eigen::Ref &m) -> double { return get_elem(m); }, - py::arg{}.noconvert()); - // Also test a row-major-only no-copy const ref: - m.def( - "get_elem_rm_nocopy", - [](Eigen::Ref> &m) -> long { - return m(2, 1); - }, - py::arg{}.noconvert()); - - // test_issue738, test_zero_length - // Issue #738: 1×N or N×1 2D matrices were neither accepted nor properly copied with an - // incompatible stride value on the length-1 dimension--but that should be allowed (without - // requiring a copy!) because the stride value can be safely ignored on a size-1 dimension. - // Similarly, 0×N or N×0 matrices were not accepted--again, these should be allowed since - // they contain no data. This particularly affects numpy ≥ 1.23, which sets the strides to - // 0 if any dimension size is 0. - m.def("iss738_f1", - &adjust_matrix &>, - py::arg{}.noconvert()); - m.def("iss738_f2", - &adjust_matrix> &>, - py::arg{}.noconvert()); - - // test_issue1105 - // Issue #1105: when converting from a numpy two-dimensional (Nx1) or (1xN) value into a dense - // eigen Vector or RowVector, the argument would fail to load because the numpy copy would - // fail: numpy won't broadcast a Nx1 into a 1-dimensional vector. - m.def("iss1105_col", [](const Eigen::VectorXd &) { return true; }); - m.def("iss1105_row", [](const Eigen::RowVectorXd &) { return true; }); - - // test_named_arguments - // Make sure named arguments are working properly: - m.def( - "matrix_multiply", - [](const py::EigenDRef &A, - const py::EigenDRef &B) -> Eigen::MatrixXd { - if (A.cols() != B.rows()) { - throw std::domain_error("Nonconformable matrices!"); - } - return A * B; - }, - py::arg("A"), - py::arg("B")); - - // test_custom_operator_new - py::class_(m, "CustomOperatorNew") - .def(py::init<>()) - .def_readonly("a", &CustomOperatorNew::a) - .def_readonly("b", &CustomOperatorNew::b); - - // test_eigen_ref_life_support - // In case of a failure (the caster's temp array does not live long enough), creating - // a new array (np.ones(10)) increases the chances that the temp array will be garbage - // collected and/or that its memory will be overridden with different values. - m.def("get_elem_direct", [](const Eigen::Ref &v) { - py::module_::import("numpy").attr("ones")(10); - return v(5); - }); - m.def("get_elem_indirect", [](std::vector> v) { - py::module_::import("numpy").attr("ones")(10); - return v[0](5); - }); -} diff --git a/pybind11/tests/test_eigen.py b/pybind11/tests/test_eigen.py deleted file mode 100644 index a1c114aed..000000000 --- a/pybind11/tests/test_eigen.py +++ /dev/null @@ -1,775 +0,0 @@ -import pytest - -from pybind11_tests import ConstructorStats - -np = pytest.importorskip("numpy") -m = pytest.importorskip("pybind11_tests.eigen") - - -ref = np.array( - [ - [0.0, 3, 0, 0, 0, 11], - [22, 0, 0, 0, 17, 11], - [7, 5, 0, 1, 0, 11], - [0, 0, 0, 0, 0, 11], - [0, 0, 14, 0, 8, 11], - ] -) - - -def assert_equal_ref(mat): - np.testing.assert_array_equal(mat, ref) - - -def assert_sparse_equal_ref(sparse_mat): - assert_equal_ref(sparse_mat.toarray()) - - -def test_fixed(): - assert_equal_ref(m.fixed_c()) - assert_equal_ref(m.fixed_r()) - assert_equal_ref(m.fixed_copy_r(m.fixed_r())) - assert_equal_ref(m.fixed_copy_c(m.fixed_c())) - assert_equal_ref(m.fixed_copy_r(m.fixed_c())) - assert_equal_ref(m.fixed_copy_c(m.fixed_r())) - - -def test_dense(): - assert_equal_ref(m.dense_r()) - assert_equal_ref(m.dense_c()) - assert_equal_ref(m.dense_copy_r(m.dense_r())) - assert_equal_ref(m.dense_copy_c(m.dense_c())) - assert_equal_ref(m.dense_copy_r(m.dense_c())) - assert_equal_ref(m.dense_copy_c(m.dense_r())) - - -def test_partially_fixed(): - ref2 = np.array([[0.0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]]) - np.testing.assert_array_equal(m.partial_copy_four_rm_r(ref2), ref2) - np.testing.assert_array_equal(m.partial_copy_four_rm_c(ref2), ref2) - np.testing.assert_array_equal(m.partial_copy_four_rm_r(ref2[:, 1]), ref2[:, [1]]) - np.testing.assert_array_equal(m.partial_copy_four_rm_c(ref2[0, :]), ref2[[0], :]) - np.testing.assert_array_equal( - m.partial_copy_four_rm_r(ref2[:, (0, 2)]), ref2[:, (0, 2)] - ) - np.testing.assert_array_equal( - m.partial_copy_four_rm_c(ref2[(3, 1, 2), :]), ref2[(3, 1, 2), :] - ) - - np.testing.assert_array_equal(m.partial_copy_four_cm_r(ref2), ref2) - np.testing.assert_array_equal(m.partial_copy_four_cm_c(ref2), ref2) - np.testing.assert_array_equal(m.partial_copy_four_cm_r(ref2[:, 1]), ref2[:, [1]]) - np.testing.assert_array_equal(m.partial_copy_four_cm_c(ref2[0, :]), ref2[[0], :]) - np.testing.assert_array_equal( - m.partial_copy_four_cm_r(ref2[:, (0, 2)]), ref2[:, (0, 2)] - ) - np.testing.assert_array_equal( - m.partial_copy_four_cm_c(ref2[(3, 1, 2), :]), ref2[(3, 1, 2), :] - ) - - # TypeError should be raise for a shape mismatch - functions = [ - m.partial_copy_four_rm_r, - m.partial_copy_four_rm_c, - m.partial_copy_four_cm_r, - m.partial_copy_four_cm_c, - ] - matrix_with_wrong_shape = [[1, 2], [3, 4]] - for f in functions: - with pytest.raises(TypeError) as excinfo: - f(matrix_with_wrong_shape) - assert "incompatible function arguments" in str(excinfo.value) - - -def test_mutator_descriptors(): - zr = np.arange(30, dtype="float32").reshape(5, 6) # row-major - zc = zr.reshape(6, 5).transpose() # column-major - - m.fixed_mutator_r(zr) - m.fixed_mutator_c(zc) - m.fixed_mutator_a(zr) - m.fixed_mutator_a(zc) - with pytest.raises(TypeError) as excinfo: - m.fixed_mutator_r(zc) - 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[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[numpy.float32[5, 6], flags.writeable]) -> None" in str( - excinfo.value - ) - zr.flags.writeable = False - with pytest.raises(TypeError): - m.fixed_mutator_r(zr) - with pytest.raises(TypeError): - m.fixed_mutator_a(zr) - - -def test_cpp_casting(): - assert m.cpp_copy(m.fixed_r()) == 22.0 - assert m.cpp_copy(m.fixed_c()) == 22.0 - z = np.array([[5.0, 6], [7, 8]]) - assert m.cpp_copy(z) == 7.0 - assert m.cpp_copy(m.get_cm_ref()) == 21.0 - assert m.cpp_copy(m.get_rm_ref()) == 21.0 - assert m.cpp_ref_c(m.get_cm_ref()) == 21.0 - assert m.cpp_ref_r(m.get_rm_ref()) == 21.0 - with pytest.raises(RuntimeError) as excinfo: - # Can't reference m.fixed_c: it contains floats, m.cpp_ref_any wants doubles - m.cpp_ref_any(m.fixed_c()) - assert "Unable to cast Python instance" in str(excinfo.value) - with pytest.raises(RuntimeError) as excinfo: - # Can't reference m.fixed_r: it contains floats, m.cpp_ref_any wants doubles - m.cpp_ref_any(m.fixed_r()) - assert "Unable to cast Python instance" in str(excinfo.value) - assert m.cpp_ref_any(m.ReturnTester.create()) == 1.0 - - assert m.cpp_ref_any(m.get_cm_ref()) == 21.0 - assert m.cpp_ref_any(m.get_cm_ref()) == 21.0 - - -def test_pass_readonly_array(): - z = np.full((5, 6), 42.0) - z.flags.writeable = False - np.testing.assert_array_equal(z, m.fixed_copy_r(z)) - np.testing.assert_array_equal(m.fixed_r_const(), m.fixed_r()) - assert not m.fixed_r_const().flags.writeable - np.testing.assert_array_equal(m.fixed_copy_r(m.fixed_r_const()), m.fixed_r_const()) - - -def test_nonunit_stride_from_python(): - counting_mat = np.arange(9.0, dtype=np.float32).reshape((3, 3)) - second_row = counting_mat[1, :] - second_col = counting_mat[:, 1] - np.testing.assert_array_equal(m.double_row(second_row), 2.0 * second_row) - np.testing.assert_array_equal(m.double_col(second_row), 2.0 * second_row) - np.testing.assert_array_equal(m.double_complex(second_row), 2.0 * second_row) - np.testing.assert_array_equal(m.double_row(second_col), 2.0 * second_col) - np.testing.assert_array_equal(m.double_col(second_col), 2.0 * second_col) - np.testing.assert_array_equal(m.double_complex(second_col), 2.0 * second_col) - - counting_3d = np.arange(27.0, dtype=np.float32).reshape((3, 3, 3)) - slices = [counting_3d[0, :, :], counting_3d[:, 0, :], counting_3d[:, :, 0]] - for ref_mat in slices: - np.testing.assert_array_equal(m.double_mat_cm(ref_mat), 2.0 * ref_mat) - np.testing.assert_array_equal(m.double_mat_rm(ref_mat), 2.0 * ref_mat) - - # Mutator: - m.double_threer(second_row) - m.double_threec(second_col) - np.testing.assert_array_equal(counting_mat, [[0.0, 2, 2], [6, 16, 10], [6, 14, 8]]) - - -def test_negative_stride_from_python(msg): - """Eigen doesn't support (as of yet) negative strides. When a function takes an Eigen matrix by - copy or const reference, we can pass a numpy array that has negative strides. Otherwise, an - exception will be thrown as Eigen will not be able to map the numpy array.""" - - counting_mat = np.arange(9.0, dtype=np.float32).reshape((3, 3)) - counting_mat = counting_mat[::-1, ::-1] - second_row = counting_mat[1, :] - second_col = counting_mat[:, 1] - np.testing.assert_array_equal(m.double_row(second_row), 2.0 * second_row) - np.testing.assert_array_equal(m.double_col(second_row), 2.0 * second_row) - np.testing.assert_array_equal(m.double_complex(second_row), 2.0 * second_row) - np.testing.assert_array_equal(m.double_row(second_col), 2.0 * second_col) - np.testing.assert_array_equal(m.double_col(second_col), 2.0 * second_col) - np.testing.assert_array_equal(m.double_complex(second_col), 2.0 * second_col) - - counting_3d = np.arange(27.0, dtype=np.float32).reshape((3, 3, 3)) - counting_3d = counting_3d[::-1, ::-1, ::-1] - slices = [counting_3d[0, :, :], counting_3d[:, 0, :], counting_3d[:, :, 0]] - for ref_mat in slices: - np.testing.assert_array_equal(m.double_mat_cm(ref_mat), 2.0 * ref_mat) - np.testing.assert_array_equal(m.double_mat_rm(ref_mat), 2.0 * ref_mat) - - # Mutator: - with pytest.raises(TypeError) as excinfo: - m.double_threer(second_row) - assert ( - msg(excinfo.value) - == """ - double_threer(): incompatible function arguments. The following argument types are supported: - 1. (arg0: numpy.ndarray[numpy.float32[1, 3], flags.writeable]) -> None - - Invoked with: """ - + repr(np.array([5.0, 4.0, 3.0], dtype="float32")) - ) - - with pytest.raises(TypeError) as excinfo: - m.double_threec(second_col) - assert ( - msg(excinfo.value) - == """ - double_threec(): incompatible function arguments. The following argument types are supported: - 1. (arg0: numpy.ndarray[numpy.float32[3, 1], flags.writeable]) -> None - - Invoked with: """ - + repr(np.array([7.0, 4.0, 1.0], dtype="float32")) - ) - - -def test_nonunit_stride_to_python(): - assert np.all(m.diagonal(ref) == ref.diagonal()) - assert np.all(m.diagonal_1(ref) == ref.diagonal(1)) - for i in range(-5, 7): - assert np.all(m.diagonal_n(ref, i) == ref.diagonal(i)), f"m.diagonal_n({i})" - - assert np.all(m.block(ref, 2, 1, 3, 3) == ref[2:5, 1:4]) - assert np.all(m.block(ref, 1, 4, 4, 2) == ref[1:, 4:]) - assert np.all(m.block(ref, 1, 4, 3, 2) == ref[1:4, 4:]) - - -def test_eigen_ref_to_python(): - chols = [m.cholesky1, m.cholesky2, m.cholesky3, m.cholesky4] - for i, chol in enumerate(chols, start=1): - mymat = chol(np.array([[1.0, 2, 4], [2, 13, 23], [4, 23, 77]])) - assert np.all( - mymat == np.array([[1, 0, 0], [2, 3, 0], [4, 5, 6]]) - ), f"cholesky{i}" - - -def assign_both(a1, a2, r, c, v): - a1[r, c] = v - a2[r, c] = v - - -def array_copy_but_one(a, r, c, v): - z = np.array(a, copy=True) - z[r, c] = v - return z - - -def test_eigen_return_references(): - """Tests various ways of returning references and non-referencing copies""" - - master = np.ones((10, 10)) - a = m.ReturnTester() - a_get1 = a.get() - assert not a_get1.flags.owndata and a_get1.flags.writeable - assign_both(a_get1, master, 3, 3, 5) - a_get2 = a.get_ptr() - assert not a_get2.flags.owndata and a_get2.flags.writeable - assign_both(a_get1, master, 2, 3, 6) - - a_view1 = a.view() - assert not a_view1.flags.owndata and not a_view1.flags.writeable - with pytest.raises(ValueError): - a_view1[2, 3] = 4 - a_view2 = a.view_ptr() - assert not a_view2.flags.owndata and not a_view2.flags.writeable - with pytest.raises(ValueError): - a_view2[2, 3] = 4 - - a_copy1 = a.copy_get() - assert a_copy1.flags.owndata and a_copy1.flags.writeable - np.testing.assert_array_equal(a_copy1, master) - a_copy1[7, 7] = -44 # Shouldn't affect anything else - c1want = array_copy_but_one(master, 7, 7, -44) - a_copy2 = a.copy_view() - assert a_copy2.flags.owndata and a_copy2.flags.writeable - np.testing.assert_array_equal(a_copy2, master) - a_copy2[4, 4] = -22 # Shouldn't affect anything else - c2want = array_copy_but_one(master, 4, 4, -22) - - a_ref1 = a.ref() - assert not a_ref1.flags.owndata and a_ref1.flags.writeable - assign_both(a_ref1, master, 1, 1, 15) - a_ref2 = a.ref_const() - assert not a_ref2.flags.owndata and not a_ref2.flags.writeable - with pytest.raises(ValueError): - a_ref2[5, 5] = 33 - a_ref3 = a.ref_safe() - assert not a_ref3.flags.owndata and a_ref3.flags.writeable - assign_both(a_ref3, master, 0, 7, 99) - a_ref4 = a.ref_const_safe() - assert not a_ref4.flags.owndata and not a_ref4.flags.writeable - with pytest.raises(ValueError): - a_ref4[7, 0] = 987654321 - - a_copy3 = a.copy_ref() - assert a_copy3.flags.owndata and a_copy3.flags.writeable - np.testing.assert_array_equal(a_copy3, master) - a_copy3[8, 1] = 11 - c3want = array_copy_but_one(master, 8, 1, 11) - a_copy4 = a.copy_ref_const() - assert a_copy4.flags.owndata and a_copy4.flags.writeable - np.testing.assert_array_equal(a_copy4, master) - a_copy4[8, 4] = 88 - c4want = array_copy_but_one(master, 8, 4, 88) - - a_block1 = a.block(3, 3, 2, 2) - assert not a_block1.flags.owndata and a_block1.flags.writeable - a_block1[0, 0] = 55 - master[3, 3] = 55 - a_block2 = a.block_safe(2, 2, 3, 2) - assert not a_block2.flags.owndata and a_block2.flags.writeable - a_block2[2, 1] = -123 - master[4, 3] = -123 - a_block3 = a.block_const(6, 7, 4, 3) - assert not a_block3.flags.owndata and not a_block3.flags.writeable - with pytest.raises(ValueError): - a_block3[2, 2] = -44444 - - a_copy5 = a.copy_block(2, 2, 2, 3) - assert a_copy5.flags.owndata and a_copy5.flags.writeable - np.testing.assert_array_equal(a_copy5, master[2:4, 2:5]) - a_copy5[1, 1] = 777 - c5want = array_copy_but_one(master[2:4, 2:5], 1, 1, 777) - - a_corn1 = a.corners() - assert not a_corn1.flags.owndata and a_corn1.flags.writeable - a_corn1 *= 50 - a_corn1[1, 1] = 999 - master[0, 0] = 50 - master[0, 9] = 50 - master[9, 0] = 50 - master[9, 9] = 999 - a_corn2 = a.corners_const() - assert not a_corn2.flags.owndata and not a_corn2.flags.writeable - with pytest.raises(ValueError): - a_corn2[1, 0] = 51 - - # All of the changes made all the way along should be visible everywhere - # now (except for the copies, of course) - np.testing.assert_array_equal(a_get1, master) - np.testing.assert_array_equal(a_get2, master) - np.testing.assert_array_equal(a_view1, master) - np.testing.assert_array_equal(a_view2, master) - np.testing.assert_array_equal(a_ref1, master) - np.testing.assert_array_equal(a_ref2, master) - np.testing.assert_array_equal(a_ref3, master) - np.testing.assert_array_equal(a_ref4, master) - np.testing.assert_array_equal(a_block1, master[3:5, 3:5]) - np.testing.assert_array_equal(a_block2, master[2:5, 2:4]) - np.testing.assert_array_equal(a_block3, master[6:10, 7:10]) - np.testing.assert_array_equal( - a_corn1, master[0 :: master.shape[0] - 1, 0 :: master.shape[1] - 1] - ) - np.testing.assert_array_equal( - a_corn2, master[0 :: master.shape[0] - 1, 0 :: master.shape[1] - 1] - ) - - np.testing.assert_array_equal(a_copy1, c1want) - np.testing.assert_array_equal(a_copy2, c2want) - np.testing.assert_array_equal(a_copy3, c3want) - np.testing.assert_array_equal(a_copy4, c4want) - np.testing.assert_array_equal(a_copy5, c5want) - - -def assert_keeps_alive(cl, method, *args): - cstats = ConstructorStats.get(cl) - start_with = cstats.alive() - a = cl() - assert cstats.alive() == start_with + 1 - z = method(a, *args) - assert cstats.alive() == start_with + 1 - del a - # Here's the keep alive in action: - assert cstats.alive() == start_with + 1 - del z - # Keep alive should have expired: - assert cstats.alive() == start_with - - -def test_eigen_keepalive(): - a = m.ReturnTester() - cstats = ConstructorStats.get(m.ReturnTester) - assert cstats.alive() == 1 - unsafe = [a.ref(), a.ref_const(), a.block(1, 2, 3, 4)] - copies = [ - a.copy_get(), - a.copy_view(), - a.copy_ref(), - a.copy_ref_const(), - a.copy_block(4, 3, 2, 1), - ] - del a - assert cstats.alive() == 0 - del unsafe - del copies - - for meth in [ - m.ReturnTester.get, - m.ReturnTester.get_ptr, - m.ReturnTester.view, - m.ReturnTester.view_ptr, - m.ReturnTester.ref_safe, - m.ReturnTester.ref_const_safe, - m.ReturnTester.corners, - m.ReturnTester.corners_const, - ]: - assert_keeps_alive(m.ReturnTester, meth) - - for meth in [m.ReturnTester.block_safe, m.ReturnTester.block_const]: - assert_keeps_alive(m.ReturnTester, meth, 4, 3, 2, 1) - - -def test_eigen_ref_mutators(): - """Tests Eigen's ability to mutate numpy values""" - - orig = np.array([[1.0, 2, 3], [4, 5, 6], [7, 8, 9]]) - zr = np.array(orig) - zc = np.array(orig, order="F") - m.add_rm(zr, 1, 0, 100) - assert np.all(zr == np.array([[1.0, 2, 3], [104, 5, 6], [7, 8, 9]])) - m.add_cm(zc, 1, 0, 200) - assert np.all(zc == np.array([[1.0, 2, 3], [204, 5, 6], [7, 8, 9]])) - - m.add_any(zr, 1, 0, 20) - assert np.all(zr == np.array([[1.0, 2, 3], [124, 5, 6], [7, 8, 9]])) - m.add_any(zc, 1, 0, 10) - assert np.all(zc == np.array([[1.0, 2, 3], [214, 5, 6], [7, 8, 9]])) - - # Can't reference a col-major array with a row-major Ref, and vice versa: - with pytest.raises(TypeError): - m.add_rm(zc, 1, 0, 1) - with pytest.raises(TypeError): - m.add_cm(zr, 1, 0, 1) - - # Overloads: - m.add1(zr, 1, 0, -100) - m.add2(zr, 1, 0, -20) - assert np.all(zr == orig) - m.add1(zc, 1, 0, -200) - m.add2(zc, 1, 0, -10) - assert np.all(zc == orig) - - # a non-contiguous slice (this won't work on either the row- or - # column-contiguous refs, but should work for the any) - cornersr = zr[0::2, 0::2] - cornersc = zc[0::2, 0::2] - - assert np.all(cornersr == np.array([[1.0, 3], [7, 9]])) - assert np.all(cornersc == np.array([[1.0, 3], [7, 9]])) - - with pytest.raises(TypeError): - m.add_rm(cornersr, 0, 1, 25) - with pytest.raises(TypeError): - m.add_cm(cornersr, 0, 1, 25) - with pytest.raises(TypeError): - m.add_rm(cornersc, 0, 1, 25) - with pytest.raises(TypeError): - m.add_cm(cornersc, 0, 1, 25) - m.add_any(cornersr, 0, 1, 25) - m.add_any(cornersc, 0, 1, 44) - assert np.all(zr == np.array([[1.0, 2, 28], [4, 5, 6], [7, 8, 9]])) - assert np.all(zc == np.array([[1.0, 2, 47], [4, 5, 6], [7, 8, 9]])) - - # You shouldn't be allowed to pass a non-writeable array to a mutating Eigen method: - zro = zr[0:4, 0:4] - zro.flags.writeable = False - with pytest.raises(TypeError): - m.add_rm(zro, 0, 0, 0) - with pytest.raises(TypeError): - m.add_any(zro, 0, 0, 0) - with pytest.raises(TypeError): - m.add1(zro, 0, 0, 0) - with pytest.raises(TypeError): - m.add2(zro, 0, 0, 0) - - # integer array shouldn't be passable to a double-matrix-accepting mutating func: - zi = np.array([[1, 2], [3, 4]]) - with pytest.raises(TypeError): - m.add_rm(zi) - - -def test_numpy_ref_mutators(): - """Tests numpy mutating Eigen matrices (for returned Eigen::Ref<...>s)""" - - m.reset_refs() # In case another test already changed it - - zc = m.get_cm_ref() - zcro = m.get_cm_const_ref() - zr = m.get_rm_ref() - zrro = m.get_rm_const_ref() - - assert [zc[1, 2], zcro[1, 2], zr[1, 2], zrro[1, 2]] == [23] * 4 - - assert not zc.flags.owndata and zc.flags.writeable - assert not zr.flags.owndata and zr.flags.writeable - assert not zcro.flags.owndata and not zcro.flags.writeable - assert not zrro.flags.owndata and not zrro.flags.writeable - - zc[1, 2] = 99 - expect = np.array([[11.0, 12, 13], [21, 22, 99], [31, 32, 33]]) - # We should have just changed zc, of course, but also zcro and the original eigen matrix - assert np.all(zc == expect) - assert np.all(zcro == expect) - assert np.all(m.get_cm_ref() == expect) - - zr[1, 2] = 99 - assert np.all(zr == expect) - assert np.all(zrro == expect) - assert np.all(m.get_rm_ref() == expect) - - # Make sure the readonly ones are numpy-readonly: - with pytest.raises(ValueError): - zcro[1, 2] = 6 - with pytest.raises(ValueError): - zrro[1, 2] = 6 - - # We should be able to explicitly copy like this (and since we're copying, - # the const should drop away) - y1 = np.array(m.get_cm_const_ref()) - - assert y1.flags.owndata and y1.flags.writeable - # We should get copies of the eigen data, which was modified above: - assert y1[1, 2] == 99 - y1[1, 2] += 12 - assert y1[1, 2] == 111 - assert zc[1, 2] == 99 # Make sure we aren't referencing the original - - -def test_both_ref_mutators(): - """Tests a complex chain of nested eigen/numpy references""" - - m.reset_refs() # In case another test already changed it - - z = m.get_cm_ref() # numpy -> eigen - z[0, 2] -= 3 - z2 = m.incr_matrix(z, 1) # numpy -> eigen -> numpy -> eigen - z2[1, 1] += 6 - z3 = m.incr_matrix(z, 2) # (numpy -> eigen)^3 - z3[2, 2] += -5 - z4 = m.incr_matrix(z, 3) # (numpy -> eigen)^4 - z4[1, 1] -= 1 - z5 = m.incr_matrix(z, 4) # (numpy -> eigen)^5 - z5[0, 0] = 0 - assert np.all(z == z2) - assert np.all(z == z3) - assert np.all(z == z4) - assert np.all(z == z5) - expect = np.array([[0.0, 22, 20], [31, 37, 33], [41, 42, 38]]) - assert np.all(z == expect) - - y = np.array(range(100), dtype="float64").reshape(10, 10) - y2 = m.incr_matrix_any(y, 10) # np -> eigen -> np - y3 = m.incr_matrix_any( - y2[0::2, 0::2], -33 - ) # np -> eigen -> np slice -> np -> eigen -> np - y4 = m.even_rows(y3) # numpy -> eigen slice -> (... y3) - y5 = m.even_cols(y4) # numpy -> eigen slice -> (... y4) - y6 = m.incr_matrix_any(y5, 1000) # numpy -> eigen -> (... y5) - - # Apply same mutations using just numpy: - yexpect = np.array(range(100), dtype="float64").reshape(10, 10) - yexpect += 10 - yexpect[0::2, 0::2] -= 33 - yexpect[0::4, 0::4] += 1000 - assert np.all(y6 == yexpect[0::4, 0::4]) - assert np.all(y5 == yexpect[0::4, 0::4]) - assert np.all(y4 == yexpect[0::4, 0::2]) - assert np.all(y3 == yexpect[0::2, 0::2]) - assert np.all(y2 == yexpect) - assert np.all(y == yexpect) - - -def test_nocopy_wrapper(): - # get_elem requires a column-contiguous matrix reference, but should be - # callable with other types of matrix (via copying): - int_matrix_colmajor = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], order="F") - dbl_matrix_colmajor = np.array( - int_matrix_colmajor, dtype="double", order="F", copy=True - ) - int_matrix_rowmajor = np.array(int_matrix_colmajor, order="C", copy=True) - dbl_matrix_rowmajor = np.array( - int_matrix_rowmajor, dtype="double", order="C", copy=True - ) - - # All should be callable via get_elem: - assert m.get_elem(int_matrix_colmajor) == 8 - assert m.get_elem(dbl_matrix_colmajor) == 8 - assert m.get_elem(int_matrix_rowmajor) == 8 - assert m.get_elem(dbl_matrix_rowmajor) == 8 - - # All but the second should fail with m.get_elem_nocopy: - with pytest.raises(TypeError) as excinfo: - m.get_elem_nocopy(int_matrix_colmajor) - assert "get_elem_nocopy(): incompatible function arguments." in str( - excinfo.value - ) and ", flags.f_contiguous" in str(excinfo.value) - assert m.get_elem_nocopy(dbl_matrix_colmajor) == 8 - with pytest.raises(TypeError) as excinfo: - m.get_elem_nocopy(int_matrix_rowmajor) - assert "get_elem_nocopy(): incompatible function arguments." in str( - excinfo.value - ) and ", flags.f_contiguous" in str(excinfo.value) - with pytest.raises(TypeError) as excinfo: - m.get_elem_nocopy(dbl_matrix_rowmajor) - assert "get_elem_nocopy(): incompatible function arguments." in str( - excinfo.value - ) and ", flags.f_contiguous" in str(excinfo.value) - - # For the row-major test, we take a long matrix in row-major, so only the third is allowed: - with pytest.raises(TypeError) as excinfo: - m.get_elem_rm_nocopy(int_matrix_colmajor) - assert "get_elem_rm_nocopy(): incompatible function arguments." in str( - excinfo.value - ) and ", flags.c_contiguous" in str(excinfo.value) - with pytest.raises(TypeError) as excinfo: - m.get_elem_rm_nocopy(dbl_matrix_colmajor) - assert "get_elem_rm_nocopy(): incompatible function arguments." in str( - excinfo.value - ) and ", flags.c_contiguous" in str(excinfo.value) - assert m.get_elem_rm_nocopy(int_matrix_rowmajor) == 8 - with pytest.raises(TypeError) as excinfo: - m.get_elem_rm_nocopy(dbl_matrix_rowmajor) - assert "get_elem_rm_nocopy(): incompatible function arguments." in str( - excinfo.value - ) and ", flags.c_contiguous" in str(excinfo.value) - - -def test_eigen_ref_life_support(): - """Ensure the lifetime of temporary arrays created by the `Ref` caster - - The `Ref` caster sometimes creates a copy which needs to stay alive. This needs to - happen both for directs casts (just the array) or indirectly (e.g. list of arrays). - """ - - a = np.full(shape=10, fill_value=8, dtype=np.int8) - assert m.get_elem_direct(a) == 8 - - list_of_a = [a] - assert m.get_elem_indirect(list_of_a) == 8 - - -def test_special_matrix_objects(): - assert np.all(m.incr_diag(7) == np.diag([1.0, 2, 3, 4, 5, 6, 7])) - - asymm = np.array([[1.0, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]) - symm_lower = np.array(asymm) - symm_upper = np.array(asymm) - for i in range(4): - for j in range(i + 1, 4): - symm_lower[i, j] = symm_lower[j, i] - symm_upper[j, i] = symm_upper[i, j] - - assert np.all(m.symmetric_lower(asymm) == symm_lower) - assert np.all(m.symmetric_upper(asymm) == symm_upper) - - -def test_dense_signature(doc): - assert ( - doc(m.double_col) - == """ - 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[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(): - a = np.array([[1.0, 2], [3, 4], [5, 6]]) - b = np.ones((2, 1)) - - assert np.all(m.matrix_multiply(a, b) == np.array([[3.0], [7], [11]])) - assert np.all(m.matrix_multiply(A=a, B=b) == np.array([[3.0], [7], [11]])) - assert np.all(m.matrix_multiply(B=b, A=a) == np.array([[3.0], [7], [11]])) - - with pytest.raises(ValueError) as excinfo: - m.matrix_multiply(b, a) - assert str(excinfo.value) == "Nonconformable matrices!" - - with pytest.raises(ValueError) as excinfo: - m.matrix_multiply(A=b, B=a) - assert str(excinfo.value) == "Nonconformable matrices!" - - with pytest.raises(ValueError) as excinfo: - m.matrix_multiply(B=a, A=b) - assert str(excinfo.value) == "Nonconformable matrices!" - - -def test_sparse(): - pytest.importorskip("scipy") - assert_sparse_equal_ref(m.sparse_r()) - assert_sparse_equal_ref(m.sparse_c()) - assert_sparse_equal_ref(m.sparse_copy_r(m.sparse_r())) - assert_sparse_equal_ref(m.sparse_copy_c(m.sparse_c())) - assert_sparse_equal_ref(m.sparse_copy_r(m.sparse_c())) - assert_sparse_equal_ref(m.sparse_copy_c(m.sparse_r())) - - -def test_sparse_signature(doc): - pytest.importorskip("scipy") - assert ( - doc(m.sparse_copy_r) - == """ - sparse_copy_r(arg0: scipy.sparse.csr_matrix[numpy.float32]) -> scipy.sparse.csr_matrix[numpy.float32] - """ - ) - assert ( - doc(m.sparse_copy_c) - == """ - sparse_copy_c(arg0: scipy.sparse.csc_matrix[numpy.float32]) -> scipy.sparse.csc_matrix[numpy.float32] - """ - ) - - -def test_issue738(): - """Ignore strides on a length-1 dimension (even if they would be incompatible length > 1)""" - assert np.all(m.iss738_f1(np.array([[1.0, 2, 3]])) == np.array([[1.0, 102, 203]])) - assert np.all( - m.iss738_f1(np.array([[1.0], [2], [3]])) == np.array([[1.0], [12], [23]]) - ) - - assert np.all(m.iss738_f2(np.array([[1.0, 2, 3]])) == np.array([[1.0, 102, 203]])) - assert np.all( - m.iss738_f2(np.array([[1.0], [2], [3]])) == np.array([[1.0], [12], [23]]) - ) - - -@pytest.mark.parametrize("func", [m.iss738_f1, m.iss738_f2]) -@pytest.mark.parametrize("sizes", [(0, 2), (2, 0)]) -def test_zero_length(func, sizes): - """Ignore strides on a length-0 dimension (even if they would be incompatible length > 1)""" - assert np.all(func(np.zeros(sizes)) == np.zeros(sizes)) - - -def test_issue1105(): - """Issue 1105: 1xN or Nx1 input arrays weren't accepted for eigen - compile-time row vectors or column vector""" - assert m.iss1105_row(np.ones((1, 7))) - assert m.iss1105_col(np.ones((7, 1))) - - # These should still fail (incompatible dimensions): - with pytest.raises(TypeError) as excinfo: - m.iss1105_row(np.ones((7, 1))) - assert "incompatible function arguments" in str(excinfo.value) - with pytest.raises(TypeError) as excinfo: - m.iss1105_col(np.ones((1, 7))) - assert "incompatible function arguments" in str(excinfo.value) - - -def test_custom_operator_new(): - """Using Eigen types as member variables requires a class-specific - operator new with proper alignment""" - - o = m.CustomOperatorNew() - np.testing.assert_allclose(o.a, 0.0) - np.testing.assert_allclose(o.b.diagonal(), 1.0) diff --git a/pybind11/tests/test_eigen_matrix.cpp b/pybind11/tests/test_eigen_matrix.cpp index 4e6689a79..1a7f39c0a 100644 --- a/pybind11/tests/test_eigen_matrix.cpp +++ b/pybind11/tests/test_eigen_matrix.cpp @@ -7,13 +7,23 @@ BSD-style license that can be found in the LICENSE file. */ +<<<<<<<< HEAD:pybind11/tests/test_eigen.cpp +#include +======== #include +>>>>>>>> 3a9158898134e05c9a2bb59b6f02ee7f7d7e18e0:pybind11/tests/test_eigen_matrix.cpp #include #include "constructor_stats.h" #include "pybind11_tests.h" +<<<<<<<< HEAD:pybind11/tests/test_eigen.cpp +#if defined(_MSC_VER) +# pragma warning(disable : 4996) // C4996: std::unary_negation is deprecated +#endif +======== PYBIND11_WARNING_DISABLE_MSVC(4996) +>>>>>>>> 3a9158898134e05c9a2bb59b6f02ee7f7d7e18e0:pybind11/tests/test_eigen_matrix.cpp #include @@ -55,7 +65,11 @@ void reset_refs() { } // Returns element 2,1 from a matrix (used to test copy/nocopy) +<<<<<<<< HEAD:pybind11/tests/test_eigen.cpp +double get_elem(const Eigen::Ref &m) { return m(2, 1); }; +======== double get_elem(const Eigen::Ref &m) { return m(2, 1); } +>>>>>>>> 3a9158898134e05c9a2bb59b6f02ee7f7d7e18e0:pybind11/tests/test_eigen_matrix.cpp // Returns a matrix with 10*r + 100*c added to each matrix element (to help test that the matrix // reference is referencing rows/columns correctly). @@ -195,6 +209,13 @@ TEST_SUBMODULE(eigen_matrix, m) { // Return a block of a matrix (gives non-standard strides) m.def("block", +<<<<<<<< HEAD:pybind11/tests/test_eigen.cpp + [](const Eigen::Ref &x, + int start_row, + int start_col, + int block_rows, + int block_cols) { return x.block(start_row, start_col, block_rows, block_cols); }); +======== [m](const py::object &x_obj, int start_row, int start_col, @@ -229,6 +250,7 @@ TEST_SUBMODULE(eigen_matrix, m) { return x.block(start_row, start_col, block_rows, block_cols); }, py::keep_alive<0, 1>()); +>>>>>>>> 3a9158898134e05c9a2bb59b6f02ee7f7d7e18e0:pybind11/tests/test_eigen_matrix.cpp // test_eigen_return_references, test_eigen_keepalive // return value referencing/copying tests: @@ -440,8 +462,4 @@ TEST_SUBMODULE(eigen_matrix, m) { py::module_::import("numpy").attr("ones")(10); return v[0](5); }); - m.def("round_trip_vector", [](const Eigen::VectorXf &x) -> Eigen::VectorXf { return x; }); - m.def("round_trip_dense", [](const DenseMatrixR &m) -> DenseMatrixR { return m; }); - m.def("round_trip_dense_ref", - [](const Eigen::Ref &m) -> Eigen::Ref { return m; }); } diff --git a/pybind11/tests/test_eigen_matrix.py b/pybind11/tests/test_eigen_matrix.py index 9324c2a7d..e1d7433f1 100644 --- a/pybind11/tests/test_eigen_matrix.py +++ b/pybind11/tests/test_eigen_matrix.py @@ -2,7 +2,6 @@ from __future__ import annotations import pytest -import env # noqa: F401 from pybind11_tests import ConstructorStats np = pytest.importorskip("numpy") @@ -95,20 +94,19 @@ def test_mutator_descriptors(): with pytest.raises(TypeError) as excinfo: m.fixed_mutator_r(zc) assert ( - '(arg0: typing.Annotated[numpy.typing.NDArray[numpy.float32], "[5, 6]",' - ' "flags.writeable", "flags.c_contiguous"]) -> None' in str(excinfo.value) + "(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: typing.Annotated[numpy.typing.NDArray[numpy.float32], "[5, 6]",' - ' "flags.writeable", "flags.f_contiguous"]) -> None' in str(excinfo.value) + "(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: typing.Annotated[numpy.typing.NDArray[numpy.float32], "[5, 6]", "flags.writeable"]) -> None' - in str(excinfo.value) + assert "(arg0: numpy.ndarray[numpy.float32[5, 6], flags.writeable]) -> None" in str( + excinfo.value ) zr.flags.writeable = False with pytest.raises(TypeError): @@ -202,7 +200,7 @@ def test_negative_stride_from_python(msg): msg(excinfo.value) == """ double_threer(): incompatible function arguments. The following argument types are supported: - 1. (arg0: typing.Annotated[numpy.typing.NDArray[numpy.float32], "[1, 3]", "flags.writeable"]) -> None + 1. (arg0: numpy.ndarray[numpy.float32[1, 3], flags.writeable]) -> None Invoked with: """ + repr(np.array([5.0, 4.0, 3.0], dtype="float32")) @@ -214,7 +212,7 @@ def test_negative_stride_from_python(msg): msg(excinfo.value) == """ double_threec(): incompatible function arguments. The following argument types are supported: - 1. (arg0: typing.Annotated[numpy.typing.NDArray[numpy.float32], "[3, 1]", "flags.writeable"]) -> None + 1. (arg0: numpy.ndarray[numpy.float32[3, 1], flags.writeable]) -> None Invoked with: """ + repr(np.array([7.0, 4.0, 1.0], dtype="float32")) @@ -245,9 +243,9 @@ def test_eigen_ref_to_python(): chols = [m.cholesky1, m.cholesky2, m.cholesky3, m.cholesky4] for i, chol in enumerate(chols, start=1): mymat = chol(np.array([[1.0, 2, 4], [2, 13, 23], [4, 23, 77]])) - assert np.all(mymat == np.array([[1, 0, 0], [2, 3, 0], [4, 5, 6]])), ( - f"cholesky{i}" - ) + assert np.all( + mymat == np.array([[1, 0, 0], [2, 3, 0], [4, 5, 6]]) + ), f"cholesky{i}" def assign_both(a1, a2, r, c, v): @@ -396,7 +394,6 @@ def test_eigen_return_references(): np.testing.assert_array_equal(a_copy5, c5want) -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def assert_keeps_alive(cl, method, *args): cstats = ConstructorStats.get(cl) start_with = cstats.alive() @@ -412,7 +409,6 @@ def assert_keeps_alive(cl, method, *args): assert cstats.alive() == start_with -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_eigen_keepalive(): a = m.ReturnTester() cstats = ConstructorStats.get(m.ReturnTester) @@ -635,16 +631,16 @@ def test_nocopy_wrapper(): with pytest.raises(TypeError) as excinfo: m.get_elem_nocopy(int_matrix_colmajor) assert "get_elem_nocopy(): incompatible function arguments." in str(excinfo.value) - assert ', "flags.f_contiguous"' in str(excinfo.value) + assert ", flags.f_contiguous" in str(excinfo.value) assert m.get_elem_nocopy(dbl_matrix_colmajor) == 8 with pytest.raises(TypeError) as excinfo: m.get_elem_nocopy(int_matrix_rowmajor) assert "get_elem_nocopy(): incompatible function arguments." in str(excinfo.value) - assert ', "flags.f_contiguous"' in str(excinfo.value) + assert ", flags.f_contiguous" in str(excinfo.value) with pytest.raises(TypeError) as excinfo: m.get_elem_nocopy(dbl_matrix_rowmajor) assert "get_elem_nocopy(): incompatible function arguments." in str(excinfo.value) - assert ', "flags.f_contiguous"' in str(excinfo.value) + assert ", flags.f_contiguous" in str(excinfo.value) # For the row-major test, we take a long matrix in row-major, so only the third is allowed: with pytest.raises(TypeError) as excinfo: @@ -652,20 +648,20 @@ def test_nocopy_wrapper(): assert "get_elem_rm_nocopy(): incompatible function arguments." in str( excinfo.value ) - assert ', "flags.c_contiguous"' in str(excinfo.value) + assert ", flags.c_contiguous" in str(excinfo.value) with pytest.raises(TypeError) as excinfo: m.get_elem_rm_nocopy(dbl_matrix_colmajor) assert "get_elem_rm_nocopy(): incompatible function arguments." in str( excinfo.value ) - assert ', "flags.c_contiguous"' in str(excinfo.value) + assert ", flags.c_contiguous" in str(excinfo.value) assert m.get_elem_rm_nocopy(int_matrix_rowmajor) == 8 with pytest.raises(TypeError) as excinfo: m.get_elem_rm_nocopy(dbl_matrix_rowmajor) assert "get_elem_rm_nocopy(): incompatible function arguments." in str( excinfo.value ) - assert ', "flags.c_contiguous"' in str(excinfo.value) + assert ", flags.c_contiguous" in str(excinfo.value) def test_eigen_ref_life_support(): @@ -701,25 +697,25 @@ def test_dense_signature(doc): assert ( doc(m.double_col) == """ - double_col(arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float32, "[m, 1]"]) -> typing.Annotated[numpy.typing.NDArray[numpy.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: typing.Annotated[numpy.typing.ArrayLike, numpy.float32, "[1, n]"]) -> typing.Annotated[numpy.typing.NDArray[numpy.float32], "[1, n]"] + double_row(arg0: numpy.ndarray[numpy.float32[1, n]]) -> numpy.ndarray[numpy.float32[1, n]] """ ) assert doc(m.double_complex) == ( """ - double_complex(arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.complex64, "[m, 1]"])""" - """ -> typing.Annotated[numpy.typing.NDArray[numpy.complex64], "[m, 1]"] + 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: typing.Annotated[numpy.typing.ArrayLike, numpy.float32, "[m, n]"])""" - """ -> typing.Annotated[numpy.typing.NDArray[numpy.float32], "[m, n]"] + double_mat_rm(arg0: numpy.ndarray[numpy.float32[m, n]])""" + """ -> numpy.ndarray[numpy.float32[m, n]] """ ) @@ -818,22 +814,3 @@ def test_custom_operator_new(): o = m.CustomOperatorNew() np.testing.assert_allclose(o.a, 0.0) np.testing.assert_allclose(o.b.diagonal(), 1.0) - - -def test_arraylike_signature(doc): - assert doc(m.round_trip_vector) == ( - 'round_trip_vector(arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float32, "[m, 1]"])' - ' -> typing.Annotated[numpy.typing.NDArray[numpy.float32], "[m, 1]"]' - ) - assert doc(m.round_trip_dense) == ( - 'round_trip_dense(arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float32, "[m, n]"])' - ' -> typing.Annotated[numpy.typing.NDArray[numpy.float32], "[m, n]"]' - ) - assert doc(m.round_trip_dense_ref) == ( - 'round_trip_dense_ref(arg0: typing.Annotated[numpy.typing.NDArray[numpy.float32], "[m, n]", "flags.writeable", "flags.c_contiguous"])' - ' -> typing.Annotated[numpy.typing.NDArray[numpy.float32], "[m, n]", "flags.writeable", "flags.c_contiguous"]' - ) - m.round_trip_vector([1.0, 2.0]) - m.round_trip_dense([[1.0, 2.0], [3.0, 4.0]]) - with pytest.raises(TypeError, match="incompatible function arguments"): - m.round_trip_dense_ref([[1.0, 2.0], [3.0, 4.0]]) diff --git a/pybind11/tests/test_eigen_tensor.py b/pybind11/tests/test_eigen_tensor.py index 4b018551b..a2b99d9d7 100644 --- a/pybind11/tests/test_eigen_tensor.py +++ b/pybind11/tests/test_eigen_tensor.py @@ -4,8 +4,6 @@ import sys import pytest -import env # noqa: F401 - np = pytest.importorskip("numpy") eigen_tensor = pytest.importorskip("pybind11_tests.eigen_tensor") submodules = [eigen_tensor.c_style, eigen_tensor.f_style] @@ -63,7 +61,6 @@ def assert_equal_tensor_ref(mat, writeable=True, modified=None): @pytest.mark.parametrize("m", submodules) @pytest.mark.parametrize("member_name", ["member", "member_view"]) -@pytest.mark.skipif("env.GRAALPY", reason="Different refcounting mechanism") def test_reference_internal(m, member_name): if not hasattr(sys, "getrefcount"): pytest.skip("No reference counting") @@ -271,46 +268,23 @@ def test_round_trip_references_actually_refer(m): @pytest.mark.parametrize("m", submodules) def test_doc_string(m, doc): assert ( - doc(m.copy_tensor) - == 'copy_tensor() -> typing.Annotated[numpy.typing.NDArray[numpy.float64], "[?, ?, ?]"]' + doc(m.copy_tensor) == "copy_tensor() -> numpy.ndarray[numpy.float64[?, ?, ?]]" ) assert ( doc(m.copy_fixed_tensor) - == 'copy_fixed_tensor() -> typing.Annotated[numpy.typing.NDArray[numpy.float64], "[3, 5, 2]"]' + == "copy_fixed_tensor() -> numpy.ndarray[numpy.float64[3, 5, 2]]" ) assert ( doc(m.reference_const_tensor) - == 'reference_const_tensor() -> typing.Annotated[numpy.typing.NDArray[numpy.float64], "[?, ?, ?]"]' + == "reference_const_tensor() -> numpy.ndarray[numpy.float64[?, ?, ?]]" ) - order_flag = f'"flags.{m.needed_options.lower()}_contiguous"' + order_flag = f"flags.{m.needed_options.lower()}_contiguous" assert doc(m.round_trip_view_tensor) == ( - f'round_trip_view_tensor(arg0: typing.Annotated[numpy.typing.NDArray[numpy.float64], "[?, ?, ?]", "flags.writeable", {order_flag}])' - f' -> typing.Annotated[numpy.typing.NDArray[numpy.float64], "[?, ?, ?]", "flags.writeable", {order_flag}]' + f"round_trip_view_tensor(arg0: numpy.ndarray[numpy.float64[?, ?, ?], flags.writeable, {order_flag}])" + f" -> numpy.ndarray[numpy.float64[?, ?, ?], flags.writeable, {order_flag}]" ) assert doc(m.round_trip_const_view_tensor) == ( - f'round_trip_const_view_tensor(arg0: typing.Annotated[numpy.typing.NDArray[numpy.float64], "[?, ?, ?]", {order_flag}])' - ' -> typing.Annotated[numpy.typing.NDArray[numpy.float64], "[?, ?, ?]"]' + f"round_trip_const_view_tensor(arg0: numpy.ndarray[numpy.float64[?, ?, ?], {order_flag}])" + " -> numpy.ndarray[numpy.float64[?, ?, ?]]" ) - - -@pytest.mark.parametrize("m", submodules) -def test_arraylike_signature(m, doc): - order_flag = f'"flags.{m.needed_options.lower()}_contiguous"' - assert doc(m.round_trip_tensor) == ( - 'round_trip_tensor(arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float64, "[?, ?, ?]"])' - ' -> typing.Annotated[numpy.typing.NDArray[numpy.float64], "[?, ?, ?]"]' - ) - assert doc(m.round_trip_tensor_noconvert) == ( - 'round_trip_tensor_noconvert(tensor: typing.Annotated[numpy.typing.NDArray[numpy.float64], "[?, ?, ?]"])' - ' -> typing.Annotated[numpy.typing.NDArray[numpy.float64], "[?, ?, ?]"]' - ) - assert doc(m.round_trip_view_tensor) == ( - f'round_trip_view_tensor(arg0: typing.Annotated[numpy.typing.NDArray[numpy.float64], "[?, ?, ?]", "flags.writeable", {order_flag}])' - f' -> typing.Annotated[numpy.typing.NDArray[numpy.float64], "[?, ?, ?]", "flags.writeable", {order_flag}]' - ) - m.round_trip_tensor(tensor_ref.tolist()) - with pytest.raises(TypeError, match="incompatible function arguments"): - m.round_trip_tensor_noconvert(tensor_ref.tolist()) - with pytest.raises(TypeError, match="incompatible function arguments"): - m.round_trip_view_tensor(tensor_ref.tolist()) diff --git a/pybind11/tests/test_embed/CMakeLists.txt b/pybind11/tests/test_embed/CMakeLists.txt index f646458d1..9b539cd42 100644 --- a/pybind11/tests/test_embed/CMakeLists.txt +++ b/pybind11/tests/test_embed/CMakeLists.txt @@ -1,10 +1,8 @@ possibly_uninitialized(PYTHON_MODULE_EXTENSION Python_INTERPRETER_ID) -if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" - OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy" - OR "${PYTHON_MODULE_EXTENSION}" MATCHES "graalpy") - message(STATUS "Skipping embed test on PyPy or GraalPy") - add_custom_target(cpptest) # Dummy target on PyPy or GraalPy. Embedding is not supported. +if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy") + message(STATUS "Skipping embed test on PyPy") + add_custom_target(cpptest) # Dummy target on PyPy. Embedding is not supported. set(_suppress_unused_variable_warning "${DOWNLOAD_CATCH}") return() endif() diff --git a/pybind11/tests/test_enum.py b/pybind11/tests/test_enum.py index 044ef1803..9914b9001 100644 --- a/pybind11/tests/test_enum.py +++ b/pybind11/tests/test_enum.py @@ -1,15 +1,11 @@ # ruff: noqa: SIM201 SIM300 SIM202 from __future__ import annotations -import re - import pytest -import env # noqa: F401 from pybind11_tests import enums as m -@pytest.mark.xfail("env.GRAALPY", reason="TODO should get fixed on GraalPy side") def test_unscoped_enum(): assert str(m.UnscopedEnum.EOne) == "UnscopedEnum.EOne" assert str(m.UnscopedEnum.ETwo) == "UnscopedEnum.ETwo" @@ -197,7 +193,6 @@ def test_implicit_conversion(): assert repr(x) == "{: 3, : 4}" -@pytest.mark.xfail("env.GRAALPY", reason="TODO should get fixed on GraalPy side") def test_binary_operators(): assert int(m.Flags.Read) == 4 assert int(m.Flags.Write) == 2 @@ -273,61 +268,3 @@ def test_docstring_signatures(): def test_str_signature(): for enum_type in [m.ScopedEnum, m.UnscopedEnum]: assert enum_type.__str__.__doc__.startswith("__str__") - - -def test_generated_dunder_methods_pos_only(): - for enum_type in [m.ScopedEnum, m.UnscopedEnum]: - for binary_op in [ - "__eq__", - "__ne__", - "__ge__", - "__gt__", - "__lt__", - "__le__", - "__and__", - "__rand__", - # "__or__", # fail with some compilers (__doc__ = "Return self|value.") - # "__ror__", # fail with some compilers (__doc__ = "Return value|self.") - "__xor__", - "__rxor__", - "__rxor__", - ]: - method = getattr(enum_type, binary_op, None) - if method is not None: - assert ( - re.match( - rf"^{binary_op}\(self: [\w\.]+, other: [\w\.]+, /\)", - method.__doc__, - ) - is not None - ) - for unary_op in [ - "__int__", - "__index__", - "__hash__", - "__str__", - "__repr__", - ]: - method = getattr(enum_type, unary_op, None) - if method is not None: - assert ( - re.match( - rf"^{unary_op}\(self: [\w\.]+, /\)", - method.__doc__, - ) - is not None - ) - assert ( - re.match( - r"^__getstate__\(self: [\w\.]+, /\)", - enum_type.__getstate__.__doc__, - ) - is not None - ) - assert ( - re.match( - r"^__setstate__\(self: [\w\.]+, state: [\w\.]+, /\)", - enum_type.__setstate__.__doc__, - ) - is not None - ) diff --git a/pybind11/tests/test_eval.py b/pybind11/tests/test_eval.py index 8ac1907c7..45b68ece7 100644 --- a/pybind11/tests/test_eval.py +++ b/pybind11/tests/test_eval.py @@ -19,7 +19,7 @@ def test_evals(capture): assert m.test_eval_failure() -@pytest.mark.xfail("env.PYPY or env.GRAALPY", raises=RuntimeError) +@pytest.mark.xfail("env.PYPY", raises=RuntimeError) def test_eval_file(): filename = os.path.join(os.path.dirname(__file__), "test_eval_call.py") assert m.test_eval_file(filename) diff --git a/pybind11/tests/test_exceptions.cpp b/pybind11/tests/test_exceptions.cpp index 0a970065b..c1d05bb24 100644 --- a/pybind11/tests/test_exceptions.cpp +++ b/pybind11/tests/test_exceptions.cpp @@ -111,16 +111,6 @@ struct PythonAlreadySetInDestructor { py::str s; }; -struct CustomData { - explicit CustomData(const std::string &a) : a(a) {} - std::string a; -}; - -struct MyException7 { - explicit MyException7(const CustomData &message) : message(message) {} - CustomData message; -}; - TEST_SUBMODULE(exceptions, m) { m.def("throw_std_exception", []() { throw std::runtime_error("This exception was intentionally thrown."); }); @@ -395,33 +385,4 @@ TEST_SUBMODULE(exceptions, m) { // m.def("pass_exception_void", [](const py::exception&) {}); // Does not compile. m.def("return_exception_void", []() { return py::exception(); }); - - m.def("throws7", []() { - auto data = CustomData("abc"); - throw MyException7(data); - }); - - py::class_(m, "CustomData", py::module_local()) - .def(py::init()) - .def_readwrite("a", &CustomData::a); - - PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store - PythonMyException7_storage; - PythonMyException7_storage.call_once_and_store_result([&]() { - auto mod = py::module_::import("custom_exceptions"); - py::object obj = mod.attr("PythonMyException7"); - return obj; - }); - - py::register_local_exception_translator([](std::exception_ptr p) { - try { - if (p) { - std::rethrow_exception(p); - } - } catch (const MyException7 &e) { - auto exc_type = PythonMyException7_storage.get_stored(); - py::object exc_inst = exc_type(e.message); - PyErr_SetObject(PyExc_Exception, exc_inst.ptr()); - } - }); } diff --git a/pybind11/tests/test_exceptions.py b/pybind11/tests/test_exceptions.py index 47214a702..db2a551e4 100644 --- a/pybind11/tests/test_exceptions.py +++ b/pybind11/tests/test_exceptions.py @@ -3,7 +3,6 @@ from __future__ import annotations import sys import pytest -from custom_exceptions import PythonMyException7 import env import pybind11_cross_module_tests as cm @@ -76,7 +75,7 @@ def test_cross_module_exceptions(msg): # TODO: FIXME @pytest.mark.xfail( - "env.MACOS and env.PYPY", + "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", ) @@ -104,24 +103,28 @@ def ignore_pytest_unraisable_warning(f): @pytest.mark.xfail(env.PYPY, reason="Failure on PyPy 3.8 (7.3.7)", strict=False) @ignore_pytest_unraisable_warning def test_python_alreadyset_in_destructor(monkeypatch, capsys): + hooked = False triggered = False - # Don't take `sys.unraisablehook`, as that's overwritten by pytest - default_hook = sys.__unraisablehook__ + if hasattr(sys, "unraisablehook"): # Python 3.8+ + hooked = True + # Don't take `sys.unraisablehook`, as that's overwritten by pytest + default_hook = sys.__unraisablehook__ - def hook(unraisable_hook_args): - exc_type, exc_value, exc_tb, err_msg, obj = unraisable_hook_args - if obj == "already_set demo": - nonlocal triggered - triggered = True - default_hook(unraisable_hook_args) - return + def hook(unraisable_hook_args): + exc_type, exc_value, exc_tb, err_msg, obj = unraisable_hook_args + if obj == "already_set demo": + nonlocal triggered + triggered = True + default_hook(unraisable_hook_args) + return - # Use monkeypatch so pytest can apply and remove the patch as appropriate - monkeypatch.setattr(sys, "unraisablehook", hook) + # Use monkeypatch so pytest can apply and remove the patch as appropriate + monkeypatch.setattr(sys, "unraisablehook", hook) assert m.python_alreadyset_in_destructor("already_set demo") is True - assert triggered is True + if hooked: + assert triggered is True _, captured_stderr = capsys.readouterr() assert captured_stderr.startswith("Exception ignored in: 'already_set demo'") @@ -196,12 +199,7 @@ def test_custom(msg): raise RuntimeError("Exception error: caught child from parent") from err assert msg(excinfo.value) == "this is a helper-defined translated exception" - with pytest.raises(PythonMyException7) as excinfo: - m.throws7() - assert msg(excinfo.value) == "[PythonMyException7]: abc" - -@pytest.mark.xfail("env.GRAALPY", reason="TODO should get fixed on GraalPy side") def test_nested_throws(capture): """Tests nested (e.g. C++ -> Python -> C++) exception handling""" @@ -370,7 +368,6 @@ def _test_flaky_exception_failure_point_init_py_3_12(): "env.PYPY and sys.version_info[:2] < (3, 12)", reason="PyErr_NormalizeException Segmentation fault", ) -@pytest.mark.xfail("env.GRAALPY", reason="TODO should be fixed on GraalPy side") def test_flaky_exception_failure_point_init(): if sys.version_info[:2] < (3, 12): _test_flaky_exception_failure_point_init_before_py_3_12() @@ -378,7 +375,6 @@ def test_flaky_exception_failure_point_init(): _test_flaky_exception_failure_point_init_py_3_12() -@pytest.mark.xfail("env.GRAALPY", reason="TODO should be fixed on GraalPy side") def test_flaky_exception_failure_point_str(): what, py_err_set_after_what = m.error_already_set_what( FlakyException, ("failure_point_str",) diff --git a/pybind11/tests/test_factory_constructors.py b/pybind11/tests/test_factory_constructors.py index 1d3a9bcdd..0ddad5e32 100644 --- a/pybind11/tests/test_factory_constructors.py +++ b/pybind11/tests/test_factory_constructors.py @@ -4,13 +4,11 @@ import re import pytest -import env # noqa: F401 from pybind11_tests import ConstructorStats from pybind11_tests import factory_constructors as m from pybind11_tests.factory_constructors import tag -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_init_factory_basic(): """Tests py::init_factory() wrapper around various ways of returning the object""" @@ -104,7 +102,6 @@ def test_init_factory_signature(msg): ) -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_init_factory_casting(): """Tests py::init_factory() wrapper with various upcasting and downcasting returns""" @@ -153,7 +150,6 @@ def test_init_factory_casting(): ] -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_init_factory_alias(): """Tests py::init_factory() wrapper with value conversions and alias types""" @@ -224,7 +220,6 @@ def test_init_factory_alias(): ] -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_init_factory_dual(): """Tests init factory functions with dual main/alias factory functions""" from pybind11_tests.factory_constructors import TestFactory7 @@ -307,7 +302,6 @@ def test_init_factory_dual(): ] -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_no_placement_new(capture): """Prior to 2.2, `py::init<...>` relied on the type supporting placement new; this tests a class without placement new support.""" @@ -356,7 +350,6 @@ def strip_comments(s): return re.sub(r"\s+#.*", "", s) -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_reallocation_a(capture, msg): """When the constructor is overloaded, previous overloads can require a preallocated value. This test makes sure that such preallocated values only happen when they might be necessary, @@ -379,7 +372,6 @@ def test_reallocation_a(capture, msg): ) -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_reallocation_b(capture, msg): with capture: create_and_destroy(1.5) @@ -396,7 +388,6 @@ def test_reallocation_b(capture, msg): ) -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_reallocation_c(capture, msg): with capture: create_and_destroy(2, 3) @@ -411,7 +402,6 @@ def test_reallocation_c(capture, msg): ) -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_reallocation_d(capture, msg): with capture: create_and_destroy(2.5, 3) @@ -427,7 +417,6 @@ def test_reallocation_d(capture, msg): ) -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_reallocation_e(capture, msg): with capture: create_and_destroy(3.5, 4.5) @@ -443,7 +432,6 @@ def test_reallocation_e(capture, msg): ) -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_reallocation_f(capture, msg): with capture: create_and_destroy(4, 0.5) @@ -460,7 +448,6 @@ def test_reallocation_f(capture, msg): ) -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_reallocation_g(capture, msg): with capture: create_and_destroy(5, "hi") diff --git a/pybind11/tests/test_gil_scoped.py b/pybind11/tests/test_gil_scoped.py index 2db771a50..eab92093c 100644 --- a/pybind11/tests/test_gil_scoped.py +++ b/pybind11/tests/test_gil_scoped.py @@ -108,10 +108,6 @@ def test_nested_acquire(): @pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") -@pytest.mark.skipif( - env.GRAALPY and sys.platform == "darwin", - reason="Transiently crashes on GraalPy on OS X", -) def test_multi_acquire_release_cross_module(): for bits in range(16 * 8): internals_ids = m.test_multi_acquire_release_cross_module(bits) @@ -215,10 +211,6 @@ def _run_in_threads(test_fn, num_threads, parallel): @pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) -@pytest.mark.skipif( - "env.GRAALPY", - reason="GraalPy transiently complains about unfinished threads at process exit", -) def test_run_in_process_one_thread(test_fn): """Makes sure there is no GIL deadlock when running in a thread. @@ -229,10 +221,6 @@ def test_run_in_process_one_thread(test_fn): @pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) -@pytest.mark.skipif( - "env.GRAALPY", - reason="GraalPy transiently complains about unfinished threads at process exit", -) 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. @@ -243,10 +231,6 @@ def test_run_in_process_multiple_threads_parallel(test_fn): @pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) -@pytest.mark.skipif( - "env.GRAALPY", - reason="GraalPy transiently complains about unfinished threads at process exit", -) 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. @@ -257,10 +241,6 @@ def test_run_in_process_multiple_threads_sequential(test_fn): @pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) -@pytest.mark.skipif( - "env.GRAALPY", - reason="GraalPy transiently complains about unfinished threads at process exit", -) def test_run_in_process_direct(test_fn): """Makes sure there is no GIL deadlock when using processes. diff --git a/pybind11/tests/test_kwargs_and_defaults.cpp b/pybind11/tests/test_kwargs_and_defaults.cpp index 831947f16..bc76ec7c2 100644 --- a/pybind11/tests/test_kwargs_and_defaults.cpp +++ b/pybind11/tests/test_kwargs_and_defaults.cpp @@ -322,10 +322,4 @@ TEST_SUBMODULE(kwargs_and_defaults, m) { py::pos_only{}, py::arg("i"), py::arg("j")); - - // Test support for args and kwargs subclasses - m.def("args_kwargs_subclass_function", - [](const py::Args &args, const py::KWArgs &kwargs) { - return py::make_tuple(args, kwargs); - }); } diff --git a/pybind11/tests/test_kwargs_and_defaults.py b/pybind11/tests/test_kwargs_and_defaults.py index e558d8ad2..b9b1a7ea8 100644 --- a/pybind11/tests/test_kwargs_and_defaults.py +++ b/pybind11/tests/test_kwargs_and_defaults.py @@ -2,7 +2,6 @@ from __future__ import annotations import pytest -import env # noqa: F401 from pybind11_tests import kwargs_and_defaults as m @@ -18,10 +17,6 @@ def test_function_signatures(doc): assert ( doc(m.args_kwargs_function) == "args_kwargs_function(*args, **kwargs) -> tuple" ) - assert ( - doc(m.args_kwargs_subclass_function) - == "args_kwargs_subclass_function(*args: str, **kwargs: str) -> tuple" - ) assert ( doc(m.KWClass.foo0) == "foo0(self: m.kwargs_and_defaults.KWClass, arg0: int, arg1: float) -> None" @@ -103,7 +98,6 @@ def test_arg_and_kwargs(): args = "a1", "a2" kwargs = {"arg3": "a3", "arg4": 4} assert m.args_kwargs_function(*args, **kwargs) == (args, kwargs) - assert m.args_kwargs_subclass_function(*args, **kwargs) == (args, kwargs) def test_mixed_args_and_kwargs(msg): @@ -384,7 +378,6 @@ def test_signatures(): ) -@pytest.mark.skipif("env.GRAALPY", reason="Different refcounting mechanism") def test_args_refcount(): """Issue/PR #1216 - py::args elements get double-inc_ref()ed when combined with regular arguments""" @@ -420,12 +413,6 @@ def test_args_refcount(): ) assert refcount(myval) == expected - assert m.args_kwargs_subclass_function(7, 8, myval, a=1, b=myval) == ( - (7, 8, myval), - {"a": 1, "b": myval}, - ) - assert refcount(myval) == expected - exp3 = refcount(myval, myval, myval) assert m.args_refcount(myval, myval, myval) == (exp3, exp3, exp3) assert refcount(myval) == expected diff --git a/pybind11/tests/test_methods_and_attributes.cpp b/pybind11/tests/test_methods_and_attributes.cpp index e324c8bdd..f433847c7 100644 --- a/pybind11/tests/test_methods_and_attributes.cpp +++ b/pybind11/tests/test_methods_and_attributes.cpp @@ -294,7 +294,7 @@ TEST_SUBMODULE(methods_and_attributes, m) { static_cast( &ExampleMandA::overloaded)); }) - .def("__str__", &ExampleMandA::toString, py::pos_only()) + .def("__str__", &ExampleMandA::toString) .def_readwrite("value", &ExampleMandA::value); // test_copy_method diff --git a/pybind11/tests/test_methods_and_attributes.py b/pybind11/tests/test_methods_and_attributes.py index cecc18464..dfa31f546 100644 --- a/pybind11/tests/test_methods_and_attributes.py +++ b/pybind11/tests/test_methods_and_attributes.py @@ -4,7 +4,7 @@ import sys import pytest -import env +import env # noqa: F401 from pybind11_tests import ConstructorStats from pybind11_tests import methods_and_attributes as m @@ -19,13 +19,6 @@ NO_DELETER_MSG = ( ) -def test_self_only_pos_only(): - assert ( - m.ExampleMandA.__str__.__doc__ - == "__str__(self: pybind11_tests.methods_and_attributes.ExampleMandA, /) -> str\n" - ) - - def test_methods_and_attributes(): instance1 = m.ExampleMandA() instance2 = m.ExampleMandA(32) @@ -75,9 +68,6 @@ def test_methods_and_attributes(): instance1.value = 100 assert str(instance1) == "ExampleMandA[value=100]" - if env.GRAALPY: - pytest.skip("ConstructorStats is incompatible with GraalPy.") - cstats = ConstructorStats.get(m.ExampleMandA) assert cstats.alive() == 2 del instance1, instance2 @@ -326,8 +316,6 @@ def test_dynamic_attributes(): instance.__dict__ = [] assert str(excinfo.value) == "__dict__ must be set to a dictionary, not a 'list'" - if env.GRAALPY: - pytest.skip("ConstructorStats is incompatible with GraalPy.") cstats = ConstructorStats.get(m.DynamicClass) assert cstats.alive() == 1 del instance @@ -349,7 +337,6 @@ def test_dynamic_attributes(): # https://foss.heptapod.net/pypy/pypy/-/issues/2447 @pytest.mark.xfail("env.PYPY") -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_cyclic_gc(): # One object references itself instance = m.DynamicClass() diff --git a/pybind11/tests/test_modules.py b/pybind11/tests/test_modules.py index ad898be89..95835e14e 100644 --- a/pybind11/tests/test_modules.py +++ b/pybind11/tests/test_modules.py @@ -39,9 +39,6 @@ def test_reference_internal(): assert str(b.get_a2()) == "A[43]" assert str(b.a2) == "A[43]" - if env.GRAALPY: - pytest.skip("ConstructorStats is incompatible with GraalPy.") - astats, bstats = ConstructorStats.get(ms.A), ConstructorStats.get(ms.B) assert astats.alive() == 2 assert bstats.alive() == 1 @@ -81,13 +78,6 @@ def test_pydoc(): assert pydoc.text.docmodule(pybind11_tests) -def test_module_handle_type_name(): - assert ( - m.def_submodule.__doc__ - == "def_submodule(arg0: types.ModuleType, arg1: str) -> types.ModuleType\n" - ) - - def test_duplicate_registration(): """Registering two things with the same name""" @@ -107,9 +97,9 @@ def test_def_submodule_failures(): sm = m.def_submodule(m, b"ScratchSubModuleName") # Using bytes to show it works. assert sm.__name__ == m.__name__ + "." + "ScratchSubModuleName" malformed_utf8 = b"\x80" - if env.PYPY or env.GRAALPY: + if env.PYPY: # It is not worth the effort finding a trigger for a failure when running with PyPy. - pytest.skip("Sufficiently exercised on platforms other than PyPy/GraalPy.") + pytest.skip("Sufficiently exercised on platforms other than PyPy.") else: # Meant to trigger PyModule_GetName() failure: sm_name_orig = sm.__name__ diff --git a/pybind11/tests/test_multiple_inheritance.py b/pybind11/tests/test_multiple_inheritance.py index 6f5a656f5..d445824b5 100644 --- a/pybind11/tests/test_multiple_inheritance.py +++ b/pybind11/tests/test_multiple_inheritance.py @@ -2,7 +2,7 @@ from __future__ import annotations import pytest -import env +import env # noqa: F401 from pybind11_tests import ConstructorStats from pybind11_tests import multiple_inheritance as m @@ -279,9 +279,8 @@ def test_mi_unaligned_base(): c = m.I801C() d = m.I801D() - if not env.GRAALPY: - # + 4 below because we have the two instances, and each instance has offset base I801B2 - assert ConstructorStats.detail_reg_inst() == n_inst + 4 + # + 4 below because we have the two instances, and each instance has offset base I801B2 + assert ConstructorStats.detail_reg_inst() == n_inst + 4 b1c = m.i801b1_c(c) assert b1c is c b2c = m.i801b2_c(c) @@ -291,9 +290,6 @@ def test_mi_unaligned_base(): b2d = m.i801b2_d(d) assert b2d is d - if env.GRAALPY: - pytest.skip("ConstructorStats is incompatible with GraalPy.") - assert ConstructorStats.detail_reg_inst() == n_inst + 4 # no extra instances del c, b1c, b2c assert ConstructorStats.detail_reg_inst() == n_inst + 2 @@ -316,8 +312,7 @@ def test_mi_base_return(): assert d1.a == 1 assert d1.b == 2 - if not env.GRAALPY: - assert ConstructorStats.detail_reg_inst() == n_inst + 4 + assert ConstructorStats.detail_reg_inst() == n_inst + 4 c2 = m.i801c_b2() assert type(c2) is m.I801C @@ -329,13 +324,12 @@ def test_mi_base_return(): assert d2.a == 1 assert d2.b == 2 - if not env.GRAALPY: - assert ConstructorStats.detail_reg_inst() == n_inst + 8 + assert ConstructorStats.detail_reg_inst() == n_inst + 8 - del c2 - assert ConstructorStats.detail_reg_inst() == n_inst + 6 - del c1, d1, d2 - assert ConstructorStats.detail_reg_inst() == n_inst + del c2 + assert ConstructorStats.detail_reg_inst() == n_inst + 6 + del c1, d1, d2 + assert ConstructorStats.detail_reg_inst() == n_inst # Returning an unregistered derived type with a registered base; we won't # pick up the derived type, obviously, but should still work (as an object diff --git a/pybind11/tests/test_numpy_array.cpp b/pybind11/tests/test_numpy_array.cpp index 1bfca33bb..c2f754208 100644 --- a/pybind11/tests/test_numpy_array.cpp +++ b/pybind11/tests/test_numpy_array.cpp @@ -156,55 +156,6 @@ py::handle auxiliaries(T &&r, T2 &&r2) { return l.release(); } -template -PyObjectType convert_to_pyobjecttype(py::object obj); - -template <> -PyObject *convert_to_pyobjecttype(py::object obj) { - return obj.release().ptr(); -} - -template <> -py::handle convert_to_pyobjecttype(py::object obj) { - return obj.release(); -} - -template <> -py::object convert_to_pyobjecttype(py::object obj) { - return obj; -} - -template -std::string pass_array_return_sum_str_values(const py::array_t &objs) { - std::string sum_str_values; - for (const auto &obj : objs) { - sum_str_values += py::str(obj.attr("value")); - } - return sum_str_values; -} - -template -py::list pass_array_return_as_list(const py::array_t &objs) { - return objs; -} - -template -py::array_t return_array_cpp_loop(const py::list &objs) { - py::size_t arr_size = py::len(objs); - py::array_t arr_from_list(static_cast(arr_size)); - PyObjectType *data = arr_from_list.mutable_data(); - for (py::size_t i = 0; i < arr_size; i++) { - assert(!data[i]); - data[i] = convert_to_pyobjecttype(objs[i].attr("value")); - } - return arr_from_list; -} - -template -py::array_t return_array_from_list(const py::list &objs) { - return objs; -} - // note: declaration at local scope would create a dangling reference! static int data_i = 42; @@ -569,30 +520,28 @@ TEST_SUBMODULE(numpy_array, sm) { sm.def("round_trip_float", [](double d) { return d; }); sm.def("pass_array_pyobject_ptr_return_sum_str_values", - pass_array_return_sum_str_values); - sm.def("pass_array_handle_return_sum_str_values", - pass_array_return_sum_str_values); - sm.def("pass_array_object_return_sum_str_values", - pass_array_return_sum_str_values); + [](const py::array_t &objs) { + std::string sum_str_values; + for (const auto &obj : objs) { + sum_str_values += py::str(obj.attr("value")); + } + return sum_str_values; + }); - sm.def("pass_array_pyobject_ptr_return_as_list", pass_array_return_as_list); - sm.def("pass_array_handle_return_as_list", pass_array_return_as_list); - sm.def("pass_array_object_return_as_list", pass_array_return_as_list); + sm.def("pass_array_pyobject_ptr_return_as_list", + [](const py::array_t &objs) -> py::list { return objs; }); - sm.def("return_array_pyobject_ptr_cpp_loop", return_array_cpp_loop); - sm.def("return_array_handle_cpp_loop", return_array_cpp_loop); - sm.def("return_array_object_cpp_loop", return_array_cpp_loop); + sm.def("return_array_pyobject_ptr_cpp_loop", [](const py::list &objs) { + py::size_t arr_size = py::len(objs); + py::array_t arr_from_list(static_cast(arr_size)); + PyObject **data = arr_from_list.mutable_data(); + for (py::size_t i = 0; i < arr_size; i++) { + assert(data[i] == nullptr); + data[i] = py::cast(objs[i].attr("value")); + } + return arr_from_list; + }); - sm.def("return_array_pyobject_ptr_from_list", return_array_from_list); - sm.def("return_array_handle_from_list", return_array_from_list); - sm.def("return_array_object_from_list", return_array_from_list); - - sm.def( - "round_trip_array_t", - [](const py::array_t &x) -> py::array_t { return x; }, - py::arg("x")); - sm.def( - "round_trip_array_t_noconvert", - [](const py::array_t &x) -> py::array_t { return x; }, - py::arg("x").noconvert()); + sm.def("return_array_pyobject_ptr_from_list", + [](const py::list &objs) -> py::array_t { return objs; }); } diff --git a/pybind11/tests/test_numpy_array.py b/pybind11/tests/test_numpy_array.py index 3a3f22a64..bc7b3d555 100644 --- a/pybind11/tests/test_numpy_array.py +++ b/pybind11/tests/test_numpy_array.py @@ -242,7 +242,6 @@ def test_wrap(): assert_references(a1m, a2, a1) -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_numpy_view(capture): with capture: ac = m.ArrayClass() @@ -321,13 +320,13 @@ def test_overload_resolution(msg): msg(excinfo.value) == """ overloaded(): incompatible function arguments. The following argument types are supported: - 1. (arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float64]) -> str - 2. (arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float32]) -> str - 3. (arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.int32]) -> str - 4. (arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.uint16]) -> str - 5. (arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.int64]) -> str - 6. (arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.complex128]) -> str - 7. (arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.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' """ @@ -343,8 +342,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.typing.NDArray[numpy.int32]) -> str - 2. (arg0: numpy.typing.NDArray[numpy.float64]) -> str + 1. (arg0: numpy.ndarray[numpy.int32]) -> str + 2. (arg0: numpy.ndarray[numpy.float64]) -> str Invoked with: """ @@ -466,7 +465,7 @@ def test_array_resize(): assert b.shape == (8, 8) -@pytest.mark.xfail("env.PYPY or env.GRAALPY") +@pytest.mark.xfail("env.PYPY") def test_array_create_and_resize(): a = m.create_and_resize(2) assert a.size == 4 @@ -528,7 +527,7 @@ def test_index_using_ellipsis(): ], ) def test_format_descriptors_for_floating_point_types(test_func): - assert "numpy.typing.ArrayLike, numpy.float" in test_func.__doc__ + assert "numpy.ndarray[numpy.float" in test_func.__doc__ @pytest.mark.parametrize("forcecast", [False, True]) @@ -629,75 +628,45 @@ def UnwrapPyValueHolder(vhs): return [vh.value for vh in vhs] -PASS_ARRAY_PYOBJECT_RETURN_SUM_STR_VALUES_FUNCTIONS = [ - m.pass_array_pyobject_ptr_return_sum_str_values, - m.pass_array_handle_return_sum_str_values, - m.pass_array_object_return_sum_str_values, -] - - -@pytest.mark.parametrize( - "pass_array", PASS_ARRAY_PYOBJECT_RETURN_SUM_STR_VALUES_FUNCTIONS -) -def test_pass_array_object_return_sum_str_values_ndarray(pass_array): +def test_pass_array_pyobject_ptr_return_sum_str_values_ndarray(): # Intentionally all temporaries, do not change. assert ( - pass_array(np.array(WrapWithPyValueHolder(-3, "four", 5.0), dtype=object)) + m.pass_array_pyobject_ptr_return_sum_str_values( + np.array(WrapWithPyValueHolder(-3, "four", 5.0), dtype=object) + ) == "-3four5.0" ) -@pytest.mark.parametrize( - "pass_array", PASS_ARRAY_PYOBJECT_RETURN_SUM_STR_VALUES_FUNCTIONS -) -def test_pass_array_object_return_sum_str_values_list(pass_array): +def test_pass_array_pyobject_ptr_return_sum_str_values_list(): # Intentionally all temporaries, do not change. - assert pass_array(WrapWithPyValueHolder(2, "three", -4.0)) == "2three-4.0" + assert ( + m.pass_array_pyobject_ptr_return_sum_str_values( + WrapWithPyValueHolder(2, "three", -4.0) + ) + == "2three-4.0" + ) -@pytest.mark.parametrize( - "pass_array", - [ - m.pass_array_pyobject_ptr_return_as_list, - m.pass_array_handle_return_as_list, - m.pass_array_object_return_as_list, - ], -) -def test_pass_array_object_return_as_list(pass_array): +def test_pass_array_pyobject_ptr_return_as_list(): # Intentionally all temporaries, do not change. assert UnwrapPyValueHolder( - pass_array(np.array(WrapWithPyValueHolder(-1, "two", 3.0), dtype=object)) + m.pass_array_pyobject_ptr_return_as_list( + np.array(WrapWithPyValueHolder(-1, "two", 3.0), dtype=object) + ) ) == [-1, "two", 3.0] @pytest.mark.parametrize( - ("return_array", "unwrap"), + ("return_array_pyobject_ptr", "unwrap"), [ (m.return_array_pyobject_ptr_cpp_loop, list), - (m.return_array_handle_cpp_loop, list), - (m.return_array_object_cpp_loop, list), (m.return_array_pyobject_ptr_from_list, UnwrapPyValueHolder), - (m.return_array_handle_from_list, UnwrapPyValueHolder), - (m.return_array_object_from_list, UnwrapPyValueHolder), ], ) -def test_return_array_object_cpp_loop(return_array, unwrap): +def test_return_array_pyobject_ptr_cpp_loop(return_array_pyobject_ptr, unwrap): # Intentionally all temporaries, do not change. - arr_from_list = return_array(WrapWithPyValueHolder(6, "seven", -8.0)) + arr_from_list = return_array_pyobject_ptr(WrapWithPyValueHolder(6, "seven", -8.0)) assert isinstance(arr_from_list, np.ndarray) assert arr_from_list.dtype == np.dtype("O") assert unwrap(arr_from_list) == [6, "seven", -8.0] - - -def test_arraylike_signature(doc): - assert ( - doc(m.round_trip_array_t) - == "round_trip_array_t(x: typing.Annotated[numpy.typing.ArrayLike, numpy.float32]) -> numpy.typing.NDArray[numpy.float32]" - ) - assert ( - doc(m.round_trip_array_t_noconvert) - == "round_trip_array_t_noconvert(x: numpy.typing.NDArray[numpy.float32]) -> numpy.typing.NDArray[numpy.float32]" - ) - m.round_trip_array_t([1, 2, 3]) - with pytest.raises(TypeError, match="incompatible function arguments"): - m.round_trip_array_t_noconvert([1, 2, 3]) diff --git a/pybind11/tests/test_numpy_dtypes.cpp b/pybind11/tests/test_numpy_dtypes.cpp index 04bf19f3e..596d90274 100644 --- a/pybind11/tests/test_numpy_dtypes.cpp +++ b/pybind11/tests/test_numpy_dtypes.cpp @@ -11,9 +11,6 @@ #include "pybind11_tests.h" -#include -#include - #ifdef __GNUC__ # define PYBIND11_PACKED(cls) cls __attribute__((__packed__)) #else @@ -300,15 +297,6 @@ py::list test_dtype_ctors() { return list; } -template -py::array_t dispatch_array_increment(py::array_t arr) { - py::array_t res(arr.shape(0)); - for (py::ssize_t i = 0; i < arr.shape(0); ++i) { - res.mutable_at(i) = T(arr.at(i) + 1); - } - return res; -} - struct A {}; struct B {}; @@ -508,98 +496,6 @@ TEST_SUBMODULE(numpy_dtypes, m) { } return list; }); - m.def("test_dtype_num_of", []() -> py::list { - py::list res; -#define TEST_DTYPE(T) res.append(py::make_tuple(py::dtype::of().num(), py::dtype::num_of())); - TEST_DTYPE(bool) - TEST_DTYPE(signed char) - TEST_DTYPE(unsigned char) - TEST_DTYPE(short) - TEST_DTYPE(unsigned short) - TEST_DTYPE(int) - TEST_DTYPE(unsigned int) - TEST_DTYPE(long) - TEST_DTYPE(unsigned long) - TEST_DTYPE(long long) - TEST_DTYPE(unsigned long long) - TEST_DTYPE(float) - TEST_DTYPE(double) - TEST_DTYPE(long double) - TEST_DTYPE(std::complex) - TEST_DTYPE(std::complex) - TEST_DTYPE(std::complex) - TEST_DTYPE(int8_t) - TEST_DTYPE(uint8_t) - TEST_DTYPE(int16_t) - TEST_DTYPE(uint16_t) - TEST_DTYPE(int32_t) - TEST_DTYPE(uint32_t) - TEST_DTYPE(int64_t) - TEST_DTYPE(uint64_t) -#undef TEST_DTYPE - return res; - }); - m.def("test_dtype_normalized_num", []() -> py::list { - py::list res; -#define TEST_DTYPE(NT, T) \ - res.append(py::make_tuple(py::dtype(py::detail::npy_api::NT).normalized_num(), \ - py::dtype::num_of())); - TEST_DTYPE(NPY_BOOL_, bool) - TEST_DTYPE(NPY_BYTE_, signed char); - TEST_DTYPE(NPY_UBYTE_, unsigned char); - TEST_DTYPE(NPY_SHORT_, short); - TEST_DTYPE(NPY_USHORT_, unsigned short); - TEST_DTYPE(NPY_INT_, int); - TEST_DTYPE(NPY_UINT_, unsigned int); - TEST_DTYPE(NPY_LONG_, long); - TEST_DTYPE(NPY_ULONG_, unsigned long); - TEST_DTYPE(NPY_LONGLONG_, long long); - TEST_DTYPE(NPY_ULONGLONG_, unsigned long long); - TEST_DTYPE(NPY_FLOAT_, float); - TEST_DTYPE(NPY_DOUBLE_, double); - TEST_DTYPE(NPY_LONGDOUBLE_, long double); - TEST_DTYPE(NPY_CFLOAT_, std::complex); - TEST_DTYPE(NPY_CDOUBLE_, std::complex); - TEST_DTYPE(NPY_CLONGDOUBLE_, std::complex); - TEST_DTYPE(NPY_INT8_, int8_t); - TEST_DTYPE(NPY_UINT8_, uint8_t); - TEST_DTYPE(NPY_INT16_, int16_t); - TEST_DTYPE(NPY_UINT16_, uint16_t); - TEST_DTYPE(NPY_INT32_, int32_t); - TEST_DTYPE(NPY_UINT32_, uint32_t); - TEST_DTYPE(NPY_INT64_, int64_t); - TEST_DTYPE(NPY_UINT64_, uint64_t); -#undef TEST_DTYPE - return res; - }); - m.def("test_dtype_switch", [](const py::array &arr) -> py::array { - switch (arr.dtype().normalized_num()) { - case py::dtype::num_of(): - return dispatch_array_increment(arr); - case py::dtype::num_of(): - return dispatch_array_increment(arr); - case py::dtype::num_of(): - return dispatch_array_increment(arr); - case py::dtype::num_of(): - return dispatch_array_increment(arr); - case py::dtype::num_of(): - return dispatch_array_increment(arr); - case py::dtype::num_of(): - return dispatch_array_increment(arr); - case py::dtype::num_of(): - return dispatch_array_increment(arr); - case py::dtype::num_of(): - return dispatch_array_increment(arr); - case py::dtype::num_of(): - return dispatch_array_increment(arr); - case py::dtype::num_of(): - return dispatch_array_increment(arr); - case py::dtype::num_of(): - return dispatch_array_increment(arr); - default: - throw std::runtime_error("Unsupported dtype"); - } - }); m.def("test_dtype_methods", []() { py::list list; auto dt1 = py::dtype::of(); diff --git a/pybind11/tests/test_numpy_dtypes.py b/pybind11/tests/test_numpy_dtypes.py index 685a76fd3..8ae239ed8 100644 --- a/pybind11/tests/test_numpy_dtypes.py +++ b/pybind11/tests/test_numpy_dtypes.py @@ -188,28 +188,6 @@ def test_dtype(simple_dtype): chr(np.dtype(ch).flags) for ch in expected_chars ] - for a, b in m.test_dtype_num_of(): - assert a == b - - for a, b in m.test_dtype_normalized_num(): - assert a == b - - arr = np.array([4, 84, 21, 36]) - # Note: "ulong" does not work in NumPy 1.x, so we use "L" - assert (m.test_dtype_switch(arr.astype("byte")) == arr + 1).all() - assert (m.test_dtype_switch(arr.astype("ubyte")) == arr + 1).all() - assert (m.test_dtype_switch(arr.astype("short")) == arr + 1).all() - assert (m.test_dtype_switch(arr.astype("ushort")) == arr + 1).all() - assert (m.test_dtype_switch(arr.astype("intc")) == arr + 1).all() - assert (m.test_dtype_switch(arr.astype("uintc")) == arr + 1).all() - assert (m.test_dtype_switch(arr.astype("long")) == arr + 1).all() - assert (m.test_dtype_switch(arr.astype("L")) == arr + 1).all() - assert (m.test_dtype_switch(arr.astype("longlong")) == arr + 1).all() - assert (m.test_dtype_switch(arr.astype("ulonglong")) == arr + 1).all() - assert (m.test_dtype_switch(arr.astype("single")) == arr + 1).all() - assert (m.test_dtype_switch(arr.astype("double")) == arr + 1).all() - assert (m.test_dtype_switch(arr.astype("longdouble")) == arr + 1).all() - def test_recarray(simple_dtype, packed_dtype): elements = [(False, 0, 0.0, -0.0), (True, 1, 1.5, -2.5), (False, 2, 3.0, -5.0)] @@ -373,7 +351,7 @@ def test_complex_array(): def test_signature(doc): assert ( doc(m.create_rec_nested) - == "create_rec_nested(arg0: int) -> numpy.typing.NDArray[NestedStruct]" + == "create_rec_nested(arg0: int) -> numpy.ndarray[NestedStruct]" ) diff --git a/pybind11/tests/test_numpy_vectorize.py b/pybind11/tests/test_numpy_vectorize.py index 0768759d1..ce38d72d9 100644 --- a/pybind11/tests/test_numpy_vectorize.py +++ b/pybind11/tests/test_numpy_vectorize.py @@ -150,7 +150,7 @@ def test_docs(doc): assert ( doc(m.vectorized_func) == """ - vectorized_func(arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.int32], arg1: typing.Annotated[numpy.typing.ArrayLike, numpy.float32], arg2: typing.Annotated[numpy.typing.ArrayLike, numpy.float64]) -> object + vectorized_func(arg0: numpy.ndarray[numpy.int32], arg1: numpy.ndarray[numpy.float32], arg2: numpy.ndarray[numpy.float64]) -> object """ ) @@ -212,12 +212,12 @@ def test_passthrough_arguments(doc): + ", ".join( [ "arg0: float", - "arg1: typing.Annotated[numpy.typing.ArrayLike, numpy.float64]", - "arg2: typing.Annotated[numpy.typing.ArrayLike, numpy.float64]", - "arg3: typing.Annotated[numpy.typing.ArrayLike, numpy.int32]", + "arg1: numpy.ndarray[numpy.float64]", + "arg2: numpy.ndarray[numpy.float64]", + "arg3: numpy.ndarray[numpy.int32]", "arg4: int", "arg5: m.numpy_vectorize.NonPODClass", - "arg6: typing.Annotated[numpy.typing.ArrayLike, numpy.float64]", + "arg6: numpy.ndarray[numpy.float64]", ] ) + ") -> object" diff --git a/pybind11/tests/test_opaque_types.py b/pybind11/tests/test_opaque_types.py index 56c9b5db1..342086436 100644 --- a/pybind11/tests/test_opaque_types.py +++ b/pybind11/tests/test_opaque_types.py @@ -2,7 +2,6 @@ from __future__ import annotations import pytest -import env from pybind11_tests import ConstructorStats, UserType from pybind11_tests import opaque_types as m @@ -31,9 +30,7 @@ def test_pointers(msg): living_before = ConstructorStats.get(UserType).alive() assert m.get_void_ptr_value(m.return_void_ptr()) == 0x1234 assert m.get_void_ptr_value(UserType()) # Should also work for other C++ types - - if not env.GRAALPY: - assert ConstructorStats.get(UserType).alive() == living_before + assert ConstructorStats.get(UserType).alive() == living_before with pytest.raises(TypeError) as excinfo: m.get_void_ptr_value([1, 2, 3]) # This should not work diff --git a/pybind11/tests/test_operator_overloading.py b/pybind11/tests/test_operator_overloading.py index 47949042d..b6760902d 100644 --- a/pybind11/tests/test_operator_overloading.py +++ b/pybind11/tests/test_operator_overloading.py @@ -2,12 +2,10 @@ from __future__ import annotations import pytest -import env from pybind11_tests import ConstructorStats from pybind11_tests import operators as m -@pytest.mark.xfail("env.GRAALPY", reason="TODO should get fixed on GraalPy side") def test_operator_overloading(): v1 = m.Vector2(1, 2) v2 = m.Vector(3, -1) @@ -51,9 +49,6 @@ def test_operator_overloading(): v2 /= v1 assert str(v2) == "[2.000000, 8.000000]" - if env.GRAALPY: - pytest.skip("ConstructorStats is incompatible with GraalPy.") - cstats = ConstructorStats.get(m.Vector2) assert cstats.alive() == 3 del v1 @@ -88,7 +83,6 @@ def test_operator_overloading(): assert cstats.move_assignments == 0 -@pytest.mark.xfail("env.GRAALPY", reason="TODO should get fixed on GraalPy side") def test_operators_notimplemented(): """#393: need to return NotSupported to ensure correct arithmetic operator behavior""" diff --git a/pybind11/tests/test_pickling.py b/pybind11/tests/test_pickling.py index d3551efc1..ad67a1df9 100644 --- a/pybind11/tests/test_pickling.py +++ b/pybind11/tests/test_pickling.py @@ -20,7 +20,7 @@ def test_pickle_simple_callable(): # all C Python versions. with pytest.raises(TypeError) as excinfo: pickle.dumps(m.simple_callable) - assert re.search("can.*t pickle .*[Cc]apsule.* object", str(excinfo.value)) + assert re.search("can.*t pickle .*PyCapsule.* object", str(excinfo.value)) @pytest.mark.parametrize("cls_name", ["Pickleable", "PickleableNew"]) @@ -93,20 +93,3 @@ def test_roundtrip_simple_cpp_derived(): # Issue #3062: pickleable base C++ classes can incur object slicing # if derived typeid is not registered with pybind11 assert not m.check_dynamic_cast_SimpleCppDerived(p2) - - -def test_new_style_pickle_getstate_pos_only(): - assert ( - re.match( - r"^__getstate__\(self: [\w\.]+, /\)", m.PickleableNew.__getstate__.__doc__ - ) - is not None - ) - if hasattr(m, "PickleableWithDictNew"): - assert ( - re.match( - r"^__getstate__\(self: [\w\.]+, /\)", - m.PickleableWithDictNew.__getstate__.__doc__, - ) - is not None - ) diff --git a/pybind11/tests/test_pytypes.cpp b/pybind11/tests/test_pytypes.cpp index 5160e9f40..19f65ce7e 100644 --- a/pybind11/tests/test_pytypes.cpp +++ b/pybind11/tests/test_pytypes.cpp @@ -7,7 +7,6 @@ BSD-style license that can be found in the LICENSE file. */ -#include #include #include "pybind11_tests.h" @@ -138,45 +137,6 @@ typedef py::typing::TypeVar<"V"> TypeVarV; } // namespace typevar #endif -// Custom type for testing arg_name/return_name type hints -// RealNumber: -// * in arguments -> float | int -// * in return -> float -// The choice of types is not really useful, but just made different for testing purposes. -// According to `PEP 484 – Type Hints` annotating with `float` also allows `int`, -// so using `float | int` could be replaced by just `float`. - -struct RealNumber { - double value; -}; - -namespace pybind11 { -namespace detail { - -template <> -struct type_caster { - PYBIND11_TYPE_CASTER(RealNumber, io_name("Union[float, int]", "float")); - - static handle cast(const RealNumber &number, return_value_policy, handle) { - return py::float_(number.value).release(); - } - - bool load(handle src, bool convert) { - // If we're in no-convert mode, only load if given a float - if (!convert && !py::isinstance(src)) { - return false; - } - if (!py::isinstance(src) && !py::isinstance(src)) { - return false; - } - value.value = src.cast(); - return true; - } -}; - -} // namespace detail -} // namespace pybind11 - TEST_SUBMODULE(pytypes, m) { m.def("obj_class_name", [](py::handle obj) { return py::detail::obj_class_name(obj.ptr()); }); @@ -190,18 +150,6 @@ TEST_SUBMODULE(pytypes, m) { m.def("get_iterator", [] { return py::iterator(); }); // test_iterable m.def("get_iterable", [] { return py::iterable(); }); - m.def("get_first_item_from_iterable", [](const py::iterable &iter) { - // This tests the postfix increment operator - py::iterator it = iter.begin(); - py::iterator it2 = it++; - return *it2; - }); - m.def("get_second_item_from_iterable", [](const py::iterable &iter) { - // This tests the prefix increment operator - py::iterator it = iter.begin(); - ++it; - return *it; - }); m.def("get_frozenset_from_iterable", [](const py::iterable &iter) { return py::frozenset(iter); }); m.def("get_list_from_iterable", [](const py::iterable &iter) { return py::list(iter); }); @@ -971,19 +919,6 @@ TEST_SUBMODULE(pytypes, m) { .value("BLUE", literals::Color::BLUE); m.def("annotate_literal", [](literals::LiteralFoo &o) -> py::object { return o; }); - // Literal with `@`, `%`, `{`, `}`, and `->` - m.def("identity_literal_exclamation", [](const py::typing::Literal<"\"!\""> &x) { return x; }); - m.def("identity_literal_at", [](const py::typing::Literal<"\"@\""> &x) { return x; }); - m.def("identity_literal_percent", [](const py::typing::Literal<"\"%\""> &x) { return x; }); - m.def("identity_literal_curly_open", [](const py::typing::Literal<"\"{\""> &x) { return x; }); - m.def("identity_literal_curly_close", [](const py::typing::Literal<"\"}\""> &x) { return x; }); - m.def("identity_literal_arrow_with_io_name", - [](const py::typing::Literal<"\"->\""> &x, const RealNumber &) { return x; }); - m.def("identity_literal_arrow_with_callable", - [](const py::typing::Callable\""> &, - const RealNumber &)> &x) { return x; }); - m.def("identity_literal_all_special_chars", - [](const py::typing::Literal<"\"!@!!->{%}\""> &x) { return x; }); m.def("annotate_generic_containers", [](const py::typing::List &l) -> py::typing::List { return l; @@ -1051,144 +986,4 @@ TEST_SUBMODULE(pytypes, m) { #else m.attr("defined_PYBIND11_TEST_PYTYPES_HAS_RANGES") = false; #endif - -#if defined(__cpp_inline_variables) - // Exercises const char* overload: - m.attr_with_type_hint>("list_int") = py::list(); - // Exercises py::handle overload: - m.attr_with_type_hint>(py::str("set_str")) = py::set(); - - struct Empty {}; - py::class_(m, "EmptyAnnotationClass"); - - struct Static {}; - auto static_class = py::class_(m, "Static"); - static_class.def(py::init()); - static_class.attr_with_type_hint>("x") = 1.0; - static_class.attr_with_type_hint>>( - "dict_str_int") - = py::dict(); - - struct Instance {}; - auto instance = py::class_(m, "Instance", py::dynamic_attr()); - instance.def(py::init()); - instance.attr_with_type_hint("y"); - - m.def("attr_with_type_hint_float_x", - [](py::handle obj) { obj.attr_with_type_hint("x"); }); - - m.attr_with_type_hint>("CONST_INT") = 3; - - m.attr("defined___cpp_inline_variables") = true; -#else - m.attr("defined___cpp_inline_variables") = false; -#endif - m.def("half_of_number", [](const RealNumber &x) { return RealNumber{x.value / 2}; }); - m.def( - "half_of_number_convert", - [](const RealNumber &x) { return RealNumber{x.value / 2}; }, - py::arg("x")); - m.def( - "half_of_number_noconvert", - [](const RealNumber &x) { return RealNumber{x.value / 2}; }, - py::arg("x").noconvert()); - // std::vector - m.def("half_of_number_vector", [](const std::vector &x) { - std::vector result; - result.reserve(x.size()); - for (auto num : x) { - result.push_back(RealNumber{num.value / 2}); - } - return result; - }); - // Tuple - m.def("half_of_number_tuple", [](const py::typing::Tuple &x) { - py::typing::Tuple result - = py::make_tuple(RealNumber{x[0].cast().value / 2}, - RealNumber{x[1].cast().value / 2}); - return result; - }); - // Tuple - m.def("half_of_number_tuple_ellipsis", - [](const py::typing::Tuple &x) { - py::typing::Tuple result(x.size()); - for (size_t i = 0; i < x.size(); ++i) { - result[i] = x[i].cast().value / 2; - } - return result; - }); - // Dict - m.def("half_of_number_dict", [](const py::typing::Dict &x) { - py::typing::Dict result; - for (auto it : x) { - result[it.first] = RealNumber{it.second.cast().value / 2}; - } - return result; - }); - // List - m.def("half_of_number_list", [](const py::typing::List &x) { - py::typing::List result; - for (auto num : x) { - result.append(RealNumber{num.cast().value / 2}); - } - return result; - }); - // List> - m.def("half_of_number_nested_list", - [](const py::typing::List> &x) { - py::typing::List> result_lists; - for (auto nums : x) { - py::typing::List result; - for (auto num : nums) { - result.append(RealNumber{num.cast().value / 2}); - } - result_lists.append(result); - } - return result_lists; - }); - // Set - m.def("identity_set", [](const py::typing::Set &x) { return x; }); - // Iterable - m.def("identity_iterable", [](const py::typing::Iterable &x) { return x; }); - // Iterator - m.def("identity_iterator", [](const py::typing::Iterator &x) { return x; }); - // Callable identity - m.def("identity_callable", - [](const py::typing::Callable &x) { return x; }); - // Callable identity - m.def("identity_callable_ellipsis", - [](const py::typing::Callable &x) { return x; }); - // Nested Callable identity - m.def("identity_nested_callable", - [](const py::typing::Callable( - py::typing::Callable)> &x) { return x; }); - // Callable - m.def("apply_callable", - [](const RealNumber &x, const py::typing::Callable &f) { - return f(x).cast(); - }); - // Callable - m.def("apply_callable_ellipsis", - [](const RealNumber &x, const py::typing::Callable &f) { - return f(x).cast(); - }); - // Union - m.def("identity_union", [](const py::typing::Union &x) { return x; }); - // Optional - m.def("identity_optional", [](const py::typing::Optional &x) { return x; }); - // TypeGuard - m.def("check_type_guard", - [](const py::typing::List &x) - -> py::typing::TypeGuard> { - for (const auto &item : x) { - if (!py::isinstance(item)) { - return false; - } - } - return true; - }); - // TypeIs - m.def("check_type_is", [](const py::object &x) -> py::typing::TypeIs { - return py::isinstance(x); - }); } diff --git a/pybind11/tests/test_pytypes.py b/pybind11/tests/test_pytypes.py index 18932311e..6f015eec8 100644 --- a/pybind11/tests/test_pytypes.py +++ b/pybind11/tests/test_pytypes.py @@ -52,11 +52,6 @@ def test_from_iterable(pytype, from_iter_func): def test_iterable(doc): assert doc(m.get_iterable) == "get_iterable() -> Iterable" - lst = [1, 2, 3] - i = m.get_first_item_from_iterable(lst) - assert i == 1 - i = m.get_second_item_from_iterable(lst) - assert i == 2 def test_float(doc): @@ -67,13 +62,13 @@ def test_list(capture, doc): assert m.list_no_args() == [] assert m.list_ssize_t() == [] assert m.list_size_t() == [] - lst = [1, 2] - m.list_insert_ssize_t(lst) - assert lst == [1, 83, 2] - m.list_insert_size_t(lst) - assert lst == [1, 83, 2, 57] - m.list_clear(lst) - assert lst == [] + lins = [1, 2] + m.list_insert_ssize_t(lins) + assert lins == [1, 83, 2] + m.list_insert_size_t(lins) + assert lins == [1, 83, 2, 57] + m.list_clear(lins) + assert lins == [] with capture: lst = m.get_list() @@ -267,7 +262,6 @@ def test_str(doc): m.str_from_std_string_input, ], ) -@pytest.mark.xfail("env.GRAALPY", reason="TODO should be fixed on GraalPy side") def test_surrogate_pairs_unicode_error(func): input_str = "\ud83d\ude4f".encode("utf-8", "surrogatepass") with pytest.raises(UnicodeDecodeError): @@ -426,7 +420,6 @@ def test_accessor_moves(): pytest.skip("Not defined: PYBIND11_HANDLE_REF_DEBUG") -@pytest.mark.xfail("env.GRAALPY", reason="TODO should be fixed on GraalPy side") def test_constructors(): """C++ default and converting constructors are equivalent to type calls in Python""" types = [bytes, bytearray, str, bool, int, float, tuple, list, dict, set] @@ -719,7 +712,6 @@ def test_pass_bytes_or_unicode_to_string_types(): m.pass_to_pybind11_str(malformed_utf8) -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") @pytest.mark.parametrize( ("create_weakref", "create_weakref_with_callback"), [ @@ -773,10 +765,7 @@ def test_weakref_err(create_weakref, has_callback): ob = C() # Should raise TypeError on CPython - cm = pytest.raises(TypeError) - if env.PYPY or env.GRAALPY: - cm = contextlib.nullcontext() - with cm: + with pytest.raises(TypeError) if not env.PYPY else contextlib.nullcontext(): _ = create_weakref(ob, callback) if has_callback else create_weakref(ob) @@ -1044,39 +1033,6 @@ def test_literal(doc): doc(m.annotate_literal) == 'annotate_literal(arg0: Literal[26, 0x1A, "hello world", b"hello world", u"hello world", True, Color.RED, None]) -> object' ) - # The characters !, @, %, {, } and -> are used in the signature parser as special characters, but Literal should escape those for the parser to work. - assert ( - doc(m.identity_literal_exclamation) - == 'identity_literal_exclamation(arg0: Literal["!"]) -> Literal["!"]' - ) - assert ( - doc(m.identity_literal_at) - == 'identity_literal_at(arg0: Literal["@"]) -> Literal["@"]' - ) - assert ( - doc(m.identity_literal_percent) - == 'identity_literal_percent(arg0: Literal["%"]) -> Literal["%"]' - ) - assert ( - doc(m.identity_literal_curly_open) - == 'identity_literal_curly_open(arg0: Literal["{"]) -> Literal["{"]' - ) - assert ( - doc(m.identity_literal_curly_close) - == 'identity_literal_curly_close(arg0: Literal["}"]) -> Literal["}"]' - ) - assert ( - doc(m.identity_literal_arrow_with_io_name) - == 'identity_literal_arrow_with_io_name(arg0: Literal["->"], arg1: Union[float, int]) -> Literal["->"]' - ) - assert ( - doc(m.identity_literal_arrow_with_callable) - == 'identity_literal_arrow_with_callable(arg0: Callable[[Literal["->"], Union[float, int]], float]) -> Callable[[Literal["->"], Union[float, int]], float]' - ) - assert ( - doc(m.identity_literal_all_special_chars) - == 'identity_literal_all_special_chars(arg0: Literal["!@!!->{%}"]) -> Literal["!@!!->{%}"]' - ) @pytest.mark.skipif( @@ -1134,196 +1090,3 @@ def test_list_ranges(tested_list, expected): def test_dict_ranges(tested_dict, expected): assert m.dict_iterator_default_initialization() assert m.transform_dict_plus_one(tested_dict) == expected - - -# https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older -def get_annotations_helper(o): - if isinstance(o, type): - return o.__dict__.get("__annotations__", None) - return getattr(o, "__annotations__", None) - - -@pytest.mark.skipif( - not m.defined___cpp_inline_variables, - reason="C++17 feature __cpp_inline_variables not available.", -) -def test_module_attribute_types() -> None: - module_annotations = get_annotations_helper(m) - - assert module_annotations["list_int"] == "list[int]" - assert module_annotations["set_str"] == "set[str]" - - -@pytest.mark.skipif( - not m.defined___cpp_inline_variables, - reason="C++17 feature __cpp_inline_variables not available.", -) -@pytest.mark.skipif( - sys.version_info < (3, 10), - reason="get_annotations function does not exist until Python3.10", -) -def test_get_annotations_compliance() -> None: - from inspect import get_annotations - - module_annotations = get_annotations(m) - - assert module_annotations["list_int"] == "list[int]" - assert module_annotations["set_str"] == "set[str]" - - -@pytest.mark.skipif( - not m.defined___cpp_inline_variables, - reason="C++17 feature __cpp_inline_variables not available.", -) -def test_class_attribute_types() -> None: - empty_annotations = get_annotations_helper(m.EmptyAnnotationClass) - static_annotations = get_annotations_helper(m.Static) - instance_annotations = get_annotations_helper(m.Instance) - - assert empty_annotations is None - assert static_annotations["x"] == "ClassVar[float]" - assert static_annotations["dict_str_int"] == "ClassVar[dict[str, int]]" - - assert m.Static.x == 1.0 - - m.Static.x = 3.0 - static = m.Static() - assert static.x == 3.0 - - static.dict_str_int["hi"] = 3 - assert m.Static().dict_str_int == {"hi": 3} - - assert instance_annotations["y"] == "float" - instance1 = m.Instance() - instance1.y = 4.0 - - instance2 = m.Instance() - instance2.y = 5.0 - - assert instance1.y != instance2.y - - -@pytest.mark.skipif( - not m.defined___cpp_inline_variables, - reason="C++17 feature __cpp_inline_variables not available.", -) -def test_redeclaration_attr_with_type_hint() -> None: - obj = m.Instance() - m.attr_with_type_hint_float_x(obj) - assert get_annotations_helper(obj)["x"] == "float" - with pytest.raises( - RuntimeError, match=r'^__annotations__\["x"\] was set already\.$' - ): - m.attr_with_type_hint_float_x(obj) - - -@pytest.mark.skipif( - not m.defined___cpp_inline_variables, - reason="C++17 feature __cpp_inline_variables not available.", -) -def test_final_annotation() -> None: - module_annotations = get_annotations_helper(m) - assert module_annotations["CONST_INT"] == "Final[int]" - - -def test_arg_return_type_hints(doc): - assert doc(m.half_of_number) == "half_of_number(arg0: Union[float, int]) -> float" - assert ( - doc(m.half_of_number_convert) - == "half_of_number_convert(x: Union[float, int]) -> float" - ) - assert ( - doc(m.half_of_number_noconvert) == "half_of_number_noconvert(x: float) -> float" - ) - assert m.half_of_number(2.0) == 1.0 - assert m.half_of_number(2) == 1.0 - assert m.half_of_number(0) == 0 - assert isinstance(m.half_of_number(0), float) - assert not isinstance(m.half_of_number(0), int) - # std::vector - assert ( - doc(m.half_of_number_vector) - == "half_of_number_vector(arg0: list[Union[float, int]]) -> list[float]" - ) - # Tuple - assert ( - doc(m.half_of_number_tuple) - == "half_of_number_tuple(arg0: tuple[Union[float, int], Union[float, int]]) -> tuple[float, float]" - ) - # Tuple - assert ( - doc(m.half_of_number_tuple_ellipsis) - == "half_of_number_tuple_ellipsis(arg0: tuple[Union[float, int], ...]) -> tuple[float, ...]" - ) - # Dict - assert ( - doc(m.half_of_number_dict) - == "half_of_number_dict(arg0: dict[str, Union[float, int]]) -> dict[str, float]" - ) - # List - assert ( - doc(m.half_of_number_list) - == "half_of_number_list(arg0: list[Union[float, int]]) -> list[float]" - ) - # List> - assert ( - doc(m.half_of_number_nested_list) - == "half_of_number_nested_list(arg0: list[list[Union[float, int]]]) -> list[list[float]]" - ) - # Set - assert ( - doc(m.identity_set) - == "identity_set(arg0: set[Union[float, int]]) -> set[float]" - ) - # Iterable - assert ( - doc(m.identity_iterable) - == "identity_iterable(arg0: Iterable[Union[float, int]]) -> Iterable[float]" - ) - # Iterator - assert ( - doc(m.identity_iterator) - == "identity_iterator(arg0: Iterator[Union[float, int]]) -> Iterator[float]" - ) - # Callable identity - assert ( - doc(m.identity_callable) - == "identity_callable(arg0: Callable[[Union[float, int]], float]) -> Callable[[Union[float, int]], float]" - ) - # Callable identity - assert ( - doc(m.identity_callable_ellipsis) - == "identity_callable_ellipsis(arg0: Callable[..., float]) -> Callable[..., float]" - ) - # Nested Callable identity - assert ( - doc(m.identity_nested_callable) - == "identity_nested_callable(arg0: Callable[[Callable[[Union[float, int]], float]], Callable[[Union[float, int]], float]]) -> Callable[[Callable[[Union[float, int]], float]], Callable[[Union[float, int]], float]]" - ) - # Callable - assert ( - doc(m.apply_callable) - == "apply_callable(arg0: Union[float, int], arg1: Callable[[Union[float, int]], float]) -> float" - ) - # Callable - assert ( - doc(m.apply_callable_ellipsis) - == "apply_callable_ellipsis(arg0: Union[float, int], arg1: Callable[..., float]) -> float" - ) - # Union - assert ( - doc(m.identity_union) - == "identity_union(arg0: Union[Union[float, int], str]) -> Union[float, str]" - ) - # Optional - assert ( - doc(m.identity_optional) - == "identity_optional(arg0: Optional[Union[float, int]]) -> Optional[float]" - ) - # TypeGuard - assert ( - doc(m.check_type_guard) - == "check_type_guard(arg0: list[object]) -> TypeGuard[list[float]]" - ) - # TypeIs - assert doc(m.check_type_is) == "check_type_is(arg0: object) -> TypeIs[float]" diff --git a/pybind11/tests/test_sequences_and_iterators.py b/pybind11/tests/test_sequences_and_iterators.py index 6fba6fba3..f609f553d 100644 --- a/pybind11/tests/test_sequences_and_iterators.py +++ b/pybind11/tests/test_sequences_and_iterators.py @@ -1,11 +1,8 @@ from __future__ import annotations -import re - import pytest from pytest import approx # noqa: PT013 -import env from pybind11_tests import ConstructorStats from pybind11_tests import sequences_and_iterators as m @@ -113,8 +110,7 @@ def test_sequence(): cstats = ConstructorStats.get(m.Sequence) s = m.Sequence(5) - if not env.GRAALPY: - assert cstats.values() == ["of size", "5"] + assert cstats.values() == ["of size", "5"] assert "Sequence" in repr(s) assert len(s) == 5 @@ -127,19 +123,16 @@ def test_sequence(): assert s[3] == approx(56.78, rel=1e-05) rev = reversed(s) - if not env.GRAALPY: - assert cstats.values() == ["of size", "5"] + assert cstats.values() == ["of size", "5"] rev2 = s[::-1] - if not env.GRAALPY: - assert cstats.values() == ["of size", "5"] + assert cstats.values() == ["of size", "5"] it = iter(m.Sequence(0)) for _ in range(3): # __next__ must continue to raise StopIteration with pytest.raises(StopIteration): next(it) - if not env.GRAALPY: - assert cstats.values() == ["of size", "0"] + assert cstats.values() == ["of size", "0"] expected = [0, 56.78, 0, 0, 12.34] assert rev == approx(expected, rel=1e-05) @@ -147,14 +140,10 @@ def test_sequence(): assert rev == rev2 rev[0::2] = m.Sequence([2.0, 2.0, 2.0]) - if not env.GRAALPY: - assert cstats.values() == ["of size", "3", "from std::vector"] + assert cstats.values() == ["of size", "3", "from std::vector"] assert rev == approx([2, 56.78, 2, 0, 2], rel=1e-05) - if env.GRAALPY: - pytest.skip("ConstructorStats is incompatible with GraalPy.") - assert cstats.alive() == 4 del it assert cstats.alive() == 3 @@ -255,12 +244,16 @@ def test_python_iterator_in_cpp(): def test_iterator_passthrough(): """#181: iterator passthrough did not compile""" + from pybind11_tests.sequences_and_iterators import iterator_passthrough + values = [3, 5, 7, 9, 11, 13, 15] - assert list(m.iterator_passthrough(iter(values))) == values + assert list(iterator_passthrough(iter(values))) == values def test_iterator_rvp(): """#388: Can't make iterators via make_iterator() with different r/v policies""" + import pybind11_tests.sequences_and_iterators as m + assert list(m.make_iterator_1()) == [1, 2, 3] assert list(m.make_iterator_2()) == [1, 2, 3] assert not isinstance(m.make_iterator_1(), type(m.make_iterator_2())) @@ -272,25 +265,3 @@ def test_carray_iterator(): arr_h = m.CArrayHolder(*args_gt) args = list(arr_h) assert args_gt == args - - -def test_generated_dunder_methods_pos_only(): - string_map = m.StringMap({"hi": "bye", "black": "white"}) - for it in ( - m.make_iterator_1(), - m.make_iterator_2(), - m.iterator_passthrough(iter([3, 5, 7])), - iter(m.Sequence(5)), - iter(string_map), - string_map.items(), - string_map.values(), - iter(m.CArrayHolder(*[float(i) for i in range(3)])), - ): - assert ( - re.match(r"^__iter__\(self: [\w\.]+, /\)", type(it).__iter__.__doc__) - is not None - ) - assert ( - re.match(r"^__next__\(self: [\w\.]+, /\)", type(it).__next__.__doc__) - is not None - ) diff --git a/pybind11/tests/test_smart_ptr.py b/pybind11/tests/test_smart_ptr.py index ab8a1ce62..bf0ae4aeb 100644 --- a/pybind11/tests/test_smart_ptr.py +++ b/pybind11/tests/test_smart_ptr.py @@ -2,13 +2,10 @@ from __future__ import annotations import pytest -import env # noqa: F401 - m = pytest.importorskip("pybind11_tests.smart_ptr") from pybind11_tests import ConstructorStats # noqa: E402 -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_smart_ptr(capture): # Object1 for i, o in enumerate( @@ -121,7 +118,6 @@ def test_smart_ptr_refcounting(): assert m.test_object1_refcounting() -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_unique_nodelete(): o = m.MyObject4(23) assert o.value == 23 @@ -133,7 +129,6 @@ def test_unique_nodelete(): assert cstats.alive() == 0 -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_unique_nodelete4a(): o = m.MyObject4a(23) assert o.value == 23 @@ -145,7 +140,6 @@ def test_unique_nodelete4a(): assert cstats.alive() == 0 -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_unique_deleter(): m.MyObject4a(0) o = m.MyObject4b(23) @@ -162,7 +156,6 @@ def test_unique_deleter(): assert cstats4b.alive() == 0 -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_large_holder(): o = m.MyObject5(5) assert o.value == 5 @@ -172,7 +165,6 @@ def test_large_holder(): assert cstats.alive() == 0 -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_shared_ptr_and_references(): s = m.SharedPtrRef() stats = ConstructorStats.get(m.A) @@ -204,7 +196,6 @@ def test_shared_ptr_and_references(): assert stats.alive() == 0 -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_shared_ptr_from_this_and_references(): s = m.SharedFromThisRef() stats = ConstructorStats.get(m.B) @@ -251,7 +242,6 @@ def test_shared_ptr_from_this_and_references(): assert y is z -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_move_only_holder(): a = m.TypeWithMoveOnlyHolder.make() b = m.TypeWithMoveOnlyHolder.make_as_object() @@ -263,7 +253,6 @@ def test_move_only_holder(): assert stats.alive() == 0 -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_holder_with_addressof_operator(): # this test must not throw exception from c++ a = m.TypeForHolderWithAddressOf.make() @@ -294,7 +283,6 @@ def test_holder_with_addressof_operator(): assert stats.alive() == 0 -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_move_only_holder_with_addressof_operator(): a = m.TypeForMoveOnlyHolderWithAddressOf.make() a.print_object() @@ -313,8 +301,9 @@ def test_smart_ptr_from_default(): instance = m.HeldByDefaultHolder() with pytest.raises(RuntimeError) as excinfo: m.HeldByDefaultHolder.load_shared_ptr(instance) - assert "Unable to load a custom holder type from a default-holder instance" in str( - excinfo.value + assert ( + "Unable to load a custom holder type from a " + "default-holder instance" in str(excinfo.value) ) diff --git a/pybind11/tests/test_stl.cpp b/pybind11/tests/test_stl.cpp index 9ddd951e0..e7db8aaac 100644 --- a/pybind11/tests/test_stl.cpp +++ b/pybind11/tests/test_stl.cpp @@ -16,7 +16,6 @@ # define PYBIND11_HAS_FILESYSTEM_IS_OPTIONAL #endif #include -#include #include #include @@ -168,14 +167,6 @@ struct type_caster> } // namespace detail } // namespace PYBIND11_NAMESPACE -int pass_std_vector_int(const std::vector &v) { - int zum = 100; - for (const int i : v) { - zum += 2 * i; - } - return zum; -} - TEST_SUBMODULE(stl, m) { // test_vector m.def("cast_vector", []() { return std::vector{1}; }); @@ -202,23 +193,6 @@ TEST_SUBMODULE(stl, m) { m.def("cast_array", []() { return std::array{{1, 2}}; }); m.def("load_array", [](const std::array &a) { return a[0] == 1 && a[1] == 2; }); - struct NoDefaultCtor { - explicit constexpr NoDefaultCtor(int val) : val{val} {} - int val; - }; - - struct NoDefaultCtorArray { - explicit constexpr NoDefaultCtorArray(int i) - : arr{{NoDefaultCtor(10 + i), NoDefaultCtor(20 + i)}} {} - std::array arr; - }; - - // test_array_no_default_ctor - py::class_(m, "NoDefaultCtor").def_readonly("val", &NoDefaultCtor::val); - py::class_(m, "NoDefaultCtorArray") - .def(py::init()) - .def_readwrite("arr", &NoDefaultCtorArray::arr); - // test_valarray m.def("cast_valarray", []() { return std::valarray{1, 4, 9}; }); m.def("load_valarray", [](const std::valarray &v) { @@ -454,57 +428,7 @@ TEST_SUBMODULE(stl, m) { #ifdef PYBIND11_HAS_FILESYSTEM // test_fs_path m.attr("has_filesystem") = true; - m.def("parent_path", [](const std::filesystem::path &path) { return path.parent_path(); }); - m.def("parent_paths", [](const std::vector &paths) { - std::vector result; - result.reserve(paths.size()); - for (const auto &path : paths) { - result.push_back(path.parent_path()); - } - return result; - }); - m.def("parent_paths_list", [](const py::typing::List &paths) { - py::typing::List result; - for (auto path : paths) { - result.append(path.cast().parent_path()); - } - return result; - }); - m.def("parent_paths_nested_list", - [](const py::typing::List> &paths_lists) { - py::typing::List> result_lists; - for (auto paths : paths_lists) { - py::typing::List result; - for (auto path : paths) { - result.append(path.cast().parent_path()); - } - result_lists.append(result); - } - return result_lists; - }); - m.def("parent_paths_tuple", - [](const py::typing::Tuple &paths) { - py::typing::Tuple result - = py::make_tuple(paths[0].cast().parent_path(), - paths[1].cast().parent_path()); - return result; - }); - m.def("parent_paths_tuple_ellipsis", - [](const py::typing::Tuple &paths) { - py::typing::Tuple result(paths.size()); - for (size_t i = 0; i < paths.size(); ++i) { - result[i] = paths[i].cast().parent_path(); - } - return result; - }); - m.def("parent_paths_dict", - [](const py::typing::Dict &paths) { - py::typing::Dict result; - for (auto it : paths) { - result[it.first] = it.second.cast().parent_path(); - } - return result; - }); + m.def("parent_path", [](const std::filesystem::path &p) { return p.parent_path(); }); #endif #ifdef PYBIND11_TEST_VARIANT @@ -622,30 +546,4 @@ TEST_SUBMODULE(stl, m) { []() { return new std::vector(4513); }, // Without explicitly specifying `take_ownership`, this function leaks. py::return_value_policy::take_ownership); - - m.def("pass_std_vector_int", pass_std_vector_int); - m.def("pass_std_vector_pair_int", [](const std::vector> &v) { - int zum = 0; - for (const auto &ij : v) { - zum += ij.first * 100 + ij.second; - } - return zum; - }); - m.def("pass_std_array_int_2", [](const std::array &a) { - return pass_std_vector_int(std::vector(a.begin(), a.end())) + 1; - }); - m.def("pass_std_set_int", [](const std::set &s) { - int zum = 200; - for (const int i : s) { - zum += 3 * i; - } - return zum; - }); - m.def("pass_std_map_int", [](const std::map &m) { - int zum = 500; - for (const auto &p : m) { - zum += p.first * 1000 + p.second; - } - return zum; - }); } diff --git a/pybind11/tests/test_stl.py b/pybind11/tests/test_stl.py index f2ff727d9..65fda54cc 100644 --- a/pybind11/tests/test_stl.py +++ b/pybind11/tests/test_stl.py @@ -2,7 +2,6 @@ from __future__ import annotations import pytest -import env # noqa: F401 from pybind11_tests import ConstructorStats, UserType from pybind11_tests import stl as m @@ -49,13 +48,6 @@ def test_array(doc): ) -def test_array_no_default_ctor(): - lst = m.NoDefaultCtorArray(3) - assert [e.val for e in lst.arr] == [13, 23] - lst.arr = m.NoDefaultCtorArray(4).arr - assert [e.val for e in lst.arr] == [14, 24] - - def test_valarray(doc): """std::valarray <-> list""" lst = m.cast_valarray() @@ -246,7 +238,7 @@ def test_reference_sensitive_optional(): @pytest.mark.skipif(not hasattr(m, "has_filesystem"), reason="no ") -def test_fs_path(doc): +def test_fs_path(): from pathlib import Path class PseudoStrPath: @@ -257,59 +249,11 @@ def test_fs_path(doc): def __fspath__(self): return b"foo/bar" - # Single argument assert m.parent_path(Path("foo/bar")) == Path("foo") assert m.parent_path("foo/bar") == Path("foo") assert m.parent_path(b"foo/bar") == Path("foo") assert m.parent_path(PseudoStrPath()) == Path("foo") assert m.parent_path(PseudoBytesPath()) == Path("foo") - assert ( - doc(m.parent_path) - == "parent_path(arg0: Union[os.PathLike, str, bytes]) -> pathlib.Path" - ) - # std::vector - assert m.parent_paths(["foo/bar", "foo/baz"]) == [Path("foo"), Path("foo")] - assert ( - doc(m.parent_paths) - == "parent_paths(arg0: list[Union[os.PathLike, str, bytes]]) -> list[pathlib.Path]" - ) - # py::typing::List - assert m.parent_paths_list(["foo/bar", "foo/baz"]) == [Path("foo"), Path("foo")] - assert ( - doc(m.parent_paths_list) - == "parent_paths_list(arg0: list[Union[os.PathLike, str, bytes]]) -> list[pathlib.Path]" - ) - # Nested py::typing::List - assert m.parent_paths_nested_list([["foo/bar"], ["foo/baz", "foo/buzz"]]) == [ - [Path("foo")], - [Path("foo"), Path("foo")], - ] - assert ( - doc(m.parent_paths_nested_list) - == "parent_paths_nested_list(arg0: list[list[Union[os.PathLike, str, bytes]]]) -> list[list[pathlib.Path]]" - ) - # py::typing::Tuple - assert m.parent_paths_tuple(("foo/bar", "foo/baz")) == (Path("foo"), Path("foo")) - assert ( - doc(m.parent_paths_tuple) - == "parent_paths_tuple(arg0: tuple[Union[os.PathLike, str, bytes], Union[os.PathLike, str, bytes]]) -> tuple[pathlib.Path, pathlib.Path]" - ) - # py::typing::Dict - assert m.parent_paths_dict( - { - "key1": Path("foo/bar"), - "key2": "foo/baz", - "key3": b"foo/buzz", - } - ) == { - "key1": Path("foo"), - "key2": Path("foo"), - "key3": Path("foo"), - } - assert ( - doc(m.parent_paths_dict) - == "parent_paths_dict(arg0: dict[str, Union[os.PathLike, str, bytes]]) -> dict[str, pathlib.Path]" - ) @pytest.mark.skipif(not hasattr(m, "load_variant"), reason="no ") @@ -411,7 +355,6 @@ def test_function_with_string_and_vector_string_arg(): assert m.func_with_string_or_vector_string_arg_overload("A") == 3 -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_stl_ownership(): cstats = ConstructorStats.get(m.Placeholder) assert cstats.alive() == 0 @@ -438,129 +381,3 @@ def test_return_vector_bool_raw_ptr(): v = m.return_vector_bool_raw_ptr() assert isinstance(v, list) assert len(v) == 4513 - - -@pytest.mark.parametrize( - ("fn", "offset"), [(m.pass_std_vector_int, 0), (m.pass_std_array_int_2, 1)] -) -def test_pass_std_vector_int(fn, offset): - assert fn([7, 13]) == 140 + offset - assert fn({6, 2}) == 116 + offset - assert fn({"x": 8, "y": 11}.values()) == 138 + offset - assert fn({3: None, 9: None}.keys()) == 124 + offset - assert fn(i for i in [4, 17]) == 142 + offset - assert fn(map(lambda i: i * 3, [8, 7])) == 190 + offset # noqa: C417 - with pytest.raises(TypeError): - fn({"x": 0, "y": 1}) - with pytest.raises(TypeError): - fn({}) - - -def test_pass_std_vector_pair_int(): - fn = m.pass_std_vector_pair_int - assert fn({1: 2, 3: 4}.items()) == 406 - assert fn(zip([5, 17], [13, 9])) == 2222 - - -def test_list_caster_fully_consumes_generator_object(): - def gen_invalid(): - yield from [1, 2.0, 3] - - gen_obj = gen_invalid() - with pytest.raises(TypeError): - m.pass_std_vector_int(gen_obj) - assert not tuple(gen_obj) - - -def test_pass_std_set_int(): - fn = m.pass_std_set_int - assert fn({3, 15}) == 254 - assert fn({5: None, 12: None}.keys()) == 251 - with pytest.raises(TypeError): - fn([]) - with pytest.raises(TypeError): - fn({}) - with pytest.raises(TypeError): - fn({}.values()) - with pytest.raises(TypeError): - fn(i for i in []) - - -def test_set_caster_dict_keys_failure(): - dict_keys = {1: None, 2.0: None, 3: None}.keys() - # The asserts does not really exercise anything in pybind11, but if one of - # them fails in some future version of Python, the set_caster load - # implementation may need to be revisited. - assert tuple(dict_keys) == (1, 2.0, 3) - assert tuple(dict_keys) == (1, 2.0, 3) - with pytest.raises(TypeError): - m.pass_std_set_int(dict_keys) - assert tuple(dict_keys) == (1, 2.0, 3) - - -class FakePyMappingMissingItems: - def __getitem__(self, _): - raise RuntimeError("Not expected to be called.") - - -class FakePyMappingWithItems(FakePyMappingMissingItems): - def items(self): - return ((1, 3), (2, 4)) - - -class FakePyMappingBadItems(FakePyMappingMissingItems): - def items(self): - return ((1, 2), (3, "x")) - - -class FakePyMappingItemsNotCallable(FakePyMappingMissingItems): - @property - def items(self): - return ((1, 2), (3, 4)) - - -class FakePyMappingItemsWithArg(FakePyMappingMissingItems): - def items(self, _): - return ((1, 2), (3, 4)) - - -class FakePyMappingGenObj(FakePyMappingMissingItems): - def __init__(self, gen_obj): - super().__init__() - self.gen_obj = gen_obj - - def items(self): - yield from self.gen_obj - - -def test_pass_std_map_int(): - fn = m.pass_std_map_int - assert fn({1: 2, 3: 4}) == 4506 - with pytest.raises(TypeError): - fn([]) - assert fn(FakePyMappingWithItems()) == 3507 - with pytest.raises(TypeError): - fn(FakePyMappingMissingItems()) - with pytest.raises(TypeError): - fn(FakePyMappingBadItems()) - with pytest.raises(TypeError): - fn(FakePyMappingItemsNotCallable()) - with pytest.raises(TypeError): - fn(FakePyMappingItemsWithArg()) - - -@pytest.mark.parametrize( - ("items", "expected_exception"), - [ - (((1, 2), (3, "x"), (4, 5)), TypeError), - (((1, 2), (3, 4, 5), (6, 7)), ValueError), - ], -) -def test_map_caster_fully_consumes_generator_object(items, expected_exception): - def gen_invalid(): - yield from items - - gen_obj = gen_invalid() - with pytest.raises(expected_exception): - m.pass_std_map_int(FakePyMappingGenObj(gen_obj)) - assert not tuple(gen_obj) diff --git a/pybind11/tests/test_stl_binders.py b/pybind11/tests/test_stl_binders.py index 9856ba462..09e784e2e 100644 --- a/pybind11/tests/test_stl_binders.py +++ b/pybind11/tests/test_stl_binders.py @@ -302,25 +302,6 @@ def test_map_delitem(): assert list(mm) == ["b"] assert list(mm.items()) == [("b", 2.5)] - with pytest.raises(KeyError) as excinfo: - mm["a_long_key"] - assert "a_long_key" in str(excinfo.value) - - with pytest.raises(KeyError) as excinfo: - del mm["a_long_key"] - assert "a_long_key" in str(excinfo.value) - - cut_length = 100 - k_very_long = "ab" * cut_length + "xyz" - with pytest.raises(KeyError) as excinfo: - mm[k_very_long] - assert k_very_long in str(excinfo.value) - k_very_long += "@" - with pytest.raises(KeyError) as excinfo: - mm[k_very_long] - k_repr = k_very_long[:cut_length] + "✄✄✄" + k_very_long[-cut_length:] - assert k_repr in str(excinfo.value) - um = m.UnorderedMapStringDouble() um["ua"] = 1.1 um["ub"] = 2.6 diff --git a/pybind11/tests/test_thread.cpp b/pybind11/tests/test_thread.cpp index eabf39afa..e727109d7 100644 --- a/pybind11/tests/test_thread.cpp +++ b/pybind11/tests/test_thread.cpp @@ -28,9 +28,6 @@ struct IntStruct { int value; }; -struct EmptyStruct {}; -EmptyStruct SharedInstance; - } // namespace TEST_SUBMODULE(thread, m) { @@ -64,9 +61,6 @@ TEST_SUBMODULE(thread, m) { }, py::call_guard()); - py::class_(m, "EmptyStruct") - .def_readonly_static("SharedInstance", &SharedInstance); - // NOTE: std::string_view also uses loader_life_support to ensure that // the string contents remain alive, but that's a C++ 17 feature. } diff --git a/pybind11/tests/test_thread.py b/pybind11/tests/test_thread.py index e9d7bafb2..f12020e41 100644 --- a/pybind11/tests/test_thread.py +++ b/pybind11/tests/test_thread.py @@ -47,22 +47,3 @@ def test_implicit_conversion_no_gil(): x.start() for x in [c, b, a]: x.join() - - -@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") -def test_bind_shared_instance(): - nb_threads = 4 - b = threading.Barrier(nb_threads) - - def access_shared_instance(): - b.wait() - for _ in range(1000): - m.EmptyStruct.SharedInstance # noqa: B018 - - threads = [ - threading.Thread(target=access_shared_instance) for _ in range(nb_threads) - ] - for thread in threads: - thread.start() - for thread in threads: - thread.join() diff --git a/pybind11/tests/test_type_caster_pyobject_ptr.cpp b/pybind11/tests/test_type_caster_pyobject_ptr.cpp index 758c4ee09..a45c08b64 100644 --- a/pybind11/tests/test_type_caster_pyobject_ptr.cpp +++ b/pybind11/tests/test_type_caster_pyobject_ptr.cpp @@ -37,8 +37,7 @@ struct WithPyObjectPtrReturnTrampoline : WithPyObjectPtrReturn { std::string call_return_pyobject_ptr(const WithPyObjectPtrReturn *base_class_ptr) { PyObject *returned_obj = base_class_ptr->return_pyobject_ptr(); -// It is not worth the trouble doing something special for PyPy/GraalPy -#if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON) +#if !defined(PYPY_VERSION) // It is not worth the trouble doing something special for PyPy. if (Py_REFCNT(returned_obj) != 1) { py::pybind11_fail(__FILE__ ":" PYBIND11_TOSTRING(__LINE__)); } diff --git a/pybind11/tests/test_unnamed_namespace_a.cpp b/pybind11/tests/test_unnamed_namespace_a.cpp index 26e9cb751..2152e64bd 100644 --- a/pybind11/tests/test_unnamed_namespace_a.cpp +++ b/pybind11/tests/test_unnamed_namespace_a.cpp @@ -10,6 +10,7 @@ TEST_SUBMODULE(unnamed_namespace_a, m) { } else { m.attr("unnamed_namespace_a_any_struct") = py::none(); } + m.attr("PYBIND11_INTERNALS_VERSION") = PYBIND11_INTERNALS_VERSION; m.attr("defined_WIN32_or__WIN32") = #if defined(WIN32) || defined(_WIN32) true; diff --git a/pybind11/tests/test_unnamed_namespace_a.py b/pybind11/tests/test_unnamed_namespace_a.py index fabf1312a..0fa1fa323 100644 --- a/pybind11/tests/test_unnamed_namespace_a.py +++ b/pybind11/tests/test_unnamed_namespace_a.py @@ -5,7 +5,13 @@ import pytest from pybind11_tests import unnamed_namespace_a as m from pybind11_tests import unnamed_namespace_b as mb -XFAIL_CONDITION = "not m.defined_WIN32_or__WIN32 and (m.defined___clang__ or m.defined__LIBCPP_VERSION)" +XFAIL_CONDITION = ( + "(m.PYBIND11_INTERNALS_VERSION <= 4 and (m.defined___clang__ or not m.defined___GLIBCXX__))" + " or " + "(m.PYBIND11_INTERNALS_VERSION >= 5 and not m.defined_WIN32_or__WIN32" + " and " + "(m.defined___clang__ or m.defined__LIBCPP_VERSION))" +) XFAIL_REASON = "Known issues: https://github.com/pybind/pybind11/pull/4319" diff --git a/pybind11/tests/test_virtual_functions.py b/pybind11/tests/test_virtual_functions.py index 617c87b8e..bc0177787 100644 --- a/pybind11/tests/test_virtual_functions.py +++ b/pybind11/tests/test_virtual_functions.py @@ -4,7 +4,7 @@ import sys import pytest -import env +import env # noqa: F401 m = pytest.importorskip("pybind11_tests.virtual_functions") from pybind11_tests import ConstructorStats # noqa: E402 @@ -82,9 +82,6 @@ def test_override(capture, msg): """ ) - if env.GRAALPY: - pytest.skip("ConstructorStats is incompatible with GraalPy.") - cstats = ConstructorStats.get(m.ExampleVirt) assert cstats.alive() == 3 del ex12, ex12p, ex12p2 @@ -94,7 +91,6 @@ def test_override(capture, msg): assert cstats.move_constructions >= 0 -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_alias_delay_initialization1(capture): """`A` only initializes its trampoline class when we inherit from it @@ -134,7 +130,6 @@ def test_alias_delay_initialization1(capture): ) -@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") def test_alias_delay_initialization2(capture): """`A2`, unlike the above, is configured to always initialize the alias @@ -193,7 +188,7 @@ def test_alias_delay_initialization2(capture): # PyPy: Reference count > 1 causes call with noncopyable instance # to fail in ncv1.print_nc() -@pytest.mark.xfail("env.PYPY or env.GRAALPY") +@pytest.mark.xfail("env.PYPY") @pytest.mark.skipif( not hasattr(m, "NCVirt"), reason="NCVirt does not work on Intel/PGI/NVCC compilers" ) diff --git a/pybind11/tests/test_warnings.cpp b/pybind11/tests/test_warnings.cpp deleted file mode 100644 index e76f21249..000000000 --- a/pybind11/tests/test_warnings.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - tests/test_warnings.cpp -- usage of warnings::warn() and warnings categories. - - Copyright (c) 2024 Jan Iwaszkiewicz - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#include - -#include "pybind11_tests.h" - -#include - -TEST_SUBMODULE(warnings_, m) { - - // Test warning mechanism base - m.def("warn_and_return_value", []() { - std::string message = "This is simple warning"; - py::warnings::warn(message.c_str(), PyExc_Warning); - return 21; - }); - - m.def("warn_with_default_category", []() { py::warnings::warn("This is RuntimeWarning"); }); - - m.def("warn_with_different_category", - []() { py::warnings::warn("This is FutureWarning", PyExc_FutureWarning); }); - - m.def("warn_with_invalid_category", - []() { py::warnings::warn("Invalid category", PyExc_Exception); }); - - // Test custom warnings - PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store ex_storage; - ex_storage.call_once_and_store_result([&]() { - return py::warnings::new_warning_type(m, "CustomWarning", PyExc_DeprecationWarning); - }); - - m.def("warn_with_custom_type", []() { - py::warnings::warn("This is CustomWarning", ex_storage.get_stored()); - return 37; - }); - - m.def("register_duplicate_warning", - [m]() { py::warnings::new_warning_type(m, "CustomWarning", PyExc_RuntimeWarning); }); -} diff --git a/pybind11/tests/test_warnings.py b/pybind11/tests/test_warnings.py deleted file mode 100644 index 4313432c3..000000000 --- a/pybind11/tests/test_warnings.py +++ /dev/null @@ -1,68 +0,0 @@ -from __future__ import annotations - -import warnings - -import pytest - -import pybind11_tests # noqa: F401 -from pybind11_tests import warnings_ as m - - -@pytest.mark.parametrize( - ("expected_category", "expected_message", "expected_value", "module_function"), - [ - (Warning, "This is simple warning", 21, m.warn_and_return_value), - (RuntimeWarning, "This is RuntimeWarning", None, m.warn_with_default_category), - (FutureWarning, "This is FutureWarning", None, m.warn_with_different_category), - ], -) -def test_warning_simple( - expected_category, expected_message, expected_value, module_function -): - with pytest.warns(Warning) as excinfo: - value = module_function() - - assert issubclass(excinfo[0].category, expected_category) - assert str(excinfo[0].message) == expected_message - assert value == expected_value - - -def test_warning_wrong_subclass_fail(): - with pytest.raises(Exception) as excinfo: - m.warn_with_invalid_category() - - assert issubclass(excinfo.type, RuntimeError) - assert ( - str(excinfo.value) - == "pybind11::warnings::warn(): cannot raise warning, category must be a subclass of PyExc_Warning!" - ) - - -def test_warning_double_register_fail(): - with pytest.raises(Exception) as excinfo: - m.register_duplicate_warning() - - assert issubclass(excinfo.type, RuntimeError) - assert ( - str(excinfo.value) - == 'pybind11::warnings::new_warning_type(): an attribute with name "CustomWarning" exists already.' - ) - - -def test_warning_register(): - assert m.CustomWarning is not None - - with pytest.warns(m.CustomWarning) as excinfo: - warnings.warn("This is warning from Python!", m.CustomWarning, stacklevel=1) - - assert issubclass(excinfo[0].category, DeprecationWarning) - assert str(excinfo[0].message) == "This is warning from Python!" - - -def test_warning_custom(): - with pytest.warns(m.CustomWarning) as excinfo: - value = m.warn_with_custom_type() - - assert issubclass(excinfo[0].category, DeprecationWarning) - assert str(excinfo[0].message) == "This is CustomWarning" - assert value == 37 diff --git a/pybind11/tools/FindPythonLibsNew.cmake b/pybind11/tools/FindPythonLibsNew.cmake index 7351bcaa6..283b4e298 100644 --- a/pybind11/tools/FindPythonLibsNew.cmake +++ b/pybind11/tools/FindPythonLibsNew.cmake @@ -92,7 +92,7 @@ endif() # Use the Python interpreter to find the libs. if(NOT PythonLibsNew_FIND_VERSION) - set(PythonLibsNew_FIND_VERSION "3.8") + set(PythonLibsNew_FIND_VERSION "3.7") endif() if(NOT CMAKE_VERSION VERSION_LESS "3.27") @@ -200,16 +200,6 @@ if(PYBIND11_PYTHONLIBS_OVERWRITE OR NOT DEFINED PYTHON_MODULE_DEBUG_POSTFIX) endif() if(PYBIND11_PYTHONLIBS_OVERWRITE OR NOT DEFINED PYTHON_MODULE_EXTENSION) get_filename_component(PYTHON_MODULE_EXTENSION "${_PYTHON_MODULE_EXT_SUFFIX}" EXT) - if((NOT "$ENV{SETUPTOOLS_EXT_SUFFIX}" STREQUAL "") AND (NOT "$ENV{SETUPTOOLS_EXT_SUFFIX}" - STREQUAL "${PYTHON_MODULE_EXTENSION}")) - message( - AUTHOR_WARNING, - "SETUPTOOLS_EXT_SUFFIX is set to \"$ENV{SETUPTOOLS_EXT_SUFFIX}\", " - "but the auto-calculated Python extension suffix is \"${PYTHON_MODULE_EXTENSION}\". " - "This may cause problems when importing the Python extensions. " - "If you are using cross-compiling Python, you may need to " - "set PYTHON_MODULE_EXTENSION manually.") - endif() endif() # Make sure the Python has the same pointer-size as the chosen compiler diff --git a/pybind11/tools/make_changelog.py b/pybind11/tools/make_changelog.py index b499d06ba..daa966f20 100755 --- a/pybind11/tools/make_changelog.py +++ b/pybind11/tools/make_changelog.py @@ -59,9 +59,9 @@ for issue in issues: msg += "." msg += f"\n `#{issue.number} <{issue.html_url}>`_" - for cat, cat_list in cats.items(): + for cat in cats: if issue.title.lower().startswith(f"{cat}:"): - cat_list.append(msg) + cats[cat].append(msg) break else: cats["unknown"].append(msg) diff --git a/pybind11/tools/pybind11Common.cmake b/pybind11/tools/pybind11Common.cmake index 450a14156..7d8d94b11 100644 --- a/pybind11/tools/pybind11Common.cmake +++ b/pybind11/tools/pybind11Common.cmake @@ -18,7 +18,11 @@ Adds the following functions:: #]======================================================] -include_guard(GLOBAL) +# CMake 3.10 has an include_guard command, but we can't use that yet +# include_guard(global) (pre-CMake 3.10) +if(TARGET pybind11::pybind11) + return() +endif() # If we are in subdirectory mode, all IMPORTED targets must be GLOBAL. If we # are in CONFIG mode, they should be "normal" targets instead. @@ -76,31 +80,47 @@ set_property( # 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(is_config) - set(_tmp_config_target pybind11::pybind11_headers) + if(CMAKE_VERSION VERSION_LESS 3.13) + message(WARNING "CMake 3.13+ is required to build for Emscripten. Some flags will be missing") else() - set(_tmp_config_target pybind11_headers) - endif() + 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) + 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}) -set_property( - TARGET pybind11::python_link_helper - APPEND - PROPERTY INTERFACE_LINK_OPTIONS "$<$:LINKER:-undefined,dynamic_lookup>") +if(CMAKE_VERSION VERSION_LESS 3.13) + # In CMake 3.11+, you can set INTERFACE properties via the normal methods, and + # this would be simpler. + set_property( + TARGET pybind11::python_link_helper + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES "$<$:-undefined dynamic_lookup>") +else() + # link_options was added in 3.13+ + # This is safer, because you are ensured the deduplication pass in CMake will not consider + # these separate and remove one but not the other. + set_property( + TARGET pybind11::python_link_helper + APPEND + PROPERTY INTERFACE_LINK_OPTIONS "$<$:LINKER:-undefined,dynamic_lookup>") +endif() # ------------------------ Windows extras ------------------------- @@ -116,14 +136,22 @@ if(MSVC) # That's also clang-cl # /MP enables multithreaded builds (relevant when there are many files) for MSVC if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") # no Clang no Intel - # Only set these options for C++ files. This is important so that, for - # instance, projects that include other types of source files like CUDA - # .cu files don't get these options propagated to nvcc since that would - # cause the build to fail. - set_property( - TARGET pybind11::windows_extras - APPEND - PROPERTY INTERFACE_COMPILE_OPTIONS $<$>:$<$:/MP>>) + if(CMAKE_VERSION VERSION_LESS 3.11) + set_property( + TARGET pybind11::windows_extras + APPEND + PROPERTY INTERFACE_COMPILE_OPTIONS $<$>:/MP>) + else() + # Only set these options for C++ files. This is important so that, for + # instance, projects that include other types of source files like CUDA + # .cu files don't get these options propagated to nvcc since that would + # cause the build to fail. + set_property( + TARGET pybind11::windows_extras + APPEND + PROPERTY INTERFACE_COMPILE_OPTIONS + $<$>:$<$:/MP>>) + endif() endif() endif() @@ -369,7 +397,11 @@ function(_pybind11_generate_lto target prefer_thin_lto) set(is_debug "$,$>") set(not_debug "$") set(cxx_lang "$") - set(genex "$") + if(MSVC AND CMAKE_VERSION VERSION_LESS 3.11) + set(genex "${not_debug}") + else() + set(genex "$") + endif() set_property( TARGET ${target} APPEND @@ -384,10 +416,17 @@ function(_pybind11_generate_lto target prefer_thin_lto) endif() if(PYBIND11_LTO_LINKER_FLAGS) - set_property( - TARGET ${target} - APPEND - PROPERTY INTERFACE_LINK_OPTIONS "$<${not_debug}:${PYBIND11_LTO_LINKER_FLAGS}>") + if(CMAKE_VERSION VERSION_LESS 3.11) + set_property( + TARGET ${target} + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES "$<${not_debug}:${PYBIND11_LTO_LINKER_FLAGS}>") + else() + set_property( + TARGET ${target} + APPEND + PROPERTY INTERFACE_LINK_OPTIONS "$<${not_debug}:${PYBIND11_LTO_LINKER_FLAGS}>") + endif() endif() endfunction() diff --git a/pybind11/tools/pybind11GuessPythonExtSuffix.cmake b/pybind11/tools/pybind11GuessPythonExtSuffix.cmake index b550f3935..c5fb3b42c 100644 --- a/pybind11/tools/pybind11GuessPythonExtSuffix.cmake +++ b/pybind11/tools/pybind11GuessPythonExtSuffix.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.15...3.30) +cmake_minimum_required(VERSION 3.5) function(pybind11_guess_python_module_extension python) diff --git a/pybind11/tools/pybind11NewTools.cmake b/pybind11/tools/pybind11NewTools.cmake index 087784c22..a8b0800bb 100644 --- a/pybind11/tools/pybind11NewTools.cmake +++ b/pybind11/tools/pybind11NewTools.cmake @@ -5,6 +5,10 @@ # All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. +if(CMAKE_VERSION VERSION_LESS 3.12) + message(FATAL_ERROR "You cannot use the new FindPython module with CMake < 3.12") +endif() + include_guard(DIRECTORY) get_property( @@ -52,7 +56,7 @@ if(NOT Python_FOUND AND NOT Python3_FOUND) endif() find_package( - Python 3.8 REQUIRED COMPONENTS ${_pybind11_interp_component} ${_pybind11_dev_component} + Python 3.7 REQUIRED COMPONENTS ${_pybind11_interp_component} ${_pybind11_dev_component} ${_pybind11_quiet} ${_pybind11_global_keyword}) # If we are in submodule mode, export the Python targets to global targets. @@ -171,16 +175,6 @@ if(NOT _PYBIND11_CROSSCOMPILING) set(PYTHON_MODULE_EXTENSION "${_PYTHON_MODULE_EXTENSION}" CACHE INTERNAL "") - if((NOT "$ENV{SETUPTOOLS_EXT_SUFFIX}" STREQUAL "") - AND (NOT "$ENV{SETUPTOOLS_EXT_SUFFIX}" STREQUAL "${PYTHON_MODULE_EXTENSION}")) - message( - AUTHOR_WARNING, - "SETUPTOOLS_EXT_SUFFIX is set to \"$ENV{SETUPTOOLS_EXT_SUFFIX}\", " - "but the auto-calculated Python extension suffix is \"${PYTHON_MODULE_EXTENSION}\". " - "This may cause problems when importing the Python extensions. " - "If you are using cross-compiling Python, you may need to " - "set PYTHON_MODULE_EXTENSION manually.") - endif() endif() endif() else() @@ -242,6 +236,7 @@ if(TARGET ${_Python}::Python) PROPERTY INTERFACE_LINK_LIBRARIES ${_Python}::Python) endif() +# CMake 3.15+ has this if(TARGET ${_Python}::Module) set_property( TARGET pybind11::module @@ -284,6 +279,10 @@ function(pybind11_add_module target_name) target_link_libraries(${target_name} PRIVATE pybind11::embed) endif() + if(MSVC) + target_link_libraries(${target_name} PRIVATE pybind11::windows_extras) + endif() + # -fvisibility=hidden is required to allow multiple modules compiled against # different pybind versions to work properly, and for some features (e.g. # py::module_local). We force it on everything inside the `pybind11` @@ -317,7 +316,7 @@ function(pybind11_add_module target_name) if(DEFINED CMAKE_BUILD_TYPE) # see https://github.com/pybind/pybind11/issues/4454 # Use case-insensitive comparison to match the result of $ string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) - if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO|NONE) + if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO) # Strip unnecessary sections of the binary on Linux/macOS pybind11_strip(${target_name}) endif() diff --git a/pybind11/tools/pybind11Tools.cmake b/pybind11/tools/pybind11Tools.cmake index b4df3df5b..bed5e0803 100644 --- a/pybind11/tools/pybind11Tools.cmake +++ b/pybind11/tools/pybind11Tools.cmake @@ -5,7 +5,13 @@ # All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. -include_guard(GLOBAL) +# include_guard(global) (pre-CMake 3.10) +if(TARGET pybind11::python_headers) + return() +endif() + +# Built-in in CMake 3.5+ +include(CMakeParseArguments) if(pybind11_FIND_QUIETLY) set(_pybind11_quiet QUIET) @@ -37,7 +43,7 @@ endif() # A user can set versions manually too set(Python_ADDITIONAL_VERSIONS - "3.12;3.11;3.10;3.9;3.8" + "3.12;3.11;3.10;3.9;3.8;3.7" CACHE INTERNAL "") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") @@ -110,19 +116,36 @@ if(PYTHON_IS_DEBUG) PROPERTY INTERFACE_COMPILE_DEFINITIONS Py_DEBUG) endif() -# The IMPORTED INTERFACE library here is to ensure that "debug" and "release" get processed outside -# of a generator expression - https://gitlab.kitware.com/cmake/cmake/-/issues/18424, as they are -# target_link_library keywords rather than real libraries. -add_library(pybind11::_ClassicPythonLibraries IMPORTED INTERFACE) -target_link_libraries(pybind11::_ClassicPythonLibraries INTERFACE ${PYTHON_LIBRARIES}) -target_link_libraries( - pybind11::module - INTERFACE - pybind11::python_link_helper - "$<$,$>:pybind11::_ClassicPythonLibraries>") +# The <3.11 code here does not support release/debug builds at the same time, like on vcpkg +if(CMAKE_VERSION VERSION_LESS 3.11) + set_property( + TARGET pybind11::module + APPEND + PROPERTY + INTERFACE_LINK_LIBRARIES + pybind11::python_link_helper + "$<$,$>:$>" + ) -target_link_libraries(pybind11::embed INTERFACE pybind11::pybind11 - pybind11::_ClassicPythonLibraries) + set_property( + TARGET pybind11::embed + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES pybind11::pybind11 $) +else() + # The IMPORTED INTERFACE library here is to ensure that "debug" and "release" get processed outside + # of a generator expression - https://gitlab.kitware.com/cmake/cmake/-/issues/18424, as they are + # target_link_library keywords rather than real libraries. + add_library(pybind11::_ClassicPythonLibraries IMPORTED INTERFACE) + target_link_libraries(pybind11::_ClassicPythonLibraries INTERFACE ${PYTHON_LIBRARIES}) + target_link_libraries( + pybind11::module + INTERFACE + pybind11::python_link_helper + "$<$,$>:pybind11::_ClassicPythonLibraries>") + + target_link_libraries(pybind11::embed INTERFACE pybind11::pybind11 + pybind11::_ClassicPythonLibraries) +endif() function(pybind11_extension name) # The prefix and extension are provided by FindPythonLibsNew.cmake @@ -196,7 +219,7 @@ function(pybind11_add_module target_name) if(DEFINED CMAKE_BUILD_TYPE) # see https://github.com/pybind/pybind11/issues/4454 # Use case-insensitive comparison to match the result of $ string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) - if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO|NONE) + if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO) pybind11_strip(${target_name}) endif() endif() diff --git a/pybind11/tools/setup_global.py.in b/pybind11/tools/setup_global.py.in index 99b8a2b29..885ac5c72 100644 --- a/pybind11/tools/setup_global.py.in +++ b/pybind11/tools/setup_global.py.in @@ -26,14 +26,12 @@ class InstallHeadersNested(install_headers): main_headers = glob.glob("pybind11/include/pybind11/*.h") -conduit_headers = sum([glob.glob(f"pybind11/include/pybind11/conduit/*.{ext}") - for ext in ("h", "txt")], []) detail_headers = glob.glob("pybind11/include/pybind11/detail/*.h") eigen_headers = glob.glob("pybind11/include/pybind11/eigen/*.h") stl_headers = glob.glob("pybind11/include/pybind11/stl/*.h") cmake_files = glob.glob("pybind11/share/cmake/pybind11/*.cmake") pkgconfig_files = glob.glob("pybind11/share/pkgconfig/*.pc") -headers = main_headers + conduit_headers + detail_headers + eigen_headers + stl_headers +headers = main_headers + detail_headers + stl_headers + eigen_headers cmdclass = {"install_headers": InstallHeadersNested} $extra_cmd @@ -57,7 +55,6 @@ setup( (base + "share/cmake/pybind11", cmake_files), (base + "share/pkgconfig", pkgconfig_files), (base + "include/pybind11", main_headers), - (base + "include/pybind11/conduit", conduit_headers), (base + "include/pybind11/detail", detail_headers), (base + "include/pybind11/eigen", eigen_headers), (base + "include/pybind11/stl", stl_headers), diff --git a/pybind11/tools/setup_main.py.in b/pybind11/tools/setup_main.py.in index e04dc8204..6358cc7b9 100644 --- a/pybind11/tools/setup_main.py.in +++ b/pybind11/tools/setup_main.py.in @@ -14,18 +14,15 @@ setup( packages=[ "pybind11", "pybind11.include.pybind11", - "pybind11.include.pybind11.conduit", "pybind11.include.pybind11.detail", "pybind11.include.pybind11.eigen", "pybind11.include.pybind11.stl", - "pybind11.share", "pybind11.share.cmake.pybind11", "pybind11.share.pkgconfig", ], package_data={ "pybind11": ["py.typed"], "pybind11.include.pybind11": ["*.h"], - "pybind11.include.pybind11.conduit": ["*.h", "*.txt"], "pybind11.include.pybind11.detail": ["*.h"], "pybind11.include.pybind11.eigen": ["*.h"], "pybind11.include.pybind11.stl": ["*.h"], @@ -41,10 +38,7 @@ setup( ], "pipx.run": [ "pybind11 = pybind11.__main__:main", - ], - "pkg_config": [ - "pybind11 = pybind11.share.pkgconfig", - ], + ] }, cmdclass=cmdclass ) diff --git a/pybind11/tools/test-pybind11GuessPythonExtSuffix.cmake b/pybind11/tools/test-pybind11GuessPythonExtSuffix.cmake index ac90e039b..0de2c0169 100644 --- a/pybind11/tools/test-pybind11GuessPythonExtSuffix.cmake +++ b/pybind11/tools/test-pybind11GuessPythonExtSuffix.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.15...3.30) +cmake_minimum_required(VERSION 3.5) # Tests for pybind11_guess_python_module_extension # Run using `cmake -P tools/test-pybind11GuessPythonExtSuffix.cmake`