Merging 'master' into 'wrap'
						commit
						641496f2d3
					
				|  | @ -12,17 +12,6 @@ template <op_id id, op_type ot, typename L, typename R> | |||
|     template <detail::op_id id, detail::op_type ot, typename L, typename R, typename... Extra> | ||||
|     class_ &def(const detail::op_<id, ot, L, R> &op, const Extra &...extra) { | ||||
|     class_ &def_cast(const detail::op_<id, ot, L, R> &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<zombie>()->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){}; | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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: | | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
|  | @ -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*" | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 <list> 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 | ||||
|  |  | |||
|  | @ -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 <http://rgl.epfl.ch/people/wjakob>`_. 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. | ||||
|  |  | |||
|  | @ -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 <https://docs.python.org/3/c-api/index.html>`_ | ||||
| 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<T>`` 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<inty> { | ||||
|         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<user_space::Point2D> { | ||||
|         // 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<py::sequence>(src)) { | ||||
|                 return false; | ||||
|             } | ||||
|             auto seq = py::reinterpret_borrow<py::sequence>(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<py::float_>(item) && !py::isinstance<py::int_>(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<double>(); | ||||
|             value.y = seq[1].cast<double>(); | ||||
|             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 <https://en.cppreference.com/w/cpp/language/definition>`_). 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 <https://github.com/annotated-types/annotated-types>`_. | ||||
|  |  | |||
|  | @ -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<T>``       | 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``. | ||||
|  |  | |||
|  | @ -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<int>``. 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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<T>``. | ||||
|     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_<Animal, PyAnimal /* <--- trampoline */, py::smart_holder>(m, "Animal") | ||||
|         py::class_<Animal, PyAnimal /* <--- trampoline*/>(m, "Animal") | ||||
|             .def(py::init<>()) | ||||
|             .def("go", &Animal::go); | ||||
| 
 | ||||
|         py::class_<Dog, Animal, py::smart_holder>(m, "Dog") | ||||
|         py::class_<Dog, Animal>(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_<Animal, PyAnimal /* <--- trampoline */, py::smart_holder>(m, "Animal"); | ||||
|     py::class_<Animal, PyAnimal /* <--- trampoline*/>(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 AnimalBase = Animal> | ||||
|     class PyAnimal : public AnimalBase, py::trampoline_self_life_support { | ||||
|     template <class AnimalBase = Animal> 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 DogBase = Dog> | ||||
|     class PyDog : public PyAnimal<DogBase>, py::trampoline_self_life_support { | ||||
|     template <class DogBase = Dog> class PyDog : public PyAnimal<DogBase> { | ||||
|     public: | ||||
|         using PyAnimal<DogBase>::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_<Animal, PyAnimal<>, py::smart_holder> animal(m, "Animal"); | ||||
|     py::class_<Dog, Animal, PyDog<>, py::smart_holder> dog(m, "Dog"); | ||||
|     py::class_<Husky, Dog, PyDog<Husky>, py::smart_holder> husky(m, "Husky"); | ||||
|     py::class_<Animal, PyAnimal<>> animal(m, "Animal"); | ||||
|     py::class_<Dog, Animal, PyDog<>> dog(m, "Dog"); | ||||
|     py::class_<Husky, Dog, PyDog<Husky>> 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_<Example, PyExample, py::smart_holder>(m, "Example") | ||||
|     py::class_<Example, PyExample>(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_<pets::Pet>(m, "Pet") | ||||
|     py::class<pets::Pet>(m, "Pet") | ||||
|         .def("name", &pets::Pet::name); | ||||
| 
 | ||||
|     // Binding for local extension class: | ||||
|     py::class_<Dog, pets::Pet>(m, "Dog") | ||||
|     py::class<Dog, pets::Pet>(m, "Dog") | ||||
|         .def(py::init<std::string>()); | ||||
| 
 | ||||
| .. 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_<pets::Pet>(m, "Pet") | ||||
|     py::class<pets::Pet>(m, "Pet") | ||||
|         .def("get_name", &pets::Pet::name); | ||||
| 
 | ||||
|     // Binding for local extending class: | ||||
|     py::class_<Cat, pets::Pet>(m, "Cat") | ||||
|     py::class<Cat, pets::Pet>(m, "Cat") | ||||
|         .def(py::init<std::string>()); | ||||
| 
 | ||||
| .. code-block:: pycon | ||||
|  | @ -995,13 +981,13 @@ the ``py::class_`` constructor: | |||
| .. code-block:: cpp | ||||
| 
 | ||||
|     // Pet binding in dogs.cpp: | ||||
|     py::class_<pets::Pet>(m, "Pet", py::module_local()) | ||||
|     py::class<pets::Pet>(m, "Pet", py::module_local()) | ||||
|         .def("name", &pets::Pet::name); | ||||
| 
 | ||||
| .. code-block:: cpp | ||||
| 
 | ||||
|     // Pet binding in cats.cpp: | ||||
|     py::class_<pets::Pet>(m, "Pet", py::module_local()) | ||||
|     py::class<pets::Pet>(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_<A, Trampoline, py::smart_holder>(m, "A") // <-- `Trampoline` here | ||||
|     py::class_<A, Trampoline>(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_<MyClass<int>>(m, "MyClassT") | ||||
|     py::class<MyClass<int>>(m, "MyClassT") | ||||
|         .def("fn", &MyClass<int>::fn<std::string>); | ||||
| 
 | ||||
| Custom automatic downcasters | ||||
|  |  | |||
|  | @ -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<bool> 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<bool> 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. | ||||
| *   <kbd>Ctrl</kbd>-<kbd>C</kbd> sends `SIGINT`, <kbd>Ctrl</kbd>-<kbd>\\</kbd> | ||||
|     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 | ||||
|  | @ -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)` | ||||
|  |  | |||
|  | @ -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 | ||||
| <https://docs.python.org/3/library/sys.html#sys.unraisablehook>`_ | ||||
| and an auditing event is logged. | ||||
| 
 | ||||
|  |  | |||
|  | @ -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     | | ||||
|  |  | |||
|  | @ -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, PyAnimal, py::smart_holder> animal(m, "Animal"); | ||||
|         py::class_<Animal, PyAnimal> animal(m, "Animal"); | ||||
|         animal | ||||
|             .def(py::init<>()) | ||||
|             .def("go", &Animal::go); | ||||
| 
 | ||||
|         py::class_<Dog, py::smart_holder>(m, "Dog", animal) | ||||
|         py::class_<Dog>(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_<Dog>``. | ||||
| to indicate the inheritance relationship to the constructor of ``class_<Dog>``. | ||||
| 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: | ||||
|  |  | |||
|  | @ -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<T>``. | ||||
| 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_<T>`` to | ||||
| 
 | ||||
| * ``py::class_<T, py::smart_holder>``. | ||||
| 
 | ||||
| .. note:: | ||||
| 
 | ||||
|     A shorthand, ``py::classh<T>``, is provided for | ||||
|     ``py::class_<T, py::smart_holder>``. 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<T>`` and ``std::shared_ptr<T>`` **simultaneously**. | ||||
| 
 | ||||
| * Passing a Python object back to C++ via ``std::unique_ptr<T>``, 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<T>`` or ``std::shared_ptr<T>`` 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 | ||||
|   <http://en.cppreference.com/w/cpp/memory/enable_shared_from_this>`_). | ||||
| 
 | ||||
| 
 | ||||
| ``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_<Example>``: | ||||
| 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_<Example>``: | |||
| 
 | ||||
|     m.def("create_example", &create_example); | ||||
| 
 | ||||
| However, this will fail with ``py::class_<Example>`` (but works with | ||||
| ``py::class_<Example, py::smart_holder>``): | ||||
| 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<Example> 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 | ||||
|     <https://github.com/pybind/pybind11/blob/30eb39ed79d1e2eeff15219ac00773034300a5e6/include/pybind11/cast.h#L235>`_. | ||||
|     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<Type>``, 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_<Example, std::shared_ptr<Example> /* <- holder type */>(m, "Example"); | ||||
|     py::class_<Example, std::shared_ptr<Example> /* <- holder type */> obj(m, "Example"); | ||||
| 
 | ||||
| Compared to using ``py::class_<Example, py::smart_holder>``, 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<T>`` 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>()) { } | ||||
|        Child *get_child() { return child.get(); }  /* Hint: ** DON'T DO THIS ** */ | ||||
|     private: | ||||
|         std::shared_ptr<Child> child; | ||||
|     }; | ||||
| 
 | ||||
|     PYBIND11_MODULE(example, m) { | ||||
|         py::class_<Child, std::shared_ptr<Child>>(m, "Child"); | ||||
| 
 | ||||
|         py::class_<Parent, std::shared_ptr<Parent>>(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<Child> get_child() { return child; } | ||||
| 
 | ||||
| 2. Adjust the definition of ``Child`` by specifying | ||||
|    ``std::enable_shared_from_this<T>`` (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<Child> { }; | ||||
| 
 | ||||
| .. _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>()) { } | ||||
|        Child *get_child() { return child.get(); }  /* DANGER */ | ||||
|     private: | ||||
|         std::shared_ptr<Child> child; | ||||
|     }; | ||||
| 
 | ||||
|     PYBIND11_MODULE(example, m) { | ||||
|         py::class_<Child, std::shared_ptr<Child>>(m, "Child"); | ||||
| 
 | ||||
|         py::class_<Parent, std::shared_ptr<Parent>>(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<Child>`` is lost. Therefore pybind11 | ||||
| will create a second independent ``std::shared_ptr<Child>`` 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<Child>`` as a base class for | ||||
| ``Child``, or adding a member function to ``Parent`` that returns | ||||
| ``std::shared_ptr<Child>``), or if that is not feasible, by using | ||||
| ``return_value_policy::reference_internal``. What is the best approach | ||||
| depends on the exact situation. | ||||
|  |  | |||
|  | @ -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:: | ||||
| 
 | ||||
|  |  | |||
|  | @ -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}>("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("}") | ||||
|  |  | |||
|  | @ -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. | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -0,0 +1,105 @@ | |||
| .. _installing: | ||||
| 
 | ||||
| Installing the library | ||||
| ###################### | ||||
| 
 | ||||
| There are several ways to get the pybind11 source, which lives at | ||||
| `pybind/pybind11 on GitHub <https://github.com/pybind/pybind11>`_. 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 | ||||
| <https://github.com/conda-forge/pybind11-feedstock>`_: | ||||
| 
 | ||||
| .. code-block:: bash | ||||
| 
 | ||||
|     conda install -c conda-forge pybind11 | ||||
| 
 | ||||
| 
 | ||||
| Include with vcpkg | ||||
| ================== | ||||
| You can download and install pybind11 using the Microsoft `vcpkg | ||||
| <https://github.com/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 <https://github.com/Microsoft/vcpkg/>`_ on the vcpkg | ||||
| repository. | ||||
| 
 | ||||
| Global install with brew | ||||
| ======================== | ||||
| 
 | ||||
| The brew package manager (Homebrew on macOS, or Linuxbrew on Linux) has a | ||||
| `pybind11 package | ||||
| <https://github.com/Homebrew/homebrew-core/blob/master/Formula/pybind11.rb>`_. | ||||
| 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 | ||||
| <https://repology.org/project/python:pybind11/versions>`_; these are maintained | ||||
| by various packagers and the community. | ||||
|  | @ -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: | ||||
|  |  | |||
|  | @ -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 \ | ||||
|  |  | |||
|  | @ -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 <https://numpy.org/devdocs/numpy_2_0_migration_guide.html>`_ | ||||
| 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). | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<module_local> : process_attribute_default<module_local> | |||
|     static void init(const module_local &l, type_record *r) { r->module_local = l.value; } | ||||
| }; | ||||
| 
 | ||||
| template <> | ||||
| struct process_attribute<release_gil_before_calling_cpp_dtor> | ||||
|     : process_attribute_default<release_gil_before_calling_cpp_dtor> { | ||||
|     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<prepend> : process_attribute_default<prepend> { | ||||
|  |  | |||
|  | @ -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<object>(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 <typename, typename SFINAE = void> | ||||
| 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 <typename type> | ||||
| struct copyable_holder_caster< | ||||
|     type, | ||||
|     std::shared_ptr<type>, | ||||
|     enable_if_t<copyable_holder_caster_shared_ptr_with_smart_holder_support_enabled<type>::value>> | ||||
|     : public type_caster_base<type> { | ||||
| public: | ||||
|     using base = type_caster_base<type>; | ||||
|     static_assert(std::is_base_of<base, type_caster<type>>::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<copyable_holder_caster<type, std::shared_ptr<type>>>( | ||||
|                 src, convert)) { | ||||
|             sh_load_helper.maybe_set_python_instance_is_alias(src); | ||||
|             return true; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     explicit operator std::shared_ptr<type> *() { | ||||
|         if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { | ||||
|             pybind11_fail("Passing `std::shared_ptr<T> *` from Python to C++ is not supported " | ||||
|                           "(inherently unsafe)."); | ||||
|         } | ||||
|         return std::addressof(shared_ptr_storage); | ||||
|     } | ||||
| 
 | ||||
|     explicit operator std::shared_ptr<type> &() { | ||||
|         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<type> &src, return_value_policy policy, handle parent) { | ||||
|         const auto *ptr = src.get(); | ||||
|         auto st = type_caster_base<type>::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<type>::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<type> 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<std::shared_ptr<type>>(); | ||||
|             return; | ||||
|         } | ||||
|         throw cast_error("Unable to cast from non-held to held instance (T& to Holder<T>) " | ||||
| #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) | ||||
|                          "(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for " | ||||
|                          "type information)"); | ||||
| #else | ||||
|                          "of type '" | ||||
|                          + type_id<std::shared_ptr<type>>() + "''"); | ||||
| #endif | ||||
|     } | ||||
| 
 | ||||
|     template <typename T = std::shared_ptr<type>, | ||||
|               detail::enable_if_t<!std::is_constructible<T, const T &, type *>::value, int> = 0> | ||||
|     bool try_implicit_casts(handle, bool) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     template <typename T = std::shared_ptr<type>, | ||||
|               detail::enable_if_t<std::is_constructible<T, const T &, type *>::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<type>(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<remove_cv_t<type>> sh_load_helper; // Const2Mutbl
 | ||||
|     std::shared_ptr<type> shared_ptr_storage; | ||||
| }; | ||||
| 
 | ||||
| /// Specialize for the common std::shared_ptr, so users don't need to
 | ||||
| template <typename T> | ||||
| class type_caster<std::shared_ptr<T>> : public copyable_holder_caster<T, std::shared_ptr<T>> {}; | ||||
| 
 | ||||
| // 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<type>::name; | ||||
| }; | ||||
| 
 | ||||
| template <typename, typename SFINAE = void> | ||||
| 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 <typename type, typename deleter> | ||||
| struct move_only_holder_caster< | ||||
|     type, | ||||
|     std::unique_ptr<type, deleter>, | ||||
|     enable_if_t<move_only_holder_caster_unique_ptr_with_smart_holder_support_enabled<type>::value>> | ||||
|     : public type_caster_base<type> { | ||||
| public: | ||||
|     using base = type_caster_base<type>; | ||||
|     static_assert(std::is_base_of<base, type_caster<type>>::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<type, deleter> &&src, return_value_policy policy, handle parent) { | ||||
|         auto *ptr = src.get(); | ||||
|         auto st = type_caster_base<type>::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<type, deleter> &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<type>::cast(src.get(), policy, parent); | ||||
|     } | ||||
| 
 | ||||
|     bool load(handle src, bool convert) { | ||||
|         if (base::template load_impl< | ||||
|                 move_only_holder_caster<type, std::unique_ptr<type, deleter>>>(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<T>` from Python to C++ requires `py::class_<T, " | ||||
|                       "py::smart_holder>` (with T = " | ||||
|                       + clean_type_id(typeinfo->cpptype->name()) + ")"); | ||||
|     } | ||||
| 
 | ||||
|     template <typename T_> | ||||
|     using cast_op_type | ||||
|         = conditional_t<std::is_same<typename std::remove_volatile<T_>::type, | ||||
|                                      const std::unique_ptr<type, deleter> &>::value | ||||
|                             || std::is_same<typename std::remove_volatile<T_>::type, | ||||
|                                             const std::unique_ptr<const type, deleter> &>::value, | ||||
|                         const std::unique_ptr<type, deleter> &, | ||||
|                         std::unique_ptr<type, deleter>>; | ||||
| 
 | ||||
|     explicit operator std::unique_ptr<type, deleter>() { | ||||
|         if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { | ||||
|             return sh_load_helper.template load_as_unique_ptr<deleter>(value); | ||||
|         } | ||||
|         pybind11_fail("Expected to be UNREACHABLE: " __FILE__ ":" PYBIND11_TOSTRING(__LINE__)); | ||||
|     } | ||||
| 
 | ||||
|     explicit operator const std::unique_ptr<type, deleter> &() { | ||||
|         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<std::unique_ptr<type, deleter>>( | ||||
|                 new std::unique_ptr<type, deleter>{ | ||||
|                     sh_load_helper.template load_as_const_unique_ptr<deleter>( | ||||
|                         shared_ptr_storage.get())}, | ||||
|                 [](std::unique_ptr<type, deleter> *ptr) { | ||||
|                     if (!ptr) { | ||||
|                         pybind11_fail("FATAL: `const std::unique_ptr<T, D> &` 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<remove_cv_t<type>> sh_load_helper; // Const2Mutbl
 | ||||
|     std::shared_ptr<type> shared_ptr_storage; // Serves as a pseudo lock.
 | ||||
|     std::shared_ptr<std::unique_ptr<type, deleter>> unique_ptr_storage; | ||||
| }; | ||||
| 
 | ||||
| template <typename type, typename deleter> | ||||
| class type_caster<std::unique_ptr<type, deleter>> | ||||
|     : public move_only_holder_caster<type, std::unique_ptr<type, deleter>> {}; | ||||
|  | @ -1138,20 +863,18 @@ using type_caster_holder = conditional_t<is_copy_constructible<holder_type>::val | |||
|                                          copyable_holder_caster<type, holder_type>, | ||||
|                                          move_only_holder_caster<type, holder_type>>; | ||||
| 
 | ||||
| template <bool Value = false> | ||||
| struct always_construct_holder_value { | ||||
| template <typename T, bool Value = false> | ||||
| struct always_construct_holder { | ||||
|     static constexpr bool value = Value; | ||||
| }; | ||||
| 
 | ||||
| template <typename T, bool Value = false> | ||||
| struct always_construct_holder : always_construct_holder_value<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 <typename type>                                                                      \ | ||||
|     struct always_construct_holder<holder_type> : always_construct_holder_value<__VA_ARGS__> {};  \ | ||||
|     struct always_construct_holder<holder_type> : always_construct_holder<void, ##__VA_ARGS__> {  \ | ||||
|     };                                                                                            \ | ||||
|     template <typename type>                                                                      \ | ||||
|     class type_caster<holder_type, enable_if_t<!is_shared_ptr<holder_type>::value>>               \ | ||||
|         : public type_caster_holder<type, holder_type> {};                                        \ | ||||
|  | @ -1162,14 +885,10 @@ struct always_construct_holder : always_construct_holder_value<Value> {}; | |||
| template <typename base, typename holder> | ||||
| struct is_holder_type | ||||
|     : std::is_base_of<detail::type_caster_holder<base, holder>, detail::type_caster<holder>> {}; | ||||
| 
 | ||||
| // Specializations for always-supported holders:
 | ||||
| // Specialization for always-supported unique_ptr holders:
 | ||||
| template <typename base, typename deleter> | ||||
| struct is_holder_type<base, std::unique_ptr<base, deleter>> : std::true_type {}; | ||||
| 
 | ||||
| template <typename base> | ||||
| struct is_holder_type<base, smart_holder> : 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<args> { | ||||
|     static constexpr auto name = const_name("*args"); | ||||
| }; | ||||
| template <typename T> | ||||
| struct handle_type_name<Args<T>> { | ||||
|     static constexpr auto name = const_name("*args: ") + make_caster<T>::name; | ||||
| }; | ||||
| template <> | ||||
| struct handle_type_name<kwargs> { | ||||
|     static constexpr auto name = const_name("**kwargs"); | ||||
| }; | ||||
| template <typename T> | ||||
| struct handle_type_name<KWArgs<T>> { | ||||
|     static constexpr auto name = const_name("**kwargs: ") + make_caster<T>::name; | ||||
| }; | ||||
| template <> | ||||
| struct handle_type_name<obj_attr_accessor> { | ||||
|     static constexpr auto name = const_name<obj_attr_accessor>(); | ||||
|  | @ -1610,31 +1321,6 @@ object object_or_cast(T &&o) { | |||
|     return pybind11::cast(std::forward<T>(o)); | ||||
| } | ||||
| 
 | ||||
| // Declared in pytypes.h:
 | ||||
| // Implemented here so that make_caster<T> can be used.
 | ||||
| template <typename D> | ||||
| template <typename T> | ||||
| str_attr_accessor object_api<D>::attr_with_type_hint(const char *key) const { | ||||
| #if !defined(__cpp_inline_variables) | ||||
|     static_assert(always_false<T>::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<T>::name.text; | ||||
|     return {derived(), key}; | ||||
| } | ||||
| 
 | ||||
| template <typename D> | ||||
| template <typename T> | ||||
| obj_attr_accessor object_api<D>::attr_with_type_hint(handle key) const { | ||||
|     (void) attr_with_type_hint<T>(key.cast<std::string>().c_str()); | ||||
|     return {derived(), reinterpret_borrow<object>(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 <typename T> | ||||
|  | @ -1878,24 +1564,15 @@ struct function_call { | |||
|     handle init_self; | ||||
| }; | ||||
| 
 | ||||
| // See PR #5396 for the discussion that led to this
 | ||||
| template <typename Base, typename Derived, typename = void> | ||||
| struct is_same_or_base_of : std::is_same<Base, Derived> {}; | ||||
| 
 | ||||
| // Only evaluate is_base_of if Derived is complete.
 | ||||
| // is_base_of raises a compiler error if Derived is incomplete.
 | ||||
| template <typename Base, typename Derived> | ||||
| struct is_same_or_base_of<Base, Derived, decltype(void(sizeof(Derived)))> | ||||
|     : any_of<std::is_same<Base, Derived>, std::is_base_of<Base, Derived>> {}; | ||||
| 
 | ||||
| /// Helper class which loads arguments for C++ functions called from Python
 | ||||
| template <typename... Args> | ||||
| class argument_loader { | ||||
|     using indices = make_index_sequence<sizeof...(Args)>; | ||||
| 
 | ||||
|     template <typename Arg> | ||||
|     using argument_is_args = is_same_or_base_of<args, intrinsic_t<Arg>>; | ||||
|     using argument_is_args = std::is_same<intrinsic_t<Arg>, args>; | ||||
|     template <typename Arg> | ||||
|     using argument_is_kwargs = is_same_or_base_of<kwargs, intrinsic_t<Arg>>; | ||||
|     using argument_is_kwargs = std::is_same<intrinsic_t<Arg>, kwargs>; | ||||
|     // Get kwargs argument position, or -1 if not present:
 | ||||
|     static constexpr auto kwargs_pos = constexpr_last<argument_is_kwargs, Args...>(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,15 +0,0 @@ | |||
| NOTE | ||||
| ---- | ||||
| 
 | ||||
| The C++ code here | ||||
| 
 | ||||
| ** only depends on <Python.h> ** | ||||
| 
 | ||||
| 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. | ||||
|  | @ -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 <Python.h>. | ||||
| 
 | ||||
| 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<YourType>(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 <Python.h> | ||||
| #include <typeinfo> | ||||
| 
 | ||||
| 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<void *>(static_cast<const void *>(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 <typename T> | ||||
| 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<T *>(raw_ptr); | ||||
| } | ||||
| 
 | ||||
| } // namespace pybind11_conduit_v1
 | ||||
|  | @ -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 | ||||
|  | @ -1,72 +0,0 @@ | |||
| #pragma once | ||||
| 
 | ||||
| // Copyright (c) 2024 The pybind Community.
 | ||||
| 
 | ||||
| // STRONG REQUIREMENT:
 | ||||
| //   This header is a wrapper around `#include <Python.h>`, 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 <yvals.h> | ||||
| #    if _MSVC_STL_VERSION >= 143 | ||||
| #        include <crtdefs.h> | ||||
| #    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 <Python.h> | ||||
| #include <frameobject.h> | ||||
| #include <pythread.h> | ||||
| 
 | ||||
| #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 | ||||
|  | @ -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<PyObject *>(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<buffer_info> 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<int>(info->ndim); | ||||
|     view->shape = info->shape.data(); | ||||
|     view->strides = info->strides.data(); | ||||
|     view->readonly = static_cast<int>(info->readonly); | ||||
|     if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) { | ||||
|         view->format = const_cast<char *>(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; | ||||
| } | ||||
|  |  | |||
|  | @ -9,18 +9,13 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <pybind11/conduit/wrap_include_python_h.h> | ||||
| #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 <yvals.h> | ||||
| #        if _MSVC_STL_VERSION >= 143 | ||||
| #            include <crtdefs.h> | ||||
| #        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 <Python.h> | ||||
| #if PY_VERSION_HEX < 0x03070000 | ||||
| #    error "PYTHON < 3.7 IS UNSUPPORTED. pybind11 v2.12 was the last to support Python 3.6." | ||||
| #endif | ||||
| #include <frameobject.h> | ||||
| #include <pythread.h> | ||||
| 
 | ||||
| /* 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 <cstddef> | ||||
| #include <cstring> | ||||
| #include <exception> | ||||
|  | @ -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 <exception> 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 <version> 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<instance>::value, | ||||
|               "Internal error: `pybind11::detail::instance` is not standard layout!"); | ||||
| 
 | ||||
| // Some older compilers (e.g. gcc 9.4.0) require
 | ||||
| //     static_assert(always_false<T>::value, "...");
 | ||||
| // instead of
 | ||||
| //     static_assert(false, "...");
 | ||||
| // to trigger the static_assert() in a template only if it is actually instantiated.
 | ||||
| template <typename> | ||||
| 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 <typename Return, typename Class> | ||||
|     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 <typename Return, typename Class> | ||||
|     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) | ||||
|  |  | |||
|  | @ -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 <size_t N1, size_t N2> | ||||
| constexpr descr<N1 + N2 + 1> 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<N, Ts...> &d, const Args &...args) { | |||
| } | ||||
| #else | ||||
| template <size_t N, typename... Ts, typename... Args> | ||||
| constexpr auto concat(const descr<N, Ts...> &d, const Args &...args) | ||||
|     -> decltype(std::declval<descr<N + 2, Ts...>>() + concat(args...)) { | ||||
| constexpr auto concat(const descr<N, Ts...> &d, | ||||
|                       const Args &...args) -> decltype(std::declval<descr<N + 2, Ts...>>() | ||||
|                                                        + concat(args...)) { | ||||
|     return d + const_name(", ") + concat(args...); | ||||
| } | ||||
| #endif | ||||
|  | @ -174,15 +168,5 @@ constexpr descr<N + 2, Ts...> type_descr(const descr<N, Ts...> &descr) { | |||
|     return const_name("{") + descr + const_name("}"); | ||||
| } | ||||
| 
 | ||||
| template <size_t N, typename... Ts> | ||||
| constexpr descr<N + 4, Ts...> arg_descr(const descr<N, Ts...> &descr) { | ||||
|     return const_name("@^") + descr + const_name("@!"); | ||||
| } | ||||
| 
 | ||||
| template <size_t N, typename... Ts> | ||||
| constexpr descr<N + 4, Ts...> return_descr(const descr<N, Ts...> &descr) { | ||||
|     return const_name("@$") + descr + const_name("@!"); | ||||
| } | ||||
| 
 | ||||
| PYBIND11_NAMESPACE_END(detail) | ||||
| PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) | ||||
|  |  | |||
|  | @ -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 <type_traits> | ||||
| 
 | ||||
| PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) | ||||
| PYBIND11_NAMESPACE_BEGIN(detail) | ||||
| 
 | ||||
| template <typename To, typename From, typename SFINAE = void> | ||||
| struct dynamic_raw_ptr_cast_is_possible : std::false_type {}; | ||||
| 
 | ||||
| template <typename To, typename From> | ||||
| struct dynamic_raw_ptr_cast_is_possible< | ||||
|     To, | ||||
|     From, | ||||
|     detail::enable_if_t<!std::is_same<To, void>::value && std::is_polymorphic<From>::value>> | ||||
|     : std::true_type {}; | ||||
| 
 | ||||
| template <typename To, | ||||
|           typename From, | ||||
|           detail::enable_if_t<!dynamic_raw_ptr_cast_is_possible<To, From>::value, int> = 0> | ||||
| To *dynamic_raw_ptr_cast_if_possible(From * /*ptr*/) { | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| template <typename To, | ||||
|           typename From, | ||||
|           detail::enable_if_t<dynamic_raw_ptr_cast_is_possible<To, From>::value, int> = 0> | ||||
| To *dynamic_raw_ptr_cast_if_possible(From *ptr) { | ||||
|     return dynamic_cast<To *>(ptr); | ||||
| } | ||||
| 
 | ||||
| PYBIND11_NAMESPACE_END(detail) | ||||
| PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) | ||||
|  | @ -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<ExceptionTranslator> &exception_translators, | ||||
|             std::forward_list<ExceptionTranslator> &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!"); | ||||
|  |  | |||
|  | @ -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<Class> *alias_ptr, bool) { | |||
| // holder.  This also handles types like std::shared_ptr<T> and std::unique_ptr<T> where T is a
 | ||||
| // derived type (through those holder's implicit conversion from derived class holder
 | ||||
| // constructors).
 | ||||
| template <typename Class, detail::enable_if_t<!is_smart_holder<Holder<Class>>::value, int> = 0> | ||||
| template <typename Class> | ||||
| void construct(value_and_holder &v_h, Holder<Class> holder, bool need_alias) { | ||||
|     PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias); | ||||
|     auto *ptr = holder_helper<Holder<Class>>::get(holder); | ||||
|  | @ -198,74 +197,6 @@ void construct(value_and_holder &v_h, Alias<Class> &&result, bool) { | |||
|     v_h.value_ptr() = new Alias<Class>(std::move(result)); | ||||
| } | ||||
| 
 | ||||
| template <typename T, typename D> | ||||
| smart_holder init_smart_holder_from_unique_ptr(std::unique_ptr<T, D> &&unq_ptr, | ||||
|                                                bool void_cast_raw_ptr) { | ||||
|     void *void_ptr = void_cast_raw_ptr ? static_cast<void *>(unq_ptr.get()) : nullptr; | ||||
|     return smart_holder::from_unique_ptr(std::move(unq_ptr), void_ptr); | ||||
| } | ||||
| 
 | ||||
| template <typename Class, | ||||
|           typename D = std::default_delete<Cpp<Class>>, | ||||
|           detail::enable_if_t<is_smart_holder<Holder<Class>>::value, int> = 0> | ||||
| void construct(value_and_holder &v_h, std::unique_ptr<Cpp<Class>, 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<Class>(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<Class>(ptr)); | ||||
|     v_h.value_ptr() = ptr; | ||||
|     v_h.type->init_instance(v_h.inst, &smhldr); | ||||
| } | ||||
| 
 | ||||
| template <typename Class, | ||||
|           typename D = std::default_delete<Alias<Class>>, | ||||
|           detail::enable_if_t<is_smart_holder<Holder<Class>>::value, int> = 0> | ||||
| void construct(value_and_holder &v_h, | ||||
|                std::unique_ptr<Alias<Class>, 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 <typename Class, detail::enable_if_t<is_smart_holder<Holder<Class>>::value, int> = 0> | ||||
| void construct(value_and_holder &v_h, std::shared_ptr<Cpp<Class>> &&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<Class>(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 <typename Class, detail::enable_if_t<is_smart_holder<Holder<Class>>::value, int> = 0> | ||||
| void construct(value_and_holder &v_h, | ||||
|                std::shared_ptr<Alias<Class>> &&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 <typename... Args> | ||||
| struct constructor { | ||||
|  | @ -479,7 +410,7 @@ struct pickle_factory<Get, Set, RetState(Self), NewInstance(ArgState)> { | |||
| 
 | ||||
|     template <typename Class, typename... Extra> | ||||
|     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( | ||||
|  |  | |||
|  | @ -15,7 +15,6 @@ | |||
| #    include <pybind11/gil.h> | ||||
| #endif | ||||
| 
 | ||||
| #include <pybind11/conduit/pybind11_platform_abi_id.h> | ||||
| #include <pybind11/pytypes.h> | ||||
| 
 | ||||
| #include <exception> | ||||
|  | @ -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<std::type_index>; | ||||
| using type_equal_to = std::equal_to<std::type_index>; | ||||
|  | @ -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<type_info *> registered_types_cpp; | ||||
|  | @ -179,26 +195,35 @@ struct internals { | |||
|     std::forward_list<ExceptionTranslator> registered_exception_translators; | ||||
|     std::unordered_map<std::string, void *> shared_data; // Custom data to be shared across
 | ||||
|                                                          // extensions
 | ||||
|     std::forward_list<std::string> static_strings;       // Stores the std::strings backing
 | ||||
|                                                          // detail::c_str()
 | ||||
| #if PYBIND11_INTERNALS_VERSION == 4 | ||||
|     std::vector<PyObject *> unused_loader_patient_stack_remove_at_v5; | ||||
| #endif | ||||
|     std::forward_list<std::string> 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<object>(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<void *>(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<type_info *> registered_types_cpp; | ||||
|     std::forward_list<ExceptionTranslator> 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<shared_loader_life_support_data *>(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 <typename F> | ||||
| 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<pymutex> 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 <typename F> | ||||
| inline auto with_instance_map(const void *ptr, const F &cb) | ||||
|     -> decltype(cb(std::declval<instance_map &>())) { | ||||
| inline auto with_instance_map(const void *ptr, | ||||
|                               const F &cb) -> decltype(cb(std::declval<instance_map &>())) { | ||||
|     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; | ||||
|  |  | |||
|  | @ -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<T *>` | ||||
|   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<void>` (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 <functional> | ||||
| #include <memory> | ||||
| #include <stdexcept> | ||||
| #include <string> | ||||
| #include <type_traits> | ||||
| #include <typeinfo> | ||||
| #include <utility> | ||||
| 
 | ||||
| // 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 <typename T> | ||||
| static constexpr bool type_has_shared_from_this(const std::enable_shared_from_this<T> *) { | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| struct guarded_delete { | ||||
|     std::weak_ptr<void> released_ptr;    // Trick to keep the smart_holder memory footprint small.
 | ||||
|     std::function<void(void *)> del_fun; // Rare case.
 | ||||
|     void (*del_ptr)(void *);             // Common case.
 | ||||
|     bool use_del_fun; | ||||
|     bool armed_flag; | ||||
|     guarded_delete(std::function<void(void *)> &&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 <typename T, typename std::enable_if<std::is_destructible<T>::value, int>::type = 0> | ||||
| inline void builtin_delete_if_destructible(void *raw_ptr) { | ||||
|     std::default_delete<T>{}(static_cast<T *>(raw_ptr)); | ||||
| } | ||||
| 
 | ||||
| template <typename T, typename std::enable_if<!std::is_destructible<T>::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 <typename T> | ||||
| guarded_delete make_guarded_builtin_delete(bool armed_flag) { | ||||
|     return guarded_delete(builtin_delete_if_destructible<T>, armed_flag); | ||||
| } | ||||
| 
 | ||||
| template <typename T, typename D> | ||||
| struct custom_deleter { | ||||
|     D deleter; | ||||
|     explicit custom_deleter(D &&deleter) : deleter{std::forward<D>(deleter)} {} | ||||
|     void operator()(void *raw_ptr) { deleter(static_cast<T *>(raw_ptr)); } | ||||
| }; | ||||
| 
 | ||||
| template <typename T, typename D> | ||||
| guarded_delete make_guarded_custom_deleter(D &&uqp_del, bool armed_flag) { | ||||
|     return guarded_delete( | ||||
|         std::function<void(void *)>(custom_deleter<T, D>(std::forward<D>(uqp_del))), armed_flag); | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| inline bool is_std_default_delete(const std::type_info &rtti_deleter) { | ||||
|     return rtti_deleter == typeid(std::default_delete<T>) | ||||
|            || rtti_deleter == typeid(std::default_delete<T const>); | ||||
| } | ||||
| 
 | ||||
| struct smart_holder { | ||||
|     const std::type_info *rtti_uqp_del = nullptr; | ||||
|     std::shared_ptr<void> 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 <typename T> | ||||
|     static void ensure_pointee_is_destructible(const char *context) { | ||||
|         if (!std::is_destructible<T>::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 <typename T, typename D> | ||||
|     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<T>(*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<T>(*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<guarded_delete>(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<T, D>() must succeed.
 | ||||
|     template <typename T, typename D> | ||||
|     std::unique_ptr<D> extract_deleter(const char *context) const { | ||||
|         const auto *gd = std::get_deleter<guarded_delete>(vptr); | ||||
|         if (gd && gd->use_del_fun) { | ||||
|             const auto &custom_deleter_ptr = gd->del_fun.template target<custom_deleter<T, D>>(); | ||||
|             if (custom_deleter_ptr == nullptr) { | ||||
|                 throw std::runtime_error( | ||||
|                     std::string("smart_holder::extract_deleter() precondition failure (") + context | ||||
|                     + ")."); | ||||
|             } | ||||
|             static_assert(std::is_copy_constructible<D>::value, | ||||
|                           "Required for compatibility with smart_holder functionality."); | ||||
|             return std::unique_ptr<D>(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 <typename T> | ||||
|     T *as_raw_ptr_unowned() const { | ||||
|         return static_cast<T *>(vptr.get()); | ||||
|     } | ||||
| 
 | ||||
|     template <typename T> | ||||
|     static smart_holder from_raw_ptr_take_ownership(T *raw_ptr, bool void_cast_raw_ptr = false) { | ||||
|         ensure_pointee_is_destructible<T>("from_raw_ptr_take_ownership"); | ||||
|         smart_holder hld; | ||||
|         auto gd = make_guarded_builtin_delete<T>(true); | ||||
|         if (void_cast_raw_ptr) { | ||||
|             hld.vptr.reset(static_cast<void *>(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 <typename T, typename D> | ||||
|     static smart_holder from_unique_ptr(std::unique_ptr<T, D> &&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<T>(*hld.rtti_uqp_del); | ||||
|         guarded_delete gd{nullptr, false}; | ||||
|         if (hld.vptr_is_using_builtin_delete) { | ||||
|             gd = make_guarded_builtin_delete<T>(true); | ||||
|         } else { | ||||
|             gd = make_guarded_custom_deleter<T, D>(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 <typename T> | ||||
|     static smart_holder from_shared_ptr(std::shared_ptr<T> shd_ptr) { | ||||
|         smart_holder hld; | ||||
|         hld.vptr = std::static_pointer_cast<void>(shd_ptr); | ||||
|         hld.vptr_is_external_shared_ptr = true; | ||||
|         hld.is_populated = true; | ||||
|         return hld; | ||||
|     } | ||||
| 
 | ||||
|     template <typename T> | ||||
|     std::shared_ptr<T> as_shared_ptr() const { | ||||
|         return std::static_pointer_cast<T>(vptr); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| } // namespace memory
 | ||||
| } // namespace pybindit
 | ||||
|  | @ -9,17 +9,13 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <pybind11/gil.h> | ||||
| #include <pybind11/pytypes.h> | ||||
| #include <pybind11/trampoline_self_life_support.h> | ||||
| 
 | ||||
| #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 <cstdint> | ||||
|  | @ -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<loader_life_support *>(PYBIND11_TLS_GET_VALUE(get_stack_tls_key())); | ||||
|  | @ -117,6 +117,7 @@ PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector<type_ | |||
|     for (handle parent : reinterpret_borrow<tuple>(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<type_ | |||
|  * The value is cached for the lifetime of the Python type. | ||||
|  */ | ||||
| inline const std::vector<detail::type_info *> &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<PyObject *>(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<smart_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<pybindit::memory::guarded_delete>(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<void>(); | ||||
|             } | ||||
|         } | ||||
|         return nullptr; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| template <typename T, typename D> | ||||
| handle smart_holder_from_unique_ptr(std::unique_ptr<T, D> &&src, | ||||
|                                     return_value_policy policy, | ||||
|                                     handle parent, | ||||
|                                     const std::pair<const void *, const type_info *> &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<void *>(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<trampoline_self_life_support>(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<smart_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<object>(make_new_instance(tinfo->type)); | ||||
|     auto *inst_raw_ptr = reinterpret_cast<instance *>(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<void *>(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<const void *>(&smhldr)); | ||||
| 
 | ||||
|     if (policy == return_value_policy::reference_internal) { | ||||
|         keep_alive_impl(inst, parent); | ||||
|     } | ||||
| 
 | ||||
|     return inst.release(); | ||||
| } | ||||
| 
 | ||||
| template <typename T, typename D> | ||||
| handle smart_holder_from_unique_ptr(std::unique_ptr<T const, D> &&src, | ||||
|                                     return_value_policy policy, | ||||
|                                     handle parent, | ||||
|                                     const std::pair<const void *, const type_info *> &st) { | ||||
|     return smart_holder_from_unique_ptr( | ||||
|         std::unique_ptr<T, D>(const_cast<T *>(src.release()), | ||||
|                               std::move(src.get_deleter())), // Const2Mutbl
 | ||||
|         policy, | ||||
|         parent, | ||||
|         st); | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| handle smart_holder_from_shared_ptr(const std::shared_ptr<T> &src, | ||||
|                                     return_value_policy policy, | ||||
|                                     handle parent, | ||||
|                                     const std::pair<const void *, const type_info *> &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<void *>(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<object>(make_new_instance(tinfo->type)); | ||||
|     auto *inst_raw_ptr = reinterpret_cast<instance *>(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<void>(src, const_cast<void *>(st.first))); | ||||
|     tinfo->init_instance(inst_raw_ptr, static_cast<const void *>(&smhldr)); | ||||
| 
 | ||||
|     if (policy == return_value_policy::reference_internal) { | ||||
|         keep_alive_impl(inst, parent); | ||||
|     } | ||||
| 
 | ||||
|     return inst.release(); | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| handle smart_holder_from_shared_ptr(const std::shared_ptr<T const> &src, | ||||
|                                     return_value_policy policy, | ||||
|                                     handle parent, | ||||
|                                     const std::pair<const void *, const type_info *> &st) { | ||||
|     return smart_holder_from_shared_ptr(std::const_pointer_cast<T>(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<PyObject *>(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 <typename T, | ||||
|           typename D, | ||||
|           typename std::enable_if<std::is_default_constructible<D>::value, int>::type = 0> | ||||
| inline std::unique_ptr<T, D> unique_with_deleter(T *raw_ptr, std::unique_ptr<D> &&deleter) { | ||||
|     if (deleter == nullptr) { | ||||
|         return std::unique_ptr<T, D>(raw_ptr); | ||||
|     } | ||||
|     return std::unique_ptr<T, D>(raw_ptr, std::move(*deleter)); | ||||
| } | ||||
| 
 | ||||
| template <typename T, | ||||
|           typename D, | ||||
|           typename std::enable_if<!std::is_default_constructible<D>::value, int>::type = 0> | ||||
| inline std::unique_ptr<T, D> unique_with_deleter(T *raw_ptr, std::unique_ptr<D> &&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<T, D>(raw_ptr, std::move(*deleter)); | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| 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<instance *>(src.ptr())->is_alias; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     static std::shared_ptr<T> make_shared_ptr_with_responsible_parent(T *raw_ptr, handle parent) { | ||||
|         return std::shared_ptr<T>(raw_ptr, shared_ptr_parent_life_support(parent.ptr())); | ||||
|     } | ||||
| 
 | ||||
|     std::shared_ptr<T> 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<T *>(void_raw_ptr), | ||||
|                                                                responsible_parent); | ||||
|             } | ||||
|             throw std::runtime_error("Non-owning holder (load_as_shared_ptr)."); | ||||
|         } | ||||
|         auto *type_raw_ptr = static_cast<T *>(void_raw_ptr); | ||||
|         if (python_instance_is_alias) { | ||||
|             auto *vptr_gd_ptr = std::get_deleter<pybindit::memory::guarded_delete>(hld.vptr); | ||||
|             if (vptr_gd_ptr != nullptr) { | ||||
|                 std::shared_ptr<void> released_ptr = vptr_gd_ptr->released_ptr.lock(); | ||||
|                 if (released_ptr) { | ||||
|                     return std::shared_ptr<T>(released_ptr, type_raw_ptr); | ||||
|                 } | ||||
|                 std::shared_ptr<T> 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<shared_ptr_trampoline_self_life_support>(hld.vptr); | ||||
|             if (sptsls_ptr != nullptr) { | ||||
|                 // This code is reachable only if there are multiple registered_instances for the
 | ||||
|                 // same pointee.
 | ||||
|                 if (reinterpret_cast<PyObject *>(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<T>( | ||||
|                     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> void_shd_ptr = hld.template as_shared_ptr<void>(); | ||||
|         return std::shared_ptr<T>(void_shd_ptr, type_raw_ptr); | ||||
|     } | ||||
| 
 | ||||
|     template <typename D> | ||||
|     std::unique_ptr<T, D> load_as_unique_ptr(void *raw_void_ptr, | ||||
|                                              const char *context = "load_as_unique_ptr") { | ||||
|         if (!have_holder()) { | ||||
|             return unique_with_deleter<T, D>(nullptr, std::unique_ptr<D>()); | ||||
|         } | ||||
|         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<T, D>(context); | ||||
|         holder().ensure_use_count_1(context); | ||||
| 
 | ||||
|         T *raw_type_ptr = static_cast<T *>(raw_void_ptr); | ||||
| 
 | ||||
|         auto *self_life_support | ||||
|             = dynamic_raw_ptr_cast_if_possible<trampoline_self_life_support>(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<D> extracted_deleter = holder().template extract_deleter<T, D>(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<T, D>(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 <typename D> | ||||
|     std::unique_ptr<T, D> | ||||
|     load_as_const_unique_ptr(T *raw_type_ptr, const char *context = "load_as_const_unique_ptr") { | ||||
|         if (!have_holder()) { | ||||
|             return unique_with_deleter<T, D>(nullptr, std::unique_ptr<D>()); | ||||
|         } | ||||
|         holder().template ensure_compatible_rtti_uqp_del<T, D>(context); | ||||
|         return unique_with_deleter<T, D>( | ||||
|             raw_type_ptr, std::move(holder().template extract_deleter<T, D>(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<void>(); | ||||
|                 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 <typename T, typename = enable_if_t<is_copy_constructible<T>::value>> | ||||
|     static auto make_copy_constructor(const T *) | ||||
|         -> decltype(new T(std::declval<const T>()), Constructor{}) { | ||||
|     static auto make_copy_constructor(const T *) -> decltype(new T(std::declval<const T>()), | ||||
|                                                              Constructor{}) { | ||||
|         return [](const void *arg) -> void * { return new T(*reinterpret_cast<const T *>(arg)); }; | ||||
|     } | ||||
| 
 | ||||
|     template <typename T, typename = enable_if_t<is_move_constructible<T>::value>> | ||||
|     static auto make_move_constructor(const T *) | ||||
|         -> decltype(new T(std::declval<T &&>()), Constructor{}) { | ||||
|     static auto make_move_constructor(const T *) -> decltype(new T(std::declval<T &&>()), | ||||
|                                                              Constructor{}) { | ||||
|         return [](const void *arg) -> void * { | ||||
|             return new T(std::move(*const_cast<T *>(reinterpret_cast<const T *>(arg)))); | ||||
|         }; | ||||
|  |  | |||
|  | @ -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 <type_traits> | ||||
| 
 | ||||
| PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) | ||||
| 
 | ||||
| using pybindit::memory::smart_holder; | ||||
| 
 | ||||
| PYBIND11_NAMESPACE_BEGIN(detail) | ||||
| 
 | ||||
| template <typename H> | ||||
| using is_smart_holder = std::is_same<H, smart_holder>; | ||||
| 
 | ||||
| PYBIND11_NAMESPACE_END(detail) | ||||
| PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) | ||||
|  | @ -7,7 +7,6 @@ | |||
| #include "common.h" | ||||
| 
 | ||||
| #include <cstddef> | ||||
| #include <cstdint> | ||||
| #include <typeinfo> | ||||
| 
 | ||||
| PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) | ||||
|  |  | |||
|  | @ -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<Scalar>::name + io_name("", "]") + const_name(", \"[") | ||||
|         = const_name("numpy.ndarray[") + npy_format_descriptor<Scalar>::name + const_name("[") | ||||
|           + const_name<fixed_rows>(const_name<(size_t) rows>(), const_name("m")) + const_name(", ") | ||||
|           + const_name<fixed_cols>(const_name<(size_t) cols>(), const_name("n")) | ||||
|           + const_name("]\"") | ||||
|           + const_name<fixed_cols>(const_name<(size_t) cols>(), const_name("n")) + const_name("]") | ||||
|           + | ||||
|           // For a reference type (e.g. Ref<MatrixXd>) 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<show_writeable>(", \"flags.writeable\"", "") | ||||
|           + const_name<show_c_contiguous>(", \"flags.c_contiguous\"", "") | ||||
|           + const_name<show_f_contiguous>(", \"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<show_writeable>(", flags.writeable", "") | ||||
|           + const_name<show_c_contiguous>(", flags.c_contiguous", "") | ||||
|           + const_name<show_f_contiguous>(", 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<Type, enable_if_t<is_eigen_dense_plain<Type>::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<array>(eigen_ref_array<props>(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
 | ||||
|  |  | |||
|  | @ -124,16 +124,13 @@ struct eigen_tensor_helper< | |||
| template <typename Type, bool ShowDetails, bool NeedsWriteable = false> | ||||
| struct get_tensor_descriptor { | ||||
|     static constexpr auto details | ||||
|         = const_name<NeedsWriteable>(", \"flags.writeable\"", "") + const_name | ||||
|               < static_cast<int>(Type::Layout) | ||||
|           == static_cast<int>(Eigen::RowMajor) | ||||
|                  > (", \"flags.c_contiguous\"", ", \"flags.f_contiguous\""); | ||||
|         = const_name<NeedsWriteable>(", flags.writeable", "") | ||||
|           + const_name<static_cast<int>(Type::Layout) == static_cast<int>(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<typename Type::Scalar>::name + io_name("", "]") | ||||
|           + const_name(", \"[") + eigen_tensor_helper<remove_cv_t<Type>>::dimensions_descriptor | ||||
|           + const_name("]\"") + const_name<ShowDetails>(details, const_name("")) + const_name("]"); | ||||
|         = const_name("numpy.ndarray[") + npy_format_descriptor<typename Type::Scalar>::name | ||||
|           + const_name("[") + eigen_tensor_helper<remove_cv_t<Type>>::dimensions_descriptor | ||||
|           + const_name("]") + const_name<ShowDetails>(details, const_name("")) + const_name("]"); | ||||
| }; | ||||
| 
 | ||||
| // When EIGEN_AVOID_STL_ARRAY is defined, Eigen::DSizes<T, 0> does not have the begin() member
 | ||||
|  | @ -505,10 +502,7 @@ protected: | |||
|     std::unique_ptr<MapType> 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<Type, true, needs_writeable>::value); | ||||
|     static constexpr auto name = get_tensor_descriptor<Type, true, needs_writeable>::value; | ||||
|     explicit operator MapType *() { return value.get(); } | ||||
|     explicit operator MapType &() { return *value; } | ||||
|     explicit operator MapType &&() && { return std::move(*value); } | ||||
|  |  | |||
|  | @ -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<size_t>(argc); | ||||
|     // SetArgv* on python 3 takes wchar_t, so we have to convert.
 | ||||
|     std::unique_ptr<wchar_t *[]> widened_argv(new wchar_t *[argv_size]); | ||||
|     std::vector<std::unique_ptr<wchar_t[], detail::wide_char_arg_deleter>> 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.
 | ||||
|  |  | |||
|  | @ -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<eval_statements>(s, std::move(global), std::move(local)); | ||||
| } | ||||
| 
 | ||||
| #if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON) | ||||
| #if defined(PYPY_VERSION) | ||||
| template <eval_mode mode = eval_statements> | ||||
| 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 <eval_mode mode = eval_statements> | ||||
| 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 <eval_mode mode = eval_statements> | ||||
| 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 <eval_mode mode = eval_statements> | ||||
|  |  | |||
|  | @ -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); | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -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 <typename T> | ||||
| class gil_safe_call_once_and_store { | ||||
| public: | ||||
|  |  | |||
|  | @ -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<int>(); | ||||
|  | @ -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<PyArray_Proxy *>(ptr); } | ||||
| 
 | ||||
| inline const PyArray_Proxy *array_proxy(const void *ptr) { | ||||
|  | @ -752,13 +684,6 @@ public: | |||
|         return detail::npy_format_descriptor<typename std::remove_cv<T>::type>::dtype(); | ||||
|     } | ||||
| 
 | ||||
|     /// Return the type number associated with a C++ type.
 | ||||
|     /// This is the constexpr equivalent of `dtype::of<T>().num()`.
 | ||||
|     template <typename T> | ||||
|     static constexpr int num_of() { | ||||
|         return detail::npy_format_descriptor<typename std::remove_cv<T>::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 <typename T> | ||||
| struct npy_format_descriptor< | ||||
|     T, | ||||
|     enable_if_t<is_same_ignoring_cvref<T, PyObject *>::value | ||||
|                 || ((std::is_same<T, handle>::value || std::is_same<T, object>::value) | ||||
|                     && sizeof(T) == sizeof(PyObject *))>> { | ||||
| struct npy_format_descriptor<T, enable_if_t<is_same_ignoring_cvref<T, PyObject *>::value>> { | ||||
|     static constexpr auto name = const_name("object"); | ||||
| 
 | ||||
|     static constexpr int value = npy_api::NPY_OBJECT_; | ||||
|  | @ -2182,8 +2090,7 @@ vectorize_helper<Func, Return, Args...> vectorize_extractor(const Func &f, Retur | |||
| template <typename T, int Flags> | ||||
| struct handle_type_name<array_t<T, Flags>> { | ||||
|     static constexpr auto name | ||||
|         = io_name("typing.Annotated[numpy.typing.ArrayLike, ", "numpy.typing.NDArray[") | ||||
|           + npy_format_descriptor<T>::name + const_name("]"); | ||||
|         = const_name("numpy.ndarray[") + npy_format_descriptor<T>::name + const_name("]"); | ||||
| }; | ||||
| 
 | ||||
| PYBIND11_NAMESPACE_END(detail) | ||||
|  |  | |||
|  | @ -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 <cstring> | ||||
| #include <memory> | ||||
| #include <new> | ||||
| #include <stack> | ||||
| #include <string> | ||||
| #include <utility> | ||||
| #include <vector> | ||||
| 
 | ||||
| // 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<std::is_same<kw_only, Extra>...>::value, | ||||
|                            has_pos_only_args = any_of<std::is_same<pos_only, Extra>...>::value, | ||||
|                            has_arg_annotations = any_of<is_keyword<Extra>...>::value; | ||||
|             constexpr bool has_is_method = any_of<std::is_same<is_method, Extra>...>::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<bool> 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<text1, text2>).
 | ||||
|                 // 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<capsule>(PyCFunction_GET_SELF(m_ptr)); | ||||
|                 auto rec_capsule | ||||
|                     = reinterpret_borrow<capsule>(((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<char *>(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<char *>(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<module_> { | ||||
|     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 <typename PM> | ||||
| using must_be_member_function_pointer = enable_if_t<std::is_member_pointer<PM>::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 <typename T, typename D> | ||||
| struct property_cpp_function_classic { | ||||
|     template <typename PM, must_be_member_function_pointer<PM> = 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 <typename PM, must_be_member_function_pointer<PM> = 0> | ||||
|     static cpp_function read(PM pm, const handle &hdl) { | ||||
|         return readonly(pm, hdl); | ||||
|     } | ||||
| 
 | ||||
|     template <typename PM, must_be_member_function_pointer<PM> = 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 <typename T, typename D, typename SFINAE = void> | ||||
| struct property_cpp_function : detail::property_cpp_function_classic<T, D> {}; | ||||
| 
 | ||||
| PYBIND11_NAMESPACE_BEGIN(detail) | ||||
| 
 | ||||
| template <typename T, typename D, typename SFINAE = void> | ||||
| struct both_t_and_d_use_type_caster_base : std::false_type {}; | ||||
| 
 | ||||
| // `T` is assumed to be equivalent to `intrinsic_t<T>`.
 | ||||
| // `D` is may or may not be equivalent to `intrinsic_t<D>`.
 | ||||
| template <typename T, typename D> | ||||
| struct both_t_and_d_use_type_caster_base< | ||||
|     T, | ||||
|     D, | ||||
|     enable_if_t<all_of<std::is_base_of<type_caster_base<T>, type_caster<T>>, | ||||
|                        std::is_base_of<type_caster_base<intrinsic_t<D>>, make_caster<D>>>::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 <typename T, typename D> | ||||
| struct property_cpp_function_sh_raw_ptr_member { | ||||
|     using drp = typename std::remove_pointer<D>::type; | ||||
| 
 | ||||
|     template <typename PM, must_be_member_function_pointer<PM> = 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<drp> { | ||||
|                     std::shared_ptr<T> c_sp | ||||
|                         = type_caster<std::shared_ptr<T>>::shared_ptr_with_responsible_parent( | ||||
|                             c_hdl); | ||||
|                     D ptr = (*c_sp).*pm; | ||||
|                     return std::shared_ptr<drp>(c_sp, ptr); | ||||
|                 }, | ||||
|                 is_method(hdl)); | ||||
|         } | ||||
|         return property_cpp_function_classic<T, D>::readonly(pm, hdl); | ||||
|     } | ||||
| 
 | ||||
|     template <typename PM, must_be_member_function_pointer<PM> = 0> | ||||
|     static cpp_function read(PM pm, const handle &hdl) { | ||||
|         return readonly(pm, hdl); | ||||
|     } | ||||
| 
 | ||||
|     template <typename PM, must_be_member_function_pointer<PM> = 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<D>(std::move(value)); }, | ||||
|                                 is_method(hdl)); | ||||
|         } | ||||
|         return property_cpp_function_classic<T, D>::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 <typename T, typename D> | ||||
| struct property_cpp_function_sh_member_held_by_value { | ||||
|     template <typename PM, must_be_member_function_pointer<PM> = 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<typename std::add_const<D>::type> { | ||||
|                     std::shared_ptr<T> c_sp | ||||
|                         = type_caster<std::shared_ptr<T>>::shared_ptr_with_responsible_parent( | ||||
|                             c_hdl); | ||||
|                     return std::shared_ptr<typename std::add_const<D>::type>(c_sp, | ||||
|                                                                              &(c_sp.get()->*pm)); | ||||
|                 }, | ||||
|                 is_method(hdl)); | ||||
|         } | ||||
|         return property_cpp_function_classic<T, D>::readonly(pm, hdl); | ||||
|     } | ||||
| 
 | ||||
|     template <typename PM, must_be_member_function_pointer<PM> = 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<D> { | ||||
|                     std::shared_ptr<T> c_sp | ||||
|                         = type_caster<std::shared_ptr<T>>::shared_ptr_with_responsible_parent( | ||||
|                             c_hdl); | ||||
|                     return std::shared_ptr<D>(c_sp, &(c_sp.get()->*pm)); | ||||
|                 }, | ||||
|                 is_method(hdl)); | ||||
|         } | ||||
|         return property_cpp_function_classic<T, D>::read(pm, hdl); | ||||
|     } | ||||
| 
 | ||||
|     template <typename PM, must_be_member_function_pointer<PM> = 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<T, D>::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 <typename T, typename D> | ||||
| struct property_cpp_function_sh_unique_ptr_member { | ||||
|     template <typename PM, must_be_member_function_pointer<PM> = 0> | ||||
|     static cpp_function readonly(PM, const handle &) { | ||||
|         static_assert(!is_instantiation<std::unique_ptr, D>::value, | ||||
|                       "def_readonly cannot be used for std::unique_ptr members."); | ||||
|         return cpp_function{}; // Unreachable.
 | ||||
|     } | ||||
| 
 | ||||
|     template <typename PM, must_be_member_function_pointer<PM> = 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<T> c_sp | ||||
|                         = type_caster<std::shared_ptr<T>>::shared_ptr_with_responsible_parent( | ||||
|                             c_hdl); | ||||
|                     return D{std::move(c_sp.get()->*pm)}; | ||||
|                 }, | ||||
|                 is_method(hdl)); | ||||
|         } | ||||
|         return property_cpp_function_classic<T, D>::read(pm, hdl); | ||||
|     } | ||||
| 
 | ||||
|     template <typename PM, must_be_member_function_pointer<PM> = 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 <typename T, typename D> | ||||
| struct property_cpp_function< | ||||
|     T, | ||||
|     D, | ||||
|     detail::enable_if_t<detail::all_of<std::is_pointer<D>, | ||||
|                                        detail::both_t_and_d_use_type_caster_base<T, D>>::value>> | ||||
|     : detail::property_cpp_function_sh_raw_ptr_member<T, D> {}; | ||||
| 
 | ||||
| template <typename T, typename D> | ||||
| struct property_cpp_function<T, | ||||
|                              D, | ||||
|                              detail::enable_if_t<detail::all_of< | ||||
|                                  detail::none_of<std::is_pointer<D>, | ||||
|                                                  std::is_array<D>, | ||||
|                                                  detail::is_instantiation<std::unique_ptr, D>, | ||||
|                                                  detail::is_instantiation<std::shared_ptr, D>>, | ||||
|                                  detail::both_t_and_d_use_type_caster_base<T, D>>::value>> | ||||
|     : detail::property_cpp_function_sh_member_held_by_value<T, D> {}; | ||||
| 
 | ||||
| template <typename T, typename D> | ||||
| struct property_cpp_function< | ||||
|     T, | ||||
|     D, | ||||
|     detail::enable_if_t<detail::all_of< | ||||
|         detail::is_instantiation<std::unique_ptr, D>, | ||||
|         detail::both_t_and_d_use_type_caster_base<T, typename D::element_type>>::value>> | ||||
|     : detail::property_cpp_function_sh_unique_ptr_member<T, D> {}; | ||||
| 
 | ||||
| #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 <typename> | ||||
| using default_holder_type = smart_holder; | ||||
| #else | ||||
| template <typename T> | ||||
| using default_holder_type = std::unique_ptr<T>; | ||||
| #endif | ||||
| 
 | ||||
| template <typename type_, typename... options> | ||||
| class class_ : public detail::generic_type { | ||||
|     template <typename T> | ||||
|  | @ -1869,7 +1551,7 @@ public: | |||
|     using type = type_; | ||||
|     using type_alias = detail::exactly_one_t<is_subtype, void, options...>; | ||||
|     constexpr static bool has_alias = !std::is_void<type_alias>::value; | ||||
|     using holder_type = detail::exactly_one_t<is_holder, default_holder_type<type>, options...>; | ||||
|     using holder_type = detail::exactly_one_t<is_holder, std::unique_ptr<type>, options...>; | ||||
| 
 | ||||
|     static_assert(detail::all_of<is_valid_class_option<options>...>::value, | ||||
|                   "Unknown/invalid class_ template parameters provided"); | ||||
|  | @ -1900,16 +1582,8 @@ public: | |||
|         record.type_align = alignof(conditional_t<has_alias, type_alias, type> &); | ||||
|         record.holder_size = sizeof(holder_type); | ||||
|         record.init_instance = init_instance; | ||||
| 
 | ||||
|         if (detail::is_instantiation<std::unique_ptr, holder_type>::value) { | ||||
|             record.holder_enum_v = detail::holder_enum_t::std_unique_ptr; | ||||
|         } else if (detail::is_instantiation<std::shared_ptr, holder_type>::value) { | ||||
|             record.holder_enum_v = detail::holder_enum_t::std_shared_ptr; | ||||
|         } else if (std::is_same<holder_type, smart_holder>::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<std::unique_ptr, holder_type>::value; | ||||
| 
 | ||||
|         set_operator_new<type>(&record); | ||||
| 
 | ||||
|  | @ -1919,12 +1593,6 @@ public: | |||
|         /* Process optional arguments, if any */ | ||||
|         process_attributes<Extra...>::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<C, type>::value || std::is_base_of<C, type>::value, | ||||
|                       "def_readwrite() requires a class member (or base class member)"); | ||||
|         def_property(name, | ||||
|                      property_cpp_function<type, D>::read(pm, *this), | ||||
|                      property_cpp_function<type, D>::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<C, type>::value || std::is_base_of<C, type>::value, | ||||
|                       "def_readonly() requires a class member (or base class member)"); | ||||
|         def_property_readonly(name, | ||||
|                               property_cpp_function<type, D>::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 <typename H = holder_type, | ||||
|               detail::enable_if_t<!detail::is_smart_holder<H>::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<type>()); | ||||
|     } | ||||
| 
 | ||||
|     template <typename WrappedType> | ||||
|     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 <typename WrappedType, typename SomeBaseOfWrappedType> | ||||
|     static bool try_initialization_using_shared_from_this( | ||||
|         holder_type *uninitialized_location, | ||||
|         WrappedType *value_ptr_w_t, | ||||
|         const std::enable_shared_from_this<SomeBaseOfWrappedType> *) { | ||||
|         auto shd_ptr = std::dynamic_pointer_cast<WrappedType>( | ||||
|             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 <typename H = holder_type, | ||||
|               detail::enable_if_t<detail::is_smart_holder<H>::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<void *>(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<holder_type>()); | ||||
|         auto *value_ptr_w_t = v_h.value_ptr<type>(); | ||||
|         // Try downcast from `type` to `type_alias`:
 | ||||
|         inst->is_alias | ||||
|             = detail::dynamic_raw_ptr_cast_if_possible<type_alias>(value_ptr_w_t) != nullptr; | ||||
|         if (holder_void_ptr) { | ||||
|             // Note: inst->owned ignored.
 | ||||
|             auto *holder_ptr = static_cast<holder_type *>(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>().~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_<T> and py::class_<T, py::smart_holder>:
 | ||||
| // users can simply replace the `_` in `class_` with `h` or vice versa.
 | ||||
| template <typename type_, typename... options> | ||||
| using classh = class_<type_, smart_holder, options...>; | ||||
| 
 | ||||
| /// Binds an existing constructor taking arguments Args...
 | ||||
| template <typename... Args> | ||||
| detail::initimpl::constructor<Args...> 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<Type>(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<Base>( | ||||
|  | @ -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<decltype(internals::registered_types_py)::iterator, bool> | ||||
| 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<detail::type_info *>()); | ||||
|             .emplace(type, std::vector<detail::type_info *>()); | ||||
| #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_<state>(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>(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<ExceptionTranslator> &exception_translators, | ||||
|             std::forward_list<ExceptionTranslator> &local_exception_translators) { | ||||
|             (void) local_exception_translators; | ||||
|             exception_translators.push_front(std::forward<ExceptionTranslator>(translator)); | ||||
|         }); | ||||
|     detail::with_internals([&](detail::internals &internals) { | ||||
|         internals.registered_exception_translators.push_front( | ||||
|             std::forward<ExceptionTranslator>(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<ExceptionTranslator> &exception_translators, | ||||
|             std::forward_list<ExceptionTranslator> &local_exception_translators) { | ||||
|             (void) exception_translators; | ||||
|             local_exception_translators.push_front(std::forward<ExceptionTranslator>(translator)); | ||||
|         }); | ||||
|     detail::with_internals([&](detail::internals &internals) { | ||||
|         (void) internals; | ||||
|         detail::get_local_internals().registered_exception_translators.push_front( | ||||
|             std::forward<ExceptionTranslator>(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) { | ||||
|  |  | |||
|  | @ -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 <typename T> | ||||
|     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 <typename T> | ||||
|     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<PyTracebackObject *>(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<iterator &>(*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<iterator &>(*this); | ||||
|             self.advance(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void advance() { | ||||
|         value = reinterpret_steal<object>(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 <typename T> | ||||
| class Args : public args { | ||||
|     using args::args; | ||||
| }; | ||||
| 
 | ||||
| template <typename T> | ||||
| 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<D>::doc() const { | |||
|     return attr("__doc__"); | ||||
| } | ||||
| 
 | ||||
| template <typename D> | ||||
| object object_api<D>::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 <typename D> | ||||
| handle object_api<D>::get_type() const { | ||||
|     return type::handle_of(derived()); | ||||
|  |  | |||
|  | @ -11,14 +11,10 @@ | |||
| 
 | ||||
| #include "pybind11.h" | ||||
| #include "detail/common.h" | ||||
| #include "detail/descr.h" | ||||
| #include "detail/type_caster_base.h" | ||||
| 
 | ||||
| #include <deque> | ||||
| #include <initializer_list> | ||||
| #include <list> | ||||
| #include <map> | ||||
| #include <memory> | ||||
| #include <ostream> | ||||
| #include <set> | ||||
| #include <unordered_map> | ||||
|  | @ -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<const char *> 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 <typename T, typename U> | ||||
|  | @ -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<anyset>(src)) { | ||||
|             return false; | ||||
|         } | ||||
|         auto s = reinterpret_borrow<anyset>(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<Key &&>(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<anyset>(src)) { | ||||
|             value.clear(); | ||||
|             return convert_anyset(reinterpret_borrow<anyset>(src), convert); | ||||
|         } | ||||
|         if (!convert) { | ||||
|             return false; | ||||
|         } | ||||
|         assert(isinstance<iterable>(src)); | ||||
|         value.clear(); | ||||
|         return convert_iterable(reinterpret_borrow<iterable>(src), convert); | ||||
|     } | ||||
| 
 | ||||
|     template <typename T> | ||||
|     static handle cast(T &&src, return_value_policy policy, handle parent) { | ||||
|         if (!std::is_lvalue_reference<T>::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<dict>(src)) { | ||||
|             return false; | ||||
|         } | ||||
|         auto d = reinterpret_borrow<dict>(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<dict>(src)) { | ||||
|             return convert_elements(reinterpret_borrow<dict>(src), convert); | ||||
|         } | ||||
|         if (!convert) { | ||||
|             return false; | ||||
|         } | ||||
|         auto items = reinterpret_steal<object>(PyMapping_Items(src.ptr())); | ||||
|         if (!items) { | ||||
|             throw error_already_set(); | ||||
|         } | ||||
|         assert(isinstance<iterable>(items)); | ||||
|         return convert_elements(dict(reinterpret_borrow<iterable>(items)), convert); | ||||
|     } | ||||
| 
 | ||||
|     template <typename T> | ||||
|     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<Value>; | ||||
| 
 | ||||
|     bool load(handle src, bool convert) { | ||||
|         if (!PyObjectTypeIsConvertibleToStdVector(src.ptr())) { | ||||
|         if (!isinstance<sequence>(src) || isinstance<bytes>(src) || isinstance<str>(src)) { | ||||
|             return false; | ||||
|         } | ||||
|         if (isinstance<sequence>(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<iterable>(src)); | ||||
|         return convert_elements(tuple(reinterpret_borrow<iterable>(src)), convert); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     template <typename T = Type, enable_if_t<has_reserve_method<T>::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<sequence>(seq); | ||||
|         auto s = reinterpret_borrow<sequence>(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 <typename T = Type, enable_if_t<has_reserve_method<T>::value, int> = 0> | ||||
|     void reserve_maybe(const sequence &s, Type *) { | ||||
|         value.reserve(s.size()); | ||||
|     } | ||||
|     void reserve_maybe(const sequence &, void *) {} | ||||
| 
 | ||||
| public: | ||||
|     template <typename T> | ||||
|     static handle cast(T &&src, return_value_policy policy, handle parent) { | ||||
|  | @ -352,87 +220,43 @@ struct type_caster<std::deque<Type, Alloc>> : list_caster<std::deque<Type, Alloc | |||
| template <typename Type, typename Alloc> | ||||
| struct type_caster<std::list<Type, Alloc>> : list_caster<std::list<Type, Alloc>, Type> {}; | ||||
| 
 | ||||
| template <typename ArrayType, typename V, size_t... I> | ||||
| ArrayType vector_to_array_impl(V &&v, index_sequence<I...>) { | ||||
|     return {{std::move(v[I])...}}; | ||||
| } | ||||
| 
 | ||||
| // Based on https://en.cppreference.com/w/cpp/container/array/to_array
 | ||||
| template <typename ArrayType, size_t N, typename V> | ||||
| ArrayType vector_to_array(V &&v) { | ||||
|     return vector_to_array_impl<ArrayType, V>(std::forward<V>(v), make_index_sequence<N>{}); | ||||
| } | ||||
| 
 | ||||
| template <typename ArrayType, typename Value, bool Resizable, size_t Size = 0> | ||||
| struct array_caster { | ||||
|     using value_conv = make_caster<Value>; | ||||
| 
 | ||||
| private: | ||||
|     std::unique_ptr<ArrayType> value; | ||||
|     template <bool R = Resizable> | ||||
|     bool require_size(enable_if_t<R, size_t> size) { | ||||
|         if (value.size() != size) { | ||||
|             value.resize(size); | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|     template <bool R = Resizable> | ||||
|     bool require_size(enable_if_t<!R, size_t> size) { | ||||
|         return size == Size; | ||||
|     } | ||||
| 
 | ||||
|     template <bool R = Resizable, enable_if_t<R, int> = 0> | ||||
|     bool convert_elements(handle seq, bool convert) { | ||||
|         auto l = reinterpret_borrow<sequence>(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<sequence>(src)) { | ||||
|             return false; | ||||
|         } | ||||
|         auto l = reinterpret_borrow<sequence>(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<Value &&>(std::move(conv)); | ||||
|             value[ctr++] = cast_op<Value &&>(std::move(conv)); | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     template <bool R = Resizable, enable_if_t<!R, int> = 0> | ||||
|     bool convert_elements(handle seq, bool convert) { | ||||
|         auto l = reinterpret_borrow<sequence>(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<Value> temp; | ||||
|         temp.reserve(l.size()); | ||||
|         for (auto it : l) { | ||||
|             value_conv conv; | ||||
|             if (!conv.load(it, convert)) { | ||||
|                 return false; | ||||
|             } | ||||
|             temp.emplace_back(cast_op<Value &&>(std::move(conv))); | ||||
|         } | ||||
|         value.reset(new ArrayType(vector_to_array<ArrayType, Size>(std::move(temp)))); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
| public: | ||||
|     bool load(handle src, bool convert) { | ||||
|         if (!PyObjectTypeIsConvertibleToStdVector(src.ptr())) { | ||||
|             return false; | ||||
|         } | ||||
|         if (isinstance<sequence>(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<iterable>(src)); | ||||
|         return convert_elements(tuple(reinterpret_borrow<iterable>(src)), convert); | ||||
|     } | ||||
| 
 | ||||
|     template <typename T> | ||||
|     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 <typename T_, enable_if_t<std::is_same<ArrayType, remove_cv_t<T_>>::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 <typename T_> | ||||
|     using cast_op_type = movable_cast_op_type<T_>; | ||||
| 
 | ||||
|     static constexpr auto name | ||||
|         = const_name<Resizable>(const_name(""), const_name("Annotated[")) + const_name("list[") | ||||
|           + value_conv::name + const_name("]") | ||||
|           + const_name<Resizable>( | ||||
|               const_name(""), const_name(", FixedSize(") + const_name<Size>() + const_name(")]")); | ||||
|     PYBIND11_TYPE_CASTER(ArrayType, | ||||
|                          const_name<Resizable>(const_name(""), const_name("Annotated[")) | ||||
|                              + const_name("list[") + value_conv::name + const_name("]") | ||||
|                              + const_name<Resizable>(const_name(""), | ||||
|                                                      const_name(", FixedSize(") | ||||
|                                                          + const_name<Size>() + const_name(")]"))); | ||||
| }; | ||||
| 
 | ||||
| template <typename Type, size_t Size> | ||||
|  |  | |||
|  | @ -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)
 | ||||
|  |  | |||
|  | @ -487,7 +487,7 @@ PYBIND11_NAMESPACE_END(detail) | |||
| //
 | ||||
| // std::vector
 | ||||
| //
 | ||||
| template <typename Vector, typename holder_type = default_holder_type<Vector>, typename... Args> | ||||
| template <typename Vector, typename holder_type = std::unique_ptr<Vector>, typename... Args> | ||||
| class_<Vector, holder_type> bind_vector(handle scope, std::string const &name, Args &&...args) { | ||||
|     using Class_ = class_<Vector, holder_type>; | ||||
| 
 | ||||
|  | @ -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<ssize_t>(len(message)), 1)]); | ||||
|     } | ||||
|     return message; | ||||
| } | ||||
| 
 | ||||
| template <typename KeyType> | ||||
| 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 Map, typename holder_type = default_holder_type<Map>, typename... Args> | ||||
| template <typename Map, typename holder_type = std::unique_ptr<Map>, typename... Args> | ||||
| class_<Map, holder_type> 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_<Map, holder_type> 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_<Map, holder_type> 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); | ||||
|     }); | ||||
|  |  | |||
|  | @ -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<smart_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) | ||||
|  | @ -16,13 +16,6 @@ | |||
| 
 | ||||
| #include <algorithm> | ||||
| 
 | ||||
| #if defined(__cpp_nontype_template_args) && __cpp_nontype_template_args >= 201911L | ||||
| #    define PYBIND11_TYPING_H_HAS_STRING_LITERAL | ||||
| #    include <numeric> | ||||
| #    include <ranges> | ||||
| #    include <string_view> | ||||
| #endif | ||||
| 
 | ||||
| PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) | ||||
| PYBIND11_NAMESPACE_BEGIN(typing) | ||||
| 
 | ||||
|  | @ -89,18 +82,6 @@ class Optional : public object { | |||
|     using object::object; | ||||
| }; | ||||
| 
 | ||||
| template <typename T> | ||||
| class Final : public object { | ||||
|     PYBIND11_OBJECT_DEFAULT(Final, object, PyObject_Type) | ||||
|     using object::object; | ||||
| }; | ||||
| 
 | ||||
| template <typename T> | ||||
| class ClassVar : public object { | ||||
|     PYBIND11_OBJECT_DEFAULT(ClassVar, object, PyObject_Type) | ||||
|     using object::object; | ||||
| }; | ||||
| 
 | ||||
| template <typename T> | ||||
| 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 <size_t N> | ||||
| struct StringLiteral { | ||||
|     constexpr StringLiteral(const char (&str)[N]) { std::copy_n(str, N, name); } | ||||
|  | @ -194,19 +176,16 @@ template <typename Return, typename... Args> | |||
| struct handle_type_name<typing::Callable<Return(Args...)>> { | ||||
|     using retval_type = conditional_t<std::is_same<Return, void>::value, void_type, Return>; | ||||
|     static constexpr auto name | ||||
|         = const_name("Callable[[") | ||||
|           + ::pybind11::detail::concat(::pybind11::detail::arg_descr(make_caster<Args>::name)...) | ||||
|           + const_name("], ") + ::pybind11::detail::return_descr(make_caster<retval_type>::name) | ||||
|           + const_name("]"); | ||||
|         = const_name("Callable[[") + ::pybind11::detail::concat(make_caster<Args>::name...) | ||||
|           + const_name("], ") + make_caster<retval_type>::name + const_name("]"); | ||||
| }; | ||||
| 
 | ||||
| template <typename Return> | ||||
| struct handle_type_name<typing::Callable<Return(ellipsis)>> { | ||||
|     // PEP 484 specifies this syntax for defining only return types of callables
 | ||||
|     using retval_type = conditional_t<std::is_same<Return, void>::value, void_type, Return>; | ||||
|     static constexpr auto name = const_name("Callable[..., ") | ||||
|                                  + ::pybind11::detail::return_descr(make_caster<retval_type>::name) | ||||
|                                  + const_name("]"); | ||||
|     static constexpr auto name | ||||
|         = const_name("Callable[..., ") + make_caster<retval_type>::name + const_name("]"); | ||||
| }; | ||||
| 
 | ||||
| template <typename T> | ||||
|  | @ -226,16 +205,6 @@ struct handle_type_name<typing::Optional<T>> { | |||
|     static constexpr auto name = const_name("Optional[") + make_caster<T>::name + const_name("]"); | ||||
| }; | ||||
| 
 | ||||
| template <typename T> | ||||
| struct handle_type_name<typing::Final<T>> { | ||||
|     static constexpr auto name = const_name("Final[") + make_caster<T>::name + const_name("]"); | ||||
| }; | ||||
| 
 | ||||
| template <typename T> | ||||
| struct handle_type_name<typing::ClassVar<T>> { | ||||
|     static constexpr auto name = const_name("ClassVar[") + make_caster<T>::name + const_name("]"); | ||||
| }; | ||||
| 
 | ||||
| template <typename T> | ||||
| struct handle_type_name<typing::TypeGuard<T>> { | ||||
|     static constexpr auto name = const_name("TypeGuard[") + make_caster<T>::name + const_name("]"); | ||||
|  | @ -257,35 +226,15 @@ struct handle_type_name<typing::Never> { | |||
| }; | ||||
| 
 | ||||
| #if defined(PYBIND11_TYPING_H_HAS_STRING_LITERAL) | ||||
| template <typing::StringLiteral StrLit> | ||||
| 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 <typing::StringLiteral... Literals> | ||||
| struct handle_type_name<typing::Literal<Literals...>> { | ||||
|     static constexpr auto name | ||||
|         = const_name("Literal[") | ||||
|           + pybind11::detail::concat(const_name(sanitize_string_literal<Literals>().name)...) | ||||
|           + const_name("]"); | ||||
|     static constexpr auto name = const_name("Literal[") | ||||
|                                  + pybind11::detail::concat(const_name(Literals.name)...) | ||||
|                                  + const_name("]"); | ||||
| }; | ||||
| template <typing::StringLiteral StrLit> | ||||
| struct handle_type_name<typing::TypeVar<StrLit>> { | ||||
|     static constexpr auto name = const_name(sanitize_string_literal<StrLit>().name); | ||||
|     static constexpr auto name = const_name(StrLit.name); | ||||
| }; | ||||
| #endif | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,75 +0,0 @@ | |||
| /*
 | ||||
|     pybind11/warnings.h: Python warnings wrappers. | ||||
| 
 | ||||
|     Copyright (c) 2024 Jan Iwaszkiewicz <jiwaszkiewicz6@gmail.com> | ||||
| 
 | ||||
|     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>() + 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<object>(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) | ||||
|  | @ -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) | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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__": | ||||
|  |  | |||
|  | @ -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(".")) | ||||
|  |  | |||
|  | @ -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. | ||||
|  |  | |||
|  | @ -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"] | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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}) | ||||
|  |  | |||
|  | @ -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-$<TARGET_FILE_NAME:pybind11_tests>.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) | ||||
| 
 | ||||
|  |  | |||
|  | @ -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}" | ||||
|  |  | |||
|  | @ -312,16 +312,8 @@ void print_created(T *inst, Values &&...values) { | |||
| } | ||||
| template <class T, typename... Values> | ||||
| 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 <class T, typename... Values> | ||||
| void print_values(T *inst, Values &&...values) { | ||||
|  |  | |||
|  | @ -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 | ||||
|  | @ -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")) | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 <pybind11/conduit/pybind11_conduit_v1.h> // VERY light-weight dependency. | ||||
| #include <pybind11/pybind11.h> // EXCLUSIVELY for PYBIND11_PLATFORM_ABI_ID | ||||
| // Potential future direction to maximize reusability:
 | ||||
| // (e.g. for use from SWIG, Cython, PyCLIF, nanobind):
 | ||||
| //     #include <pybind11/compat/platform_abi_id.h>
 | ||||
| // This would only depend on:
 | ||||
| //     1. A C++ compiler, WITHOUT requiring -fexceptions.
 | ||||
| //     2. Python.h
 | ||||
| 
 | ||||
| #include "test_cpp_conduit_traveler_types.h" | ||||
| 
 | ||||
| #include <Python.h> | ||||
| #include <typeinfo> | ||||
| 
 | ||||
| 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<void *>(static_cast<const void *>(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 <typename T> | ||||
| 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<T *>(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<pybind11_tests::test_cpp_conduit::Traveler>(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<pybind11_tests::test_cpp_conduit::PremiumTraveler>( | ||||
|             premium_traveler); | ||||
|     if (cpp_premium_traveler == nullptr) { | ||||
|         return nullptr; | ||||
|     } | ||||
|  |  | |||
|  | @ -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", | ||||
|  |  | |||
|  | @ -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 "$<TARGET_FILE:smart_holder_poc_test>" | ||||
|   WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") | ||||
| 
 | ||||
| add_dependencies(check test_pure_cpp) | ||||
|  | @ -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 <typename T> | ||||
| 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<T>(); | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| 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<T>()); | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| 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<T>(); | ||||
|     hld.release_ownership(); | ||||
|     return raw_ptr; | ||||
| } | ||||
| 
 | ||||
| template <typename T, typename D = std::default_delete<T>> | ||||
| std::unique_ptr<T, D> as_unique_ptr(smart_holder &hld) { | ||||
|     static const char *context = "as_unique_ptr"; | ||||
|     hld.ensure_compatible_rtti_uqp_del<T, D>(context); | ||||
|     hld.ensure_use_count_1(context); | ||||
|     T *raw_ptr = hld.as_raw_ptr_unowned<T>(); | ||||
|     hld.release_ownership(); | ||||
|     // KNOWN DEFECT (see PR #4850): Does not copy the deleter.
 | ||||
|     return std::unique_ptr<T, D>(raw_ptr); | ||||
| } | ||||
| 
 | ||||
| } // namespace smart_holder_poc
 | ||||
| } // namespace memory
 | ||||
| } // namespace pybindit
 | ||||
|  | @ -1,415 +0,0 @@ | |||
| #include "smart_holder_poc.h" | ||||
| 
 | ||||
| #include <functional> | ||||
| #include <memory> | ||||
| #include <type_traits> | ||||
| #include <utility> | ||||
| 
 | ||||
| // 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 <typename T> | ||||
| 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<int>'
 | ||||
|     // 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 <typename T> | ||||
| struct functor_other_delete : functor_builtin_delete<T> {}; | ||||
| 
 | ||||
| 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<int>() == 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<int>(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<helpers::movable_int>(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<int>(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<int>(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<int, helpers::functor_builtin_delete<int>>(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<int>() == 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<int>(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<int>(poc::as_raw_ptr_release_ownership<int>(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<int>(); | ||||
|     REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership<int>(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<int> new_owner = poc::as_unique_ptr<int>(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<int>(); | ||||
|     REQUIRE_THROWS_WITH(poc::as_unique_ptr<int>(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<int, helpers::functor_builtin_delete<int>>(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<int> new_owner = hld.as_shared_ptr<int>(); | ||||
|     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<int> new_owner(hld.as_raw_ptr_unowned<int>()); | ||||
|     hld.disown(); | ||||
|     REQUIRE(poc::as_lvalue_ref<int>(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<int>(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<int> new_owner(hld.as_raw_ptr_unowned<int>()); | ||||
|     hld.disown(); | ||||
|     REQUIRE(poc::as_lvalue_ref<int>(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<int> new_owner(hld.as_raw_ptr_unowned<int>()); | ||||
|     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<int> 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<int>(hld) == 19); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("from_unique_ptr+as_raw_ptr_release_ownership1", "[S]") { | ||||
|     std::unique_ptr<int> 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<int>(poc::as_raw_ptr_release_ownership<int>(hld)); | ||||
|     REQUIRE(!hld.has_pointee()); | ||||
|     REQUIRE(*new_owner == 19); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("from_unique_ptr+as_raw_ptr_release_ownership2", "[E]") { | ||||
|     std::unique_ptr<int> 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<int>(); | ||||
|     REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership<int>(hld), | ||||
|                         "Cannot disown use_count != 1 (as_raw_ptr_release_ownership)."); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("from_unique_ptr+as_unique_ptr1", "[S]") { | ||||
|     std::unique_ptr<int> orig_owner(new int(19)); | ||||
|     auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); | ||||
|     REQUIRE(orig_owner.get() == nullptr); | ||||
|     std::unique_ptr<int> new_owner = poc::as_unique_ptr<int>(hld); | ||||
|     REQUIRE(!hld.has_pointee()); | ||||
|     REQUIRE(*new_owner == 19); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("from_unique_ptr+as_unique_ptr2", "[E]") { | ||||
|     std::unique_ptr<int> 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<int>(); | ||||
|     REQUIRE_THROWS_WITH(poc::as_unique_ptr<int>(hld), | ||||
|                         "Cannot disown use_count != 1 (as_unique_ptr)."); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("from_unique_ptr+as_unique_ptr_with_deleter", "[E]") { | ||||
|     std::unique_ptr<int> 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<int, helpers::functor_builtin_delete<int>>(hld)), | ||||
|                         "Incompatible unique_ptr deleter (as_unique_ptr)."); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("from_unique_ptr+as_shared_ptr", "[S]") { | ||||
|     std::unique_ptr<int> orig_owner(new int(19)); | ||||
|     auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); | ||||
|     REQUIRE(orig_owner.get() == nullptr); | ||||
|     std::shared_ptr<int> new_owner = hld.as_shared_ptr<int>(); | ||||
|     REQUIRE(hld.has_pointee()); | ||||
|     REQUIRE(*new_owner == 19); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("from_unique_ptr_derived+as_unique_ptr_base", "[S]") { | ||||
|     std::unique_ptr<helpers::derived> orig_owner(new helpers::derived()); | ||||
|     auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); | ||||
|     REQUIRE(orig_owner.get() == nullptr); | ||||
|     std::unique_ptr<helpers::base> new_owner = poc::as_unique_ptr<helpers::base>(hld); | ||||
|     REQUIRE(!hld.has_pointee()); | ||||
|     REQUIRE(new_owner->get() == 100); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("from_unique_ptr_derived+as_unique_ptr_base2", "[E]") { | ||||
|     std::unique_ptr<helpers::derived, helpers::functor_other_delete<helpers::derived>> 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<helpers::base, helpers::functor_builtin_delete<helpers::base>>(hld)), | ||||
|         "Incompatible unique_ptr deleter (as_unique_ptr)."); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("from_unique_ptr_with_deleter+as_lvalue_ref", "[S]") { | ||||
|     std::unique_ptr<int, helpers::functor_builtin_delete<int>> 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<int>(hld) == 19); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("from_unique_ptr_with_std_function_deleter+as_lvalue_ref", "[S]") { | ||||
|     std::unique_ptr<int, std::function<void(const int *)>> 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<int>(hld) == 19); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("from_unique_ptr_with_deleter+as_raw_ptr_release_ownership", "[E]") { | ||||
|     std::unique_ptr<int, helpers::functor_builtin_delete<int>> 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<int>(hld), | ||||
|                         "Cannot disown custom deleter (as_raw_ptr_release_ownership)."); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("from_unique_ptr_with_deleter+as_unique_ptr", "[E]") { | ||||
|     std::unique_ptr<int, helpers::functor_builtin_delete<int>> 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<int>(hld), | ||||
|                         "Incompatible unique_ptr deleter (as_unique_ptr)."); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("from_unique_ptr_with_deleter+as_unique_ptr_with_deleter1", "[S]") { | ||||
|     std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19)); | ||||
|     auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); | ||||
|     REQUIRE(orig_owner.get() == nullptr); | ||||
|     std::unique_ptr<int, helpers::functor_builtin_delete<int>> new_owner | ||||
|         = poc::as_unique_ptr<int, helpers::functor_builtin_delete<int>>(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<int, helpers::functor_builtin_delete<int>> 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<int, helpers::functor_other_delete<int>>(hld)), | ||||
|                         "Incompatible unique_ptr deleter (as_unique_ptr)."); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("from_unique_ptr_with_deleter+as_shared_ptr", "[S]") { | ||||
|     std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19)); | ||||
|     auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); | ||||
|     REQUIRE(orig_owner.get() == nullptr); | ||||
|     std::shared_ptr<int> new_owner = hld.as_shared_ptr<int>(); | ||||
|     REQUIRE(hld.has_pointee()); | ||||
|     REQUIRE(*new_owner == 19); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("from_shared_ptr+as_lvalue_ref", "[S]") { | ||||
|     std::shared_ptr<int> orig_owner(new int(19)); | ||||
|     auto hld = smart_holder::from_shared_ptr(orig_owner); | ||||
|     REQUIRE(poc::as_lvalue_ref<int>(hld) == 19); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("from_shared_ptr+as_raw_ptr_release_ownership", "[E]") { | ||||
|     std::shared_ptr<int> orig_owner(new int(19)); | ||||
|     auto hld = smart_holder::from_shared_ptr(orig_owner); | ||||
|     REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership<int>(hld), | ||||
|                         "Cannot disown external shared_ptr (as_raw_ptr_release_ownership)."); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("from_shared_ptr+as_unique_ptr", "[E]") { | ||||
|     std::shared_ptr<int> orig_owner(new int(19)); | ||||
|     auto hld = smart_holder::from_shared_ptr(orig_owner); | ||||
|     REQUIRE_THROWS_WITH(poc::as_unique_ptr<int>(hld), | ||||
|                         "Cannot disown external shared_ptr (as_unique_ptr)."); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("from_shared_ptr+as_unique_ptr_with_deleter", "[E]") { | ||||
|     std::shared_ptr<int> orig_owner(new int(19)); | ||||
|     auto hld = smart_holder::from_shared_ptr(orig_owner); | ||||
|     REQUIRE_THROWS_WITH((poc::as_unique_ptr<int, helpers::functor_builtin_delete<int>>(hld)), | ||||
|                         "Missing unique_ptr deleter (as_unique_ptr)."); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("from_shared_ptr+as_shared_ptr", "[S]") { | ||||
|     std::shared_ptr<int> orig_owner(new int(19)); | ||||
|     auto hld = smart_holder::from_shared_ptr(orig_owner); | ||||
|     REQUIRE(*hld.as_shared_ptr<int>() == 19); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("error_unpopulated_holder", "[E]") { | ||||
|     smart_holder hld; | ||||
|     REQUIRE_THROWS_WITH(poc::as_lvalue_ref<int>(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<int>(hld); | ||||
|     REQUIRE_THROWS_WITH(poc::as_lvalue_ref<int>(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<int>(hld); | ||||
|     REQUIRE_THROWS_WITH(poc::as_unique_ptr<int>(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<sizeof(zombie), alignof(zombie)>::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<zombie>()->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<int> longer_living; | ||||
|     { | ||||
|         auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); | ||||
|         longer_living = hld.as_shared_ptr<int>(); | ||||
|     } | ||||
|     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<int> longer_living; | ||||
|     { | ||||
|         std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19)); | ||||
|         auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); | ||||
|         longer_living = hld.as_shared_ptr<int>(); | ||||
|     } | ||||
|     REQUIRE(*longer_living == 19); | ||||
| } | ||||
|  | @ -128,9 +128,4 @@ PYBIND11_MODULE(pybind11_tests, m, py::mod_gil_not_used()) { | |||
|     for (const auto &initializer : initializers()) { | ||||
|         initializer(m); | ||||
|     } | ||||
| 
 | ||||
|     py::class_<TestContext>(m, "TestContext") | ||||
|         .def(py::init<>(&TestContext::createNewContextForInit)) | ||||
|         .def("__enter__", &TestContext::contextEnter) | ||||
|         .def("__exit__", &TestContext::contextExit); | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
| }; | ||||
|  |  | |||
|  | @ -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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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' | ||||
|  |  | |||
|  | @ -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_<FortranMatrix, Matrix>(m, "FortranMatrix", py::buffer_protocol()) | ||||
|         .def(py::init<py::ssize_t, py::ssize_t>()) | ||||
| 
 | ||||
|         .def("rows", &FortranMatrix::rows) | ||||
|         .def("cols", &FortranMatrix::cols) | ||||
| 
 | ||||
|         /// Bare bones interface
 | ||||
|         .def("__getitem__", | ||||
|              [](const FortranMatrix &m, std::pair<py::ssize_t, py::ssize_t> 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<py::ssize_t, py::ssize_t> 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_<DiscontiguousMatrix, Matrix>(m, "DiscontiguousMatrix", py::buffer_protocol()) | ||||
|         .def(py::init<py::ssize_t, py::ssize_t, py::ssize_t, py::ssize_t>()) | ||||
| 
 | ||||
|         .def("rows", &DiscontiguousMatrix::rows) | ||||
|         .def("cols", &DiscontiguousMatrix::cols) | ||||
| 
 | ||||
|         /// Bare bones interface
 | ||||
|         .def("__getitem__", | ||||
|              [](const DiscontiguousMatrix &m, std::pair<py::ssize_t, py::ssize_t> 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<py::ssize_t, py::ssize_t> 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; | ||||
|     }); | ||||
| } | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
|  | @ -95,8 +95,8 @@ TEST_SUBMODULE(call_policies, m) { | |||
|         }, | ||||
|         py::call_guard<DependentGuard, CustomGuard>()); | ||||
| 
 | ||||
| #if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON) | ||||
|     // `py::call_guard<py::gil_scoped_release>()` should work in PyPy/GraalPy as well,
 | ||||
| #if !defined(PYPY_VERSION) | ||||
|     // `py::call_guard<py::gil_scoped_release>()` 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; | ||||
|  |  | |||
|  | @ -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() | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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<py::args, py::args>::value, ""); | ||||
| static_assert( | ||||
|     py::detail::is_same_or_base_of<py::args, | ||||
|                                    test_class::pr5396_forward_declared_class::Args>::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_<ToBeHeldByUniquePtr, std::unique_ptr<ToBeHeldByUniquePtr>>(m, "ToBeHeldByUniquePtr") | ||||
|         .def(py::init<>()); | ||||
|     m.def("pass_unique_ptr", [](std::unique_ptr<ToBeHeldByUniquePtr> &&) {}); | ||||
| 
 | ||||
|     // 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_<MismatchBase1, std::shared_ptr<MismatchBase1>>(mod, "MismatchBase1"); | ||||
|         py::class_<MismatchDerived1, std::unique_ptr<MismatchDerived1>, MismatchBase1>( | ||||
|             mod, "MismatchDerived1"); | ||||
|         py::class_<MismatchDerived1, MismatchBase1>(mod, "MismatchDerived1"); | ||||
|     }); | ||||
|     m.def("mismatched_holder_2", []() { | ||||
|         auto mod = py::module_::import("__main__"); | ||||
|         py::class_<MismatchBase2, std::unique_ptr<MismatchBase2>>(mod, "MismatchBase2"); | ||||
|         py::class_<MismatchBase2>(mod, "MismatchBase2"); | ||||
|         py::class_<MismatchDerived2, std::shared_ptr<MismatchDerived2>, 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); | ||||
|  |  | |||
|  | @ -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<T>` from Python to C++ requires `py::class_<T, py::smart_holder>` (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() | ||||
|  |  | |||
|  | @ -1,53 +0,0 @@ | |||
| #include <pybind11/pybind11.h> | ||||
| 
 | ||||
| #include "pybind11_tests.h" | ||||
| 
 | ||||
| #include <string> | ||||
| #include <unordered_map> | ||||
| 
 | ||||
| namespace pybind11_tests { | ||||
| namespace class_release_gil_before_calling_cpp_dtor { | ||||
| 
 | ||||
| using RegistryType = std::unordered_map<std::string, int>; | ||||
| 
 | ||||
| static RegistryType &PyGILState_Check_Results() { | ||||
|     static RegistryType singleton; // Local static variables have thread-safe initialization.
 | ||||
|     return singleton; | ||||
| } | ||||
| 
 | ||||
| template <int> // 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_<ProbeType<0>>(m, "ProbeType0").def(py::init<std::string>()); | ||||
| 
 | ||||
|     py::class_<ProbeType<1>>(m, "ProbeType1", py::release_gil_before_calling_cpp_dtor()) | ||||
|         .def(py::init<std::string>()); | ||||
| 
 | ||||
|     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); | ||||
|     }); | ||||
| } | ||||
|  | @ -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 | ||||
|  | @ -1,247 +0,0 @@ | |||
| #include "pybind11_tests.h" | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| 
 | ||||
| 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<atyp> held; | ||||
|     bool valid() const { return static_cast<bool>(held); } | ||||
| 
 | ||||
|     void pass_valu(std::unique_ptr<atyp> obj) { held = std::move(obj); } | ||||
|     void pass_rref(std::unique_ptr<atyp> &&obj) { held = std::move(obj); } | ||||
|     std::unique_ptr<atyp> rtrn_valu() { return std::move(held); } | ||||
|     std::unique_ptr<atyp> &rtrn_lref() { return held; } | ||||
|     const std::unique_ptr<atyp> &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<atyp>()(p); } | ||||
|     void operator()(const atyp *p) const { std::default_delete<const atyp>()(p); } | ||||
| }; | ||||
| static_assert(std::is_default_constructible<custom_deleter>::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<custom_deleter_nd>::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<atyp>       rtrn_shmp() { return std::make_shared<atyp>("rtrn_shmp"); } | ||||
| std::shared_ptr<atyp const> rtrn_shcp() { return std::shared_ptr<atyp const>(new atyp{"rtrn_shcp"}); } | ||||
| 
 | ||||
| std::string pass_shmp(std::shared_ptr<atyp>       obj) { return "pass_shmp:" + obj->mtxt; } // NOLINT
 | ||||
| std::string pass_shcp(std::shared_ptr<atyp const> obj) { return "pass_shcp:" + obj->mtxt; } // NOLINT
 | ||||
| 
 | ||||
| std::unique_ptr<atyp>       rtrn_uqmp() { return std::unique_ptr<atyp      >(new atyp{"rtrn_uqmp"}); } | ||||
| std::unique_ptr<atyp const> rtrn_uqcp() { return std::unique_ptr<atyp const>(new atyp{"rtrn_uqcp"}); } | ||||
| 
 | ||||
| std::string pass_uqmp(std::unique_ptr<atyp      > obj) { return "pass_uqmp:" + obj->mtxt; } | ||||
| std::string pass_uqcp(std::unique_ptr<atyp const> obj) { return "pass_uqcp:" + obj->mtxt; } | ||||
| 
 | ||||
| struct sddm : std::default_delete<atyp      > {}; | ||||
| struct sddc : std::default_delete<atyp const> {}; | ||||
| 
 | ||||
| std::unique_ptr<atyp,       sddm> rtrn_udmp() { return std::unique_ptr<atyp,       sddm>(new atyp{"rtrn_udmp"}); } | ||||
| std::unique_ptr<atyp const, sddc> rtrn_udcp() { return std::unique_ptr<atyp const, sddc>(new atyp{"rtrn_udcp"}); } | ||||
| 
 | ||||
| std::string pass_udmp(std::unique_ptr<atyp,       sddm> obj) { return "pass_udmp:" + obj->mtxt; } | ||||
| std::string pass_udcp(std::unique_ptr<atyp const, sddc> obj) { return "pass_udcp:" + obj->mtxt; } | ||||
| 
 | ||||
| std::unique_ptr<atyp,       custom_deleter> rtrn_udmp_del() { return std::unique_ptr<atyp,       custom_deleter>(new atyp{"rtrn_udmp_del"}, custom_deleter{"udmp_deleter"}); } | ||||
| std::unique_ptr<atyp const, custom_deleter> rtrn_udcp_del() { return std::unique_ptr<atyp const, custom_deleter>(new atyp{"rtrn_udcp_del"}, custom_deleter{"udcp_deleter"}); } | ||||
| 
 | ||||
| std::string pass_udmp_del(std::unique_ptr<atyp,       custom_deleter> obj) { return "pass_udmp_del:" + obj->mtxt + "," + obj.get_deleter().trace_txt; } | ||||
| std::string pass_udcp_del(std::unique_ptr<atyp const, custom_deleter> obj) { return "pass_udcp_del:" + obj->mtxt + "," + obj.get_deleter().trace_txt; } | ||||
| 
 | ||||
| std::unique_ptr<atyp,       custom_deleter_nd> rtrn_udmp_del_nd() { return std::unique_ptr<atyp,       custom_deleter_nd>(new atyp{"rtrn_udmp_del_nd"}, custom_deleter_nd{"udmp_deleter_nd"}); } | ||||
| std::unique_ptr<atyp const, custom_deleter_nd> rtrn_udcp_del_nd() { return std::unique_ptr<atyp const, custom_deleter_nd>(new atyp{"rtrn_udcp_del_nd"}, custom_deleter_nd{"udcp_deleter_nd"}); } | ||||
| 
 | ||||
| std::string pass_udmp_del_nd(std::unique_ptr<atyp,       custom_deleter_nd> obj) { return "pass_udmp_del_nd:" + obj->mtxt + "," + obj.get_deleter().trace_txt; } | ||||
| std::string pass_udcp_del_nd(std::unique_ptr<atyp const, custom_deleter_nd> 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<std::ptrdiff_t>(&obj); } | ||||
| 
 | ||||
| std::unique_ptr<atyp> unique_ptr_roundtrip(std::unique_ptr<atyp> obj) { return obj; } | ||||
| 
 | ||||
| std::string pass_unique_ptr_cref(const std::unique_ptr<atyp> &obj) { return obj->mtxt; } | ||||
| 
 | ||||
| const std::unique_ptr<atyp> &rtrn_unique_ptr_cref(const std::string &mtxt) { | ||||
|     static std::unique_ptr<atyp> obj{new atyp{"static_ctor_arg"}}; | ||||
|     if (!mtxt.empty()) { | ||||
|         obj->mtxt = mtxt; | ||||
|     } | ||||
|     return obj; | ||||
| } | ||||
| 
 | ||||
| const std::unique_ptr<atyp> &unique_ptr_cref_roundtrip(const std::unique_ptr<atyp> &obj) { | ||||
|     return obj; | ||||
| } | ||||
| 
 | ||||
| struct SharedPtrStash { | ||||
|     std::vector<std::shared_ptr<const atyp>> stash; | ||||
|     void Add(const std::shared_ptr<const atyp> &obj) { stash.push_back(obj); } | ||||
| }; | ||||
| 
 | ||||
| class LocalUnusualOpRef : UnusualOpRef {}; // To avoid clashing with `py::class_<UnusualOpRef>`.
 | ||||
| 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<atyp>(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<uconsumer>(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<SharedPtrStash>(m, "SharedPtrStash") | ||||
|         .def(py::init<>()) | ||||
|         .def("Add", &SharedPtrStash::Add, py::arg("obj")); | ||||
| 
 | ||||
|     m.def("py_type_handle_of_atyp", []() { | ||||
|         return py::type::handle_of<atyp>(); // Exercises static_cast in this function.
 | ||||
|     }); | ||||
| 
 | ||||
|     // Checks for type names used as arguments
 | ||||
|     m.def("args_shared_ptr", [](std::shared_ptr<atyp> p) { return p; }); | ||||
|     m.def("args_shared_ptr_const", [](std::shared_ptr<atyp const> p) { return p; }); | ||||
|     m.def("args_unique_ptr", [](std::unique_ptr<atyp> p) { return p; }); | ||||
|     m.def("args_unique_ptr_const", [](std::unique_ptr<atyp const> 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<atyp>(new atyp("rtrn_uq_automatic_reference")); }, | ||||
|         pybind11::return_value_policy::automatic_reference); | ||||
| 
 | ||||
|     m.def("pass_shared_ptr_ptr", [](std::shared_ptr<atyp> *) {}); | ||||
| 
 | ||||
|     py::classh<LocalUnusualOpRef>(m, "LocalUnusualOpRef"); | ||||
|     m.def("CallCastUnusualOpRefConstRef", | ||||
|           []() { return CastUnusualOpRefConstRef(LocalUnusualOpRef()); }); | ||||
|     m.def("CallCastUnusualOpRefMovable", | ||||
|           []() { return CastUnusualOpRefMovable(LocalUnusualOpRef()); }); | ||||
| } | ||||
| 
 | ||||
| } // namespace class_sh_basic
 | ||||
| } // namespace pybind11_tests
 | ||||
|  | @ -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<T> *` 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" | ||||
|  | @ -1,41 +0,0 @@ | |||
| #include "pybind11_tests.h" | ||||
| 
 | ||||
| #include <memory> | ||||
| 
 | ||||
| namespace pybind11_tests { | ||||
| namespace class_sh_disowning { | ||||
| 
 | ||||
| template <int SerNo> // 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<Atype<1>> at1a, std::unique_ptr<Atype<1>> at1b) { | ||||
|     return at1a->get() * 100 + at1b->get() * 10; | ||||
| } | ||||
| 
 | ||||
| int mixed(std::unique_ptr<Atype<1>> at1, std::unique_ptr<Atype<2>> at2) { | ||||
|     return at1->get() * 200 + at2->get() * 20; | ||||
| } | ||||
| 
 | ||||
| int overloaded(std::unique_ptr<Atype<1>> at1, int i) { return at1->get() * 30 + i; } | ||||
| int overloaded(std::unique_ptr<Atype<2>> 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<Atype<1>>(m, "Atype1").def(py::init<int>()).def("get", &Atype<1>::get); | ||||
|     py::classh<Atype<2>>(m, "Atype2").def(py::init<int>()).def("get", &Atype<2>::get); | ||||
| 
 | ||||
|     m.def("same_twice", same_twice); | ||||
| 
 | ||||
|     m.def("mixed", mixed); | ||||
| 
 | ||||
|     m.def("overloaded", (int (*)(std::unique_ptr<Atype<1>>, int)) &overloaded); | ||||
|     m.def("overloaded", (int (*)(std::unique_ptr<Atype<2>>, int)) &overloaded); | ||||
| } | ||||
|  | @ -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). | ||||
|  | @ -1,85 +0,0 @@ | |||
| #include "pybind11_tests.h" | ||||
| 
 | ||||
| #include <memory> | ||||
| 
 | ||||
| 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<B>) {} | ||||
| 
 | ||||
| // 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<Base1> b1) { return b1->i * 2000 + 1; } | ||||
| int disown_base2(std::unique_ptr<Base2> 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<B>(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<C0, B>(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<C1, B>(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<D, C0, C1>(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<Base1>(m, "Base1").def(py::init<int>()).def("foo", &Base1::foo); | ||||
|     py::classh<Base2>(m, "Base2").def(py::init<int>()).def("bar", &Base2::bar); | ||||
|     m.def("disown_base1", disown_base1); | ||||
|     m.def("disown_base2", disown_base2); | ||||
| } | ||||
|  | @ -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 | ||||
|  | @ -1,164 +0,0 @@ | |||
| #include "pybind11_tests.h" | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <string> | ||||
| 
 | ||||
| namespace pybind11_tests { | ||||
| namespace class_sh_factory_constructors { | ||||
| 
 | ||||
| template <int> // Using int as a trick to easily generate a series of types.
 | ||||
| struct atyp {  // Short for "any type".
 | ||||
|     std::string mtxt; | ||||
| }; | ||||
| 
 | ||||
| template <typename T> | ||||
| 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<atyp_shmp>       rtrn_shmp() { return std::make_shared<atyp_shmp>(atyp_shmp{"Shmp"}); } | ||||
| std::shared_ptr<atyp_shcp const> rtrn_shcp() { return std::shared_ptr<atyp_shcp const>(new atyp_shcp{"Shcp"}); } | ||||
| 
 | ||||
| std::unique_ptr<atyp_uqmp>       rtrn_uqmp() { return std::unique_ptr<atyp_uqmp      >(new atyp_uqmp{"Uqmp"}); } | ||||
| std::unique_ptr<atyp_uqcp const> rtrn_uqcp() { return std::unique_ptr<atyp_uqcp const>(new atyp_uqcp{"Uqcp"}); } | ||||
| 
 | ||||
| struct sddm : std::default_delete<atyp_udmp      > {}; | ||||
| struct sddc : std::default_delete<atyp_udcp const> {}; | ||||
| 
 | ||||
| std::unique_ptr<atyp_udmp,       sddm> rtrn_udmp() { return std::unique_ptr<atyp_udmp,       sddm>(new atyp_udmp{"Udmp"}); } | ||||
| std::unique_ptr<atyp_udcp const, sddc> rtrn_udcp() { return std::unique_ptr<atyp_udcp const, sddc>(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<with_alias_alias> {}; | ||||
| 
 | ||||
| } // 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<atyp_valu>(m, "atyp_valu") | ||||
|         .def(py::init(&rtrn_valu)) | ||||
|         .def("get_mtxt", get_mtxt<atyp_valu>); | ||||
| 
 | ||||
|     py::classh<atyp_rref>(m, "atyp_rref") | ||||
|         .def(py::init(&rtrn_rref)) | ||||
|         .def("get_mtxt", get_mtxt<atyp_rref>); | ||||
| 
 | ||||
|     py::classh<atyp_cref>(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<atyp_cref>); | ||||
| 
 | ||||
|     py::classh<atyp_mref>(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<atyp_mref>); | ||||
| 
 | ||||
|     py::classh<atyp_cptr>(m, "atyp_cptr") | ||||
|         // class_: ... must return a compatible ...
 | ||||
|         // classh: ... must return a compatible ...
 | ||||
|         // .def(py::init(&rtrn_cptr))
 | ||||
|         .def("get_mtxt", get_mtxt<atyp_cptr>); | ||||
| 
 | ||||
|     py::classh<atyp_mptr>(m, "atyp_mptr") | ||||
|         .def(py::init(&rtrn_mptr)) | ||||
|         .def("get_mtxt", get_mtxt<atyp_mptr>); | ||||
| 
 | ||||
|     py::classh<atyp_shmp>(m, "atyp_shmp") | ||||
|         .def(py::init(&rtrn_shmp)) | ||||
|         .def("get_mtxt", get_mtxt<atyp_shmp>); | ||||
| 
 | ||||
|     py::classh<atyp_shcp>(m, "atyp_shcp") | ||||
|         // py::class_<atyp_shcp, std::shared_ptr<atyp_shcp>>(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<atyp_shcp>); | ||||
| 
 | ||||
|     py::classh<atyp_uqmp>(m, "atyp_uqmp") | ||||
|         .def(py::init(&rtrn_uqmp)) | ||||
|         .def("get_mtxt", get_mtxt<atyp_uqmp>); | ||||
| 
 | ||||
|     py::classh<atyp_uqcp>(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<atyp_uqcp>); | ||||
| 
 | ||||
|     py::classh<atyp_udmp>(m, "atyp_udmp") | ||||
|         .def(py::init(&rtrn_udmp)) | ||||
|         .def("get_mtxt", get_mtxt<atyp_udmp>); | ||||
| 
 | ||||
|     py::classh<atyp_udcp>(m, "atyp_udcp") | ||||
|         // py::class_<atyp_udcp, std::unique_ptr<atyp_udcp, sddc>>(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<atyp_udcp>); | ||||
| 
 | ||||
|     py::classh<with_alias, with_alias_alias>(m, "with_alias") | ||||
|         .def_readonly("val", &with_alias::val) | ||||
|         .def(py::init([](int i) { | ||||
|             auto p = std::unique_ptr<with_alias_alias, sddwaa>(new with_alias_alias); | ||||
|             p->val = i * 100; | ||||
|             return p; | ||||
|         })) | ||||
|         .def(py::init([](int i, int j) { | ||||
|             auto p = std::unique_ptr<with_alias_alias>(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<with_alias_alias>(); | ||||
|             p->val = i * 100 + j * 10 + k; | ||||
|             return p; | ||||
|         })) | ||||
|         .def(py::init( | ||||
|             [](int, int, int, int) { return std::unique_ptr<with_alias>(new with_alias); }, | ||||
|             [](int, int, int, int) { | ||||
|                 return std::unique_ptr<with_alias>(new with_alias); // Invalid alias factory.
 | ||||
|             })) | ||||
|         .def(py::init([](int, int, int, int, int) { return std::make_shared<with_alias>(); }, | ||||
|                       [](int, int, int, int, int) { | ||||
|                           return std::make_shared<with_alias>(); // Invalid alias factory.
 | ||||
|                       })); | ||||
| } | ||||
|  | @ -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" | ||||
|     ) | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue