Merging 'master' into 'wrap'

release/4.3a0
Varun Agrawal 2023-10-06 12:21:06 -04:00
commit b78900563c
165 changed files with 8150 additions and 1924 deletions

2
wrap/.gitignore vendored
View File

@ -5,6 +5,8 @@ __pycache__/
*dist* *dist*
*.egg-info *.egg-info
**/.DS_Store
# Files related to code coverage stats # Files related to code coverage stats
**/.coverage **/.coverage

View File

@ -12,7 +12,8 @@ Author: Duy Nguyen Ta, Fan Jiang, Matthew Sklar, Varun Agrawal, and Frank Dellae
from typing import Any, Iterable, List, Union from typing import Any, Iterable, List, Union
from pyparsing import Optional, ParseResults, delimitedList # type: ignore from pyparsing import (Literal, Optional, ParseResults, # type: ignore
delimitedList)
from .template import Template from .template import Template
from .tokens import (COMMA, DEFAULT_ARG, EQUAL, IDENT, LOPBRACK, LPAREN, PAIR, from .tokens import (COMMA, DEFAULT_ARG, EQUAL, IDENT, LOPBRACK, LPAREN, PAIR,
@ -105,8 +106,10 @@ class ReturnType:
The return type can either be a single type or a pair such as <type1, type2>. The return type can either be a single type or a pair such as <type1, type2>.
""" """
# rule to parse optional std:: in front of `pair`
optional_std = Optional(Literal('std::')).suppress()
_pair = ( _pair = (
PAIR.suppress() # optional_std + PAIR.suppress() #
+ LOPBRACK # + LOPBRACK #
+ Type.rule("type1") # + Type.rule("type1") #
+ COMMA # + COMMA #

View File

@ -1284,7 +1284,8 @@ class MatlabWrapper(CheckMixin, FormatMixin):
[instantiated_class.name]) [instantiated_class.name])
else: else:
# Get the full namespace # Get the full namespace
class_name = ".".join(instantiated_class.parent.full_namespaces()[1:]) class_name = ".".join(
instantiated_class.parent.full_namespaces()[1:])
if class_name != "": if class_name != "":
class_name += '.' class_name += '.'
@ -1860,6 +1861,7 @@ class MatlabWrapper(CheckMixin, FormatMixin):
""" """
for c in cc_content: for c in cc_content:
if isinstance(c, list): if isinstance(c, list):
# c is a namespace
if len(c) == 0: if len(c) == 0:
continue continue
@ -1875,6 +1877,7 @@ class MatlabWrapper(CheckMixin, FormatMixin):
self.generate_content(sub_content[1], path_to_folder) self.generate_content(sub_content[1], path_to_folder)
elif isinstance(c[1], list): elif isinstance(c[1], list):
# c is a wrapped function
path_to_folder = osp.join(path, c[0]) path_to_folder = osp.join(path, c[0])
if not osp.isdir(path_to_folder): if not osp.isdir(path_to_folder):
@ -1882,11 +1885,13 @@ class MatlabWrapper(CheckMixin, FormatMixin):
os.makedirs(path_to_folder, exist_ok=True) os.makedirs(path_to_folder, exist_ok=True)
except OSError: except OSError:
pass pass
for sub_content in c[1]: for sub_content in c[1]:
path_to_file = osp.join(path_to_folder, sub_content[0]) path_to_file = osp.join(path_to_folder, sub_content[0])
with open(path_to_file, 'w') as f: with open(path_to_file, 'w') as f:
f.write(sub_content[1]) f.write(sub_content[1])
else: else:
# c is a wrapped class
path_to_file = osp.join(path, c[0]) path_to_file = osp.join(path, c[0])
if not osp.isdir(path_to_file): if not osp.isdir(path_to_file):
@ -1921,6 +1926,8 @@ class MatlabWrapper(CheckMixin, FormatMixin):
for module in modules.values(): for module in modules.values():
# Wrap the full namespace # Wrap the full namespace
self.wrap_namespace(module) self.wrap_namespace(module)
# Generate the wrapping code (both C++ and .m files)
self.generate_wrapper(module) self.generate_wrapper(module)
# Generate the corresponding .m and .cpp files # Generate the corresponding .m and .cpp files

View File

@ -63,6 +63,8 @@ Checks: |
-bugprone-unused-raii, -bugprone-unused-raii,
CheckOptions: CheckOptions:
- key: modernize-use-equals-default.IgnoreMacros
value: false
- key: performance-for-range-copy.WarnOnAllAutoCopies - key: performance-for-range-copy.WarnOnAllAutoCopies
value: true value: true
- key: performance-inefficient-string-concatenation.StrictMode - key: performance-inefficient-string-concatenation.StrictMode

View File

@ -0,0 +1,24 @@
template <op_id id, op_type ot, typename L = undefined_t, typename R = undefined_t>
template <typename ThisT>
auto &this_ = static_cast<ThisT &>(*this);
if (load_impl<ThisT>(temp, false)) {
ssize_t nd = 0;
auto trivial = broadcast(buffers, nd, shape);
auto ndim = (size_t) nd;
int nd;
ssize_t ndim() const { return detail::array_proxy(m_ptr)->nd; }
using op = op_impl<id, ot, Base, L_type, R_type>;
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) {
@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"])
struct IntStruct {
explicit IntStruct(int v) : value(v){};
~IntStruct() { value = -value; }
IntStruct(const IntStruct &) = default;
IntStruct &operator=(const IntStruct &) = default;
py::class_<IntStruct>(m, "IntStruct").def(py::init([](const int i) { return IntStruct(i); }));
py::implicitly_convertible<int, IntStruct>();
m.def("test", [](int expected, const IntStruct &in) {
[](int expected, const IntStruct &in) {

View File

@ -235,8 +235,8 @@ directory inside your pybind11 git clone. Files will be modified in place,
so you can use git to monitor the changes. so you can use git to monitor the changes.
```bash ```bash
docker run --rm -v $PWD:/mounted_pybind11 -it silkeh/clang:13 docker run --rm -v $PWD:/mounted_pybind11 -it silkeh/clang:15-bullseye
apt-get update && apt-get install -y python3-dev python3-pytest apt-get update && apt-get install -y git python3-dev python3-pytest
cmake -S /mounted_pybind11/ -B build -DCMAKE_CXX_CLANG_TIDY="$(which clang-tidy);--use-color" -DDOWNLOAD_EIGEN=ON -DDOWNLOAD_CATCH=ON -DCMAKE_CXX_STANDARD=17 cmake -S /mounted_pybind11/ -B build -DCMAKE_CXX_CLANG_TIDY="$(which clang-tidy);--use-color" -DDOWNLOAD_EIGEN=ON -DDOWNLOAD_CATCH=ON -DCMAKE_CXX_STANDARD=17
cmake --build build -j 2 cmake --build build -j 2
``` ```

View File

@ -6,7 +6,8 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
Maintainers will only make a best effort to triage PRs. Please do your best to make the issue as easy to act on as possible, and only open if clearly a problem with pybind11 (ask first if unsure). Please do your best to make the issue as easy to act on as possible, and only submit here if there is clearly a problem with pybind11 (ask first if unsure). **Note that a reproducer in a PR is much more likely to get immediate attention.**
- type: checkboxes - type: checkboxes
id: steps id: steps
attributes: attributes:
@ -20,6 +21,13 @@ body:
- label: Consider asking first in the [Gitter chat room](https://gitter.im/pybind/Lobby) or in a [Discussion](https:/pybind/pybind11/discussions/new). - label: Consider asking first in the [Gitter chat room](https://gitter.im/pybind/Lobby) or in a [Discussion](https:/pybind/pybind11/discussions/new).
required: false required: false
- type: input
id: version
attributes:
label: What version (or hash if on master) of pybind11 are you using?
validations:
required: true
- type: textarea - type: textarea
id: description id: description
attributes: attributes:
@ -40,6 +48,14 @@ body:
The code should be minimal, have no external dependencies, isolate the The code should be minimal, have no external dependencies, isolate the
function(s) that cause breakage. Submit matched and complete C++ and function(s) that cause breakage. Submit matched and complete C++ and
Python snippets that can be easily compiled and run to diagnose the Python snippets that can be easily compiled and run to diagnose the
issue. If possible, make a PR with a new, failing test to give us a issue. — Note that a reproducer in a PR is much more likely to get
starting point to work on! immediate attention: failing tests in the pybind11 CI are the best
starting point for working out fixes.
render: text render: text
- type: input
id: regression
attributes:
label: Is this a regression? Put the last known working version here if it is.
description: Put the last known working version here if this is a regression.
value: Not a regression

View File

@ -9,14 +9,19 @@ on:
- stable - stable
- v* - v*
permissions: read-all
concurrency: concurrency:
group: test-${{ github.ref }} group: test-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
env: env:
PIP_BREAK_SYSTEM_PACKAGES: 1
PIP_ONLY_BINARY: numpy PIP_ONLY_BINARY: numpy
FORCE_COLOR: 3 FORCE_COLOR: 3
PYTEST_TIMEOUT: 300 PYTEST_TIMEOUT: 300
# For cmake:
VERBOSE: 1
jobs: jobs:
# This is the "main" test suite, which tests a large number of different # This is the "main" test suite, which tests a large number of different
@ -25,15 +30,16 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
runs-on: [ubuntu-latest, windows-2022, macos-latest] runs-on: [ubuntu-20.04, windows-2022, macos-latest]
python: python:
- '3.6' - '3.6'
- '3.9' - '3.9'
- '3.10' - '3.10'
- '3.11-dev' - '3.11'
- 'pypy-3.7' - '3.12'
- 'pypy-3.8' - 'pypy-3.8'
- 'pypy-3.9' - 'pypy-3.9'
- 'pypy-3.10'
# Items in here will either be added to the build matrix (if not # 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 # present), or add new keys to an existing matrix element if all the
@ -42,12 +48,12 @@ jobs:
# We support an optional key: args, for cmake args # We support an optional key: args, for cmake args
include: include:
# Just add a key # Just add a key
- runs-on: ubuntu-latest - runs-on: ubuntu-20.04
python: '3.6' python: '3.6'
args: > args: >
-DPYBIND11_FINDPYTHON=ON -DPYBIND11_FINDPYTHON=ON
-DCMAKE_CXX_FLAGS="-D_=1" -DCMAKE_CXX_FLAGS="-D_=1"
- runs-on: ubuntu-latest - runs-on: ubuntu-20.04
python: 'pypy-3.8' python: 'pypy-3.8'
args: > args: >
-DPYBIND11_FINDPYTHON=ON -DPYBIND11_FINDPYTHON=ON
@ -69,6 +75,7 @@ jobs:
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python }} python-version: ${{ matrix.python }}
allow-prereleases: true
- name: Setup Boost (Linux) - name: Setup Boost (Linux)
# Can't use boost + define _ # Can't use boost + define _
@ -80,7 +87,7 @@ jobs:
run: brew install boost run: brew install boost
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.12 uses: jwlawson/actions-setup-cmake@v1.14
- name: Cache wheels - name: Cache wheels
if: runner.os == 'macOS' if: runner.os == 'macOS'
@ -102,10 +109,12 @@ jobs:
run: python -m pip install pytest-github-actions-annotate-failures run: python -m pip install pytest-github-actions-annotate-failures
# First build - C++11 mode and inplace # First build - C++11 mode and inplace
# More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON here.
- name: Configure C++11 ${{ matrix.args }} - name: Configure C++11 ${{ matrix.args }}
run: > run: >
cmake -S . -B . cmake -S . -B .
-DPYBIND11_WERROR=ON -DPYBIND11_WERROR=ON
-DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON
-DDOWNLOAD_CATCH=ON -DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON -DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_STANDARD=11 -DCMAKE_CXX_STANDARD=11
@ -119,7 +128,7 @@ jobs:
- name: C++11 tests - name: C++11 tests
# TODO: Figure out how to load the DLL on Python 3.8+ # TODO: Figure out how to load the DLL on Python 3.8+
if: "!(runner.os == 'Windows' && (matrix.python == 3.8 || matrix.python == 3.9 || matrix.python == '3.10' || matrix.python == '3.11-dev' || matrix.python == 'pypy-3.8'))" if: "!(runner.os == 'Windows' && (matrix.python == 3.8 || matrix.python == 3.9 || matrix.python == '3.10' || matrix.python == '3.11' || matrix.python == 'pypy-3.8'))"
run: cmake --build . --target cpptest -j 2 run: cmake --build . --target cpptest -j 2
- name: Interface test C++11 - name: Interface test C++11
@ -129,10 +138,12 @@ jobs:
run: git clean -fdx run: git clean -fdx
# Second build - C++17 mode and in a build directory # Second build - C++17 mode and in a build directory
# More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF here.
- name: Configure C++17 - name: Configure C++17
run: > run: >
cmake -S . -B build2 cmake -S . -B build2
-DPYBIND11_WERROR=ON -DPYBIND11_WERROR=ON
-DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF
-DDOWNLOAD_CATCH=ON -DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON -DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD=17
@ -146,7 +157,7 @@ jobs:
- name: C++ tests - name: C++ tests
# TODO: Figure out how to load the DLL on Python 3.8+ # TODO: Figure out how to load the DLL on Python 3.8+
if: "!(runner.os == 'Windows' && (matrix.python == 3.8 || matrix.python == 3.9 || matrix.python == '3.10' || matrix.python == '3.11-dev' || matrix.python == 'pypy-3.8'))" if: "!(runner.os == 'Windows' && (matrix.python == 3.8 || matrix.python == 3.9 || matrix.python == '3.10' || matrix.python == '3.11' || matrix.python == 'pypy-3.8'))"
run: cmake --build build2 --target cpptest run: cmake --build build2 --target cpptest
# Third build - C++17 mode with unstable ABI # Third build - C++17 mode with unstable ABI
@ -158,7 +169,6 @@ jobs:
-DDOWNLOAD_EIGEN=ON -DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD=17
-DPYBIND11_INTERNALS_VERSION=10000000 -DPYBIND11_INTERNALS_VERSION=10000000
"-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp"
${{ matrix.args }} ${{ matrix.args }}
- name: Build (unstable ABI) - name: Build (unstable ABI)
@ -173,7 +183,9 @@ jobs:
# This makes sure the setup_helpers module can build packages using # This makes sure the setup_helpers module can build packages using
# setuptools # setuptools
- name: Setuptools helpers test - name: Setuptools helpers test
run: pytest tests/extra_setuptools run: |
pip install setuptools
pytest tests/extra_setuptools
if: "!(matrix.runs-on == 'windows-2022')" if: "!(matrix.runs-on == 'windows-2022')"
@ -186,23 +198,23 @@ jobs:
- python-version: "3.9" - python-version: "3.9"
python-debug: true python-debug: true
valgrind: true valgrind: true
- python-version: "3.11-dev" - python-version: "3.11"
python-debug: false python-debug: false
name: "🐍 ${{ matrix.python-version }}${{ matrix.python-debug && '-dbg' || '' }} (deadsnakes)${{ matrix.valgrind && ' • Valgrind' || '' }} • x64" name: "🐍 ${{ matrix.python-version }}${{ matrix.python-debug && '-dbg' || '' }} (deadsnakes)${{ matrix.valgrind && ' • Valgrind' || '' }} • x64"
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Setup Python ${{ matrix.python-version }} (deadsnakes) - name: Setup Python ${{ matrix.python-version }} (deadsnakes)
uses: deadsnakes/action@v2.1.1 uses: deadsnakes/action@v3.0.1
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
debug: ${{ matrix.python-debug }} debug: ${{ matrix.python-debug }}
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.12 uses: jwlawson/actions-setup-cmake@v1.14
- name: Valgrind cache - name: Valgrind cache
if: matrix.valgrind if: matrix.valgrind
@ -235,8 +247,6 @@ jobs:
python -m pip install -r tests/requirements.txt python -m pip install -r tests/requirements.txt
- name: Configure - name: Configure
env:
SETUPTOOLS_USE_DISTUTILS: stdlib
run: > run: >
cmake -S . -B build cmake -S . -B build
-DCMAKE_BUILD_TYPE=Debug -DCMAKE_BUILD_TYPE=Debug
@ -274,18 +284,27 @@ jobs:
- dev - dev
std: std:
- 11 - 11
container_suffix:
- ""
include: include:
- clang: 5 - clang: 5
std: 14 std: 14
- clang: 10
std: 20
- clang: 10 - clang: 10
std: 17 std: 17
- clang: 11
std: 20
- clang: 12
std: 20
- clang: 13
std: 20
- clang: 14 - clang: 14
std: 20 std: 20
- clang: 15
std: 20
container_suffix: "-bullseye"
name: "🐍 3 • Clang ${{ matrix.clang }} • C++${{ matrix.std }} • x64" name: "🐍 3 • Clang ${{ matrix.clang }} • C++${{ matrix.std }} • x64"
container: "silkeh/clang:${{ matrix.clang }}" container: "silkeh/clang:${{ matrix.clang }}${{ matrix.container_suffix }}"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -318,8 +337,8 @@ jobs:
# Testing NVCC; forces sources to behave like .cu files # Testing NVCC; forces sources to behave like .cu files
cuda: cuda:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: "🐍 3.8 • CUDA 11.2 • Ubuntu 20.04" name: "🐍 3.10 • CUDA 12.2 • Ubuntu 22.04"
container: nvidia/cuda:11.2.2-devel-ubuntu20.04 container: nvidia/cuda:12.2.0-devel-ubuntu22.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -384,8 +403,9 @@ jobs:
# Testing on CentOS 7 + PGI compilers, which seems to require more workarounds # Testing on CentOS 7 + PGI compilers, which seems to require more workarounds
centos-nvhpc7: centos-nvhpc7:
if: ${{ false }} # JOB DISABLED (NEEDS WORK): https://github.com/pybind/pybind11/issues/4690
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: "🐍 3 • CentOS7 / PGI 22.3 • x64" name: "🐍 3 • CentOS7 / PGI 22.9 • x64"
container: centos:7 container: centos:7
steps: steps:
@ -395,7 +415,7 @@ jobs:
run: yum update -y && yum install -y epel-release && yum install -y git python3-devel make environment-modules cmake3 yum-utils run: yum update -y && yum install -y epel-release && yum install -y git python3-devel make environment-modules cmake3 yum-utils
- name: Install NVidia HPC SDK - name: Install NVidia HPC SDK
run: yum-config-manager --add-repo https://developer.download.nvidia.com/hpc-sdk/rhel/nvhpc.repo && yum -y install nvhpc-22.3 run: yum-config-manager --add-repo https://developer.download.nvidia.com/hpc-sdk/rhel/nvhpc.repo && yum -y install nvhpc-22.9
# On CentOS 7, we have to filter a few tests (compiler internal error) # On CentOS 7, we have to filter a few tests (compiler internal error)
# and allow deeper template recursion (not needed on CentOS 8 with a newer # and allow deeper template recursion (not needed on CentOS 8 with a newer
@ -405,12 +425,12 @@ jobs:
shell: bash shell: bash
run: | run: |
source /etc/profile.d/modules.sh source /etc/profile.d/modules.sh
module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/22.3 module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/22.9
cmake3 -S . -B build -DDOWNLOAD_CATCH=ON \ cmake3 -S . -B build -DDOWNLOAD_CATCH=ON \
-DCMAKE_CXX_STANDARD=11 \ -DCMAKE_CXX_STANDARD=11 \
-DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \
-DCMAKE_CXX_FLAGS="-Wc,--pending_instantiations=0" \ -DCMAKE_CXX_FLAGS="-Wc,--pending_instantiations=0" \
-DPYBIND11_TEST_FILTER="test_smart_ptr.cpp;test_virtual_functions.cpp" -DPYBIND11_TEST_FILTER="test_smart_ptr.cpp"
# Building before installing Pip should produce a warning but not an error # Building before installing Pip should produce a warning but not an error
- name: Build - name: Build
@ -437,14 +457,14 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
gcc:
- 7
- latest
std:
- 11
include: include:
- gcc: 10 - { gcc: 7, std: 11 }
std: 20 - { gcc: 7, std: 17 }
- { gcc: 8, std: 14 }
- { gcc: 8, std: 17 }
- { gcc: 10, std: 17 }
- { gcc: 11, std: 20 }
- { gcc: 12, std: 20 }
name: "🐍 3 • GCC ${{ matrix.gcc }} • C++${{ matrix.std }}• x64" name: "🐍 3 • GCC ${{ matrix.gcc }} • C++${{ matrix.std }}• x64"
container: "gcc:${{ matrix.gcc }}" container: "gcc:${{ matrix.gcc }}"
@ -459,7 +479,7 @@ jobs:
run: python3 -m pip install --upgrade pip run: python3 -m pip install --upgrade pip
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.12 uses: jwlawson/actions-setup-cmake@v1.14
- name: Configure - name: Configure
shell: bash shell: bash
@ -482,6 +502,24 @@ jobs:
- name: Interface test - name: Interface test
run: cmake --build build --target test_cmake_build run: cmake --build build --target test_cmake_build
- name: Configure - Exercise cmake -DPYBIND11_TEST_OVERRIDE
if: matrix.gcc == '12'
shell: bash
run: >
cmake -S . -B build_partial
-DPYBIND11_WERROR=ON
-DDOWNLOAD_CATCH=ON
-DCMAKE_CXX_STANDARD=${{ matrix.std }}
-DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)")
"-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp"
- name: Build - Exercise cmake -DPYBIND11_TEST_OVERRIDE
if: matrix.gcc == '12'
run: cmake --build build_partial -j 2
- name: Python tests - Exercise cmake -DPYBIND11_TEST_OVERRIDE
if: matrix.gcc == '12'
run: cmake --build build_partial --target pytest
# Testing on ICC using the oneAPI apt repo # Testing on ICC using the oneAPI apt repo
icc: icc:
@ -731,6 +769,9 @@ jobs:
args: -DCMAKE_CXX_STANDARD=20 args: -DCMAKE_CXX_STANDARD=20
- python: 3.8 - python: 3.8
args: -DCMAKE_CXX_STANDARD=17 args: -DCMAKE_CXX_STANDARD=17
- python: 3.7
args: -DCMAKE_CXX_STANDARD=14
name: "🐍 ${{ matrix.python }} • MSVC 2019 • x86 ${{ matrix.args }}" name: "🐍 ${{ matrix.python }} • MSVC 2019 • x86 ${{ matrix.args }}"
runs-on: windows-2019 runs-on: windows-2019
@ -745,10 +786,10 @@ jobs:
architecture: x86 architecture: x86
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.12 uses: jwlawson/actions-setup-cmake@v1.14
- name: Prepare MSVC - name: Prepare MSVC
uses: ilammy/msvc-dev-cmd@v1.10.0 uses: ilammy/msvc-dev-cmd@v1.12.1
with: with:
arch: x86 arch: x86
@ -798,10 +839,10 @@ jobs:
architecture: x86 architecture: x86
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.12 uses: jwlawson/actions-setup-cmake@v1.14
- name: Prepare MSVC - name: Prepare MSVC
uses: ilammy/msvc-dev-cmd@v1.10.0 uses: ilammy/msvc-dev-cmd@v1.12.1
with: with:
arch: x86 arch: x86
@ -849,7 +890,7 @@ jobs:
python3 -m pip install -r tests/requirements.txt python3 -m pip install -r tests/requirements.txt
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.12 uses: jwlawson/actions-setup-cmake@v1.14
- name: Configure C++20 - name: Configure C++20
run: > run: >
@ -871,6 +912,21 @@ jobs:
- name: Interface test C++20 - name: Interface test C++20
run: cmake --build build --target test_cmake_build run: cmake --build build --target test_cmake_build
- name: Configure C++20 - Exercise cmake -DPYBIND11_TEST_OVERRIDE
run: >
cmake -S . -B build_partial
-DPYBIND11_WERROR=ON
-DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_STANDARD=20
"-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp"
- name: Build C++20 - Exercise cmake -DPYBIND11_TEST_OVERRIDE
run: cmake --build build_partial -j 2
- name: Python tests - Exercise cmake -DPYBIND11_TEST_OVERRIDE
run: cmake --build build_partial --target pytest
mingw: mingw:
name: "🐍 3 • windows-latest • ${{ matrix.sys }}" name: "🐍 3 • windows-latest • ${{ matrix.sys }}"
runs-on: windows-latest runs-on: windows-latest
@ -905,7 +961,7 @@ jobs:
- name: Configure C++11 - name: Configure C++11
# LTO leads to many undefined reference like # LTO leads to many undefined reference like
# `pybind11::detail::function_call::function_call(pybind11::detail::function_call&&) # `pybind11::detail::function_call::function_call(pybind11::detail::function_call&&)
run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=11 -DDOWNLOAD_CATCH=ON -S . -B build run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=11 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -S . -B build
- name: Build C++11 - name: Build C++11
run: cmake --build build -j 2 run: cmake --build build -j 2
@ -923,7 +979,7 @@ jobs:
run: git clean -fdx run: git clean -fdx
- name: Configure C++14 - name: Configure C++14
run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=14 -DDOWNLOAD_CATCH=ON -S . -B build2 run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=14 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -S . -B build2
- name: Build C++14 - name: Build C++14
run: cmake --build build2 -j 2 run: cmake --build build2 -j 2
@ -941,7 +997,7 @@ jobs:
run: git clean -fdx run: git clean -fdx
- name: Configure C++17 - name: Configure C++17
run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=17 -DDOWNLOAD_CATCH=ON -S . -B build3 run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=17 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -S . -B build3
- name: Build C++17 - name: Build C++17
run: cmake --build build3 -j 2 run: cmake --build build3 -j 2
@ -954,3 +1010,156 @@ jobs:
- name: Interface test C++17 - name: Interface test C++17
run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build3 --target test_cmake_build run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build3 --target test_cmake_build
windows_clang:
strategy:
matrix:
os: [windows-latest]
python: ['3.10']
runs-on: "${{ matrix.os }}"
name: "🐍 ${{ matrix.python }} • ${{ matrix.os }} • clang-latest"
steps:
- name: Show env
run: env
- name: Checkout
uses: actions/checkout@v3
- name: Set up Clang
uses: egor-tensin/setup-clang@v1
- name: Setup Python ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
- name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.14
- name: Install ninja-build tool
uses: seanmiddleditch/gha-setup-ninja@v3
- name: Run pip installs
run: |
python -m pip install --upgrade pip
python -m pip install -r tests/requirements.txt
- name: Show Clang++ version
run: clang++ --version
- name: Show CMake version
run: cmake --version
# TODO: WERROR=ON
- name: Configure Clang
run: >
cmake -G Ninja -S . -B .
-DPYBIND11_WERROR=OFF
-DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF
-DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_COMPILER=clang++
-DCMAKE_CXX_STANDARD=17
- name: Build
run: cmake --build . -j 2
- name: Python tests
run: cmake --build . --target pytest -j 2
- name: C++ tests
run: cmake --build . --target cpptest -j 2
- name: Interface test
run: cmake --build . --target test_cmake_build -j 2
- name: Clean directory
run: git clean -fdx
macos_brew_install_llvm:
name: "macos-latest • brew install llvm"
runs-on: macos-latest
env:
# https://apple.stackexchange.com/questions/227026/how-to-install-recent-clang-with-homebrew
LDFLAGS: '-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib'
steps:
- name: Update PATH
run: echo "/usr/local/opt/llvm/bin" >> $GITHUB_PATH
- name: Show env
run: env
- name: Checkout
uses: actions/checkout@v3
- name: Show Clang++ version before brew install llvm
run: clang++ --version
- name: brew install llvm
run: brew install llvm
- name: Show Clang++ version after brew install llvm
run: clang++ --version
- name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.14
- name: Run pip installs
run: |
python3 -m pip install --upgrade pip
python3 -m pip install -r tests/requirements.txt
python3 -m pip install numpy
python3 -m pip install scipy
- name: Show CMake version
run: cmake --version
- name: CMake Configure
run: >
cmake -S . -B .
-DPYBIND11_WERROR=ON
-DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF
-DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_COMPILER=clang++
-DCMAKE_CXX_STANDARD=17
-DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)")
- name: Build
run: cmake --build . -j 2
- name: Python tests
run: cmake --build . --target pytest -j 2
- name: C++ tests
run: cmake --build . --target cpptest -j 2
- name: Interface test
run: cmake --build . --target test_cmake_build -j 2
- name: CMake Configure - Exercise cmake -DPYBIND11_TEST_OVERRIDE
run: >
cmake -S . -B build_partial
-DPYBIND11_WERROR=ON
-DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF
-DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_COMPILER=clang++
-DCMAKE_CXX_STANDARD=17
-DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)")
"-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp"
- name: Build - Exercise cmake -DPYBIND11_TEST_OVERRIDE
run: cmake --build build_partial -j 2
- name: Python tests - Exercise cmake -DPYBIND11_TEST_OVERRIDE
run: cmake --build build_partial --target pytest -j 2
- name: Clean directory
run: git clean -fdx

View File

@ -9,6 +9,14 @@ on:
- stable - stable
- v* - v*
permissions:
contents: read
env:
PIP_BREAK_SYSTEM_PACKAGES: 1
# For cmake:
VERBOSE: 1
jobs: jobs:
# This tests various versions of CMake in various combinations, to make sure # This tests various versions of CMake in various combinations, to make sure
# the configure step passes. # the configure step passes.
@ -16,22 +24,26 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
runs-on: [ubuntu-latest, macos-latest, windows-latest] runs-on: [ubuntu-20.04, macos-latest, windows-latest]
arch: [x64] arch: [x64]
cmake: ["3.23"] cmake: ["3.26"]
include: include:
- runs-on: ubuntu-latest - runs-on: ubuntu-20.04
arch: x64 arch: x64
cmake: 3.4 cmake: "3.5"
- runs-on: ubuntu-20.04
arch: x64
cmake: "3.27"
- runs-on: macos-latest - runs-on: macos-latest
arch: x64 arch: x64
cmake: 3.7 cmake: "3.7"
- runs-on: windows-2019 - runs-on: windows-2019
arch: x64 # x86 compilers seem to be missing on 2019 image arch: x64 # x86 compilers seem to be missing on 2019 image
cmake: 3.18 cmake: "3.18"
name: 🐍 3.7 • CMake ${{ matrix.cmake }} • ${{ matrix.runs-on }} name: 🐍 3.7 • CMake ${{ matrix.cmake }} • ${{ matrix.runs-on }}
runs-on: ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }}
@ -51,7 +63,7 @@ jobs:
# An action for adding a specific version of CMake: # An action for adding a specific version of CMake:
# https://github.com/jwlawson/actions-setup-cmake # https://github.com/jwlawson/actions-setup-cmake
- name: Setup CMake ${{ matrix.cmake }} - name: Setup CMake ${{ matrix.cmake }}
uses: jwlawson/actions-setup-cmake@v1.12 uses: jwlawson/actions-setup-cmake@v1.14
with: with:
cmake-version: ${{ matrix.cmake }} cmake-version: ${{ matrix.cmake }}

View File

@ -12,8 +12,13 @@ on:
- stable - stable
- "v*" - "v*"
permissions:
contents: read
env: env:
FORCE_COLOR: 3 FORCE_COLOR: 3
# For cmake:
VERBOSE: 1
jobs: jobs:
pre-commit: pre-commit:
@ -36,12 +41,12 @@ jobs:
# in .github/CONTRIBUTING.md and update as needed. # in .github/CONTRIBUTING.md and update as needed.
name: Clang-Tidy name: Clang-Tidy
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: silkeh/clang:13 container: silkeh/clang:15-bullseye
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Install requirements - name: Install requirements
run: apt-get update && apt-get install -y python3-dev python3-pytest run: apt-get update && apt-get install -y git python3-dev python3-pytest
- name: Configure - name: Configure
run: > run: >

View File

@ -3,14 +3,23 @@ on:
pull_request_target: pull_request_target:
types: [closed] types: [closed]
permissions: {}
jobs: jobs:
label: label:
name: Labeler name: Labeler
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps: steps:
- uses: actions/labeler@main - uses: actions/labeler@main
if: github.event.pull_request.merged == true if: >
github.event.pull_request.merged == true &&
!startsWith(github.event.pull_request.title, 'chore(deps):') &&
!startsWith(github.event.pull_request.title, 'ci(fix):') &&
!startsWith(github.event.pull_request.title, 'docs(changelog):')
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
configuration-path: .github/labeler_merged.yml configuration-path: .github/labeler_merged.yml

View File

@ -12,7 +12,11 @@ on:
types: types:
- published - published
permissions:
contents: read
env: env:
PIP_BREAK_SYSTEM_PACKAGES: 1
PIP_ONLY_BINARY: numpy PIP_ONLY_BINARY: numpy
jobs: jobs:
@ -98,13 +102,13 @@ jobs:
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
- name: Publish standard package - name: Publish standard package
uses: pypa/gh-action-pypi-publish@v1.5.0 uses: pypa/gh-action-pypi-publish@release/v1
with: with:
password: ${{ secrets.pypi_password }} password: ${{ secrets.pypi_password }}
packages_dir: standard/ packages-dir: standard/
- name: Publish global package - name: Publish global package
uses: pypa/gh-action-pypi-publish@v1.5.0 uses: pypa/gh-action-pypi-publish@release/v1
with: with:
password: ${{ secrets.pypi_password_global }} password: ${{ secrets.pypi_password_global }}
packages_dir: global/ packages-dir: global/

View File

@ -1,112 +1,116 @@
name: Upstream name: Upstream
on: on:
workflow_dispatch: workflow_dispatch:
pull_request: pull_request:
permissions:
contents: read
concurrency: concurrency:
group: upstream-${{ github.ref }} group: upstream-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
env: env:
PIP_ONLY_BINARY: numpy PIP_BREAK_SYSTEM_PACKAGES: 1
PIP_ONLY_BINARY: ":all:"
# For cmake:
VERBOSE: 1
jobs: jobs:
standard: standard:
name: "🐍 3.11 latest internals • ubuntu-latest • x64" name: "🐍 3.12 latest • ubuntu-latest • x64"
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Only runs when the 'python dev' label is selected
if: "contains(github.event.pull_request.labels.*.name, 'python dev')" if: "contains(github.event.pull_request.labels.*.name, 'python dev')"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Setup Python 3.11 - name: Setup Python 3.12
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: "3.11-dev" python-version: "3.12-dev"
- name: Setup Boost (Linux) - name: Setup Boost
if: runner.os == 'Linux'
run: sudo apt-get install libboost-dev run: sudo apt-get install libboost-dev
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.12 uses: jwlawson/actions-setup-cmake@v1.14
- name: Prepare env - name: Run pip installs
run: | run: |
python -m pip install --upgrade pip
python -m pip install -r tests/requirements.txt python -m pip install -r tests/requirements.txt
- name: Setup annotations on Linux - name: Show platform info
if: runner.os == 'Linux' run: |
run: python -m pip install pytest-github-actions-annotate-failures python -m platform
cmake --version
pip list
# First build - C++11 mode and inplace # First build - C++11 mode and inplace
- name: Configure C++11 - name: Configure C++11
run: > run: >
cmake -S . -B . cmake -S . -B build11
-DPYBIND11_WERROR=ON -DPYBIND11_WERROR=ON
-DDOWNLOAD_CATCH=ON -DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON -DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_STANDARD=11 -DCMAKE_CXX_STANDARD=11
-DCMAKE_BUILD_TYPE=Debug
- name: Build C++11 - name: Build C++11
run: cmake --build . -j 2 run: cmake --build build11 -j 2
- name: Python tests C++11 - name: Python tests C++11
run: cmake --build . --target pytest -j 2 run: cmake --build build11 --target pytest -j 2
- name: C++11 tests - name: C++11 tests
run: cmake --build . --target cpptest -j 2 run: cmake --build build11 --target cpptest -j 2
- name: Interface test C++11 - name: Interface test C++11
run: cmake --build . --target test_cmake_build run: cmake --build build11 --target test_cmake_build
- name: Clean directory
run: git clean -fdx
# Second build - C++17 mode and in a build directory # Second build - C++17 mode and in a build directory
- name: Configure C++17 - name: Configure C++17
run: > run: >
cmake -S . -B build2 cmake -S . -B build17
-DPYBIND11_WERROR=ON -DPYBIND11_WERROR=ON
-DDOWNLOAD_CATCH=ON -DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON -DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD=17
${{ matrix.args }}
${{ matrix.args2 }}
- name: Build - name: Build C++17
run: cmake --build build2 -j 2 run: cmake --build build17 -j 2
- name: Python tests - name: Python tests C++17
run: cmake --build build2 --target pytest run: cmake --build build17 --target pytest
- name: C++ tests - name: C++17 tests
run: cmake --build build2 --target cpptest run: cmake --build build17 --target cpptest
# Third build - C++17 mode with unstable ABI # Third build - C++17 mode with unstable ABI
- name: Configure (unstable ABI) - name: Configure (unstable ABI)
run: > run: >
cmake -S . -B build3 cmake -S . -B build17max
-DPYBIND11_WERROR=ON -DPYBIND11_WERROR=ON
-DDOWNLOAD_CATCH=ON -DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON -DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD=17
-DPYBIND11_INTERNALS_VERSION=10000000 -DPYBIND11_INTERNALS_VERSION=10000000
"-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp"
${{ matrix.args }}
- name: Build (unstable ABI) - name: Build (unstable ABI)
run: cmake --build build3 -j 2 run: cmake --build build17max -j 2
- name: Python tests (unstable ABI) - name: Python tests (unstable ABI)
run: cmake --build build3 --target pytest run: cmake --build build17max --target pytest
- name: Interface test - name: Interface test (unstable ABI)
run: cmake --build build2 --target test_cmake_build run: cmake --build build17max --target test_cmake_build
# This makes sure the setup_helpers module can build packages using # This makes sure the setup_helpers module can build packages using
# setuptools # setuptools
- name: Setuptools helpers test - name: Setuptools helpers test
run: pytest tests/extra_setuptools run: |
pip install setuptools
pytest tests/extra_setuptools

View File

@ -43,3 +43,4 @@ pybind11Targets.cmake
/pybind11/share/* /pybind11/share/*
/docs/_build/* /docs/_build/*
.ipynb_checkpoints/ .ipynb_checkpoints/
tests/main.cpp

View File

@ -12,10 +12,62 @@
# #
# See https://github.com/pre-commit/pre-commit # See https://github.com/pre-commit/pre-commit
ci:
autoupdate_commit_msg: "chore(deps): update pre-commit hooks"
autofix_commit_msg: "style: pre-commit fixes"
autoupdate_schedule: monthly
# third-party content
exclude: ^tools/JoinPaths.cmake$
repos: repos:
# Clang format the codebase automatically
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: "v16.0.6"
hooks:
- id: clang-format
types_or: [c++, c, cuda]
# Black, the code formatter, natively supports pre-commit
- repo: https://github.com/psf/black
rev: "23.3.0" # Keep in sync with blacken-docs
hooks:
- id: black
# Ruff, the Python auto-correcting linter written in Rust
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.276
hooks:
- id: ruff
args: ["--fix", "--show-fixes"]
# Check static types with mypy
- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.4.1"
hooks:
- id: mypy
args: []
exclude: ^(tests|docs)/
additional_dependencies:
- markdown-it-py<3 # Drop this together with dropping Python 3.7 support.
- nox
- rich
- types-setuptools
# CMake formatting
- repo: https://github.com/cheshirekow/cmake-format-precommit
rev: "v0.6.13"
hooks:
- id: cmake-format
additional_dependencies: [pyyaml]
types: [file]
files: (\.cmake|CMakeLists.txt)(.in)?$
# Standard hooks # Standard hooks
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: "v4.3.0" rev: "v4.4.0"
hooks: hooks:
- id: check-added-large-files - id: check-added-large-files
- id: check-case-conflict - id: check-case-conflict
@ -30,109 +82,38 @@ repos:
- id: requirements-txt-fixer - id: requirements-txt-fixer
- id: trailing-whitespace - id: trailing-whitespace
# Upgrade old Python syntax
- repo: https://github.com/asottile/pyupgrade
rev: "v2.37.1"
hooks:
- id: pyupgrade
args: [--py36-plus]
# Nicely sort includes
- repo: https://github.com/PyCQA/isort
rev: "5.10.1"
hooks:
- id: isort
# Black, the code formatter, natively supports pre-commit
- repo: https://github.com/psf/black
rev: "22.6.0" # Keep in sync with blacken-docs
hooks:
- id: black
# Also code format the docs # Also code format the docs
- repo: https://github.com/asottile/blacken-docs - repo: https://github.com/asottile/blacken-docs
rev: "v1.12.1" rev: "1.14.0"
hooks: hooks:
- id: blacken-docs - id: blacken-docs
additional_dependencies: additional_dependencies:
- black==22.6.0 # keep in sync with black hook - black==23.3.0 # keep in sync with black hook
# Changes tabs to spaces # Changes tabs to spaces
- repo: https://github.com/Lucas-C/pre-commit-hooks - repo: https://github.com/Lucas-C/pre-commit-hooks
rev: "v1.3.0" rev: "v1.5.1"
hooks: hooks:
- id: remove-tabs - id: remove-tabs
# Avoid directional quotes
- repo: https://github.com/sirosen/texthooks - repo: https://github.com/sirosen/texthooks
rev: "0.3.1" rev: "0.5.0"
hooks: hooks:
- id: fix-ligatures - id: fix-ligatures
- id: fix-smartquotes - id: fix-smartquotes
# Autoremoves unused imports
- repo: https://github.com/hadialqattan/pycln
rev: "v2.0.1"
hooks:
- id: pycln
stages: [manual]
# Checking for common mistakes # Checking for common mistakes
- repo: https://github.com/pre-commit/pygrep-hooks - repo: https://github.com/pre-commit/pygrep-hooks
rev: "v1.9.0" rev: "v1.10.0"
hooks: hooks:
- id: python-check-blanket-noqa
- id: python-check-blanket-type-ignore
- id: python-no-log-warn
- id: python-use-type-annotations
- id: rst-backticks - id: rst-backticks
- id: rst-directive-colons - id: rst-directive-colons
- id: rst-inline-touching-normal - id: rst-inline-touching-normal
# Automatically remove noqa that are not used
- repo: https://github.com/asottile/yesqa
rev: "v1.3.0"
hooks:
- id: yesqa
additional_dependencies: &flake8_dependencies
- flake8-bugbear
- pep8-naming
# Flake8 also supports pre-commit natively (same author)
- repo: https://github.com/PyCQA/flake8
rev: "4.0.1"
hooks:
- id: flake8
exclude: ^(docs/.*|tools/.*)$
additional_dependencies: *flake8_dependencies
# PyLint has native support - not always usable, but works for us
- repo: https://github.com/PyCQA/pylint
rev: "v2.14.4"
hooks:
- id: pylint
files: ^pybind11
# CMake formatting
- repo: https://github.com/cheshirekow/cmake-format-precommit
rev: "v0.6.13"
hooks:
- id: cmake-format
additional_dependencies: [pyyaml]
types: [file]
files: (\.cmake|CMakeLists.txt)(.in)?$
# Check static types with mypy
- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v0.961"
hooks:
- id: mypy
args: []
exclude: ^(tests|docs)/
additional_dependencies: [nox, rich]
# Checks the manifest for missing files (native support) # Checks the manifest for missing files (native support)
- repo: https://github.com/mgedmin/check-manifest - repo: https://github.com/mgedmin/check-manifest
rev: "0.48" rev: "0.49"
hooks: hooks:
- id: check-manifest - id: check-manifest
# This is a slow hook, so only run this if --hook-stage manual is passed # This is a slow hook, so only run this if --hook-stage manual is passed
@ -140,16 +121,18 @@ repos:
additional_dependencies: [cmake, ninja] additional_dependencies: [cmake, ninja]
# Check for spelling # Check for spelling
# Use tools/codespell_ignore_lines_from_errors.py
# to rebuild .codespell-ignore-lines
- repo: https://github.com/codespell-project/codespell - repo: https://github.com/codespell-project/codespell
rev: "v2.1.0" rev: "v2.2.5"
hooks: hooks:
- id: codespell - id: codespell
exclude: ".supp$" exclude: ".supp$"
args: ["-L", "nd,ot,thist"] args: ["-x.codespell-ignore-lines", "-Lccompiler"]
# Check for common shell mistakes # Check for common shell mistakes
- repo: https://github.com/shellcheck-py/shellcheck-py - repo: https://github.com/shellcheck-py/shellcheck-py
rev: "v0.8.0.4" rev: "v0.9.0.5"
hooks: hooks:
- id: shellcheck - id: shellcheck
@ -162,9 +145,9 @@ repos:
entry: PyBind|Numpy|Cmake|CCache|PyTest entry: PyBind|Numpy|Cmake|CCache|PyTest
exclude: ^\.pre-commit-config.yaml$ exclude: ^\.pre-commit-config.yaml$
# Clang format the codebase automatically # PyLint has native support - not always usable, but works for us
- repo: https://github.com/pre-commit/mirrors-clang-format - repo: https://github.com/PyCQA/pylint
rev: "v14.0.6" rev: "v3.0.0a6"
hooks: hooks:
- id: clang-format - id: pylint
types_or: [c++, c, cuda] files: ^pybind11

View File

@ -5,15 +5,15 @@
# All rights reserved. Use of this source code is governed by a # All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file. # BSD-style license that can be found in the LICENSE file.
cmake_minimum_required(VERSION 3.4) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.4...3.22)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate # some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround: # the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.22) if(${CMAKE_VERSION} VERSION_LESS 3.26)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.22) cmake_policy(VERSION 3.26)
endif() endif()
# Avoid infinite recursion if tests include this as a subdirectory # Avoid infinite recursion if tests include this as a subdirectory
@ -91,10 +91,16 @@ endif()
option(PYBIND11_INSTALL "Install pybind11 header files?" ${PYBIND11_MASTER_PROJECT}) option(PYBIND11_INSTALL "Install pybind11 header files?" ${PYBIND11_MASTER_PROJECT})
option(PYBIND11_TEST "Build pybind11 test suite?" ${PYBIND11_MASTER_PROJECT}) option(PYBIND11_TEST "Build pybind11 test suite?" ${PYBIND11_MASTER_PROJECT})
option(PYBIND11_NOPYTHON "Disable search for Python" OFF) option(PYBIND11_NOPYTHON "Disable search for Python" OFF)
option(PYBIND11_SIMPLE_GIL_MANAGEMENT
"Use simpler GIL management logic that does not support disassociation" OFF)
set(PYBIND11_INTERNALS_VERSION set(PYBIND11_INTERNALS_VERSION
"" ""
CACHE STRING "Override the ABI version, may be used to enable the unstable ABI.") CACHE STRING "Override the ABI version, may be used to enable the unstable ABI.")
if(PYBIND11_SIMPLE_GIL_MANAGEMENT)
add_compile_definitions(PYBIND11_SIMPLE_GIL_MANAGEMENT)
endif()
cmake_dependent_option( cmake_dependent_option(
USE_PYTHON_INCLUDE_DIR USE_PYTHON_INCLUDE_DIR
"Install pybind11 headers in Python include directory instead of default installation prefix" "Install pybind11 headers in Python include directory instead of default installation prefix"
@ -120,6 +126,9 @@ set(PYBIND11_HEADERS
include/pybind11/complex.h include/pybind11/complex.h
include/pybind11/options.h include/pybind11/options.h
include/pybind11/eigen.h include/pybind11/eigen.h
include/pybind11/eigen/common.h
include/pybind11/eigen/matrix.h
include/pybind11/eigen/tensor.h
include/pybind11/embed.h include/pybind11/embed.h
include/pybind11/eval.h include/pybind11/eval.h
include/pybind11/gil.h include/pybind11/gil.h
@ -131,7 +140,8 @@ set(PYBIND11_HEADERS
include/pybind11/pytypes.h include/pybind11/pytypes.h
include/pybind11/stl.h include/pybind11/stl.h
include/pybind11/stl_bind.h include/pybind11/stl_bind.h
include/pybind11/stl/filesystem.h) include/pybind11/stl/filesystem.h
include/pybind11/type_caster_pyobject_ptr.h)
# Compare with grep and warn if mismatched # Compare with grep and warn if mismatched
if(PYBIND11_MASTER_PROJECT AND NOT CMAKE_VERSION VERSION_LESS 3.12) if(PYBIND11_MASTER_PROJECT AND NOT CMAKE_VERSION VERSION_LESS 3.12)
@ -198,6 +208,9 @@ else()
endif() endif()
include("${CMAKE_CURRENT_SOURCE_DIR}/tools/pybind11Common.cmake") include("${CMAKE_CURRENT_SOURCE_DIR}/tools/pybind11Common.cmake")
# https://github.com/jtojnar/cmake-snips/#concatenating-paths-when-building-pkg-config-files
# TODO: cmake 3.20 adds the cmake_path() function, which obsoletes this snippet
include("${CMAKE_CURRENT_SOURCE_DIR}/tools/JoinPaths.cmake")
# Relative directory setting # Relative directory setting
if(USE_PYTHON_INCLUDE_DIR AND DEFINED Python_INCLUDE_DIRS) if(USE_PYTHON_INCLUDE_DIR AND DEFINED Python_INCLUDE_DIRS)
@ -262,6 +275,16 @@ if(PYBIND11_INSTALL)
NAMESPACE "pybind11::" NAMESPACE "pybind11::"
DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR}) DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR})
# pkg-config support
if(NOT prefix_for_pc_file)
set(prefix_for_pc_file "${CMAKE_INSTALL_PREFIX}")
endif()
join_paths(includedir_for_pc_file "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/tools/pybind11.pc.in"
"${CMAKE_CURRENT_BINARY_DIR}/pybind11.pc" @ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pybind11.pc"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig/")
# Uninstall target # Uninstall target
if(PYBIND11_MASTER_PROJECT) if(PYBIND11_MASTER_PROJECT)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake_uninstall.cmake.in" configure_file("${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake_uninstall.cmake.in"

View File

@ -1,5 +1,6 @@
prune tests
recursive-include pybind11/include/pybind11 *.h recursive-include pybind11/include/pybind11 *.h
recursive-include pybind11 *.py recursive-include pybind11 *.py
recursive-include pybind11 py.typed recursive-include pybind11 py.typed
include pybind11/share/cmake/pybind11/*.cmake include pybind11/share/cmake/pybind11/*.cmake
include LICENSE README.rst pyproject.toml setup.py setup.cfg include LICENSE README.rst SECURITY.md pyproject.toml setup.py setup.cfg

View File

@ -135,7 +135,7 @@ This project was created by `Wenzel
Jakob <http://rgl.epfl.ch/people/wjakob>`_. Significant features and/or Jakob <http://rgl.epfl.ch/people/wjakob>`_. Significant features and/or
improvements to the code were contributed by Jonas Adler, Lori A. Burns, improvements to the code were contributed by Jonas Adler, Lori A. Burns,
Sylvain Corlay, Eric Cousineau, Aaron Gokaslan, Ralf Grosse-Kunstleve, Trent Houliston, Axel Sylvain Corlay, Eric Cousineau, Aaron Gokaslan, Ralf Grosse-Kunstleve, Trent Houliston, Axel
Huebl, @hulucc, Yannick Jadoul, Sergey Lyskov Johan Mabille, Tomasz Miąsko, Huebl, @hulucc, Yannick Jadoul, Sergey Lyskov, Johan Mabille, Tomasz Miąsko,
Dean Moldovan, Ben Pritchard, Jason Rhinelander, Boris Schäling, Pim Dean Moldovan, Ben Pritchard, Jason Rhinelander, Boris Schäling, Pim
Schellart, Henry Schreiner, Ivan Smirnov, Boris Staletic, and Patrick Stewart. Schellart, Henry Schreiner, Ivan Smirnov, Boris Staletic, and Patrick Stewart.

13
wrap/pybind11/SECURITY.md Normal file
View File

@ -0,0 +1,13 @@
# Security Policy
## Supported Versions
Security updates are applied only to the latest release.
## Reporting a Vulnerability
If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released.
Please disclose it at [security advisory](https://github.com/pybind/pybind11/security/advisories/new).
This project is maintained by a team of volunteers on a reasonable-effort basis. As such, please give us at least 90 days to work on a fix before public exposure.

View File

@ -38,7 +38,7 @@ type is explicitly allowed.
.. code-block:: cpp .. code-block:: cpp
namespace pybind11 { namespace detail { namespace PYBIND11_NAMESPACE { namespace detail {
template <> struct type_caster<inty> { template <> struct type_caster<inty> {
public: public:
/** /**
@ -78,7 +78,7 @@ type is explicitly allowed.
return PyLong_FromLong(src.long_value); return PyLong_FromLong(src.long_value);
} }
}; };
}} // namespace pybind11::detail }} // namespace PYBIND11_NAMESPACE::detail
.. note:: .. note::

View File

@ -42,7 +42,7 @@ types:
.. code-block:: cpp .. code-block:: cpp
// `boost::optional` as an example -- can be any `std::optional`-like container // `boost::optional` as an example -- can be any `std::optional`-like container
namespace pybind11 { namespace detail { namespace PYBIND11_NAMESPACE { namespace detail {
template <typename T> template <typename T>
struct type_caster<boost::optional<T>> : optional_caster<boost::optional<T>> {}; struct type_caster<boost::optional<T>> : optional_caster<boost::optional<T>> {};
}} }}
@ -54,7 +54,7 @@ for custom variant types:
.. code-block:: cpp .. code-block:: cpp
// `boost::variant` as an example -- can be any `std::variant`-like container // `boost::variant` as an example -- can be any `std::variant`-like container
namespace pybind11 { namespace detail { namespace PYBIND11_NAMESPACE { namespace detail {
template <typename... Ts> template <typename... Ts>
struct type_caster<boost::variant<Ts...>> : variant_caster<boost::variant<Ts...>> {}; struct type_caster<boost::variant<Ts...>> : variant_caster<boost::variant<Ts...>> {};
@ -66,7 +66,7 @@ for custom variant types:
return boost::apply_visitor(args...); return boost::apply_visitor(args...);
} }
}; };
}} // namespace pybind11::detail }} // namespace PYBIND11_NAMESPACE::detail
The ``visit_helper`` specialization is not required if your ``name::variant`` provides The ``visit_helper`` specialization is not required if your ``name::variant`` provides
a ``name::visit()`` function. For any other function name, the specialization must be a ``name::visit()`` function. For any other function name, the specialization must be

View File

@ -101,8 +101,11 @@ conversion has the same overhead as implicit conversion.
m.def("str_output", m.def("str_output",
[]() { []() {
std::string s = "Send your r\xe9sum\xe9 to Alice in HR"; // Latin-1 std::string s = "Send your r\xe9sum\xe9 to Alice in HR"; // Latin-1
py::str py_s = PyUnicode_DecodeLatin1(s.data(), s.length()); py::handle py_s = PyUnicode_DecodeLatin1(s.data(), s.length(), nullptr);
return py_s; if (!py_s) {
throw py::error_already_set();
}
return py::reinterpret_steal<py::str>(py_s);
} }
); );
@ -113,7 +116,8 @@ conversion has the same overhead as implicit conversion.
The `Python C API The `Python C API
<https://docs.python.org/3/c-api/unicode.html#built-in-codecs>`_ provides <https://docs.python.org/3/c-api/unicode.html#built-in-codecs>`_ provides
several built-in codecs. several built-in codecs. Note that these all return *new* references, so
use :cpp:func:`reinterpret_steal` when converting them to a :cpp:class:`str`.
One could also use a third party encoding library such as libiconv to transcode One could also use a third party encoding library such as libiconv to transcode

View File

@ -1228,7 +1228,7 @@ whether a downcast is safe, you can proceed by specializing the
std::string bark() const { return sound; } std::string bark() const { return sound; }
}; };
namespace pybind11 { namespace PYBIND11_NAMESPACE {
template<> struct polymorphic_type_hook<Pet> { template<> struct polymorphic_type_hook<Pet> {
static const void *get(const Pet *src, const std::type_info*& type) { static const void *get(const Pet *src, const std::type_info*& type) {
// note that src may be nullptr // note that src may be nullptr
@ -1239,7 +1239,7 @@ whether a downcast is safe, you can proceed by specializing the
return src; return src;
} }
}; };
} // namespace pybind11 } // namespace PYBIND11_NAMESPACE
When pybind11 wants to convert a C++ pointer of type ``Base*`` to a When pybind11 wants to convert a C++ pointer of type ``Base*`` to a
Python object, it calls ``polymorphic_type_hook<Base>::get()`` to Python object, it calls ``polymorphic_type_hook<Base>::get()`` to

View File

@ -18,7 +18,7 @@ information, see :doc:`/compiling`.
.. code-block:: cmake .. code-block:: cmake
cmake_minimum_required(VERSION 3.4) cmake_minimum_required(VERSION 3.5...3.26)
project(example) project(example)
find_package(pybind11 REQUIRED) # or `add_subdirectory(pybind11)` find_package(pybind11 REQUIRED) # or `add_subdirectory(pybind11)`

View File

@ -177,9 +177,12 @@ section.
may be explicitly (re-)thrown to delegate it to the other, may be explicitly (re-)thrown to delegate it to the other,
previously-declared existing exception translators. previously-declared existing exception translators.
Note that ``libc++`` and ``libstdc++`` `behave differently <https://stackoverflow.com/questions/19496643/using-clang-fvisibility-hidden-and-typeinfo-and-type-erasure/28827430>`_ Note that ``libc++`` and ``libstdc++`` `behave differently under macOS
with ``-fvisibility=hidden``. Therefore exceptions that are used across ABI boundaries need to be explicitly exported, as exercised in ``tests/test_exceptions.h``. <https://stackoverflow.com/questions/19496643/using-clang-fvisibility-hidden-and-typeinfo-and-type-erasure/28827430>`_
See also: "Problems with C++ exceptions" under `GCC Wiki <https://gcc.gnu.org/wiki/Visibility>`_. with ``-fvisibility=hidden``. Therefore exceptions that are used across ABI
boundaries need to be explicitly exported, as exercised in
``tests/test_exceptions.h``. See also:
"Problems with C++ exceptions" under `GCC Wiki <https://gcc.gnu.org/wiki/Visibility>`_.
Local vs Global Exception Translators Local vs Global Exception Translators

View File

@ -39,15 +39,42 @@ The ``PYBIND11_MAKE_OPAQUE`` macro does *not* require the above workarounds.
Global Interpreter Lock (GIL) Global Interpreter Lock (GIL)
============================= =============================
When calling a C++ function from Python, the GIL is always held. The Python C API dictates that the Global Interpreter Lock (GIL) must always
be held by the current thread to safely access Python objects. As a result,
when Python calls into C++ via pybind11 the GIL must be held, and pybind11
will never implicitly release the GIL.
.. code-block:: cpp
void my_function() {
/* GIL is held when this function is called from Python */
}
PYBIND11_MODULE(example, m) {
m.def("my_function", &my_function);
}
pybind11 will ensure that the GIL is held when it knows that it is calling
Python code. For example, if a Python callback is passed to C++ code via
``std::function``, when C++ code calls the function the built-in wrapper
will acquire the GIL before calling the Python callback. Similarly, the
``PYBIND11_OVERRIDE`` family of macros will acquire the GIL before calling
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.
The classes :class:`gil_scoped_release` and :class:`gil_scoped_acquire` can be 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++ used to acquire and release the global interpreter lock in the body of a C++
function call. In this way, long-running C++ code can be parallelized using function call. In this way, long-running C++ code can be parallelized using
multiple Python threads. Taking :ref:`overriding_virtuals` as an example, this multiple Python threads, **but great care must be taken** when any
:class:`gil_scoped_release` appear: if there is any way that the C++ code
can access Python objects, :class:`gil_scoped_acquire` should be used to
reacquire the GIL. Taking :ref:`overriding_virtuals` as an example, this
could be realized as follows (important changes highlighted): could be realized as follows (important changes highlighted):
.. code-block:: cpp .. code-block:: cpp
:emphasize-lines: 8,9,31,32 :emphasize-lines: 8,30,31
class PyAnimal : public Animal { class PyAnimal : public Animal {
public: public:
@ -56,9 +83,7 @@ could be realized as follows (important changes highlighted):
/* Trampoline (need one for each virtual function) */ /* Trampoline (need one for each virtual function) */
std::string go(int n_times) { std::string go(int n_times) {
/* Acquire GIL before calling Python code */ /* PYBIND11_OVERRIDE_PURE will acquire the GIL before accessing Python state */
py::gil_scoped_acquire acquire;
PYBIND11_OVERRIDE_PURE( PYBIND11_OVERRIDE_PURE(
std::string, /* Return type */ std::string, /* Return type */
Animal, /* Parent class */ Animal, /* Parent class */
@ -78,7 +103,8 @@ could be realized as follows (important changes highlighted):
.def(py::init<>()); .def(py::init<>());
m.def("call_go", [](Animal *animal) -> std::string { m.def("call_go", [](Animal *animal) -> std::string {
/* Release GIL before calling into (potentially long-running) C++ code */ // GIL is held when called from Python code. Release GIL before
// calling into (potentially long-running) C++ code
py::gil_scoped_release release; py::gil_scoped_release release;
return call_go(animal); return call_go(animal);
}); });
@ -92,6 +118,34 @@ The ``call_go`` wrapper can also be simplified using the ``call_guard`` policy
m.def("call_go", &call_go, py::call_guard<py::gil_scoped_release>()); m.def("call_go", &call_go, py::call_guard<py::gil_scoped_release>());
Common Sources Of Global Interpreter Lock Errors
==================================================================
Failing to properly hold the Global Interpreter Lock (GIL) is one of the
more common sources of bugs within code that uses pybind11. If you are
running into GIL related errors, we highly recommend you consult the
following checklist.
- Do you have any global variables that are pybind11 objects or invoke
pybind11 functions in either their constructor or destructor? You are generally
not allowed to invoke any Python function in a global static context. We recommend
using lazy initialization and then intentionally leaking at the end of the program.
- Do you have any pybind11 objects that are members of other C++ structures? One
commonly overlooked requirement is that pybind11 objects have to increase their reference count
whenever their copy constructor is called. Thus, you need to be holding the GIL to invoke
the copy constructor of any C++ class that has a pybind11 member. This can sometimes be very
tricky to track for complicated programs Think carefully when you make a pybind11 object
a member in another struct.
- C++ destructors that invoke Python functions can be particularly troublesome as
destructors can sometimes get invoked in weird and unexpected circumstances as a result
of exceptions.
- 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).
Binding sequence data types, iterators, the slicing protocol, etc. Binding sequence data types, iterators, the slicing protocol, etc.
================================================================== ==================================================================
@ -298,6 +352,15 @@ The class ``options`` allows you to selectively suppress auto-generated signatur
m.def("add", [](int a, int b) { return a + b; }, "A function which adds two numbers"); m.def("add", [](int a, int b) { return a + b; }, "A function which adds two numbers");
} }
pybind11 also appends all members of an enum to the resulting enum docstring.
This default behavior can be disabled by using the ``disable_enum_members_docstring()``
function of the ``options`` class.
With ``disable_user_defined_docstrings()`` all user defined docstrings of
``module_::def()``, ``class_::def()`` and ``enum_()`` are disabled, but the
function signatures and enum members are included in the docstring, unless they
are disabled separately.
Note that changes to the settings affect only function bindings created during the Note that changes to the settings affect only function bindings created during the
lifetime of the ``options`` instance. When it goes out of scope at the end of the module's init function, lifetime of the ``options`` instance. When it goes out of scope at the end of the module's init function,
the default settings are restored to prevent unwanted side effects. the default settings are restored to prevent unwanted side effects.

View File

@ -433,7 +433,7 @@ following:
{ 2, 4 }, // shape (rows, cols) { 2, 4 }, // shape (rows, cols)
{ sizeof(uint8_t) * 4, sizeof(uint8_t) } // strides in bytes { sizeof(uint8_t) * 4, sizeof(uint8_t) } // strides in bytes
); );
}) });
This approach is meant for providing a ``memoryview`` for a C/C++ buffer not This approach is meant for providing a ``memoryview`` for a C/C++ buffer not
managed by Python. The user is responsible for managing the lifetime of the managed by Python. The user is responsible for managing the lifetime of the
@ -449,7 +449,7 @@ We can also use ``memoryview::from_memory`` for a simple 1D contiguous buffer:
buffer, // buffer pointer buffer, // buffer pointer
sizeof(uint8_t) * 8 // buffer size sizeof(uint8_t) * 8 // buffer size
); );
}) });
.. versionchanged:: 2.6 .. versionchanged:: 2.6
``memoryview::from_memory`` added. ``memoryview::from_memory`` added.

View File

@ -157,7 +157,7 @@ specialized:
PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr<T>); PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr<T>);
// Only needed if the type's `.get()` goes by another name // Only needed if the type's `.get()` goes by another name
namespace pybind11 { namespace detail { namespace PYBIND11_NAMESPACE { namespace detail {
template <typename T> template <typename T>
struct holder_helper<SmartPtr<T>> { // <-- specialization struct holder_helper<SmartPtr<T>> { // <-- specialization
static const T *get(const SmartPtr<T> &p) { return p.getPointer(); } static const T *get(const SmartPtr<T> &p) { return p.getPointer(); }

View File

@ -9,6 +9,364 @@ Starting with version 1.8.0, pybind11 releases use a `semantic versioning
Changes will be added here periodically from the "Suggested changelog entry" Changes will be added here periodically from the "Suggested changelog entry"
block in pull request descriptions. block in pull request descriptions.
Version 2.11.1 (July 17, 2023)
-----------------------------
Changes:
* ``PYBIND11_NO_ASSERT_GIL_HELD_INCREF_DECREF`` is now provided as an option
for disabling the default-on ``PyGILState_Check()``'s in
``pybind11::handle``'s ``inc_ref()`` & ``dec_ref()``.
`#4753 <https://github.com/pybind/pybind11/pull/4753>`_
* ``PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF`` was disabled for PyPy in general
(not just PyPy Windows).
`#4751 <https://github.com/pybind/pybind11/pull/4751>`_
Version 2.11.0 (July 14, 2023)
-----------------------------
New features:
* The newly added ``pybind11::detail::is_move_constructible`` trait can be
specialized for cases in which ``std::is_move_constructible`` does not work
as needed. This is very similar to the long-established
``pybind11::detail::is_copy_constructible``.
`#4631 <https://github.com/pybind/pybind11/pull/4631>`_
* Introduce ``recursive_container_traits``.
`#4623 <https://github.com/pybind/pybind11/pull/4623>`_
* ``pybind11/type_caster_pyobject_ptr.h`` was added to support automatic
wrapping of APIs that make use of ``PyObject *``. This header needs to
included explicitly (i.e. it is not included implicitly
with ``pybind/pybind11.h``).
`#4601 <https://github.com/pybind/pybind11/pull/4601>`_
* ``format_descriptor<>`` & ``npy_format_descriptor<>`` ``PyObject *``
specializations were added. The latter enables ``py::array_t<PyObject *>``
to/from-python conversions.
`#4674 <https://github.com/pybind/pybind11/pull/4674>`_
* ``buffer_info`` gained an ``item_type_is_equivalent_to<T>()`` member
function.
`#4674 <https://github.com/pybind/pybind11/pull/4674>`_
* The ``capsule`` API gained a user-friendly constructor
(``py::capsule(ptr, "name", dtor)``).
`#4720 <https://github.com/pybind/pybind11/pull/4720>`_
Changes:
* ``PyGILState_Check()``'s in ``pybind11::handle``'s ``inc_ref()`` &
``dec_ref()`` are now enabled by default again.
`#4246 <https://github.com/pybind/pybind11/pull/4246>`_
* ``py::initialize_interpreter()`` using ``PyConfig_InitPythonConfig()``
instead of ``PyConfig_InitIsolatedConfig()``, to obtain complete
``sys.path``.
`#4473 <https://github.com/pybind/pybind11/pull/4473>`_
* Cast errors now always include Python type information, even if
``PYBIND11_DETAILED_ERROR_MESSAGES`` is not defined. This increases binary
sizes slightly (~1.5%) but the error messages are much more informative.
`#4463 <https://github.com/pybind/pybind11/pull/4463>`_
* The docstring generation for the ``std::array``-list caster was fixed.
Previously, signatures included the size of the list in a non-standard,
non-spec compliant way. The new format conforms to PEP 593.
**Tooling for processing the docstrings may need to be updated accordingly.**
`#4679 <https://github.com/pybind/pybind11/pull/4679>`_
* Setter return values (which are inaccessible for all practical purposes) are
no longer converted to Python (only to be discarded).
`#4621 <https://github.com/pybind/pybind11/pull/4621>`_
* Allow lambda specified to function definition to be ``noexcept(true)``
in C++17.
`#4593 <https://github.com/pybind/pybind11/pull/4593>`_
* Get rid of recursive template instantiations for concatenating type
signatures on C++17 and higher.
`#4587 <https://github.com/pybind/pybind11/pull/4587>`_
* Compatibility with Python 3.12 (beta). Note that the minimum pybind11
ABI version for Python 3.12 is version 5. (The default ABI version
for Python versions up to and including 3.11 is still version 4.).
`#4570 <https://github.com/pybind/pybind11/pull/4570>`_
* With ``PYBIND11_INTERNALS_VERSION 5`` (default for Python 3.12+), MSVC builds
use ``std::hash<std::type_index>`` and ``std::equal_to<std::type_index>``
instead of string-based type comparisons. This resolves issues when binding
types defined in the unnamed namespace.
`#4319 <https://github.com/pybind/pybind11/pull/4319>`_
* Python exception ``__notes__`` (introduced with Python 3.11) are now added to
the ``error_already_set::what()`` output.
`#4678 <https://github.com/pybind/pybind11/pull/4678>`_
Build system improvements:
* CMake 3.27 support was added, CMake 3.4 support was dropped.
FindPython will be used if ``FindPythonInterp`` is not present.
`#4719 <https://github.com/pybind/pybind11/pull/4719>`_
* Update clang-tidy to 15 in CI.
`#4387 <https://github.com/pybind/pybind11/pull/4387>`_
* Moved the linting framework over to Ruff.
`#4483 <https://github.com/pybind/pybind11/pull/4483>`_
* Skip ``lto`` checks and target generation when
``CMAKE_INTERPROCEDURAL_OPTIMIZATION`` is defined.
`#4643 <https://github.com/pybind/pybind11/pull/4643>`_
* No longer inject ``-stdlib=libc++``, not needed for modern Pythons
(macOS 10.9+).
`#4639 <https://github.com/pybind/pybind11/pull/4639>`_
* PyPy 3.10 support was added, PyPy 3.7 support was dropped.
`#4728 <https://github.com/pybind/pybind11/pull/4728>`_
* Testing with Python 3.12 beta releases was added.
`#4713 <https://github.com/pybind/pybind11/pull/4713>`_
Version 2.10.4 (Mar 16, 2023)
-----------------------------
Changes:
* ``python3 -m pybind11`` gained a ``--version`` option (prints the version and
exits).
`#4526 <https://github.com/pybind/pybind11/pull/4526>`_
Bug Fixes:
* Fix a warning when pydebug is enabled on Python 3.11.
`#4461 <https://github.com/pybind/pybind11/pull/4461>`_
* Ensure ``gil_scoped_release`` RAII is non-copyable.
`#4490 <https://github.com/pybind/pybind11/pull/4490>`_
* Ensure the tests dir does not show up with new versions of setuptools.
`#4510 <https://github.com/pybind/pybind11/pull/4510>`_
* Better stacklevel for a warning in setuptools helpers.
`#4516 <https://github.com/pybind/pybind11/pull/4516>`_
Version 2.10.3 (Jan 3, 2023)
----------------------------
Changes:
* Temporarily made our GIL status assertions (added in 2.10.2) disabled by
default (re-enable manually by defining
``PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF``, will be enabled in 2.11).
`#4432 <https://github.com/pybind/pybind11/pull/4432>`_
* Improved error messages when ``inc_ref``/``dec_ref`` are called with an
invalid GIL state.
`#4427 <https://github.com/pybind/pybind11/pull/4427>`_
`#4436 <https://github.com/pybind/pybind11/pull/4436>`_
Bug Fixes:
* Some minor touchups found by static analyzers.
`#4440 <https://github.com/pybind/pybind11/pull/4440>`_
Version 2.10.2 (Dec 20, 2022)
-----------------------------
Changes:
* ``scoped_interpreter`` constructor taking ``PyConfig``.
`#4330 <https://github.com/pybind/pybind11/pull/4330>`_
* ``pybind11/eigen/tensor.h`` adds converters to and from ``Eigen::Tensor`` and
``Eigen::TensorMap``.
`#4201 <https://github.com/pybind/pybind11/pull/4201>`_
* ``PyGILState_Check()``'s were integrated to ``pybind11::handle``
``inc_ref()`` & ``dec_ref()``. The added GIL checks are guarded by
``PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF``, which is the default only if
``NDEBUG`` is not defined. (Made non-default in 2.10.3, will be active in 2.11)
`#4246 <https://github.com/pybind/pybind11/pull/4246>`_
* Add option for enable/disable enum members in docstring.
`#2768 <https://github.com/pybind/pybind11/pull/2768>`_
* Fixed typing of ``KeysView``, ``ValuesView`` and ``ItemsView`` in ``bind_map``.
`#4353 <https://github.com/pybind/pybind11/pull/4353>`_
Bug fixes:
* Bug fix affecting only Python 3.6 under very specific, uncommon conditions:
move ``PyEval_InitThreads()`` call to the correct location.
`#4350 <https://github.com/pybind/pybind11/pull/4350>`_
* Fix segfault bug when passing foreign native functions to functional.h.
`#4254 <https://github.com/pybind/pybind11/pull/4254>`_
Build system improvements:
* Support setting PYTHON_LIBRARIES manually for Windows ARM cross-compilation
(classic mode).
`#4406 <https://github.com/pybind/pybind11/pull/4406>`_
* Extend IPO/LTO detection for ICX (a.k.a IntelLLVM) compiler.
`#4402 <https://github.com/pybind/pybind11/pull/4402>`_
* Allow calling ``find_package(pybind11 CONFIG)`` multiple times from separate
directories in the same CMake project and properly link Python (new mode).
`#4401 <https://github.com/pybind/pybind11/pull/4401>`_
* ``multiprocessing_set_spawn`` in pytest fixture for added safety.
`#4377 <https://github.com/pybind/pybind11/pull/4377>`_
* Fixed a bug in two pybind11/tools cmake scripts causing "Unknown arguments specified" errors.
`#4327 <https://github.com/pybind/pybind11/pull/4327>`_
Version 2.10.1 (Oct 31, 2022)
-----------------------------
This is the first version to fully support embedding the newly released Python 3.11.
Changes:
* Allow ``pybind11::capsule`` constructor to take null destructor pointers.
`#4221 <https://github.com/pybind/pybind11/pull/4221>`_
* ``embed.h`` was changed so that ``PYTHONPATH`` is used also with Python 3.11
(established behavior).
`#4119 <https://github.com/pybind/pybind11/pull/4119>`_
* A ``PYBIND11_SIMPLE_GIL_MANAGEMENT`` option was added (cmake, C++ define),
along with many additional tests in ``test_gil_scoped.py``. The option may be
useful to try when debugging GIL-related issues, to determine if the more
complex default implementation is or is not to blame. See #4216 for
background. WARNING: Please be careful to not create ODR violations when
using the option: everything that is linked together with mutual symbol
visibility needs to be rebuilt.
`#4216 <https://github.com/pybind/pybind11/pull/4216>`_
* ``PYBIND11_EXPORT_EXCEPTION`` was made non-empty only under macOS. This makes
Linux builds safer, and enables the removal of warning suppression pragmas for
Windows.
`#4298 <https://github.com/pybind/pybind11/pull/4298>`_
Bug fixes:
* Fixed a bug where ``UnicodeDecodeError`` was not propagated from various
``py::str`` ctors when decoding surrogate utf characters.
`#4294 <https://github.com/pybind/pybind11/pull/4294>`_
* Revert perfect forwarding for ``make_iterator``. This broke at least one
valid use case. May revisit later.
`#4234 <https://github.com/pybind/pybind11/pull/4234>`_
* Fix support for safe casts to ``void*`` (regression in 2.10.0).
`#4275 <https://github.com/pybind/pybind11/pull/4275>`_
* Fix ``char8_t`` support (regression in 2.9).
`#4278 <https://github.com/pybind/pybind11/pull/4278>`_
* Unicode surrogate character in Python exception message leads to process
termination in ``error_already_set::what()``.
`#4297 <https://github.com/pybind/pybind11/pull/4297>`_
* Fix MSVC 2019 v.1924 & C++14 mode error for ``overload_cast``.
`#4188 <https://github.com/pybind/pybind11/pull/4188>`_
* Make augmented assignment operators non-const for the object-api. Behavior
was previously broken for augmented assignment operators.
`#4065 <https://github.com/pybind/pybind11/pull/4065>`_
* Add proper error checking to C++ bindings for Python list append and insert.
`#4208 <https://github.com/pybind/pybind11/pull/4208>`_
* Work-around for Nvidia's CUDA nvcc compiler in versions 11.4.0 - 11.8.0.
`#4220 <https://github.com/pybind/pybind11/pull/4220>`_
* A workaround for PyPy was added in the ``py::error_already_set``
implementation, related to PR `#1895 <https://github.com/pybind/pybind11/pull/1895>`_
released with v2.10.0.
`#4079 <https://github.com/pybind/pybind11/pull/4079>`_
* Fixed compiler errors when C++23 ``std::forward_like`` is available.
`#4136 <https://github.com/pybind/pybind11/pull/4136>`_
* Properly raise exceptions in contains methods (like when an object in unhashable).
`#4209 <https://github.com/pybind/pybind11/pull/4209>`_
* Further improve another error in exception handling.
`#4232 <https://github.com/pybind/pybind11/pull/4232>`_
* ``get_local_internals()`` was made compatible with
``finalize_interpreter()``, fixing potential freezes during interpreter
finalization.
`#4192 <https://github.com/pybind/pybind11/pull/4192>`_
Performance and style:
* Reserve space in set and STL map casters if possible. This will prevent
unnecessary rehashing / resizing by knowing the number of keys ahead of time
for Python to C++ casting. This improvement will greatly speed up the casting
of large unordered maps and sets.
`#4194 <https://github.com/pybind/pybind11/pull/4194>`_
* GIL RAII scopes are non-copyable to avoid potential bugs.
`#4183 <https://github.com/pybind/pybind11/pull/4183>`_
* Explicitly default all relevant ctors for pytypes in the ``PYBIND11_OBJECT``
macros and enforce the clang-tidy checks ``modernize-use-equals-default`` in
macros as well.
`#4017 <https://github.com/pybind/pybind11/pull/4017>`_
* Optimize iterator advancement in C++ bindings.
`#4237 <https://github.com/pybind/pybind11/pull/4237>`_
* Use the modern ``PyObject_GenericGetDict`` and ``PyObject_GenericSetDict``
for handling dynamic attribute dictionaries.
`#4106 <https://github.com/pybind/pybind11/pull/4106>`_
* Document that users should use ``PYBIND11_NAMESPACE`` instead of using ``pybind11`` when
opening namespaces. Using namespace declarations and namespace qualification
remain the same as ``pybind11``. This is done to ensure consistent symbol
visibility.
`#4098 <https://github.com/pybind/pybind11/pull/4098>`_
* Mark ``detail::forward_like`` as constexpr.
`#4147 <https://github.com/pybind/pybind11/pull/4147>`_
* Optimize unpacking_collector when processing ``arg_v`` arguments.
`#4219 <https://github.com/pybind/pybind11/pull/4219>`_
* Optimize casting C++ object to ``None``.
`#4269 <https://github.com/pybind/pybind11/pull/4269>`_
Build system improvements:
* CMake: revert overwrite behavior, now opt-in with ``PYBIND11_PYTHONLIBS_OVERRWRITE OFF``.
`#4195 <https://github.com/pybind/pybind11/pull/4195>`_
* Include a pkg-config file when installing pybind11, such as in the Python
package.
`#4077 <https://github.com/pybind/pybind11/pull/4077>`_
* Avoid stripping debug symbols when ``CMAKE_BUILD_TYPE`` is set to ``DEBUG``
instead of ``Debug``.
`#4078 <https://github.com/pybind/pybind11/pull/4078>`_
* Followup to `#3948 <https://github.com/pybind/pybind11/pull/3948>`_, fixing vcpkg again.
`#4123 <https://github.com/pybind/pybind11/pull/4123>`_
Version 2.10.0 (Jul 15, 2022) Version 2.10.0 (Jul 15, 2022)
----------------------------- -----------------------------

View File

@ -58,6 +58,16 @@ interactive Python session demonstrating this example is shown below:
Static member functions can be bound in the same way using Static member functions can be bound in the same way using
:func:`class_::def_static`. :func:`class_::def_static`.
.. note::
Binding C++ types in unnamed namespaces (also known as anonymous namespaces)
works reliably on many platforms, but not all. The `XFAIL_CONDITION` in
tests/test_unnamed_namespace_a.py encodes the currently known conditions.
For background see `#4319 <https://github.com/pybind/pybind11/pull/4319>`_.
If portability is a concern, it is therefore not recommended to bind C++
types in unnamed namespaces. It will be safest to manually pick unique
namespace names.
Keyword and default arguments Keyword and default arguments
============================= =============================
It is possible to specify keyword and default arguments using the syntax It is possible to specify keyword and default arguments using the syntax
@ -539,3 +549,7 @@ The ``name`` property returns the name of the enum value as a unicode string.
... ...
By default, these are omitted to conserve space. By default, these are omitted to conserve space.
.. warning::
Contrary to Python customs, enum values from the wrappers should not be compared using ``is``, but with ``==`` (see `#1177 <https://github.com/pybind/pybind11/issues/1177>`_ for background).

View File

@ -241,7 +241,7 @@ extension module can be created with just a few lines of code:
.. code-block:: cmake .. code-block:: cmake
cmake_minimum_required(VERSION 3.4...3.18) cmake_minimum_required(VERSION 3.5...3.26)
project(example LANGUAGES CXX) project(example LANGUAGES CXX)
add_subdirectory(pybind11) add_subdirectory(pybind11)
@ -261,6 +261,9 @@ PyPI integration, can be found in the [cmake_example]_ repository.
.. versionchanged:: 2.6 .. versionchanged:: 2.6
CMake 3.4+ is required. CMake 3.4+ is required.
.. versionchanged:: 2.11
CMake 3.5+ is required.
Further information can be found at :doc:`cmake/index`. Further information can be found at :doc:`cmake/index`.
pybind11_add_module pybind11_add_module
@ -495,7 +498,7 @@ You can use these targets to build complex applications. For example, the
.. code-block:: cmake .. code-block:: cmake
cmake_minimum_required(VERSION 3.4) cmake_minimum_required(VERSION 3.5...3.26)
project(example LANGUAGES CXX) project(example LANGUAGES CXX)
find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11) find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11)
@ -553,7 +556,7 @@ information about usage in C++, see :doc:`/advanced/embedding`.
.. code-block:: cmake .. code-block:: cmake
cmake_minimum_required(VERSION 3.4...3.18) cmake_minimum_required(VERSION 3.5...3.26)
project(example LANGUAGES CXX) project(example LANGUAGES CXX)
find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11) find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11)

View File

@ -353,12 +353,11 @@ def prepare(app):
f.write(contents) f.write(contents)
def clean_up(app, exception): def clean_up(app, exception): # noqa: ARG001
(DIR / "readme.rst").unlink() (DIR / "readme.rst").unlink()
def setup(app): def setup(app):
# Add hook for building doxygen xml when needed # Add hook for building doxygen xml when needed
app.connect("builder-inited", generate_doxygen_xml) app.connect("builder-inited", generate_doxygen_xml)

View File

@ -284,7 +284,8 @@ There are three possible solutions:
COMPONENTS Interpreter Development)`` on modern CMake (3.12+, 3.15+ better, 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 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 of the old, deprecated search tools, and these modules are much better at
finding the correct Python. 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. 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 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 yourself, because it does not know about or include things that depend on

View File

@ -33,10 +33,12 @@ If you don't have nox, you should either use ``pipx run nox`` instead, or use
- Run ``nox -s tests_packaging`` to ensure this was done correctly. - Run ``nox -s tests_packaging`` to ensure this was done correctly.
- Ensure that all the information in ``setup.cfg`` is up-to-date, like - Ensure that all the information in ``setup.cfg`` is up-to-date, like
supported Python versions. supported Python versions.
- Add release date in ``docs/changelog.rst``. - Add release date in ``docs/changelog.rst`` and integrate the output of
- Check to make sure ``nox -s make_changelog``.
`needs-changelog <https://github.com/pybind/pybind11/pulls?q=is%3Apr+is%3Aclosed+label%3A%22needs+changelog%22>`_ - Note that the ``make_changelog`` command inspects
issues are entered in the changelog (clear the label when done). `needs changelog <https://github.com/pybind/pybind11/pulls?q=is%3Apr+is%3Aclosed+label%3A%22needs+changelog%22>`_.
- Manually clear the ``needs changelog`` labels using the GitHub web
interface (very easy: start by clicking the link above).
- ``git add`` and ``git commit``, ``git push``. **Ensure CI passes**. (If it - ``git add`` and ``git commit``, ``git push``. **Ensure CI passes**. (If it
fails due to a known flake issue, either ignore or restart CI.) fails due to a known flake issue, either ignore or restart CI.)
- Add a release branch if this is a new minor version, or update the existing release branch if it is a patch version - Add a release branch if this is a new minor version, or update the existing release branch if it is a patch version

View File

@ -8,6 +8,20 @@ to a new version. But it goes into more detail. This includes things like
deprecated APIs and their replacements, build system changes, general code deprecated APIs and their replacements, build system changes, general code
modernization and other useful information. modernization and other useful information.
.. _upgrade-guide-2.11:
v2.11
=====
* The minimum version of CMake is now 3.5. A future version will likely move to
requiring something like CMake 3.15. Note that CMake 3.27 is removing the
long-deprecated support for ``FindPythonInterp`` if you set 3.27 as the
minimum or maximum supported version. To prepare for that future, CMake 3.15+
using ``FindPython`` or setting ``PYBIND11_FINDPYTHON`` is highly recommended,
otherwise pybind11 will automatically switch to using ``FindPython`` if
``FindPythonInterp`` is not available.
.. _upgrade-guide-2.9: .. _upgrade-guide-2.9:
v2.9 v2.9

View File

@ -26,6 +26,9 @@ struct is_method {
explicit is_method(const handle &c) : class_(c) {} explicit is_method(const handle &c) : class_(c) {}
}; };
/// Annotation for setters
struct is_setter {};
/// Annotation for operators /// Annotation for operators
struct is_operator {}; struct is_operator {};
@ -188,8 +191,8 @@ struct argument_record {
struct function_record { struct function_record {
function_record() function_record()
: is_constructor(false), is_new_style_constructor(false), is_stateless(false), : is_constructor(false), is_new_style_constructor(false), is_stateless(false),
is_operator(false), is_method(false), has_args(false), has_kwargs(false), is_operator(false), is_method(false), is_setter(false), has_args(false),
prepend(false) {} has_kwargs(false), prepend(false) {}
/// Function name /// Function name
char *name = nullptr; /* why no C++ strings? They generate heavier code.. */ char *name = nullptr; /* why no C++ strings? They generate heavier code.. */
@ -230,6 +233,9 @@ struct function_record {
/// True if this is a method /// True if this is a method
bool is_method : 1; bool is_method : 1;
/// True if this is a setter
bool is_setter : 1;
/// True if the function has a '*args' argument /// True if the function has a '*args' argument
bool has_args : 1; bool has_args : 1;
@ -399,7 +405,7 @@ struct process_attribute<doc> : process_attribute_default<doc> {
template <> template <>
struct process_attribute<const char *> : process_attribute_default<const char *> { struct process_attribute<const char *> : process_attribute_default<const char *> {
static void init(const char *d, function_record *r) { r->doc = const_cast<char *>(d); } static void init(const char *d, function_record *r) { r->doc = const_cast<char *>(d); }
static void init(const char *d, type_record *r) { r->doc = const_cast<char *>(d); } static void init(const char *d, type_record *r) { r->doc = d; }
}; };
template <> template <>
struct process_attribute<char *> : process_attribute<const char *> {}; struct process_attribute<char *> : process_attribute<const char *> {};
@ -426,6 +432,12 @@ struct process_attribute<is_method> : process_attribute_default<is_method> {
} }
}; };
/// Process an attribute which indicates that this function is a setter
template <>
struct process_attribute<is_setter> : process_attribute_default<is_setter> {
static void init(const is_setter &, function_record *r) { r->is_setter = true; }
};
/// Process an attribute which indicates the parent scope of a method /// Process an attribute which indicates the parent scope of a method
template <> template <>
struct process_attribute<scope> : process_attribute_default<scope> { struct process_attribute<scope> : process_attribute_default<scope> {

View File

@ -37,6 +37,9 @@ inline std::vector<ssize_t> f_strides(const std::vector<ssize_t> &shape, ssize_t
return strides; return strides;
} }
template <typename T, typename SFINAE = void>
struct compare_buffer_info;
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
/// Information record describing a Python buffer object /// Information record describing a Python buffer object
@ -150,6 +153,17 @@ struct buffer_info {
Py_buffer *view() const { return m_view; } Py_buffer *view() const { return m_view; }
Py_buffer *&view() { return m_view; } Py_buffer *&view() { return m_view; }
/* True if the buffer item type is equivalent to `T`. */
// To define "equivalent" by example:
// `buffer_info::item_type_is_equivalent_to<int>(b)` and
// `buffer_info::item_type_is_equivalent_to<long>(b)` may both be true
// on some platforms, but `int` and `unsigned` will never be equivalent.
// For the ground truth, please inspect `detail::compare_buffer_info<>`.
template <typename T>
bool item_type_is_equivalent_to() const {
return detail::compare_buffer_info<T>::compare(*this);
}
private: private:
struct private_ctr_tag {}; struct private_ctr_tag {};
@ -170,9 +184,10 @@ private:
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
template <typename T, typename SFINAE = void> template <typename T, typename SFINAE>
struct compare_buffer_info { struct compare_buffer_info {
static bool compare(const buffer_info &b) { static bool compare(const buffer_info &b) {
// NOLINTNEXTLINE(bugprone-sizeof-expression) Needed for `PyObject *`
return b.format == format_descriptor<T>::format() && b.itemsize == (ssize_t) sizeof(T); return b.format == format_descriptor<T>::format() && b.itemsize == (ssize_t) sizeof(T);
} }
}; };

View File

@ -29,6 +29,9 @@
#include <vector> #include <vector>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_WARNING_DISABLE_MSVC(4127)
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
template <typename type, typename SFINAE = void> template <typename type, typename SFINAE = void>
@ -88,7 +91,8 @@ public:
template <typename T_, \ template <typename T_, \
::pybind11::detail::enable_if_t< \ ::pybind11::detail::enable_if_t< \
std::is_same<type, ::pybind11::detail::remove_cv_t<T_>>::value, \ std::is_same<type, ::pybind11::detail::remove_cv_t<T_>>::value, \
int> = 0> \ int> \
= 0> \
static ::pybind11::handle cast( \ static ::pybind11::handle cast( \
T_ *src, ::pybind11::return_value_policy policy, ::pybind11::handle parent) { \ T_ *src, ::pybind11::return_value_policy policy, ::pybind11::handle parent) { \
if (!src) \ if (!src) \
@ -248,7 +252,7 @@ public:
return false; return false;
} }
static handle cast(T, return_value_policy /* policy */, handle /* parent */) { static handle cast(T, return_value_policy /* policy */, handle /* parent */) {
return none().inc_ref(); return none().release();
} }
PYBIND11_TYPE_CASTER(T, const_name("None")); PYBIND11_TYPE_CASTER(T, const_name("None"));
}; };
@ -291,7 +295,7 @@ public:
if (ptr) { if (ptr) {
return capsule(ptr).release(); return capsule(ptr).release();
} }
return none().inc_ref(); return none().release();
} }
template <typename T> template <typename T>
@ -389,7 +393,7 @@ struct string_caster {
// For UTF-8 we avoid the need for a temporary `bytes` object by using // For UTF-8 we avoid the need for a temporary `bytes` object by using
// `PyUnicode_AsUTF8AndSize`. // `PyUnicode_AsUTF8AndSize`.
if (PYBIND11_SILENCE_MSVC_C4127(UTF_N == 8)) { if (UTF_N == 8) {
Py_ssize_t size = -1; Py_ssize_t size = -1;
const auto *buffer const auto *buffer
= reinterpret_cast<const CharT *>(PyUnicode_AsUTF8AndSize(load_src.ptr(), &size)); = reinterpret_cast<const CharT *>(PyUnicode_AsUTF8AndSize(load_src.ptr(), &size));
@ -416,7 +420,7 @@ struct string_caster {
= reinterpret_cast<const CharT *>(PYBIND11_BYTES_AS_STRING(utfNbytes.ptr())); = reinterpret_cast<const CharT *>(PYBIND11_BYTES_AS_STRING(utfNbytes.ptr()));
size_t length = (size_t) PYBIND11_BYTES_SIZE(utfNbytes.ptr()) / sizeof(CharT); size_t length = (size_t) PYBIND11_BYTES_SIZE(utfNbytes.ptr()) / sizeof(CharT);
// Skip BOM for UTF-16/32 // Skip BOM for UTF-16/32
if (PYBIND11_SILENCE_MSVC_C4127(UTF_N > 8)) { if (UTF_N > 8) {
buffer++; buffer++;
length--; length--;
} }
@ -537,7 +541,7 @@ public:
static handle cast(const CharT *src, return_value_policy policy, handle parent) { static handle cast(const CharT *src, return_value_policy policy, handle parent) {
if (src == nullptr) { if (src == nullptr) {
return pybind11::none().inc_ref(); return pybind11::none().release();
} }
return StringCaster::cast(StringType(src), policy, parent); return StringCaster::cast(StringType(src), policy, parent);
} }
@ -572,7 +576,7 @@ public:
// figure out how long the first encoded character is in bytes to distinguish between these // figure out how long the first encoded character is in bytes to distinguish between these
// two errors. We also allow want to allow unicode characters U+0080 through U+00FF, as // two errors. We also allow want to allow unicode characters U+0080 through U+00FF, as
// those can fit into a single char value. // those can fit into a single char value.
if (PYBIND11_SILENCE_MSVC_C4127(StringCaster::UTF_N == 8) && str_len > 1 && str_len <= 4) { if (StringCaster::UTF_N == 8 && str_len > 1 && str_len <= 4) {
auto v0 = static_cast<unsigned char>(value[0]); auto v0 = static_cast<unsigned char>(value[0]);
// low bits only: 0-127 // low bits only: 0-127
// 0b110xxxxx - start of 2-byte sequence // 0b110xxxxx - start of 2-byte sequence
@ -598,7 +602,7 @@ public:
// UTF-16 is much easier: we can only have a surrogate pair for values above U+FFFF, thus a // UTF-16 is much easier: we can only have a surrogate pair for values above U+FFFF, thus a
// surrogate pair with total length 2 instantly indicates a range error (but not a "your // surrogate pair with total length 2 instantly indicates a range error (but not a "your
// string was too long" error). // string was too long" error).
else if (PYBIND11_SILENCE_MSVC_C4127(StringCaster::UTF_N == 16) && str_len == 2) { else if (StringCaster::UTF_N == 16 && str_len == 2) {
one_char = static_cast<CharT>(value[0]); one_char = static_cast<CharT>(value[0]);
if (one_char >= 0xD800 && one_char < 0xE000) { if (one_char >= 0xD800 && one_char < 0xE000) {
throw value_error("Character code point not in range(0x10000)"); throw value_error("Character code point not in range(0x10000)");
@ -960,7 +964,7 @@ struct move_always<
enable_if_t< enable_if_t<
all_of<move_is_plain_type<T>, all_of<move_is_plain_type<T>,
negation<is_copy_constructible<T>>, negation<is_copy_constructible<T>>,
std::is_move_constructible<T>, is_move_constructible<T>,
std::is_same<decltype(std::declval<make_caster<T>>().operator T &()), T &>>::value>> std::is_same<decltype(std::declval<make_caster<T>>().operator T &()), T &>>::value>>
: std::true_type {}; : std::true_type {};
template <typename T, typename SFINAE = void> template <typename T, typename SFINAE = void>
@ -971,7 +975,7 @@ struct move_if_unreferenced<
enable_if_t< enable_if_t<
all_of<move_is_plain_type<T>, all_of<move_is_plain_type<T>,
negation<move_always<T>>, negation<move_always<T>>,
std::is_move_constructible<T>, is_move_constructible<T>,
std::is_same<decltype(std::declval<make_caster<T>>().operator T &()), T &>>::value>> std::is_same<decltype(std::declval<make_caster<T>>().operator T &()), T &>>::value>>
: std::true_type {}; : std::true_type {};
template <typename T> template <typename T>
@ -1013,11 +1017,14 @@ type_caster<T, SFINAE> &load_type(type_caster<T, SFINAE> &conv, const handle &ha
"Internal error: type_caster should only be used for C++ types"); "Internal error: type_caster should only be used for C++ types");
if (!conv.load(handle, true)) { if (!conv.load(handle, true)) {
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
throw cast_error("Unable to cast Python instance to C++ type (#define " throw cast_error(
"PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); "Unable to cast Python instance of type "
+ str(type::handle_of(handle)).cast<std::string>()
+ " to C++ type '?' (#define "
"PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)");
#else #else
throw cast_error("Unable to cast Python instance of type " throw cast_error("Unable to cast Python instance of type "
+ (std::string) str(type::handle_of(handle)) + " to C++ type '" + str(type::handle_of(handle)).cast<std::string>() + " to C++ type '"
+ type_id<T>() + "'"); + type_id<T>() + "'");
#endif #endif
} }
@ -1034,7 +1041,11 @@ make_caster<T> load_type(const handle &handle) {
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
// pytype -> C++ type // pytype -> C++ type
template <typename T, detail::enable_if_t<!detail::is_pyobject<T>::value, int> = 0> template <typename T,
detail::enable_if_t<!detail::is_pyobject<T>::value
&& !detail::is_same_ignoring_cvref<T, PyObject *>::value,
int>
= 0>
T cast(const handle &handle) { T cast(const handle &handle) {
using namespace detail; using namespace detail;
static_assert(!cast_is_temporary_value_reference<T>::value, static_assert(!cast_is_temporary_value_reference<T>::value,
@ -1048,6 +1059,34 @@ T cast(const handle &handle) {
return T(reinterpret_borrow<object>(handle)); return T(reinterpret_borrow<object>(handle));
} }
// Note that `cast<PyObject *>(obj)` increments the reference count of `obj`.
// This is necessary for the case that `obj` is a temporary, and could
// not possibly be different, given
// 1. the established convention that the passed `handle` is borrowed, and
// 2. we don't want to force all generic code using `cast<T>()` to special-case
// handling of `T` = `PyObject *` (to increment the reference count there).
// It is the responsibility of the caller to ensure that the reference count
// is decremented.
template <typename T,
typename Handle,
detail::enable_if_t<detail::is_same_ignoring_cvref<T, PyObject *>::value
&& detail::is_same_ignoring_cvref<Handle, handle>::value,
int>
= 0>
T cast(Handle &&handle) {
return handle.inc_ref().ptr();
}
// To optimize way an inc_ref/dec_ref cycle:
template <typename T,
typename Object,
detail::enable_if_t<detail::is_same_ignoring_cvref<T, PyObject *>::value
&& detail::is_same_ignoring_cvref<Object, object>::value,
int>
= 0>
T cast(Object &&obj) {
return obj.release().ptr();
}
// C++ type -> py::object // C++ type -> py::object
template <typename T, detail::enable_if_t<!detail::is_pyobject<T>::value, int> = 0> template <typename T, detail::enable_if_t<!detail::is_pyobject<T>::value, int> = 0>
object cast(T &&value, object cast(T &&value,
@ -1081,12 +1120,13 @@ detail::enable_if_t<!detail::move_never<T>::value, T> move(object &&obj) {
if (obj.ref_count() > 1) { if (obj.ref_count() > 1) {
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
throw cast_error( throw cast_error(
"Unable to cast Python instance to C++ rvalue: instance has multiple references" "Unable to cast Python " + str(type::handle_of(obj)).cast<std::string>()
" (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); + " instance to C++ rvalue: instance has multiple references"
" (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)");
#else #else
throw cast_error("Unable to move from Python " + (std::string) str(type::handle_of(obj)) throw cast_error("Unable to move from Python "
+ " instance to C++ " + type_id<T>() + str(type::handle_of(obj)).cast<std::string>() + " instance to C++ "
+ " instance: instance has multiple references"); + type_id<T>() + " instance: instance has multiple references");
#endif #endif
} }
@ -1179,11 +1219,9 @@ enable_if_t<cast_is_temporary_value_reference<T>::value, T> cast_safe(object &&)
pybind11_fail("Internal error: cast_safe fallback invoked"); pybind11_fail("Internal error: cast_safe fallback invoked");
} }
template <typename T> template <typename T>
enable_if_t<std::is_same<void, intrinsic_t<T>>::value, void> cast_safe(object &&) {} enable_if_t<std::is_void<T>::value, void> cast_safe(object &&) {}
template <typename T> template <typename T>
enable_if_t<detail::none_of<cast_is_temporary_value_reference<T>, enable_if_t<detail::none_of<cast_is_temporary_value_reference<T>, std::is_void<T>>::value, T>
std::is_same<void, intrinsic_t<T>>>::value,
T>
cast_safe(object &&o) { cast_safe(object &&o) {
return pybind11::cast<T>(std::move(o)); return pybind11::cast<T>(std::move(o));
} }
@ -1193,9 +1231,10 @@ PYBIND11_NAMESPACE_END(detail)
// The overloads could coexist, i.e. the #if is not strictly speaking needed, // The overloads could coexist, i.e. the #if is not strictly speaking needed,
// but it is an easy minor optimization. // but it is an easy minor optimization.
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
inline cast_error cast_error_unable_to_convert_call_arg() { inline cast_error cast_error_unable_to_convert_call_arg(const std::string &name) {
return cast_error("Unable to convert call argument to Python object (#define " return cast_error("Unable to convert call argument '" + name
"PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); + "' to Python object (#define "
"PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)");
} }
#else #else
inline cast_error cast_error_unable_to_convert_call_arg(const std::string &name, inline cast_error cast_error_unable_to_convert_call_arg(const std::string &name,
@ -1218,7 +1257,7 @@ tuple make_tuple(Args &&...args_) {
for (size_t i = 0; i < args.size(); i++) { for (size_t i = 0; i < args.size(); i++) {
if (!args[i]) { if (!args[i]) {
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
throw cast_error_unable_to_convert_call_arg(); throw cast_error_unable_to_convert_call_arg(std::to_string(i));
#else #else
std::array<std::string, size> argtypes{{type_id<Args>()...}}; std::array<std::string, size> argtypes{{type_id<Args>()...}};
throw cast_error_unable_to_convert_call_arg(std::to_string(i), argtypes[i]); throw cast_error_unable_to_convert_call_arg(std::to_string(i), argtypes[i]);
@ -1508,7 +1547,7 @@ private:
detail::make_caster<T>::cast(std::forward<T>(x), policy, {})); detail::make_caster<T>::cast(std::forward<T>(x), policy, {}));
if (!o) { if (!o) {
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
throw cast_error_unable_to_convert_call_arg(); throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()));
#else #else
throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()), throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()),
type_id<T>()); type_id<T>());
@ -1540,12 +1579,12 @@ private:
} }
if (!a.value) { if (!a.value) {
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
throw cast_error_unable_to_convert_call_arg(); throw cast_error_unable_to_convert_call_arg(a.name);
#else #else
throw cast_error_unable_to_convert_call_arg(a.name, a.type); throw cast_error_unable_to_convert_call_arg(a.name, a.type);
#endif #endif
} }
m_kwargs[a.name] = a.value; m_kwargs[a.name] = std::move(a.value);
} }
void process(list & /*args_list*/, detail::kwargs_proxy kp) { void process(list & /*args_list*/, detail::kwargs_proxy kp) {

View File

@ -55,6 +55,9 @@ extern "C" inline int pybind11_static_set(PyObject *self, PyObject *obj, PyObjec
return PyProperty_Type.tp_descr_set(self, cls, value); return PyProperty_Type.tp_descr_set(self, cls, value);
} }
// Forward declaration to use in `make_static_property_type()`
inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type);
/** A `static_property` is the same as a `property` but the `__get__()` and `__set__()` /** A `static_property` is the same as a `property` but the `__get__()` and `__set__()`
methods are modified to always use the object type instead of a concrete instance. methods are modified to always use the object type instead of a concrete instance.
Return value: New reference. */ Return value: New reference. */
@ -87,6 +90,13 @@ inline PyTypeObject *make_static_property_type() {
pybind11_fail("make_static_property_type(): failure in PyType_Ready()!"); pybind11_fail("make_static_property_type(): failure in PyType_Ready()!");
} }
# if PY_VERSION_HEX >= 0x030C0000
// PRE 3.12 FEATURE FREEZE. PLEASE REVIEW AFTER FREEZE.
// Since Python-3.12 property-derived types are required to
// have dynamic attributes (to set `__doc__`)
enable_dynamic_attributes(heap_type);
# endif
setattr((PyObject *) type, "__module__", str("pybind11_builtins")); setattr((PyObject *) type, "__module__", str("pybind11_builtins"));
PYBIND11_SET_OLDPY_QUALNAME(type, name_obj); PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
@ -435,9 +445,17 @@ inline void clear_instance(PyObject *self) {
/// Instance destructor function for all pybind11 types. It calls `type_info.dealloc` /// Instance destructor function for all pybind11 types. It calls `type_info.dealloc`
/// to destroy the C++ object itself, while the rest is Python bookkeeping. /// to destroy the C++ object itself, while the rest is Python bookkeeping.
extern "C" inline void pybind11_object_dealloc(PyObject *self) { extern "C" inline void pybind11_object_dealloc(PyObject *self) {
auto *type = Py_TYPE(self);
// If this is a GC tracked object, untrack it first
// Note that the track call is implicitly done by the
// default tp_alloc, which we never override.
if (PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC) != 0) {
PyObject_GC_UnTrack(self);
}
clear_instance(self); clear_instance(self);
auto *type = Py_TYPE(self);
type->tp_free(self); type->tp_free(self);
#if PY_VERSION_HEX < 0x03080000 #if PY_VERSION_HEX < 0x03080000
@ -502,31 +520,6 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass) {
return (PyObject *) heap_type; return (PyObject *) heap_type;
} }
/// dynamic_attr: Support for `d = instance.__dict__`.
extern "C" inline PyObject *pybind11_get_dict(PyObject *self, void *) {
PyObject *&dict = *_PyObject_GetDictPtr(self);
if (!dict) {
dict = PyDict_New();
}
Py_XINCREF(dict);
return dict;
}
/// dynamic_attr: Support for `instance.__dict__ = dict()`.
extern "C" inline int pybind11_set_dict(PyObject *self, PyObject *new_dict, void *) {
if (!PyDict_Check(new_dict)) {
PyErr_Format(PyExc_TypeError,
"__dict__ must be set to a dictionary, not a '%.200s'",
get_fully_qualified_tp_name(Py_TYPE(new_dict)).c_str());
return -1;
}
PyObject *&dict = *_PyObject_GetDictPtr(self);
Py_INCREF(new_dict);
Py_CLEAR(dict);
dict = new_dict;
return 0;
}
/// dynamic_attr: Allow the garbage collector to traverse the internal instance `__dict__`. /// dynamic_attr: Allow the garbage collector to traverse the internal instance `__dict__`.
extern "C" inline int pybind11_traverse(PyObject *self, visitproc visit, void *arg) { extern "C" inline int pybind11_traverse(PyObject *self, visitproc visit, void *arg) {
PyObject *&dict = *_PyObject_GetDictPtr(self); PyObject *&dict = *_PyObject_GetDictPtr(self);
@ -558,9 +551,17 @@ inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type) {
type->tp_traverse = pybind11_traverse; type->tp_traverse = pybind11_traverse;
type->tp_clear = pybind11_clear; type->tp_clear = pybind11_clear;
static PyGetSetDef getset[] = { static PyGetSetDef getset[] = {{
{const_cast<char *>("__dict__"), pybind11_get_dict, pybind11_set_dict, nullptr, nullptr}, #if PY_VERSION_HEX < 0x03070000
{nullptr, nullptr, nullptr, nullptr, nullptr}}; const_cast<char *>("__dict__"),
#else
"__dict__",
#endif
PyObject_GenericGetDict,
PyObject_GenericSetDict,
nullptr,
nullptr},
{nullptr, nullptr, nullptr, nullptr, nullptr}};
type->tp_getset = getset; type->tp_getset = getset;
} }

View File

@ -10,15 +10,76 @@
#pragma once #pragma once
#define PYBIND11_VERSION_MAJOR 2 #define PYBIND11_VERSION_MAJOR 2
#define PYBIND11_VERSION_MINOR 10 #define PYBIND11_VERSION_MINOR 11
#define PYBIND11_VERSION_PATCH 0 #define PYBIND11_VERSION_PATCH 1
// Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html // Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html
// Additional convention: 0xD = dev // Additional convention: 0xD = dev
#define PYBIND11_VERSION_HEX 0x020A0000 #define PYBIND11_VERSION_HEX 0x020B0100
#define PYBIND11_NAMESPACE_BEGIN(name) namespace name { // Define some generic pybind11 helper macros for warning management.
#define PYBIND11_NAMESPACE_END(name) } //
// Note that compiler-specific push/pop pairs are baked into the
// PYBIND11_NAMESPACE_BEGIN/PYBIND11_NAMESPACE_END pair of macros. Therefore manual
// PYBIND11_WARNING_PUSH/PYBIND11_WARNING_POP are usually only needed in `#include` sections.
//
// If you find you need to suppress a warning, please try to make the suppression as local as
// possible using these macros. Please also be sure to push/pop with the pybind11 macros. Please
// only use compiler specifics if you need to check specific versions, e.g. Apple Clang vs. vanilla
// Clang.
#if defined(_MSC_VER)
# define PYBIND11_COMPILER_MSVC
# define PYBIND11_PRAGMA(...) __pragma(__VA_ARGS__)
# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(warning(push))
# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(warning(pop))
#elif defined(__INTEL_COMPILER)
# define PYBIND11_COMPILER_INTEL
# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__)
# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(warning push)
# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(warning pop)
#elif defined(__clang__)
# 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 push)
#elif defined(__GNUC__)
# define PYBIND11_COMPILER_GCC
# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__)
# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(GCC diagnostic push)
# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(GCC diagnostic pop)
#endif
#ifdef PYBIND11_COMPILER_MSVC
# define PYBIND11_WARNING_DISABLE_MSVC(name) PYBIND11_PRAGMA(warning(disable : name))
#else
# define PYBIND11_WARNING_DISABLE_MSVC(name)
#endif
#ifdef PYBIND11_COMPILER_CLANG
# define PYBIND11_WARNING_DISABLE_CLANG(name) PYBIND11_PRAGMA(clang diagnostic ignored name)
#else
# define PYBIND11_WARNING_DISABLE_CLANG(name)
#endif
#ifdef PYBIND11_COMPILER_GCC
# define PYBIND11_WARNING_DISABLE_GCC(name) PYBIND11_PRAGMA(GCC diagnostic ignored name)
#else
# define PYBIND11_WARNING_DISABLE_GCC(name)
#endif
#ifdef PYBIND11_COMPILER_INTEL
# define PYBIND11_WARNING_DISABLE_INTEL(name) PYBIND11_PRAGMA(warning disable name)
#else
# define PYBIND11_WARNING_DISABLE_INTEL(name)
#endif
#define PYBIND11_NAMESPACE_BEGIN(name) \
namespace name { \
PYBIND11_WARNING_PUSH
#define PYBIND11_NAMESPACE_END(name) \
PYBIND11_WARNING_POP \
}
// Robust support for some features and loading modules compiled against different pybind versions // Robust support for some features and loading modules compiled against different pybind versions
// requires forcing hidden visibility on pybind code, so we enforce this by setting the attribute // requires forcing hidden visibility on pybind code, so we enforce this by setting the attribute
@ -96,13 +157,10 @@
#endif #endif
#if !defined(PYBIND11_EXPORT_EXCEPTION) #if !defined(PYBIND11_EXPORT_EXCEPTION)
# ifdef __MINGW32__ # if defined(__apple_build_version__)
// workaround for:
// error: 'dllexport' implies default visibility, but xxx has already been declared with a
// different visibility
# define PYBIND11_EXPORT_EXCEPTION
# else
# define PYBIND11_EXPORT_EXCEPTION PYBIND11_EXPORT # define PYBIND11_EXPORT_EXCEPTION PYBIND11_EXPORT
# else
# define PYBIND11_EXPORT_EXCEPTION
# endif # endif
#endif #endif
@ -154,9 +212,9 @@
/// Include Python header, disable linking to pythonX_d.lib on Windows in debug mode /// Include Python header, disable linking to pythonX_d.lib on Windows in debug mode
#if defined(_MSC_VER) #if defined(_MSC_VER)
# pragma warning(push) PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_MSVC(4505)
// C4505: 'PySlice_GetIndicesEx': unreferenced local function has been removed (PyPy only) // C4505: 'PySlice_GetIndicesEx': unreferenced local function has been removed (PyPy only)
# pragma warning(disable : 4505)
# if defined(_DEBUG) && !defined(Py_DEBUG) # if defined(_DEBUG) && !defined(Py_DEBUG)
// Workaround for a VS 2022 issue. // Workaround for a VS 2022 issue.
// NOTE: This workaround knowingly violates the Python.h include order requirement: // NOTE: This workaround knowingly violates the Python.h include order requirement:
@ -205,11 +263,8 @@
# endif # endif
#endif #endif
#if defined(__cpp_lib_char8_t) && __cpp_lib_char8_t >= 201811L
# define PYBIND11_HAS_U8STRING
#endif
#include <Python.h> #include <Python.h>
// Reminder: WITH_THREAD is always defined if PY_VERSION_HEX >= 0x03070000
#if PY_VERSION_HEX < 0x03060000 #if PY_VERSION_HEX < 0x03060000
# error "PYTHON < 3.6 IS UNSUPPORTED. pybind11 v2.9 was the last to support Python 2 and 3.5." # error "PYTHON < 3.6 IS UNSUPPORTED. pybind11 v2.9 was the last to support Python 2 and 3.5."
#endif #endif
@ -233,12 +288,16 @@
# undef copysign # undef copysign
#endif #endif
#if defined(PYPY_VERSION) && !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT)
# define PYBIND11_SIMPLE_GIL_MANAGEMENT
#endif
#if defined(_MSC_VER) #if defined(_MSC_VER)
# if defined(PYBIND11_DEBUG_MARKER) # if defined(PYBIND11_DEBUG_MARKER)
# define _DEBUG # define _DEBUG
# undef PYBIND11_DEBUG_MARKER # undef PYBIND11_DEBUG_MARKER
# endif # endif
# pragma warning(pop) PYBIND11_WARNING_POP
#endif #endif
#include <cstddef> #include <cstddef>
@ -259,6 +318,17 @@
# endif # endif
#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
#endif
// See description of PR #4246:
#if !defined(PYBIND11_NO_ASSERT_GIL_HELD_INCREF_DECREF) && !defined(NDEBUG) \
&& !defined(PYPY_VERSION) && !defined(PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF)
# define PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF
#endif
// #define PYBIND11_STR_LEGACY_PERMISSIVE // #define PYBIND11_STR_LEGACY_PERMISSIVE
// If DEFINED, pybind11::str can hold PyUnicodeObject or PyBytesObject // If DEFINED, pybind11::str can hold PyUnicodeObject or PyBytesObject
// (probably surprising and never documented, but this was the // (probably surprising and never documented, but this was the
@ -363,7 +433,7 @@
/** \rst /** \rst
This macro creates the entry point that will be invoked when the Python interpreter This macro creates the entry point that will be invoked when the Python interpreter
imports an extension module. The module name is given as the fist argument and it imports an extension module. The module name is given as the first argument and it
should not be in quotes. The second macro argument defines a variable of type should not be in quotes. The second macro argument defines a variable of type
`py::module_` which can be used to initialize the module. `py::module_` which can be used to initialize the module.
@ -588,6 +658,10 @@ template <class T>
using remove_cvref_t = typename remove_cvref<T>::type; using remove_cvref_t = typename remove_cvref<T>::type;
#endif #endif
/// Example usage: is_same_ignoring_cvref<T, PyObject *>::value
template <typename T, typename U>
using is_same_ignoring_cvref = std::is_same<detail::remove_cvref_t<T>, U>;
/// Index sequences /// Index sequences
#if defined(PYBIND11_CPP14) #if defined(PYBIND11_CPP14)
using std::index_sequence; using std::index_sequence;
@ -681,7 +755,16 @@ template <typename C, typename R, typename... A>
struct remove_class<R (C::*)(A...) const> { struct remove_class<R (C::*)(A...) const> {
using type = R(A...); using type = R(A...);
}; };
#ifdef __cpp_noexcept_function_type
template <typename C, typename R, typename... A>
struct remove_class<R (C::*)(A...) noexcept> {
using type = R(A...);
};
template <typename C, typename R, typename... A>
struct remove_class<R (C::*)(A...) const noexcept> {
using type = R(A...);
};
#endif
/// Helper template to strip away type modifiers /// Helper template to strip away type modifiers
template <typename T> template <typename T>
struct intrinsic_type { struct intrinsic_type {
@ -898,12 +981,6 @@ using expand_side_effects = bool[];
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
#if defined(_MSC_VER)
# pragma warning(push)
# pragma warning(disable : 4275)
// warning C4275: An exported class was derived from a class that wasn't exported.
// Can be ignored when derived from a STL class.
#endif
/// C++ bindings of builtin Python exceptions /// C++ bindings of builtin Python exceptions
class PYBIND11_EXPORT_EXCEPTION builtin_exception : public std::runtime_error { class PYBIND11_EXPORT_EXCEPTION builtin_exception : public std::runtime_error {
public: public:
@ -911,9 +988,6 @@ public:
/// Set the error using the Python C API /// Set the error using the Python C API
virtual void set_error() const = 0; virtual void set_error() const = 0;
}; };
#if defined(_MSC_VER)
# pragma warning(pop)
#endif
#define PYBIND11_RUNTIME_EXCEPTION(name, type) \ #define PYBIND11_RUNTIME_EXCEPTION(name, type) \
class PYBIND11_EXPORT_EXCEPTION name : public builtin_exception { \ class PYBIND11_EXPORT_EXCEPTION name : public builtin_exception { \
@ -948,6 +1022,15 @@ PYBIND11_RUNTIME_EXCEPTION(reference_cast_error, PyExc_RuntimeError) /// Used in
template <typename T, typename SFINAE = void> template <typename T, typename SFINAE = void>
struct format_descriptor {}; struct format_descriptor {};
template <typename T>
struct format_descriptor<
T,
detail::enable_if_t<detail::is_same_ignoring_cvref<T, PyObject *>::value>> {
static constexpr const char c = 'O';
static constexpr const char value[2] = {c, '\0'};
static std::string format() { return std::string(1, c); }
};
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
// Returns the index of the given type in the type char array below, and in the list in numpy.h // Returns the index of the given type in the type char array below, and in the list in numpy.h
// The order here is: bool; 8 ints ((signed,unsigned)x(8,16,32,64)bits); float,double,long double; // The order here is: bool; 8 ints ((signed,unsigned)x(8,16,32,64)bits); float,double,long double;
@ -1033,12 +1116,7 @@ PYBIND11_NAMESPACE_END(detail)
/// - regular: static_cast<Return (Class::*)(Arg0, Arg1, Arg2)>(&Class::func) /// - regular: static_cast<Return (Class::*)(Arg0, Arg1, Arg2)>(&Class::func)
/// - sweet: overload_cast<Arg0, Arg1, Arg2>(&Class::func) /// - sweet: overload_cast<Arg0, Arg1, Arg2>(&Class::func)
template <typename... Args> template <typename... Args>
# if (defined(_MSC_VER) && _MSC_VER < 1920) /* MSVC 2017 */ \ static constexpr detail::overload_cast_impl<Args...> overload_cast{};
|| (defined(__clang__) && __clang_major__ == 5)
static constexpr detail::overload_cast_impl<Args...> overload_cast = {};
# else
static constexpr detail::overload_cast_impl<Args...> overload_cast;
# endif
#endif #endif
/// Const member function selector for overload_cast /// Const member function selector for overload_cast
@ -1147,20 +1225,28 @@ constexpr
# define PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(...) # define PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(...)
#endif #endif
#if defined(_MSC_VER) // All versions (as of July 2021). #if defined(__clang__) \
&& (defined(__apple_build_version__) /* AppleClang 13.0.0.13000029 was the only data point \
// warning C4127: Conditional expression is constant available. */ \
constexpr inline bool silence_msvc_c4127(bool cond) { return cond; } || (__clang_major__ >= 7 \
&& __clang_major__ <= 12) /* Clang 3, 5, 13, 14, 15 do not generate the warning. */ \
# define PYBIND11_SILENCE_MSVC_C4127(...) ::pybind11::detail::silence_msvc_c4127(__VA_ARGS__) )
# define PYBIND11_DETECTED_CLANG_WITH_MISLEADING_CALL_STD_MOVE_EXPLICITLY_WARNING
#else // Example:
# define PYBIND11_SILENCE_MSVC_C4127(...) __VA_ARGS__ // tests/test_kwargs_and_defaults.cpp:46:68: error: local variable 'args' will be copied despite
// being returned by name [-Werror,-Wreturn-std-move]
// m.def("args_function", [](py::args args) -> py::tuple { return args; });
// ^~~~
// test_kwargs_and_defaults.cpp:46:68: note: call 'std::move' explicitly to avoid copying
// m.def("args_function", [](py::args args) -> py::tuple { return args; });
// ^~~~
// std::move(args)
#endif #endif
// Pybind offers detailed error messages by default for all builts that are debug (through the // Pybind offers detailed error messages by default for all builts that are debug (through the
// negation of ndebug). This can also be manually enabled by users, for any builds, through // negation of NDEBUG). This can also be manually enabled by users, for any builds, through
// defining PYBIND11_DETAILED_ERROR_MESSAGES. // defining PYBIND11_DETAILED_ERROR_MESSAGES. This information is primarily useful for those
// who are writing (as opposed to merely using) libraries that use pybind11.
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) && !defined(NDEBUG) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) && !defined(NDEBUG)
# define PYBIND11_DETAILED_ERROR_MESSAGES # define PYBIND11_DETAILED_ERROR_MESSAGES
#endif #endif

View File

@ -143,11 +143,24 @@ constexpr descr<N, Ts...> concat(const descr<N, Ts...> &descr) {
return descr; return descr;
} }
#ifdef __cpp_fold_expressions
template <size_t N1, size_t N2, typename... Ts1, typename... Ts2>
constexpr descr<N1 + N2 + 2, Ts1..., Ts2...> operator,(const descr<N1, Ts1...> &a,
const descr<N2, Ts2...> &b) {
return a + const_name(", ") + b;
}
template <size_t N, typename... Ts, typename... Args>
constexpr auto concat(const descr<N, Ts...> &d, const Args &...args) {
return (d, ..., args);
}
#else
template <size_t N, typename... Ts, typename... Args> template <size_t N, typename... Ts, typename... Args>
constexpr auto concat(const descr<N, Ts...> &d, const Args &...args) constexpr auto concat(const descr<N, Ts...> &d, const Args &...args)
-> decltype(std::declval<descr<N + 2, Ts...>>() + concat(args...)) { -> decltype(std::declval<descr<N + 2, Ts...>>() + concat(args...)) {
return d + const_name(", ") + concat(args...); return d + const_name(", ") + concat(args...);
} }
#endif
template <size_t N, typename... Ts> template <size_t N, typename... Ts>
constexpr descr<N + 2, Ts...> type_descr(const descr<N, Ts...> &descr) { constexpr descr<N + 2, Ts...> type_descr(const descr<N, Ts...> &descr) {

View File

@ -12,6 +12,9 @@
#include "class.h" #include "class.h"
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_WARNING_DISABLE_MSVC(4127)
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
template <> template <>
@ -115,7 +118,7 @@ template <typename Class>
void construct(value_and_holder &v_h, Cpp<Class> *ptr, bool need_alias) { void construct(value_and_holder &v_h, Cpp<Class> *ptr, bool need_alias) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias); PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
no_nullptr(ptr); no_nullptr(ptr);
if (PYBIND11_SILENCE_MSVC_C4127(Class::has_alias) && need_alias && !is_alias<Class>(ptr)) { if (Class::has_alias && need_alias && !is_alias<Class>(ptr)) {
// We're going to try to construct an alias by moving the cpp type. Whether or not // We're going to try to construct an alias by moving the cpp type. Whether or not
// that succeeds, we still need to destroy the original cpp pointer (either the // that succeeds, we still need to destroy the original cpp pointer (either the
// moved away leftover, if the alias construction works, or the value itself if we // moved away leftover, if the alias construction works, or the value itself if we
@ -156,7 +159,7 @@ void construct(value_and_holder &v_h, Holder<Class> holder, bool need_alias) {
auto *ptr = holder_helper<Holder<Class>>::get(holder); auto *ptr = holder_helper<Holder<Class>>::get(holder);
no_nullptr(ptr); no_nullptr(ptr);
// If we need an alias, check that the held pointer is actually an alias instance // If we need an alias, check that the held pointer is actually an alias instance
if (PYBIND11_SILENCE_MSVC_C4127(Class::has_alias) && need_alias && !is_alias<Class>(ptr)) { if (Class::has_alias && need_alias && !is_alias<Class>(ptr)) {
throw type_error("pybind11::init(): construction failed: returned holder-wrapped instance " throw type_error("pybind11::init(): construction failed: returned holder-wrapped instance "
"is not an alias instance"); "is not an alias instance");
} }
@ -172,9 +175,9 @@ void construct(value_and_holder &v_h, Holder<Class> holder, bool need_alias) {
template <typename Class> template <typename Class>
void construct(value_and_holder &v_h, Cpp<Class> &&result, bool need_alias) { void construct(value_and_holder &v_h, Cpp<Class> &&result, bool need_alias) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias); PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
static_assert(std::is_move_constructible<Cpp<Class>>::value, static_assert(is_move_constructible<Cpp<Class>>::value,
"pybind11::init() return-by-value factory function requires a movable class"); "pybind11::init() return-by-value factory function requires a movable class");
if (PYBIND11_SILENCE_MSVC_C4127(Class::has_alias) && need_alias) { if (Class::has_alias && need_alias) {
construct_alias_from_cpp<Class>(is_alias_constructible<Class>{}, v_h, std::move(result)); construct_alias_from_cpp<Class>(is_alias_constructible<Class>{}, v_h, std::move(result));
} else { } else {
v_h.value_ptr() = new Cpp<Class>(std::move(result)); v_h.value_ptr() = new Cpp<Class>(std::move(result));
@ -187,7 +190,7 @@ void construct(value_and_holder &v_h, Cpp<Class> &&result, bool need_alias) {
template <typename Class> template <typename Class>
void construct(value_and_holder &v_h, Alias<Class> &&result, bool) { void construct(value_and_holder &v_h, Alias<Class> &&result, bool) {
static_assert( static_assert(
std::is_move_constructible<Alias<Class>>::value, is_move_constructible<Alias<Class>>::value,
"pybind11::init() return-by-alias-value factory function requires a movable alias class"); "pybind11::init() return-by-alias-value factory function requires a movable alias class");
v_h.value_ptr() = new Alias<Class>(std::move(result)); v_h.value_ptr() = new Alias<Class>(std::move(result));
} }
@ -206,10 +209,11 @@ struct constructor {
extra...); extra...);
} }
template <typename Class, template <
typename... Extra, typename Class,
enable_if_t<Class::has_alias && std::is_constructible<Cpp<Class>, Args...>::value, typename... Extra,
int> = 0> enable_if_t<Class::has_alias && std::is_constructible<Cpp<Class>, Args...>::value, int>
= 0>
static void execute(Class &cl, const Extra &...extra) { static void execute(Class &cl, const Extra &...extra) {
cl.def( cl.def(
"__init__", "__init__",
@ -226,10 +230,11 @@ struct constructor {
extra...); extra...);
} }
template <typename Class, template <
typename... Extra, typename Class,
enable_if_t<Class::has_alias && !std::is_constructible<Cpp<Class>, Args...>::value, typename... Extra,
int> = 0> enable_if_t<Class::has_alias && !std::is_constructible<Cpp<Class>, Args...>::value, int>
= 0>
static void execute(Class &cl, const Extra &...extra) { static void execute(Class &cl, const Extra &...extra) {
cl.def( cl.def(
"__init__", "__init__",
@ -245,10 +250,11 @@ struct constructor {
// Implementing class for py::init_alias<...>() // Implementing class for py::init_alias<...>()
template <typename... Args> template <typename... Args>
struct alias_constructor { struct alias_constructor {
template <typename Class, template <
typename... Extra, typename Class,
enable_if_t<Class::has_alias && std::is_constructible<Alias<Class>, Args...>::value, typename... Extra,
int> = 0> enable_if_t<Class::has_alias && std::is_constructible<Alias<Class>, Args...>::value, int>
= 0>
static void execute(Class &cl, const Extra &...extra) { static void execute(Class &cl, const Extra &...extra) {
cl.def( cl.def(
"__init__", "__init__",

View File

@ -9,6 +9,12 @@
#pragma once #pragma once
#include "common.h"
#if defined(WITH_THREAD) && defined(PYBIND11_SIMPLE_GIL_MANAGEMENT)
# include "../gil.h"
#endif
#include "../pytypes.h" #include "../pytypes.h"
#include <exception> #include <exception>
@ -28,15 +34,26 @@
/// further ABI-incompatible changes may be made before the ABI is officially /// further ABI-incompatible changes may be made before the ABI is officially
/// changed to the new version. /// changed to the new version.
#ifndef PYBIND11_INTERNALS_VERSION #ifndef PYBIND11_INTERNALS_VERSION
# define PYBIND11_INTERNALS_VERSION 4 # if PY_VERSION_HEX >= 0x030C0000
// Version bump for Python 3.12+, before first 3.12 beta release.
# define PYBIND11_INTERNALS_VERSION 5
# else
# define PYBIND11_INTERNALS_VERSION 4
# endif
#endif #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) PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
using ExceptionTranslator = void (*)(std::exception_ptr); using ExceptionTranslator = void (*)(std::exception_ptr);
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
constexpr const char *internals_function_record_capsule_name = "pybind11_function_record_capsule";
// Forward declarations // Forward declarations
inline PyTypeObject *make_static_property_type(); inline PyTypeObject *make_static_property_type();
inline PyTypeObject *make_default_metaclass(); inline PyTypeObject *make_default_metaclass();
@ -49,7 +66,7 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass);
// `Py_LIMITED_API` anyway. // `Py_LIMITED_API` anyway.
# if PYBIND11_INTERNALS_VERSION > 4 # if PYBIND11_INTERNALS_VERSION > 4
# define PYBIND11_TLS_KEY_REF Py_tss_t & # define PYBIND11_TLS_KEY_REF Py_tss_t &
# ifdef __GNUC__ # if defined(__GNUC__) && !defined(__INTEL_COMPILER)
// Clang on macOS warns due to `Py_tss_NEEDS_INIT` not specifying an initializer // Clang on macOS warns due to `Py_tss_NEEDS_INIT` not specifying an initializer
// for every field. // for every field.
# define PYBIND11_TLS_KEY_INIT(var) \ # define PYBIND11_TLS_KEY_INIT(var) \
@ -106,7 +123,8 @@ inline void tls_replace_value(PYBIND11_TLS_KEY_REF key, void *value) {
// libstdc++, this doesn't happen: equality and the type_index hash are based on the type name, // 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 // which works. If not under a known-good stl, provide our own name-based hash and equality
// functions that use the type name. // functions that use the type name.
#if defined(__GLIBCXX__) #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; } 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_hash = std::hash<std::type_index>;
using type_equal_to = std::equal_to<std::type_index>; using type_equal_to = std::equal_to<std::type_index>;
@ -169,11 +187,23 @@ struct internals {
PyTypeObject *default_metaclass; PyTypeObject *default_metaclass;
PyObject *instance_base; PyObject *instance_base;
#if defined(WITH_THREAD) #if defined(WITH_THREAD)
// Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined:
PYBIND11_TLS_KEY_INIT(tstate) PYBIND11_TLS_KEY_INIT(tstate)
# if PYBIND11_INTERNALS_VERSION > 4 # if PYBIND11_INTERNALS_VERSION > 4
PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key) PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key)
# endif // PYBIND11_INTERNALS_VERSION > 4 # endif // PYBIND11_INTERNALS_VERSION > 4
// Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined:
PyInterpreterState *istate = nullptr; 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() { ~internals() {
# if PYBIND11_INTERNALS_VERSION > 4 # if PYBIND11_INTERNALS_VERSION > 4
PYBIND11_TLS_FREE(loader_life_support_tls_key); PYBIND11_TLS_FREE(loader_life_support_tls_key);
@ -401,6 +431,38 @@ inline void translate_local_exception(std::exception_ptr p) {
} }
#endif #endif
inline object get_python_state_dict() {
object state_dict;
#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
PyInterpreterState *istate = _PyInterpreterState_Get();
# else
PyInterpreterState *istate = PyInterpreterState_Get();
# endif
if (istate) {
state_dict = reinterpret_borrow<object>(PyInterpreterState_GetDict(istate));
}
#endif
if (!state_dict) {
raise_from(PyExc_SystemError, "pybind11::detail::get_python_state_dict() FAILED");
}
return state_dict;
}
inline object get_internals_obj_from_state_dict(handle state_dict) {
return reinterpret_borrow<object>(dict_getitemstring(state_dict.ptr(), PYBIND11_INTERNALS_ID));
}
inline internals **get_internals_pp_from_capsule(handle obj) {
void *raw_ptr = PyCapsule_GetPointer(obj.ptr(), /*name=*/nullptr);
if (raw_ptr == nullptr) {
raise_from(PyExc_SystemError, "pybind11::detail::get_internals_pp_from_capsule() FAILED");
}
return static_cast<internals **>(raw_ptr);
}
/// Return a reference to the current `internals` data /// Return a reference to the current `internals` data
PYBIND11_NOINLINE internals &get_internals() { PYBIND11_NOINLINE internals &get_internals() {
auto **&internals_pp = get_internals_pp(); auto **&internals_pp = get_internals_pp();
@ -408,21 +470,29 @@ PYBIND11_NOINLINE internals &get_internals() {
return **internals_pp; return **internals_pp;
} }
#if defined(WITH_THREAD)
# if defined(PYBIND11_SIMPLE_GIL_MANAGEMENT)
gil_scoped_acquire gil;
# else
// Ensure that the GIL is held since we will need to make Python calls. // Ensure that the GIL is held since we will need to make Python calls.
// Cannot use py::gil_scoped_acquire here since that constructor calls get_internals. // Cannot use py::gil_scoped_acquire here since that constructor calls get_internals.
struct gil_scoped_acquire_local { struct gil_scoped_acquire_local {
gil_scoped_acquire_local() : state(PyGILState_Ensure()) {} gil_scoped_acquire_local() : state(PyGILState_Ensure()) {}
gil_scoped_acquire_local(const gil_scoped_acquire_local &) = delete;
gil_scoped_acquire_local &operator=(const gil_scoped_acquire_local &) = delete;
~gil_scoped_acquire_local() { PyGILState_Release(state); } ~gil_scoped_acquire_local() { PyGILState_Release(state); }
const PyGILState_STATE state; const PyGILState_STATE state;
} gil; } gil;
# endif
#endif
error_scope err_scope; error_scope err_scope;
PYBIND11_STR_TYPE id(PYBIND11_INTERNALS_ID); dict state_dict = get_python_state_dict();
auto builtins = handle(PyEval_GetBuiltins()); if (object internals_obj = get_internals_obj_from_state_dict(state_dict)) {
if (builtins.contains(id) && isinstance<capsule>(builtins[id])) { internals_pp = get_internals_pp_from_capsule(internals_obj);
internals_pp = static_cast<internals **>(capsule(builtins[id])); }
if (internals_pp && *internals_pp) {
// We loaded builtins through python's builtins, which means that our `error_already_set` // We loaded the internals through `state_dict`, which means that our `error_already_set`
// and `builtin_exception` may be different local classes than the ones set up in the // and `builtin_exception` may be different local classes than the ones set up in the
// initial exception translator, below, so add another for our local exception classes. // initial exception translator, below, so add another for our local exception classes.
// //
@ -440,16 +510,15 @@ PYBIND11_NOINLINE internals &get_internals() {
internals_ptr = new internals(); internals_ptr = new internals();
#if defined(WITH_THREAD) #if defined(WITH_THREAD)
# if PY_VERSION_HEX < 0x03090000
PyEval_InitThreads();
# endif
PyThreadState *tstate = PyThreadState_Get(); PyThreadState *tstate = PyThreadState_Get();
// NOLINTNEXTLINE(bugprone-assignment-in-if-condition)
if (!PYBIND11_TLS_KEY_CREATE(internals_ptr->tstate)) { if (!PYBIND11_TLS_KEY_CREATE(internals_ptr->tstate)) {
pybind11_fail("get_internals: could not successfully initialize the tstate TSS key!"); pybind11_fail("get_internals: could not successfully initialize the tstate TSS key!");
} }
PYBIND11_TLS_REPLACE_VALUE(internals_ptr->tstate, tstate); PYBIND11_TLS_REPLACE_VALUE(internals_ptr->tstate, tstate);
# if PYBIND11_INTERNALS_VERSION > 4 # if PYBIND11_INTERNALS_VERSION > 4
// NOLINTNEXTLINE(bugprone-assignment-in-if-condition)
if (!PYBIND11_TLS_KEY_CREATE(internals_ptr->loader_life_support_tls_key)) { if (!PYBIND11_TLS_KEY_CREATE(internals_ptr->loader_life_support_tls_key)) {
pybind11_fail("get_internals: could not successfully initialize the " pybind11_fail("get_internals: could not successfully initialize the "
"loader_life_support TSS key!"); "loader_life_support TSS key!");
@ -457,7 +526,7 @@ PYBIND11_NOINLINE internals &get_internals() {
# endif # endif
internals_ptr->istate = tstate->interp; internals_ptr->istate = tstate->interp;
#endif #endif
builtins[id] = capsule(internals_pp); state_dict[PYBIND11_INTERNALS_ID] = capsule(internals_pp);
internals_ptr->registered_exception_translators.push_front(&translate_exception); internals_ptr->registered_exception_translators.push_front(&translate_exception);
internals_ptr->static_property_type = make_static_property_type(); internals_ptr->static_property_type = make_static_property_type();
internals_ptr->default_metaclass = make_default_metaclass(); internals_ptr->default_metaclass = make_default_metaclass();
@ -489,6 +558,7 @@ struct local_internals {
struct shared_loader_life_support_data { struct shared_loader_life_support_data {
PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key) PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key)
shared_loader_life_support_data() { shared_loader_life_support_data() {
// NOLINTNEXTLINE(bugprone-assignment-in-if-condition)
if (!PYBIND11_TLS_KEY_CREATE(loader_life_support_tls_key)) { if (!PYBIND11_TLS_KEY_CREATE(loader_life_support_tls_key)) {
pybind11_fail("local_internals: could not successfully initialize the " pybind11_fail("local_internals: could not successfully initialize the "
"loader_life_support TLS key!"); "loader_life_support TLS key!");
@ -512,8 +582,13 @@ struct local_internals {
/// Works like `get_internals`, but for things which are locally registered. /// Works like `get_internals`, but for things which are locally registered.
inline local_internals &get_local_internals() { inline local_internals &get_local_internals() {
static local_internals locals; // Current static can be created in the interpreter finalization routine. If the later will be
return locals; // destroyed in another static variable destructor, creation of this static there will cause
// static deinitialization fiasco. In order to avoid it we avoid destruction of the
// local_internals static. One can read more about the problem and current solution here:
// https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables
static auto *locals = new local_internals();
return *locals;
} }
/// Constructs a std::string with the given arguments, stores it in `internals`, and returns its /// Constructs a std::string with the given arguments, stores it in `internals`, and returns its
@ -527,6 +602,25 @@ const char *c_str(Args &&...args) {
return strings.front().c_str(); return strings.front().c_str();
} }
inline const char *get_function_record_capsule_name() {
#if PYBIND11_INTERNALS_VERSION > 4
return get_internals().function_record_capsule_name.c_str();
#else
return nullptr;
#endif
}
// Determine whether or not the following capsule contains a pybind11 function record.
// Note that we use `internals` to make sure that only ABI compatible records are touched.
//
// This check is currently used in two places:
// - An important optimization in functional.h to avoid overhead in C++ -> Python -> C++
// - The sibling feature of cpp_function to allow overloads
inline bool is_function_record_capsule(const capsule &cap) {
// Pointer equality as we rely on internals() to ensure unique pointers
return cap.name() == get_function_record_capsule_name();
}
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
/// Returns a named pointer that is shared among all extension modules (using the same /// Returns a named pointer that is shared among all extension modules (using the same

View File

@ -258,9 +258,9 @@ struct value_and_holder {
// Main constructor for a found value/holder: // Main constructor for a found value/holder:
value_and_holder(instance *i, const detail::type_info *type, size_t vpos, size_t index) value_and_holder(instance *i, const detail::type_info *type, size_t vpos, size_t index)
: inst{i}, index{index}, type{type}, vh{inst->simple_layout : inst{i}, index{index}, type{type},
? inst->simple_value_holder vh{inst->simple_layout ? inst->simple_value_holder
: &inst->nonsimple.values_and_holders[vpos]} {} : &inst->nonsimple.values_and_holders[vpos]} {}
// Default constructor (used to signal a value-and-holder not found by get_value_and_holder()) // Default constructor (used to signal a value-and-holder not found by get_value_and_holder())
value_and_holder() = default; value_and_holder() = default;
@ -822,23 +822,179 @@ using movable_cast_op_type
typename std::add_rvalue_reference<intrinsic_t<T>>::type, typename std::add_rvalue_reference<intrinsic_t<T>>::type,
typename std::add_lvalue_reference<intrinsic_t<T>>::type>>; typename std::add_lvalue_reference<intrinsic_t<T>>::type>>;
// Does the container have a mapped type and is it recursive?
// Implemented by specializations below.
template <typename Container, typename SFINAE = void>
struct container_mapped_type_traits {
static constexpr bool has_mapped_type = false;
static constexpr bool has_recursive_mapped_type = false;
};
template <typename Container>
struct container_mapped_type_traits<
Container,
typename std::enable_if<
std::is_same<typename Container::mapped_type, Container>::value>::type> {
static constexpr bool has_mapped_type = true;
static constexpr bool has_recursive_mapped_type = true;
};
template <typename Container>
struct container_mapped_type_traits<
Container,
typename std::enable_if<
negation<std::is_same<typename Container::mapped_type, Container>>::value>::type> {
static constexpr bool has_mapped_type = true;
static constexpr bool has_recursive_mapped_type = false;
};
// Does the container have a value type and is it recursive?
// Implemented by specializations below.
template <typename Container, typename SFINAE = void>
struct container_value_type_traits : std::false_type {
static constexpr bool has_value_type = false;
static constexpr bool has_recursive_value_type = false;
};
template <typename Container>
struct container_value_type_traits<
Container,
typename std::enable_if<
std::is_same<typename Container::value_type, Container>::value>::type> {
static constexpr bool has_value_type = true;
static constexpr bool has_recursive_value_type = true;
};
template <typename Container>
struct container_value_type_traits<
Container,
typename std::enable_if<
negation<std::is_same<typename Container::value_type, Container>>::value>::type> {
static constexpr bool has_value_type = true;
static constexpr bool has_recursive_value_type = false;
};
/*
* Tag to be used for representing the bottom of recursively defined types.
* Define this tag so we don't have to use void.
*/
struct recursive_bottom {};
/*
* Implementation detail of `recursive_container_traits` below.
* `T` is the `value_type` of the container, which might need to be modified to
* avoid recursive types and const types.
*/
template <typename T, bool is_this_a_map>
struct impl_type_to_check_recursively {
/*
* If the container is recursive, then no further recursion should be done.
*/
using if_recursive = recursive_bottom;
/*
* Otherwise yield `T` unchanged.
*/
using if_not_recursive = T;
};
/*
* For pairs - only as value type of a map -, the first type should remove the `const`.
* Also, if the map is recursive, then the recursive checking should consider
* the first type only.
*/
template <typename A, typename B>
struct impl_type_to_check_recursively<std::pair<A, B>, /* is_this_a_map = */ true> {
using if_recursive = typename std::remove_const<A>::type;
using if_not_recursive = std::pair<typename std::remove_const<A>::type, B>;
};
/*
* Implementation of `recursive_container_traits` below.
*/
template <typename Container, typename SFINAE = void>
struct impl_recursive_container_traits {
using type_to_check_recursively = recursive_bottom;
};
template <typename Container>
struct impl_recursive_container_traits<
Container,
typename std::enable_if<container_value_type_traits<Container>::has_value_type>::type> {
static constexpr bool is_recursive
= container_mapped_type_traits<Container>::has_recursive_mapped_type
|| container_value_type_traits<Container>::has_recursive_value_type;
/*
* This member dictates which type Pybind11 should check recursively in traits
* such as `is_move_constructible`, `is_copy_constructible`, `is_move_assignable`, ...
* Direct access to `value_type` should be avoided:
* 1. `value_type` might recursively contain the type again
* 2. `value_type` of STL map types is `std::pair<A const, B>`, the `const`
* should be removed.
*
*/
using type_to_check_recursively = typename std::conditional<
is_recursive,
typename impl_type_to_check_recursively<
typename Container::value_type,
container_mapped_type_traits<Container>::has_mapped_type>::if_recursive,
typename impl_type_to_check_recursively<
typename Container::value_type,
container_mapped_type_traits<Container>::has_mapped_type>::if_not_recursive>::type;
};
/*
* This trait defines the `type_to_check_recursively` which is needed to properly
* handle recursively defined traits such as `is_move_constructible` without going
* into an infinite recursion.
* Should be used instead of directly accessing the `value_type`.
* It cancels the recursion by returning the `recursive_bottom` tag.
*
* The default definition of `type_to_check_recursively` is as follows:
*
* 1. By default, it is `recursive_bottom`, so that the recursion is canceled.
* 2. If the type is non-recursive and defines a `value_type`, then the `value_type` is used.
* If the `value_type` is a pair and a `mapped_type` is defined,
* then the `const` is removed from the first type.
* 3. If the type is recursive and `value_type` is not a pair, then `recursive_bottom` is returned.
* 4. If the type is recursive and `value_type` is a pair and a `mapped_type` is defined,
* then `const` is removed from the first type and the first type is returned.
*
* This behavior can be extended by the user as seen in test_stl_binders.cpp.
*
* This struct is exactly the same as impl_recursive_container_traits.
* The duplication achieves that user-defined specializations don't compete
* with internal specializations, but take precedence.
*/
template <typename Container, typename SFINAE = void>
struct recursive_container_traits : impl_recursive_container_traits<Container> {};
template <typename T>
struct is_move_constructible
: all_of<std::is_move_constructible<T>,
is_move_constructible<
typename recursive_container_traits<T>::type_to_check_recursively>> {};
template <>
struct is_move_constructible<recursive_bottom> : std::true_type {};
// Likewise for std::pair
// (after C++17 it is mandatory that the move constructor not exist when the two types aren't
// themselves move constructible, but this can not be relied upon when T1 or T2 are themselves
// containers).
template <typename T1, typename T2>
struct is_move_constructible<std::pair<T1, T2>>
: all_of<is_move_constructible<T1>, is_move_constructible<T2>> {};
// std::is_copy_constructible isn't quite enough: it lets std::vector<T> (and similar) through when // std::is_copy_constructible isn't quite enough: it lets std::vector<T> (and similar) through when
// T is non-copyable, but code containing such a copy constructor fails to actually compile. // T is non-copyable, but code containing such a copy constructor fails to actually compile.
template <typename T, typename SFINAE = void> template <typename T>
struct is_copy_constructible : std::is_copy_constructible<T> {}; struct is_copy_constructible
: all_of<std::is_copy_constructible<T>,
is_copy_constructible<
typename recursive_container_traits<T>::type_to_check_recursively>> {};
// Specialization for types that appear to be copy constructible but also look like stl containers template <>
// (we specifically check for: has `value_type` and `reference` with `reference = value_type&`): if struct is_copy_constructible<recursive_bottom> : std::true_type {};
// so, copy constructability depends on whether the value_type is copy constructible.
template <typename Container>
struct is_copy_constructible<
Container,
enable_if_t<
all_of<std::is_copy_constructible<Container>,
std::is_same<typename Container::value_type &, typename Container::reference>,
// Avoid infinite recursion
negation<std::is_same<Container, typename Container::value_type>>>::value>>
: is_copy_constructible<typename Container::value_type> {};
// Likewise for std::pair // Likewise for std::pair
// (after C++17 it is mandatory that the copy constructor not exist when the two types aren't // (after C++17 it is mandatory that the copy constructor not exist when the two types aren't
@ -849,14 +1005,16 @@ struct is_copy_constructible<std::pair<T1, T2>>
: all_of<is_copy_constructible<T1>, is_copy_constructible<T2>> {}; : all_of<is_copy_constructible<T1>, is_copy_constructible<T2>> {};
// The same problems arise with std::is_copy_assignable, so we use the same workaround. // The same problems arise with std::is_copy_assignable, so we use the same workaround.
template <typename T, typename SFINAE = void> template <typename T>
struct is_copy_assignable : std::is_copy_assignable<T> {}; struct is_copy_assignable
template <typename Container> : all_of<
struct is_copy_assignable<Container, std::is_copy_assignable<T>,
enable_if_t<all_of<std::is_copy_assignable<Container>, is_copy_assignable<typename recursive_container_traits<T>::type_to_check_recursively>> {
std::is_same<typename Container::value_type &, };
typename Container::reference>>::value>>
: is_copy_assignable<typename Container::value_type> {}; template <>
struct is_copy_assignable<recursive_bottom> : std::true_type {};
template <typename T1, typename T2> template <typename T1, typename T2>
struct is_copy_assignable<std::pair<T1, T2>> struct is_copy_assignable<std::pair<T1, T2>>
: all_of<is_copy_assignable<T1>, is_copy_assignable<T2>> {}; : all_of<is_copy_assignable<T1>, is_copy_assignable<T2>> {};
@ -994,7 +1152,7 @@ protected:
return [](const void *arg) -> void * { return new T(*reinterpret_cast<const T *>(arg)); }; return [](const void *arg) -> void * { return new T(*reinterpret_cast<const T *>(arg)); };
} }
template <typename T, typename = enable_if_t<std::is_move_constructible<T>::value>> template <typename T, typename = enable_if_t<is_move_constructible<T>::value>>
static auto make_move_constructor(const T *) static auto make_move_constructor(const T *)
-> decltype(new T(std::declval<T &&>()), Constructor{}) { -> decltype(new T(std::declval<T &&>()), Constructor{}) {
return [](const void *arg) -> void * { return [](const void *arg) -> void * {
@ -1006,5 +1164,14 @@ protected:
static Constructor make_move_constructor(...) { return nullptr; } static Constructor make_move_constructor(...) { return nullptr; }
}; };
PYBIND11_NOINLINE std::string type_info_description(const std::type_info &ti) {
if (auto *type_data = get_type_info(ti)) {
handle th((PyObject *) type_data->type);
return th.attr("__module__").cast<std::string>() + '.'
+ th.attr("__qualname__").cast<std::string>();
}
return clean_type_id(ti.name());
}
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@ -9,700 +9,4 @@
#pragma once #pragma once
/* HINT: To suppress warnings originating from the Eigen headers, use -isystem. #include "eigen/matrix.h"
See also:
https://stackoverflow.com/questions/2579576/i-dir-vs-isystem-dir
https://stackoverflow.com/questions/1741816/isystem-for-ms-visual-studio-c-compiler
*/
#include "numpy.h"
// The C4127 suppression was introduced for Eigen 3.4.0. In theory we could
// make it version specific, or even remove it later, but considering that
// 1. C4127 is generally far more distracting than useful for modern template code, and
// 2. we definitely want to ignore any MSVC warnings originating from Eigen code,
// it is probably best to keep this around indefinitely.
#if defined(_MSC_VER)
# pragma warning(push)
# pragma warning(disable : 4127) // C4127: conditional expression is constant
# pragma warning(disable : 5054) // https://github.com/pybind/pybind11/pull/3741
// C5054: operator '&': deprecated between enumerations of different types
#endif
#include <Eigen/Core>
#include <Eigen/SparseCore>
#if defined(_MSC_VER)
# pragma warning(pop)
#endif
// Eigen prior to 3.2.7 doesn't have proper move constructors--but worse, some classes get implicit
// move constructors that break things. We could detect this an explicitly copy, but an extra copy
// of matrices seems highly undesirable.
static_assert(EIGEN_VERSION_AT_LEAST(3, 2, 7),
"Eigen support in pybind11 requires Eigen >= 3.2.7");
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
// Provide a convenience alias for easier pass-by-ref usage with fully dynamic strides:
using EigenDStride = Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>;
template <typename MatrixType>
using EigenDRef = Eigen::Ref<MatrixType, 0, EigenDStride>;
template <typename MatrixType>
using EigenDMap = Eigen::Map<MatrixType, 0, EigenDStride>;
PYBIND11_NAMESPACE_BEGIN(detail)
#if EIGEN_VERSION_AT_LEAST(3, 3, 0)
using EigenIndex = Eigen::Index;
template <typename Scalar, int Flags, typename StorageIndex>
using EigenMapSparseMatrix = Eigen::Map<Eigen::SparseMatrix<Scalar, Flags, StorageIndex>>;
#else
using EigenIndex = EIGEN_DEFAULT_DENSE_INDEX_TYPE;
template <typename Scalar, int Flags, typename StorageIndex>
using EigenMapSparseMatrix = Eigen::MappedSparseMatrix<Scalar, Flags, StorageIndex>;
#endif
// Matches Eigen::Map, Eigen::Ref, blocks, etc:
template <typename T>
using is_eigen_dense_map = all_of<is_template_base_of<Eigen::DenseBase, T>,
std::is_base_of<Eigen::MapBase<T, Eigen::ReadOnlyAccessors>, T>>;
template <typename T>
using is_eigen_mutable_map = std::is_base_of<Eigen::MapBase<T, Eigen::WriteAccessors>, T>;
template <typename T>
using is_eigen_dense_plain
= all_of<negation<is_eigen_dense_map<T>>, is_template_base_of<Eigen::PlainObjectBase, T>>;
template <typename T>
using is_eigen_sparse = is_template_base_of<Eigen::SparseMatrixBase, T>;
// Test for objects inheriting from EigenBase<Derived> that aren't captured by the above. This
// basically covers anything that can be assigned to a dense matrix but that don't have a typical
// matrix data layout that can be copied from their .data(). For example, DiagonalMatrix and
// SelfAdjointView fall into this category.
template <typename T>
using is_eigen_other
= all_of<is_template_base_of<Eigen::EigenBase, T>,
negation<any_of<is_eigen_dense_map<T>, is_eigen_dense_plain<T>, is_eigen_sparse<T>>>>;
// Captures numpy/eigen conformability status (returned by EigenProps::conformable()):
template <bool EigenRowMajor>
struct EigenConformable {
bool conformable = false;
EigenIndex rows = 0, cols = 0;
EigenDStride stride{0, 0}; // Only valid if negativestrides is false!
bool negativestrides = false; // If true, do not use stride!
// NOLINTNEXTLINE(google-explicit-constructor)
EigenConformable(bool fits = false) : conformable{fits} {}
// Matrix type:
EigenConformable(EigenIndex r, EigenIndex c, EigenIndex rstride, EigenIndex cstride)
: conformable{true}, rows{r}, cols{c},
// TODO: when Eigen bug #747 is fixed, remove the tests for non-negativity.
// http://eigen.tuxfamily.org/bz/show_bug.cgi?id=747
stride{EigenRowMajor ? (rstride > 0 ? rstride : 0)
: (cstride > 0 ? cstride : 0) /* outer stride */,
EigenRowMajor ? (cstride > 0 ? cstride : 0)
: (rstride > 0 ? rstride : 0) /* inner stride */},
negativestrides{rstride < 0 || cstride < 0} {}
// Vector type:
EigenConformable(EigenIndex r, EigenIndex c, EigenIndex stride)
: EigenConformable(r, c, r == 1 ? c * stride : stride, c == 1 ? r : r * stride) {}
template <typename props>
bool stride_compatible() const {
// To have compatible strides, we need (on both dimensions) one of fully dynamic strides,
// matching strides, or a dimension size of 1 (in which case the stride value is
// irrelevant). Alternatively, if any dimension size is 0, the strides are not relevant
// (and numpy ≥ 1.23 sets the strides to 0 in that case, so we need to check explicitly).
if (negativestrides) {
return false;
}
if (rows == 0 || cols == 0) {
return true;
}
return (props::inner_stride == Eigen::Dynamic || props::inner_stride == stride.inner()
|| (EigenRowMajor ? cols : rows) == 1)
&& (props::outer_stride == Eigen::Dynamic || props::outer_stride == stride.outer()
|| (EigenRowMajor ? rows : cols) == 1);
}
// NOLINTNEXTLINE(google-explicit-constructor)
operator bool() const { return conformable; }
};
template <typename Type>
struct eigen_extract_stride {
using type = Type;
};
template <typename PlainObjectType, int MapOptions, typename StrideType>
struct eigen_extract_stride<Eigen::Map<PlainObjectType, MapOptions, StrideType>> {
using type = StrideType;
};
template <typename PlainObjectType, int Options, typename StrideType>
struct eigen_extract_stride<Eigen::Ref<PlainObjectType, Options, StrideType>> {
using type = StrideType;
};
// Helper struct for extracting information from an Eigen type
template <typename Type_>
struct EigenProps {
using Type = Type_;
using Scalar = typename Type::Scalar;
using StrideType = typename eigen_extract_stride<Type>::type;
static constexpr EigenIndex rows = Type::RowsAtCompileTime, cols = Type::ColsAtCompileTime,
size = Type::SizeAtCompileTime;
static constexpr bool row_major = Type::IsRowMajor,
vector
= Type::IsVectorAtCompileTime, // At least one dimension has fixed size 1
fixed_rows = rows != Eigen::Dynamic, fixed_cols = cols != Eigen::Dynamic,
fixed = size != Eigen::Dynamic, // Fully-fixed size
dynamic = !fixed_rows && !fixed_cols; // Fully-dynamic size
template <EigenIndex i, EigenIndex ifzero>
using if_zero = std::integral_constant<EigenIndex, i == 0 ? ifzero : i>;
static constexpr EigenIndex inner_stride
= if_zero<StrideType::InnerStrideAtCompileTime, 1>::value,
outer_stride = if_zero < StrideType::OuterStrideAtCompileTime,
vector ? size
: row_major ? cols
: rows > ::value;
static constexpr bool dynamic_stride
= inner_stride == Eigen::Dynamic && outer_stride == Eigen::Dynamic;
static constexpr bool requires_row_major
= !dynamic_stride && !vector && (row_major ? inner_stride : outer_stride) == 1;
static constexpr bool requires_col_major
= !dynamic_stride && !vector && (row_major ? outer_stride : inner_stride) == 1;
// Takes an input array and determines whether we can make it fit into the Eigen type. If
// the array is a vector, we attempt to fit it into either an Eigen 1xN or Nx1 vector
// (preferring the latter if it will fit in either, i.e. for a fully dynamic matrix type).
static EigenConformable<row_major> conformable(const array &a) {
const auto dims = a.ndim();
if (dims < 1 || dims > 2) {
return false;
}
if (dims == 2) { // Matrix type: require exact match (or dynamic)
EigenIndex np_rows = a.shape(0), np_cols = a.shape(1),
np_rstride = a.strides(0) / static_cast<ssize_t>(sizeof(Scalar)),
np_cstride = a.strides(1) / static_cast<ssize_t>(sizeof(Scalar));
if ((PYBIND11_SILENCE_MSVC_C4127(fixed_rows) && np_rows != rows)
|| (PYBIND11_SILENCE_MSVC_C4127(fixed_cols) && np_cols != cols)) {
return false;
}
return {np_rows, np_cols, np_rstride, np_cstride};
}
// Otherwise we're storing an n-vector. Only one of the strides will be used, but
// whichever is used, we want the (single) numpy stride value.
const EigenIndex n = a.shape(0),
stride = a.strides(0) / static_cast<ssize_t>(sizeof(Scalar));
if (vector) { // Eigen type is a compile-time vector
if (PYBIND11_SILENCE_MSVC_C4127(fixed) && size != n) {
return false; // Vector size mismatch
}
return {rows == 1 ? 1 : n, cols == 1 ? 1 : n, stride};
}
if (fixed) {
// The type has a fixed size, but is not a vector: abort
return false;
}
if (fixed_cols) {
// Since this isn't a vector, cols must be != 1. We allow this only if it exactly
// equals the number of elements (rows is Dynamic, and so 1 row is allowed).
if (cols != n) {
return false;
}
return {1, n, stride};
} // Otherwise it's either fully dynamic, or column dynamic; both become a column vector
if (PYBIND11_SILENCE_MSVC_C4127(fixed_rows) && rows != n) {
return false;
}
return {n, 1, stride};
}
static constexpr bool show_writeable
= is_eigen_dense_map<Type>::value && is_eigen_mutable_map<Type>::value;
static constexpr bool show_order = is_eigen_dense_map<Type>::value;
static constexpr bool show_c_contiguous = show_order && requires_row_major;
static constexpr bool show_f_contiguous
= !show_c_contiguous && show_order && requires_col_major;
static constexpr auto descriptor
= 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("]")
+
// 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 '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,
// otherwise it'll make a copy. writeable lets you turn off the writeable flag for the array.
template <typename props>
handle
eigen_array_cast(typename props::Type const &src, handle base = handle(), bool writeable = true) {
constexpr ssize_t elem_size = sizeof(typename props::Scalar);
array a;
if (props::vector) {
a = array({src.size()}, {elem_size * src.innerStride()}, src.data(), base);
} else {
a = array({src.rows(), src.cols()},
{elem_size * src.rowStride(), elem_size * src.colStride()},
src.data(),
base);
}
if (!writeable) {
array_proxy(a.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_;
}
return a.release();
}
// Takes an lvalue ref to some Eigen type and a (python) base object, creating a numpy array that
// reference the Eigen object's data with `base` as the python-registered base class (if omitted,
// the base will be set to None, and lifetime management is up to the caller). The numpy array is
// non-writeable if the given type is const.
template <typename props, typename Type>
handle eigen_ref_array(Type &src, handle parent = none()) {
// none here is to get past array's should-we-copy detection, which currently always
// copies when there is no base. Setting the base to None should be harmless.
return eigen_array_cast<props>(src, parent, !std::is_const<Type>::value);
}
// Takes a pointer to some dense, plain Eigen type, builds a capsule around it, then returns a
// numpy array that references the encapsulated data with a python-side reference to the capsule to
// tie its destruction to that of any dependent python objects. Const-ness is determined by
// whether or not the Type of the pointer given is const.
template <typename props, typename Type, typename = enable_if_t<is_eigen_dense_plain<Type>::value>>
handle eigen_encapsulate(Type *src) {
capsule base(src, [](void *o) { delete static_cast<Type *>(o); });
return eigen_ref_array<props>(*src, base);
}
// Type caster for regular, dense matrix types (e.g. MatrixXd), but not maps/refs/etc. of dense
// types.
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_dense_plain<Type>::value>> {
using Scalar = typename Type::Scalar;
using props = EigenProps<Type>;
bool load(handle src, bool convert) {
// If we're in no-convert mode, only load if given an array of the correct type
if (!convert && !isinstance<array_t<Scalar>>(src)) {
return false;
}
// Coerce into an array, but don't do type conversion yet; the copy below handles it.
auto buf = array::ensure(src);
if (!buf) {
return false;
}
auto dims = buf.ndim();
if (dims < 1 || dims > 2) {
return false;
}
auto fits = props::conformable(buf);
if (!fits) {
return false;
}
// Allocate the new type, then build a numpy reference into it
value = Type(fits.rows, fits.cols);
auto ref = reinterpret_steal<array>(eigen_ref_array<props>(value));
if (dims == 1) {
ref = ref.squeeze();
} else if (ref.ndim() == 1) {
buf = buf.squeeze();
}
int result = detail::npy_api::get().PyArray_CopyInto_(ref.ptr(), buf.ptr());
if (result < 0) { // Copy failed!
PyErr_Clear();
return false;
}
return true;
}
private:
// Cast implementation
template <typename CType>
static handle cast_impl(CType *src, return_value_policy policy, handle parent) {
switch (policy) {
case return_value_policy::take_ownership:
case return_value_policy::automatic:
return eigen_encapsulate<props>(src);
case return_value_policy::move:
return eigen_encapsulate<props>(new CType(std::move(*src)));
case return_value_policy::copy:
return eigen_array_cast<props>(*src);
case return_value_policy::reference:
case return_value_policy::automatic_reference:
return eigen_ref_array<props>(*src);
case return_value_policy::reference_internal:
return eigen_ref_array<props>(*src, parent);
default:
throw cast_error("unhandled return_value_policy: should not happen!");
};
}
public:
// Normal returned non-reference, non-const value:
static handle cast(Type &&src, return_value_policy /* policy */, handle parent) {
return cast_impl(&src, return_value_policy::move, parent);
}
// If you return a non-reference const, we mark the numpy array readonly:
static handle cast(const Type &&src, return_value_policy /* policy */, handle parent) {
return cast_impl(&src, return_value_policy::move, parent);
}
// lvalue reference return; default (automatic) becomes copy
static handle cast(Type &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast_impl(&src, policy, parent);
}
// const lvalue reference return; default (automatic) becomes copy
static handle cast(const Type &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast(&src, policy, parent);
}
// non-const pointer return
static handle cast(Type *src, return_value_policy policy, handle parent) {
return cast_impl(src, policy, parent);
}
// const pointer return
static handle cast(const Type *src, return_value_policy policy, handle parent) {
return cast_impl(src, policy, parent);
}
static constexpr auto name = props::descriptor;
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type *() { return &value; }
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type &() { return value; }
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type &&() && { return std::move(value); }
template <typename T>
using cast_op_type = movable_cast_op_type<T>;
private:
Type value;
};
// Base class for casting reference/map/block/etc. objects back to python.
template <typename MapType>
struct eigen_map_caster {
private:
using props = EigenProps<MapType>;
public:
// Directly referencing a ref/map's data is a bit dangerous (whatever the map/ref points to has
// to stay around), but we'll allow it under the assumption that you know what you're doing
// (and have an appropriate keep_alive in place). We return a numpy array pointing directly at
// the ref's data (The numpy array ends up read-only if the ref was to a const matrix type.)
// Note that this means you need to ensure you don't destroy the object in some other way (e.g.
// with an appropriate keep_alive, or with a reference to a statically allocated matrix).
static handle cast(const MapType &src, return_value_policy policy, handle parent) {
switch (policy) {
case return_value_policy::copy:
return eigen_array_cast<props>(src);
case return_value_policy::reference_internal:
return eigen_array_cast<props>(src, parent, is_eigen_mutable_map<MapType>::value);
case return_value_policy::reference:
case return_value_policy::automatic:
case return_value_policy::automatic_reference:
return eigen_array_cast<props>(src, none(), is_eigen_mutable_map<MapType>::value);
default:
// move, take_ownership don't make any sense for a ref/map:
pybind11_fail("Invalid return_value_policy for Eigen Map/Ref/Block type");
}
}
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
// you end up here if you try anyway.
bool load(handle, bool) = delete;
operator MapType() = delete;
template <typename>
using cast_op_type = MapType;
};
// We can return any map-like object (but can only load Refs, specialized next):
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_dense_map<Type>::value>> : eigen_map_caster<Type> {};
// Loader for Ref<...> arguments. See the documentation for info on how to make this work without
// copying (it requires some extra effort in many cases).
template <typename PlainObjectType, typename StrideType>
struct type_caster<
Eigen::Ref<PlainObjectType, 0, StrideType>,
enable_if_t<is_eigen_dense_map<Eigen::Ref<PlainObjectType, 0, StrideType>>::value>>
: public eigen_map_caster<Eigen::Ref<PlainObjectType, 0, StrideType>> {
private:
using Type = Eigen::Ref<PlainObjectType, 0, StrideType>;
using props = EigenProps<Type>;
using Scalar = typename props::Scalar;
using MapType = Eigen::Map<PlainObjectType, 0, StrideType>;
using Array
= array_t<Scalar,
array::forcecast
| ((props::row_major ? props::inner_stride : props::outer_stride) == 1
? array::c_style
: (props::row_major ? props::outer_stride : props::inner_stride) == 1
? array::f_style
: 0)>;
static constexpr bool need_writeable = is_eigen_mutable_map<Type>::value;
// Delay construction (these have no default constructor)
std::unique_ptr<MapType> map;
std::unique_ptr<Type> ref;
// Our array. When possible, this is just a numpy array pointing to the source data, but
// sometimes we can't avoid copying (e.g. input is not a numpy array at all, has an
// incompatible layout, or is an array of a type that needs to be converted). Using a numpy
// temporary (rather than an Eigen temporary) saves an extra copy when we need both type
// conversion and storage order conversion. (Note that we refuse to use this temporary copy
// when loading an argument for a Ref<M> with M non-const, i.e. a read-write reference).
Array copy_or_ref;
public:
bool load(handle src, bool convert) {
// First check whether what we have is already an array of the right type. If not, we
// can't avoid a copy (because the copy is also going to do type conversion).
bool need_copy = !isinstance<Array>(src);
EigenConformable<props::row_major> fits;
if (!need_copy) {
// We don't need a converting copy, but we also need to check whether the strides are
// compatible with the Ref's stride requirements
auto aref = reinterpret_borrow<Array>(src);
if (aref && (!need_writeable || aref.writeable())) {
fits = props::conformable(aref);
if (!fits) {
return false; // Incompatible dimensions
}
if (!fits.template stride_compatible<props>()) {
need_copy = true;
} else {
copy_or_ref = std::move(aref);
}
} else {
need_copy = true;
}
}
if (need_copy) {
// We need to copy: If we need a mutable reference, or we're not supposed to convert
// (either because we're in the no-convert overload pass, or because we're explicitly
// instructed not to copy (via `py::arg().noconvert()`) we have to fail loading.
if (!convert || need_writeable) {
return false;
}
Array copy = Array::ensure(src);
if (!copy) {
return false;
}
fits = props::conformable(copy);
if (!fits || !fits.template stride_compatible<props>()) {
return false;
}
copy_or_ref = std::move(copy);
loader_life_support::add_patient(copy_or_ref);
}
ref.reset();
map.reset(new MapType(data(copy_or_ref),
fits.rows,
fits.cols,
make_stride(fits.stride.outer(), fits.stride.inner())));
ref.reset(new Type(*map));
return true;
}
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type *() { return ref.get(); }
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type &() { return *ref; }
template <typename _T>
using cast_op_type = pybind11::detail::cast_op_type<_T>;
private:
template <typename T = Type, enable_if_t<is_eigen_mutable_map<T>::value, int> = 0>
Scalar *data(Array &a) {
return a.mutable_data();
}
template <typename T = Type, enable_if_t<!is_eigen_mutable_map<T>::value, int> = 0>
const Scalar *data(Array &a) {
return a.data();
}
// Attempt to figure out a constructor of `Stride` that will work.
// If both strides are fixed, use a default constructor:
template <typename S>
using stride_ctor_default = bool_constant<S::InnerStrideAtCompileTime != Eigen::Dynamic
&& S::OuterStrideAtCompileTime != Eigen::Dynamic
&& std::is_default_constructible<S>::value>;
// Otherwise, if there is a two-index constructor, assume it is (outer,inner) like
// Eigen::Stride, and use it:
template <typename S>
using stride_ctor_dual
= bool_constant<!stride_ctor_default<S>::value
&& std::is_constructible<S, EigenIndex, EigenIndex>::value>;
// Otherwise, if there is a one-index constructor, and just one of the strides is dynamic, use
// it (passing whichever stride is dynamic).
template <typename S>
using stride_ctor_outer
= bool_constant<!any_of<stride_ctor_default<S>, stride_ctor_dual<S>>::value
&& S::OuterStrideAtCompileTime == Eigen::Dynamic
&& S::InnerStrideAtCompileTime != Eigen::Dynamic
&& std::is_constructible<S, EigenIndex>::value>;
template <typename S>
using stride_ctor_inner
= bool_constant<!any_of<stride_ctor_default<S>, stride_ctor_dual<S>>::value
&& S::InnerStrideAtCompileTime == Eigen::Dynamic
&& S::OuterStrideAtCompileTime != Eigen::Dynamic
&& std::is_constructible<S, EigenIndex>::value>;
template <typename S = StrideType, enable_if_t<stride_ctor_default<S>::value, int> = 0>
static S make_stride(EigenIndex, EigenIndex) {
return S();
}
template <typename S = StrideType, enable_if_t<stride_ctor_dual<S>::value, int> = 0>
static S make_stride(EigenIndex outer, EigenIndex inner) {
return S(outer, inner);
}
template <typename S = StrideType, enable_if_t<stride_ctor_outer<S>::value, int> = 0>
static S make_stride(EigenIndex outer, EigenIndex) {
return S(outer);
}
template <typename S = StrideType, enable_if_t<stride_ctor_inner<S>::value, int> = 0>
static S make_stride(EigenIndex, EigenIndex inner) {
return S(inner);
}
};
// type_caster for special matrix types (e.g. DiagonalMatrix), which are EigenBase, but not
// EigenDense (i.e. they don't have a data(), at least not with the usual matrix layout).
// load() is not supported, but we can cast them into the python domain by first copying to a
// regular Eigen::Matrix, then casting that.
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_other<Type>::value>> {
protected:
using Matrix
= Eigen::Matrix<typename Type::Scalar, Type::RowsAtCompileTime, Type::ColsAtCompileTime>;
using props = EigenProps<Matrix>;
public:
static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) {
handle h = eigen_encapsulate<props>(new Matrix(src));
return h;
}
static handle cast(const Type *src, return_value_policy policy, handle parent) {
return cast(*src, policy, parent);
}
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
// you end up here if you try anyway.
bool load(handle, bool) = delete;
operator Type() = delete;
template <typename>
using cast_op_type = Type;
};
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_sparse<Type>::value>> {
using Scalar = typename Type::Scalar;
using StorageIndex = remove_reference_t<decltype(*std::declval<Type>().outerIndexPtr())>;
using Index = typename Type::Index;
static constexpr bool rowMajor = Type::IsRowMajor;
bool load(handle src, bool) {
if (!src) {
return false;
}
auto obj = reinterpret_borrow<object>(src);
object sparse_module = module_::import("scipy.sparse");
object matrix_type = sparse_module.attr(rowMajor ? "csr_matrix" : "csc_matrix");
if (!type::handle_of(obj).is(matrix_type)) {
try {
obj = matrix_type(obj);
} catch (const error_already_set &) {
return false;
}
}
auto values = array_t<Scalar>((object) obj.attr("data"));
auto innerIndices = array_t<StorageIndex>((object) obj.attr("indices"));
auto outerIndices = array_t<StorageIndex>((object) obj.attr("indptr"));
auto shape = pybind11::tuple((pybind11::object) obj.attr("shape"));
auto nnz = obj.attr("nnz").cast<Index>();
if (!values || !innerIndices || !outerIndices) {
return false;
}
value = EigenMapSparseMatrix<Scalar,
Type::Flags &(Eigen::RowMajor | Eigen::ColMajor),
StorageIndex>(shape[0].cast<Index>(),
shape[1].cast<Index>(),
std::move(nnz),
outerIndices.mutable_data(),
innerIndices.mutable_data(),
values.mutable_data());
return true;
}
static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) {
const_cast<Type &>(src).makeCompressed();
object matrix_type
= module_::import("scipy.sparse").attr(rowMajor ? "csr_matrix" : "csc_matrix");
array data(src.nonZeros(), src.valuePtr());
array outerIndices((rowMajor ? src.rows() : src.cols()) + 1, src.outerIndexPtr());
array innerIndices(src.nonZeros(), src.innerIndexPtr());
return matrix_type(pybind11::make_tuple(
std::move(data), std::move(innerIndices), std::move(outerIndices)),
pybind11::make_tuple(src.rows(), src.cols()))
.release();
}
PYBIND11_TYPE_CASTER(Type,
const_name<(Type::IsRowMajor) != 0>("scipy.sparse.csr_matrix[",
"scipy.sparse.csc_matrix[")
+ npy_format_descriptor<Scalar>::name + const_name("]"));
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@ -0,0 +1,9 @@
// Copyright (c) 2023 The pybind Community.
#pragma once
// Common message for `static_assert()`s, which are useful to easily
// preempt much less obvious errors.
#define PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED \
"Pointer types (in particular `PyObject *`) are not supported as scalar types for Eigen " \
"types."

View File

@ -0,0 +1,714 @@
/*
pybind11/eigen/matrix.h: Transparent conversion for dense and sparse Eigen matrices
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
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 "../numpy.h"
#include "common.h"
/* HINT: To suppress warnings originating from the Eigen headers, use -isystem.
See also:
https://stackoverflow.com/questions/2579576/i-dir-vs-isystem-dir
https://stackoverflow.com/questions/1741816/isystem-for-ms-visual-studio-c-compiler
*/
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_MSVC(5054) // https://github.com/pybind/pybind11/pull/3741
// C5054: operator '&': deprecated between enumerations of different types
#if defined(__MINGW32__)
PYBIND11_WARNING_DISABLE_GCC("-Wmaybe-uninitialized")
#endif
#include <Eigen/Core>
#include <Eigen/SparseCore>
PYBIND11_WARNING_POP
// Eigen prior to 3.2.7 doesn't have proper move constructors--but worse, some classes get implicit
// move constructors that break things. We could detect this an explicitly copy, but an extra copy
// of matrices seems highly undesirable.
static_assert(EIGEN_VERSION_AT_LEAST(3, 2, 7),
"Eigen matrix support in pybind11 requires Eigen >= 3.2.7");
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_WARNING_DISABLE_MSVC(4127)
// Provide a convenience alias for easier pass-by-ref usage with fully dynamic strides:
using EigenDStride = Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>;
template <typename MatrixType>
using EigenDRef = Eigen::Ref<MatrixType, 0, EigenDStride>;
template <typename MatrixType>
using EigenDMap = Eigen::Map<MatrixType, 0, EigenDStride>;
PYBIND11_NAMESPACE_BEGIN(detail)
#if EIGEN_VERSION_AT_LEAST(3, 3, 0)
using EigenIndex = Eigen::Index;
template <typename Scalar, int Flags, typename StorageIndex>
using EigenMapSparseMatrix = Eigen::Map<Eigen::SparseMatrix<Scalar, Flags, StorageIndex>>;
#else
using EigenIndex = EIGEN_DEFAULT_DENSE_INDEX_TYPE;
template <typename Scalar, int Flags, typename StorageIndex>
using EigenMapSparseMatrix = Eigen::MappedSparseMatrix<Scalar, Flags, StorageIndex>;
#endif
// Matches Eigen::Map, Eigen::Ref, blocks, etc:
template <typename T>
using is_eigen_dense_map = all_of<is_template_base_of<Eigen::DenseBase, T>,
std::is_base_of<Eigen::MapBase<T, Eigen::ReadOnlyAccessors>, T>>;
template <typename T>
using is_eigen_mutable_map = std::is_base_of<Eigen::MapBase<T, Eigen::WriteAccessors>, T>;
template <typename T>
using is_eigen_dense_plain
= all_of<negation<is_eigen_dense_map<T>>, is_template_base_of<Eigen::PlainObjectBase, T>>;
template <typename T>
using is_eigen_sparse = is_template_base_of<Eigen::SparseMatrixBase, T>;
// Test for objects inheriting from EigenBase<Derived> that aren't captured by the above. This
// basically covers anything that can be assigned to a dense matrix but that don't have a typical
// matrix data layout that can be copied from their .data(). For example, DiagonalMatrix and
// SelfAdjointView fall into this category.
template <typename T>
using is_eigen_other
= all_of<is_template_base_of<Eigen::EigenBase, T>,
negation<any_of<is_eigen_dense_map<T>, is_eigen_dense_plain<T>, is_eigen_sparse<T>>>>;
// Captures numpy/eigen conformability status (returned by EigenProps::conformable()):
template <bool EigenRowMajor>
struct EigenConformable {
bool conformable = false;
EigenIndex rows = 0, cols = 0;
EigenDStride stride{0, 0}; // Only valid if negativestrides is false!
bool negativestrides = false; // If true, do not use stride!
// NOLINTNEXTLINE(google-explicit-constructor)
EigenConformable(bool fits = false) : conformable{fits} {}
// Matrix type:
EigenConformable(EigenIndex r, EigenIndex c, EigenIndex rstride, EigenIndex cstride)
: conformable{true}, rows{r}, cols{c},
// TODO: when Eigen bug #747 is fixed, remove the tests for non-negativity.
// http://eigen.tuxfamily.org/bz/show_bug.cgi?id=747
stride{EigenRowMajor ? (rstride > 0 ? rstride : 0)
: (cstride > 0 ? cstride : 0) /* outer stride */,
EigenRowMajor ? (cstride > 0 ? cstride : 0)
: (rstride > 0 ? rstride : 0) /* inner stride */},
negativestrides{rstride < 0 || cstride < 0} {}
// Vector type:
EigenConformable(EigenIndex r, EigenIndex c, EigenIndex stride)
: EigenConformable(r, c, r == 1 ? c * stride : stride, c == 1 ? r : r * stride) {}
template <typename props>
bool stride_compatible() const {
// To have compatible strides, we need (on both dimensions) one of fully dynamic strides,
// matching strides, or a dimension size of 1 (in which case the stride value is
// irrelevant). Alternatively, if any dimension size is 0, the strides are not relevant
// (and numpy ≥ 1.23 sets the strides to 0 in that case, so we need to check explicitly).
if (negativestrides) {
return false;
}
if (rows == 0 || cols == 0) {
return true;
}
return (props::inner_stride == Eigen::Dynamic || props::inner_stride == stride.inner()
|| (EigenRowMajor ? cols : rows) == 1)
&& (props::outer_stride == Eigen::Dynamic || props::outer_stride == stride.outer()
|| (EigenRowMajor ? rows : cols) == 1);
}
// NOLINTNEXTLINE(google-explicit-constructor)
operator bool() const { return conformable; }
};
template <typename Type>
struct eigen_extract_stride {
using type = Type;
};
template <typename PlainObjectType, int MapOptions, typename StrideType>
struct eigen_extract_stride<Eigen::Map<PlainObjectType, MapOptions, StrideType>> {
using type = StrideType;
};
template <typename PlainObjectType, int Options, typename StrideType>
struct eigen_extract_stride<Eigen::Ref<PlainObjectType, Options, StrideType>> {
using type = StrideType;
};
// Helper struct for extracting information from an Eigen type
template <typename Type_>
struct EigenProps {
using Type = Type_;
using Scalar = typename Type::Scalar;
using StrideType = typename eigen_extract_stride<Type>::type;
static constexpr EigenIndex rows = Type::RowsAtCompileTime, cols = Type::ColsAtCompileTime,
size = Type::SizeAtCompileTime;
static constexpr bool row_major = Type::IsRowMajor,
vector
= Type::IsVectorAtCompileTime, // At least one dimension has fixed size 1
fixed_rows = rows != Eigen::Dynamic, fixed_cols = cols != Eigen::Dynamic,
fixed = size != Eigen::Dynamic, // Fully-fixed size
dynamic = !fixed_rows && !fixed_cols; // Fully-dynamic size
template <EigenIndex i, EigenIndex ifzero>
using if_zero = std::integral_constant<EigenIndex, i == 0 ? ifzero : i>;
static constexpr EigenIndex inner_stride
= if_zero<StrideType::InnerStrideAtCompileTime, 1>::value,
outer_stride = if_zero < StrideType::OuterStrideAtCompileTime,
vector ? size
: row_major ? cols
: rows > ::value;
static constexpr bool dynamic_stride
= inner_stride == Eigen::Dynamic && outer_stride == Eigen::Dynamic;
static constexpr bool requires_row_major
= !dynamic_stride && !vector && (row_major ? inner_stride : outer_stride) == 1;
static constexpr bool requires_col_major
= !dynamic_stride && !vector && (row_major ? outer_stride : inner_stride) == 1;
// Takes an input array and determines whether we can make it fit into the Eigen type. If
// the array is a vector, we attempt to fit it into either an Eigen 1xN or Nx1 vector
// (preferring the latter if it will fit in either, i.e. for a fully dynamic matrix type).
static EigenConformable<row_major> conformable(const array &a) {
const auto dims = a.ndim();
if (dims < 1 || dims > 2) {
return false;
}
if (dims == 2) { // Matrix type: require exact match (or dynamic)
EigenIndex np_rows = a.shape(0), np_cols = a.shape(1),
np_rstride = a.strides(0) / static_cast<ssize_t>(sizeof(Scalar)),
np_cstride = a.strides(1) / static_cast<ssize_t>(sizeof(Scalar));
if ((fixed_rows && np_rows != rows) || (fixed_cols && np_cols != cols)) {
return false;
}
return {np_rows, np_cols, np_rstride, np_cstride};
}
// Otherwise we're storing an n-vector. Only one of the strides will be used, but
// whichever is used, we want the (single) numpy stride value.
const EigenIndex n = a.shape(0),
stride = a.strides(0) / static_cast<ssize_t>(sizeof(Scalar));
if (vector) { // Eigen type is a compile-time vector
if (fixed && size != n) {
return false; // Vector size mismatch
}
return {rows == 1 ? 1 : n, cols == 1 ? 1 : n, stride};
}
if (fixed) {
// The type has a fixed size, but is not a vector: abort
return false;
}
if (fixed_cols) {
// Since this isn't a vector, cols must be != 1. We allow this only if it exactly
// equals the number of elements (rows is Dynamic, and so 1 row is allowed).
if (cols != n) {
return false;
}
return {1, n, stride};
} // Otherwise it's either fully dynamic, or column dynamic; both become a column vector
if (fixed_rows && rows != n) {
return false;
}
return {n, 1, stride};
}
static constexpr bool show_writeable
= is_eigen_dense_map<Type>::value && is_eigen_mutable_map<Type>::value;
static constexpr bool show_order = is_eigen_dense_map<Type>::value;
static constexpr bool show_c_contiguous = show_order && requires_row_major;
static constexpr bool show_f_contiguous
= !show_c_contiguous && show_order && requires_col_major;
static constexpr auto descriptor
= 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("]")
+
// 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 '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,
// otherwise it'll make a copy. writeable lets you turn off the writeable flag for the array.
template <typename props>
handle
eigen_array_cast(typename props::Type const &src, handle base = handle(), bool writeable = true) {
constexpr ssize_t elem_size = sizeof(typename props::Scalar);
array a;
if (props::vector) {
a = array({src.size()}, {elem_size * src.innerStride()}, src.data(), base);
} else {
a = array({src.rows(), src.cols()},
{elem_size * src.rowStride(), elem_size * src.colStride()},
src.data(),
base);
}
if (!writeable) {
array_proxy(a.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_;
}
return a.release();
}
// Takes an lvalue ref to some Eigen type and a (python) base object, creating a numpy array that
// reference the Eigen object's data with `base` as the python-registered base class (if omitted,
// the base will be set to None, and lifetime management is up to the caller). The numpy array is
// non-writeable if the given type is const.
template <typename props, typename Type>
handle eigen_ref_array(Type &src, handle parent = none()) {
// none here is to get past array's should-we-copy detection, which currently always
// copies when there is no base. Setting the base to None should be harmless.
return eigen_array_cast<props>(src, parent, !std::is_const<Type>::value);
}
// Takes a pointer to some dense, plain Eigen type, builds a capsule around it, then returns a
// numpy array that references the encapsulated data with a python-side reference to the capsule to
// tie its destruction to that of any dependent python objects. Const-ness is determined by
// whether or not the Type of the pointer given is const.
template <typename props, typename Type, typename = enable_if_t<is_eigen_dense_plain<Type>::value>>
handle eigen_encapsulate(Type *src) {
capsule base(src, [](void *o) { delete static_cast<Type *>(o); });
return eigen_ref_array<props>(*src, base);
}
// Type caster for regular, dense matrix types (e.g. MatrixXd), but not maps/refs/etc. of dense
// types.
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_dense_plain<Type>::value>> {
using Scalar = typename Type::Scalar;
static_assert(!std::is_pointer<Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using props = EigenProps<Type>;
bool load(handle src, bool convert) {
// If we're in no-convert mode, only load if given an array of the correct type
if (!convert && !isinstance<array_t<Scalar>>(src)) {
return false;
}
// Coerce into an array, but don't do type conversion yet; the copy below handles it.
auto buf = array::ensure(src);
if (!buf) {
return false;
}
auto dims = buf.ndim();
if (dims < 1 || dims > 2) {
return false;
}
auto fits = props::conformable(buf);
if (!fits) {
return false;
}
// Allocate the new type, then build a numpy reference into it
value = Type(fits.rows, fits.cols);
auto ref = reinterpret_steal<array>(eigen_ref_array<props>(value));
if (dims == 1) {
ref = ref.squeeze();
} else if (ref.ndim() == 1) {
buf = buf.squeeze();
}
int result = detail::npy_api::get().PyArray_CopyInto_(ref.ptr(), buf.ptr());
if (result < 0) { // Copy failed!
PyErr_Clear();
return false;
}
return true;
}
private:
// Cast implementation
template <typename CType>
static handle cast_impl(CType *src, return_value_policy policy, handle parent) {
switch (policy) {
case return_value_policy::take_ownership:
case return_value_policy::automatic:
return eigen_encapsulate<props>(src);
case return_value_policy::move:
return eigen_encapsulate<props>(new CType(std::move(*src)));
case return_value_policy::copy:
return eigen_array_cast<props>(*src);
case return_value_policy::reference:
case return_value_policy::automatic_reference:
return eigen_ref_array<props>(*src);
case return_value_policy::reference_internal:
return eigen_ref_array<props>(*src, parent);
default:
throw cast_error("unhandled return_value_policy: should not happen!");
};
}
public:
// Normal returned non-reference, non-const value:
static handle cast(Type &&src, return_value_policy /* policy */, handle parent) {
return cast_impl(&src, return_value_policy::move, parent);
}
// If you return a non-reference const, we mark the numpy array readonly:
static handle cast(const Type &&src, return_value_policy /* policy */, handle parent) {
return cast_impl(&src, return_value_policy::move, parent);
}
// lvalue reference return; default (automatic) becomes copy
static handle cast(Type &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast_impl(&src, policy, parent);
}
// const lvalue reference return; default (automatic) becomes copy
static handle cast(const Type &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast(&src, policy, parent);
}
// non-const pointer return
static handle cast(Type *src, return_value_policy policy, handle parent) {
return cast_impl(src, policy, parent);
}
// const pointer return
static handle cast(const Type *src, return_value_policy policy, handle parent) {
return cast_impl(src, policy, parent);
}
static constexpr auto name = props::descriptor;
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type *() { return &value; }
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type &() { return value; }
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type &&() && { return std::move(value); }
template <typename T>
using cast_op_type = movable_cast_op_type<T>;
private:
Type value;
};
// Base class for casting reference/map/block/etc. objects back to python.
template <typename MapType>
struct eigen_map_caster {
static_assert(!std::is_pointer<typename MapType::Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
private:
using props = EigenProps<MapType>;
public:
// Directly referencing a ref/map's data is a bit dangerous (whatever the map/ref points to has
// to stay around), but we'll allow it under the assumption that you know what you're doing
// (and have an appropriate keep_alive in place). We return a numpy array pointing directly at
// the ref's data (The numpy array ends up read-only if the ref was to a const matrix type.)
// Note that this means you need to ensure you don't destroy the object in some other way (e.g.
// with an appropriate keep_alive, or with a reference to a statically allocated matrix).
static handle cast(const MapType &src, return_value_policy policy, handle parent) {
switch (policy) {
case return_value_policy::copy:
return eigen_array_cast<props>(src);
case return_value_policy::reference_internal:
return eigen_array_cast<props>(src, parent, is_eigen_mutable_map<MapType>::value);
case return_value_policy::reference:
case return_value_policy::automatic:
case return_value_policy::automatic_reference:
return eigen_array_cast<props>(src, none(), is_eigen_mutable_map<MapType>::value);
default:
// move, take_ownership don't make any sense for a ref/map:
pybind11_fail("Invalid return_value_policy for Eigen Map/Ref/Block type");
}
}
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
// you end up here if you try anyway.
bool load(handle, bool) = delete;
operator MapType() = delete;
template <typename>
using cast_op_type = MapType;
};
// We can return any map-like object (but can only load Refs, specialized next):
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_dense_map<Type>::value>> : eigen_map_caster<Type> {};
// Loader for Ref<...> arguments. See the documentation for info on how to make this work without
// copying (it requires some extra effort in many cases).
template <typename PlainObjectType, typename StrideType>
struct type_caster<
Eigen::Ref<PlainObjectType, 0, StrideType>,
enable_if_t<is_eigen_dense_map<Eigen::Ref<PlainObjectType, 0, StrideType>>::value>>
: public eigen_map_caster<Eigen::Ref<PlainObjectType, 0, StrideType>> {
private:
using Type = Eigen::Ref<PlainObjectType, 0, StrideType>;
using props = EigenProps<Type>;
using Scalar = typename props::Scalar;
static_assert(!std::is_pointer<Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using MapType = Eigen::Map<PlainObjectType, 0, StrideType>;
using Array
= array_t<Scalar,
array::forcecast
| ((props::row_major ? props::inner_stride : props::outer_stride) == 1
? array::c_style
: (props::row_major ? props::outer_stride : props::inner_stride) == 1
? array::f_style
: 0)>;
static constexpr bool need_writeable = is_eigen_mutable_map<Type>::value;
// Delay construction (these have no default constructor)
std::unique_ptr<MapType> map;
std::unique_ptr<Type> ref;
// Our array. When possible, this is just a numpy array pointing to the source data, but
// sometimes we can't avoid copying (e.g. input is not a numpy array at all, has an
// incompatible layout, or is an array of a type that needs to be converted). Using a numpy
// temporary (rather than an Eigen temporary) saves an extra copy when we need both type
// conversion and storage order conversion. (Note that we refuse to use this temporary copy
// when loading an argument for a Ref<M> with M non-const, i.e. a read-write reference).
Array copy_or_ref;
public:
bool load(handle src, bool convert) {
// First check whether what we have is already an array of the right type. If not, we
// can't avoid a copy (because the copy is also going to do type conversion).
bool need_copy = !isinstance<Array>(src);
EigenConformable<props::row_major> fits;
if (!need_copy) {
// We don't need a converting copy, but we also need to check whether the strides are
// compatible with the Ref's stride requirements
auto aref = reinterpret_borrow<Array>(src);
if (aref && (!need_writeable || aref.writeable())) {
fits = props::conformable(aref);
if (!fits) {
return false; // Incompatible dimensions
}
if (!fits.template stride_compatible<props>()) {
need_copy = true;
} else {
copy_or_ref = std::move(aref);
}
} else {
need_copy = true;
}
}
if (need_copy) {
// We need to copy: If we need a mutable reference, or we're not supposed to convert
// (either because we're in the no-convert overload pass, or because we're explicitly
// instructed not to copy (via `py::arg().noconvert()`) we have to fail loading.
if (!convert || need_writeable) {
return false;
}
Array copy = Array::ensure(src);
if (!copy) {
return false;
}
fits = props::conformable(copy);
if (!fits || !fits.template stride_compatible<props>()) {
return false;
}
copy_or_ref = std::move(copy);
loader_life_support::add_patient(copy_or_ref);
}
ref.reset();
map.reset(new MapType(data(copy_or_ref),
fits.rows,
fits.cols,
make_stride(fits.stride.outer(), fits.stride.inner())));
ref.reset(new Type(*map));
return true;
}
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type *() { return ref.get(); }
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type &() { return *ref; }
template <typename _T>
using cast_op_type = pybind11::detail::cast_op_type<_T>;
private:
template <typename T = Type, enable_if_t<is_eigen_mutable_map<T>::value, int> = 0>
Scalar *data(Array &a) {
return a.mutable_data();
}
template <typename T = Type, enable_if_t<!is_eigen_mutable_map<T>::value, int> = 0>
const Scalar *data(Array &a) {
return a.data();
}
// Attempt to figure out a constructor of `Stride` that will work.
// If both strides are fixed, use a default constructor:
template <typename S>
using stride_ctor_default = bool_constant<S::InnerStrideAtCompileTime != Eigen::Dynamic
&& S::OuterStrideAtCompileTime != Eigen::Dynamic
&& std::is_default_constructible<S>::value>;
// Otherwise, if there is a two-index constructor, assume it is (outer,inner) like
// Eigen::Stride, and use it:
template <typename S>
using stride_ctor_dual
= bool_constant<!stride_ctor_default<S>::value
&& std::is_constructible<S, EigenIndex, EigenIndex>::value>;
// Otherwise, if there is a one-index constructor, and just one of the strides is dynamic, use
// it (passing whichever stride is dynamic).
template <typename S>
using stride_ctor_outer
= bool_constant<!any_of<stride_ctor_default<S>, stride_ctor_dual<S>>::value
&& S::OuterStrideAtCompileTime == Eigen::Dynamic
&& S::InnerStrideAtCompileTime != Eigen::Dynamic
&& std::is_constructible<S, EigenIndex>::value>;
template <typename S>
using stride_ctor_inner
= bool_constant<!any_of<stride_ctor_default<S>, stride_ctor_dual<S>>::value
&& S::InnerStrideAtCompileTime == Eigen::Dynamic
&& S::OuterStrideAtCompileTime != Eigen::Dynamic
&& std::is_constructible<S, EigenIndex>::value>;
template <typename S = StrideType, enable_if_t<stride_ctor_default<S>::value, int> = 0>
static S make_stride(EigenIndex, EigenIndex) {
return S();
}
template <typename S = StrideType, enable_if_t<stride_ctor_dual<S>::value, int> = 0>
static S make_stride(EigenIndex outer, EigenIndex inner) {
return S(outer, inner);
}
template <typename S = StrideType, enable_if_t<stride_ctor_outer<S>::value, int> = 0>
static S make_stride(EigenIndex outer, EigenIndex) {
return S(outer);
}
template <typename S = StrideType, enable_if_t<stride_ctor_inner<S>::value, int> = 0>
static S make_stride(EigenIndex, EigenIndex inner) {
return S(inner);
}
};
// type_caster for special matrix types (e.g. DiagonalMatrix), which are EigenBase, but not
// EigenDense (i.e. they don't have a data(), at least not with the usual matrix layout).
// load() is not supported, but we can cast them into the python domain by first copying to a
// regular Eigen::Matrix, then casting that.
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_other<Type>::value>> {
static_assert(!std::is_pointer<typename Type::Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
protected:
using Matrix
= Eigen::Matrix<typename Type::Scalar, Type::RowsAtCompileTime, Type::ColsAtCompileTime>;
using props = EigenProps<Matrix>;
public:
static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) {
handle h = eigen_encapsulate<props>(new Matrix(src));
return h;
}
static handle cast(const Type *src, return_value_policy policy, handle parent) {
return cast(*src, policy, parent);
}
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
// you end up here if you try anyway.
bool load(handle, bool) = delete;
operator Type() = delete;
template <typename>
using cast_op_type = Type;
};
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_sparse<Type>::value>> {
using Scalar = typename Type::Scalar;
static_assert(!std::is_pointer<Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using StorageIndex = remove_reference_t<decltype(*std::declval<Type>().outerIndexPtr())>;
using Index = typename Type::Index;
static constexpr bool rowMajor = Type::IsRowMajor;
bool load(handle src, bool) {
if (!src) {
return false;
}
auto obj = reinterpret_borrow<object>(src);
object sparse_module = module_::import("scipy.sparse");
object matrix_type = sparse_module.attr(rowMajor ? "csr_matrix" : "csc_matrix");
if (!type::handle_of(obj).is(matrix_type)) {
try {
obj = matrix_type(obj);
} catch (const error_already_set &) {
return false;
}
}
auto values = array_t<Scalar>((object) obj.attr("data"));
auto innerIndices = array_t<StorageIndex>((object) obj.attr("indices"));
auto outerIndices = array_t<StorageIndex>((object) obj.attr("indptr"));
auto shape = pybind11::tuple((pybind11::object) obj.attr("shape"));
auto nnz = obj.attr("nnz").cast<Index>();
if (!values || !innerIndices || !outerIndices) {
return false;
}
value = EigenMapSparseMatrix<Scalar,
Type::Flags &(Eigen::RowMajor | Eigen::ColMajor),
StorageIndex>(shape[0].cast<Index>(),
shape[1].cast<Index>(),
std::move(nnz),
outerIndices.mutable_data(),
innerIndices.mutable_data(),
values.mutable_data());
return true;
}
static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) {
const_cast<Type &>(src).makeCompressed();
object matrix_type
= module_::import("scipy.sparse").attr(rowMajor ? "csr_matrix" : "csc_matrix");
array data(src.nonZeros(), src.valuePtr());
array outerIndices((rowMajor ? src.rows() : src.cols()) + 1, src.outerIndexPtr());
array innerIndices(src.nonZeros(), src.innerIndexPtr());
return matrix_type(pybind11::make_tuple(
std::move(data), std::move(innerIndices), std::move(outerIndices)),
pybind11::make_tuple(src.rows(), src.cols()))
.release();
}
PYBIND11_TYPE_CASTER(Type,
const_name<(Type::IsRowMajor) != 0>("scipy.sparse.csr_matrix[",
"scipy.sparse.csc_matrix[")
+ npy_format_descriptor<Scalar>::name + const_name("]"));
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@ -0,0 +1,516 @@
/*
pybind11/eigen/tensor.h: Transparent conversion for Eigen tensors
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 "../numpy.h"
#include "common.h"
#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)
static_assert(__GNUC__ > 5, "Eigen Tensor support in pybind11 requires GCC > 5.0");
#endif
// Disable warnings for Eigen
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_MSVC(4554)
PYBIND11_WARNING_DISABLE_MSVC(4127)
#if defined(__MINGW32__)
PYBIND11_WARNING_DISABLE_GCC("-Wmaybe-uninitialized")
#endif
#include <unsupported/Eigen/CXX11/Tensor>
PYBIND11_WARNING_POP
static_assert(EIGEN_VERSION_AT_LEAST(3, 3, 0),
"Eigen Tensor support in pybind11 requires Eigen >= 3.3.0");
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_WARNING_DISABLE_MSVC(4127)
PYBIND11_NAMESPACE_BEGIN(detail)
inline bool is_tensor_aligned(const void *data) {
return (reinterpret_cast<std::size_t>(data) % EIGEN_DEFAULT_ALIGN_BYTES) == 0;
}
template <typename T>
constexpr int compute_array_flag_from_tensor() {
static_assert((static_cast<int>(T::Layout) == static_cast<int>(Eigen::RowMajor))
|| (static_cast<int>(T::Layout) == static_cast<int>(Eigen::ColMajor)),
"Layout must be row or column major");
return (static_cast<int>(T::Layout) == static_cast<int>(Eigen::RowMajor)) ? array::c_style
: array::f_style;
}
template <typename T>
struct eigen_tensor_helper {};
template <typename Scalar_, int NumIndices_, int Options_, typename IndexType>
struct eigen_tensor_helper<Eigen::Tensor<Scalar_, NumIndices_, Options_, IndexType>> {
using Type = Eigen::Tensor<Scalar_, NumIndices_, Options_, IndexType>;
using ValidType = void;
static Eigen::DSizes<typename Type::Index, Type::NumIndices> get_shape(const Type &f) {
return f.dimensions();
}
static constexpr bool
is_correct_shape(const Eigen::DSizes<typename Type::Index, Type::NumIndices> & /*shape*/) {
return true;
}
template <typename T>
struct helper {};
template <size_t... Is>
struct helper<index_sequence<Is...>> {
static constexpr auto value = concat(const_name(((void) Is, "?"))...);
};
static constexpr auto dimensions_descriptor
= helper<decltype(make_index_sequence<Type::NumIndices>())>::value;
template <typename... Args>
static Type *alloc(Args &&...args) {
return new Type(std::forward<Args>(args)...);
}
static void free(Type *tensor) { delete tensor; }
};
template <typename Scalar_, typename std::ptrdiff_t... Indices, int Options_, typename IndexType>
struct eigen_tensor_helper<
Eigen::TensorFixedSize<Scalar_, Eigen::Sizes<Indices...>, Options_, IndexType>> {
using Type = Eigen::TensorFixedSize<Scalar_, Eigen::Sizes<Indices...>, Options_, IndexType>;
using ValidType = void;
static constexpr Eigen::DSizes<typename Type::Index, Type::NumIndices>
get_shape(const Type & /*f*/) {
return get_shape();
}
static constexpr Eigen::DSizes<typename Type::Index, Type::NumIndices> get_shape() {
return Eigen::DSizes<typename Type::Index, Type::NumIndices>(Indices...);
}
static bool
is_correct_shape(const Eigen::DSizes<typename Type::Index, Type::NumIndices> &shape) {
return get_shape() == shape;
}
static constexpr auto dimensions_descriptor = concat(const_name<Indices>()...);
template <typename... Args>
static Type *alloc(Args &&...args) {
Eigen::aligned_allocator<Type> allocator;
return ::new (allocator.allocate(1)) Type(std::forward<Args>(args)...);
}
static void free(Type *tensor) {
Eigen::aligned_allocator<Type> allocator;
tensor->~Type();
allocator.deallocate(tensor, 1);
}
};
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");
static constexpr auto value
= 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
// function. Falling back to a simple loop works around this issue.
//
// We need to disable the type-limits warning for the inner loop when size = 0.
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_GCC("-Wtype-limits")
template <typename T, int size>
std::vector<T> convert_dsizes_to_vector(const Eigen::DSizes<T, size> &arr) {
std::vector<T> result(size);
for (size_t i = 0; i < size; i++) {
result[i] = arr[i];
}
return result;
}
template <typename T, int size>
Eigen::DSizes<T, size> get_shape_for_array(const array &arr) {
Eigen::DSizes<T, size> result;
const T *shape = arr.shape();
for (size_t i = 0; i < size; i++) {
result[i] = shape[i];
}
return result;
}
PYBIND11_WARNING_POP
template <typename Type>
struct type_caster<Type, typename eigen_tensor_helper<Type>::ValidType> {
static_assert(!std::is_pointer<typename Type::Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using Helper = eigen_tensor_helper<Type>;
static constexpr auto temp_name = get_tensor_descriptor<Type, false>::value;
PYBIND11_TYPE_CASTER(Type, temp_name);
bool load(handle src, bool convert) {
if (!convert) {
if (!isinstance<array>(src)) {
return false;
}
array temp = array::ensure(src);
if (!temp) {
return false;
}
if (!temp.dtype().is(dtype::of<typename Type::Scalar>())) {
return false;
}
}
array_t<typename Type::Scalar, compute_array_flag_from_tensor<Type>()> arr(
reinterpret_borrow<object>(src));
if (arr.ndim() != Type::NumIndices) {
return false;
}
auto shape = get_shape_for_array<typename Type::Index, Type::NumIndices>(arr);
if (!Helper::is_correct_shape(shape)) {
return false;
}
#if EIGEN_VERSION_AT_LEAST(3, 4, 0)
auto data_pointer = arr.data();
#else
// Handle Eigen bug
auto data_pointer = const_cast<typename Type::Scalar *>(arr.data());
#endif
if (is_tensor_aligned(arr.data())) {
value = Eigen::TensorMap<const Type, Eigen::Aligned>(data_pointer, shape);
} else {
value = Eigen::TensorMap<const Type>(data_pointer, shape);
}
return true;
}
static handle cast(Type &&src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::reference
|| policy == return_value_policy::reference_internal) {
pybind11_fail("Cannot use a reference return value policy for an rvalue");
}
return cast_impl(&src, return_value_policy::move, parent);
}
static handle cast(const Type &&src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::reference
|| policy == return_value_policy::reference_internal) {
pybind11_fail("Cannot use a reference return value policy for an rvalue");
}
return cast_impl(&src, return_value_policy::move, parent);
}
static handle cast(Type &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast_impl(&src, policy, parent);
}
static handle cast(const Type &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast(&src, policy, parent);
}
static handle cast(Type *src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic) {
policy = return_value_policy::take_ownership;
} else if (policy == return_value_policy::automatic_reference) {
policy = return_value_policy::reference;
}
return cast_impl(src, policy, parent);
}
static handle cast(const Type *src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic) {
policy = return_value_policy::take_ownership;
} else if (policy == return_value_policy::automatic_reference) {
policy = return_value_policy::reference;
}
return cast_impl(src, policy, parent);
}
template <typename C>
static handle cast_impl(C *src, return_value_policy policy, handle parent) {
object parent_object;
bool writeable = false;
switch (policy) {
case return_value_policy::move:
if (std::is_const<C>::value) {
pybind11_fail("Cannot move from a constant reference");
}
src = Helper::alloc(std::move(*src));
parent_object
= capsule(src, [](void *ptr) { Helper::free(reinterpret_cast<Type *>(ptr)); });
writeable = true;
break;
case return_value_policy::take_ownership:
if (std::is_const<C>::value) {
// This cast is ugly, and might be UB in some cases, but we don't have an
// alternative here as we must free that memory
Helper::free(const_cast<Type *>(src));
pybind11_fail("Cannot take ownership of a const reference");
}
parent_object
= capsule(src, [](void *ptr) { Helper::free(reinterpret_cast<Type *>(ptr)); });
writeable = true;
break;
case return_value_policy::copy:
writeable = true;
break;
case return_value_policy::reference:
parent_object = none();
writeable = !std::is_const<C>::value;
break;
case return_value_policy::reference_internal:
// Default should do the right thing
if (!parent) {
pybind11_fail("Cannot use reference internal when there is no parent");
}
parent_object = reinterpret_borrow<object>(parent);
writeable = !std::is_const<C>::value;
break;
default:
pybind11_fail("pybind11 bug in eigen.h, please file a bug report");
}
auto result = array_t<typename Type::Scalar, compute_array_flag_from_tensor<Type>()>(
convert_dsizes_to_vector(Helper::get_shape(*src)), src->data(), parent_object);
if (!writeable) {
array_proxy(result.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_;
}
return result.release();
}
};
template <typename StoragePointerType,
bool needs_writeable,
enable_if_t<!needs_writeable, bool> = true>
StoragePointerType get_array_data_for_type(array &arr) {
#if EIGEN_VERSION_AT_LEAST(3, 4, 0)
return reinterpret_cast<StoragePointerType>(arr.data());
#else
// Handle Eigen bug
return reinterpret_cast<StoragePointerType>(const_cast<void *>(arr.data()));
#endif
}
template <typename StoragePointerType,
bool needs_writeable,
enable_if_t<needs_writeable, bool> = true>
StoragePointerType get_array_data_for_type(array &arr) {
return reinterpret_cast<StoragePointerType>(arr.mutable_data());
}
template <typename T, typename = void>
struct get_storage_pointer_type;
template <typename MapType>
struct get_storage_pointer_type<MapType, void_t<typename MapType::StoragePointerType>> {
using SPT = typename MapType::StoragePointerType;
};
template <typename MapType>
struct get_storage_pointer_type<MapType, void_t<typename MapType::PointerArgType>> {
using SPT = typename MapType::PointerArgType;
};
template <typename Type, int Options>
struct type_caster<Eigen::TensorMap<Type, Options>,
typename eigen_tensor_helper<remove_cv_t<Type>>::ValidType> {
static_assert(!std::is_pointer<typename Type::Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using MapType = Eigen::TensorMap<Type, Options>;
using Helper = eigen_tensor_helper<remove_cv_t<Type>>;
bool load(handle src, bool /*convert*/) {
// Note that we have a lot more checks here as we want to make sure to avoid copies
if (!isinstance<array>(src)) {
return false;
}
auto arr = reinterpret_borrow<array>(src);
if ((arr.flags() & compute_array_flag_from_tensor<Type>()) == 0) {
return false;
}
if (!arr.dtype().is(dtype::of<typename Type::Scalar>())) {
return false;
}
if (arr.ndim() != Type::NumIndices) {
return false;
}
constexpr bool is_aligned = (Options & Eigen::Aligned) != 0;
if (is_aligned && !is_tensor_aligned(arr.data())) {
return false;
}
auto shape = get_shape_for_array<typename Type::Index, Type::NumIndices>(arr);
if (!Helper::is_correct_shape(shape)) {
return false;
}
if (needs_writeable && !arr.writeable()) {
return false;
}
auto result = get_array_data_for_type<typename get_storage_pointer_type<MapType>::SPT,
needs_writeable>(arr);
value.reset(new MapType(std::move(result), std::move(shape)));
return true;
}
static handle cast(MapType &&src, return_value_policy policy, handle parent) {
return cast_impl(&src, policy, parent);
}
static handle cast(const MapType &&src, return_value_policy policy, handle parent) {
return cast_impl(&src, policy, parent);
}
static handle cast(MapType &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast_impl(&src, policy, parent);
}
static handle cast(const MapType &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast(&src, policy, parent);
}
static handle cast(MapType *src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic) {
policy = return_value_policy::take_ownership;
} else if (policy == return_value_policy::automatic_reference) {
policy = return_value_policy::reference;
}
return cast_impl(src, policy, parent);
}
static handle cast(const MapType *src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic) {
policy = return_value_policy::take_ownership;
} else if (policy == return_value_policy::automatic_reference) {
policy = return_value_policy::reference;
}
return cast_impl(src, policy, parent);
}
template <typename C>
static handle cast_impl(C *src, return_value_policy policy, handle parent) {
object parent_object;
constexpr bool writeable = !std::is_const<C>::value;
switch (policy) {
case return_value_policy::reference:
parent_object = none();
break;
case return_value_policy::reference_internal:
// Default should do the right thing
if (!parent) {
pybind11_fail("Cannot use reference internal when there is no parent");
}
parent_object = reinterpret_borrow<object>(parent);
break;
case return_value_policy::take_ownership:
delete src;
// fallthrough
default:
// move, take_ownership don't make any sense for a ref/map:
pybind11_fail("Invalid return_value_policy for Eigen Map type, must be either "
"reference or reference_internal");
}
auto result = array_t<typename Type::Scalar, compute_array_flag_from_tensor<Type>()>(
convert_dsizes_to_vector(Helper::get_shape(*src)),
src->data(),
std::move(parent_object));
if (!writeable) {
array_proxy(result.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_;
}
return result.release();
}
#if EIGEN_VERSION_AT_LEAST(3, 4, 0)
static constexpr bool needs_writeable = !std::is_const<typename std::remove_pointer<
typename get_storage_pointer_type<MapType>::SPT>::type>::value;
#else
// Handle Eigen bug
static constexpr bool needs_writeable = !std::is_const<Type>::value;
#endif
protected:
// TODO: Move to std::optional once std::optional has more support
std::unique_ptr<MapType> value;
public:
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); }
template <typename T_>
using cast_op_type = ::pybind11::detail::movable_cast_op_type<T_>;
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@ -86,38 +86,26 @@ inline wchar_t *widen_chars(const char *safe_arg) {
return widened_arg; return widened_arg;
} }
PYBIND11_NAMESPACE_END(detail) inline void precheck_interpreter() {
/** \rst
Initialize the Python interpreter. No other pybind11 or CPython API functions can be
called before this is done; with the exception of `PYBIND11_EMBEDDED_MODULE`. The
optional `init_signal_handlers` parameter can be used to skip the registration of
signal handlers (see the `Python documentation`_ for details). Calling this function
again after the interpreter has already been initialized is a fatal error.
If initializing the Python interpreter fails, then the program is terminated. (This
is controlled by the CPython runtime and is an exception to pybind11's normal behavior
of throwing exceptions on errors.)
The remaining optional parameters, `argc`, `argv`, and `add_program_dir_to_path` are
used to populate ``sys.argv`` and ``sys.path``.
See the |PySys_SetArgvEx documentation|_ for details.
.. _Python documentation: https://docs.python.org/3/c-api/init.html#c.Py_InitializeEx
.. |PySys_SetArgvEx documentation| replace:: ``PySys_SetArgvEx`` documentation
.. _PySys_SetArgvEx documentation: https://docs.python.org/3/c-api/init.html#c.PySys_SetArgvEx
\endrst */
inline void initialize_interpreter(bool init_signal_handlers = true,
int argc = 0,
const char *const *argv = nullptr,
bool add_program_dir_to_path = true) {
if (Py_IsInitialized() != 0) { if (Py_IsInitialized() != 0) {
pybind11_fail("The interpreter is already running"); pybind11_fail("The interpreter is already running");
} }
}
#if PY_VERSION_HEX < 0x030B0000 #if !defined(PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX)
# define PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX (0x03080000)
#endif
#if PY_VERSION_HEX < PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX
inline void initialize_interpreter_pre_pyconfig(bool init_signal_handlers,
int argc,
const char *const *argv,
bool add_program_dir_to_path) {
detail::precheck_interpreter();
Py_InitializeEx(init_signal_handlers ? 1 : 0); Py_InitializeEx(init_signal_handlers ? 1 : 0);
# if defined(WITH_THREAD) && PY_VERSION_HEX < 0x03070000
PyEval_InitThreads();
# endif
// Before it was special-cased in python 3.8, passing an empty or null argv // 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. // caused a segfault, so we have to reimplement the special case ourselves.
@ -147,24 +135,30 @@ inline void initialize_interpreter(bool init_signal_handlers = true,
auto *pysys_argv = widened_argv.get(); auto *pysys_argv = widened_argv.get();
PySys_SetArgvEx(argc, pysys_argv, static_cast<int>(add_program_dir_to_path)); PySys_SetArgvEx(argc, pysys_argv, static_cast<int>(add_program_dir_to_path));
#else }
PyConfig config; #endif
PyConfig_InitIsolatedConfig(&config);
config.install_signal_handlers = init_signal_handlers ? 1 : 0;
PyStatus status = PyConfig_SetBytesArgv(&config, argc, const_cast<char *const *>(argv)); PYBIND11_NAMESPACE_END(detail)
if (PyStatus_Exception(status)) {
#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX
inline void initialize_interpreter(PyConfig *config,
int argc = 0,
const char *const *argv = nullptr,
bool add_program_dir_to_path = true) {
detail::precheck_interpreter();
PyStatus status = PyConfig_SetBytesArgv(config, argc, const_cast<char *const *>(argv));
if (PyStatus_Exception(status) != 0) {
// A failure here indicates a character-encoding failure or the python // A failure here indicates a character-encoding failure or the python
// interpreter out of memory. Give up. // interpreter out of memory. Give up.
PyConfig_Clear(&config); PyConfig_Clear(config);
throw std::runtime_error(PyStatus_IsError(status) ? status.err_msg throw std::runtime_error(PyStatus_IsError(status) != 0 ? status.err_msg
: "Failed to prepare CPython"); : "Failed to prepare CPython");
} }
status = Py_InitializeFromConfig(&config); status = Py_InitializeFromConfig(config);
PyConfig_Clear(&config); if (PyStatus_Exception(status) != 0) {
if (PyStatus_Exception(status)) { PyConfig_Clear(config);
throw std::runtime_error(PyStatus_IsError(status) ? status.err_msg throw std::runtime_error(PyStatus_IsError(status) != 0 ? status.err_msg
: "Failed to init CPython"); : "Failed to init CPython");
} }
if (add_program_dir_to_path) { if (add_program_dir_to_path) {
PyRun_SimpleString("import sys, os.path; " PyRun_SimpleString("import sys, os.path; "
@ -172,6 +166,44 @@ inline void initialize_interpreter(bool init_signal_handlers = true,
"os.path.abspath(os.path.dirname(sys.argv[0])) " "os.path.abspath(os.path.dirname(sys.argv[0])) "
"if sys.argv and os.path.exists(sys.argv[0]) else '')"); "if sys.argv and os.path.exists(sys.argv[0]) else '')");
} }
PyConfig_Clear(config);
}
#endif
/** \rst
Initialize the Python interpreter. No other pybind11 or CPython API functions can be
called before this is done; with the exception of `PYBIND11_EMBEDDED_MODULE`. The
optional `init_signal_handlers` parameter can be used to skip the registration of
signal handlers (see the `Python documentation`_ for details). Calling this function
again after the interpreter has already been initialized is a fatal error.
If initializing the Python interpreter fails, then the program is terminated. (This
is controlled by the CPython runtime and is an exception to pybind11's normal behavior
of throwing exceptions on errors.)
The remaining optional parameters, `argc`, `argv`, and `add_program_dir_to_path` are
used to populate ``sys.argv`` and ``sys.path``.
See the |PySys_SetArgvEx documentation|_ for details.
.. _Python documentation: https://docs.python.org/3/c-api/init.html#c.Py_InitializeEx
.. |PySys_SetArgvEx documentation| replace:: ``PySys_SetArgvEx`` documentation
.. _PySys_SetArgvEx documentation: https://docs.python.org/3/c-api/init.html#c.PySys_SetArgvEx
\endrst */
inline void initialize_interpreter(bool init_signal_handlers = true,
int argc = 0,
const char *const *argv = nullptr,
bool add_program_dir_to_path = true) {
#if PY_VERSION_HEX < PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX
detail::initialize_interpreter_pre_pyconfig(
init_signal_handlers, argc, argv, add_program_dir_to_path);
#else
PyConfig config;
PyConfig_InitPythonConfig(&config);
// See PR #4473 for background
config.parse_argv = 0;
config.install_signal_handlers = init_signal_handlers ? 1 : 0;
initialize_interpreter(&config, argc, argv, add_program_dir_to_path);
#endif #endif
} }
@ -211,16 +243,14 @@ inline void initialize_interpreter(bool init_signal_handlers = true,
\endrst */ \endrst */
inline void finalize_interpreter() { inline void finalize_interpreter() {
handle builtins(PyEval_GetBuiltins());
const char *id = PYBIND11_INTERNALS_ID;
// Get the internals pointer (without creating it if it doesn't exist). It's possible for the // Get the internals pointer (without creating it if it doesn't exist). It's possible for the
// internals to be created during Py_Finalize() (e.g. if a py::capsule calls `get_internals()` // internals to be created during Py_Finalize() (e.g. if a py::capsule calls `get_internals()`
// during destruction), so we get the pointer-pointer here and check it after Py_Finalize(). // during destruction), so we get the pointer-pointer here and check it after Py_Finalize().
detail::internals **internals_ptr_ptr = detail::get_internals_pp(); detail::internals **internals_ptr_ptr = detail::get_internals_pp();
// It could also be stashed in builtins, so look there too: // It could also be stashed in state_dict, so look there too:
if (builtins.contains(id) && isinstance<capsule>(builtins[id])) { if (object internals_obj
internals_ptr_ptr = capsule(builtins[id]); = get_internals_obj_from_state_dict(detail::get_python_state_dict())) {
internals_ptr_ptr = detail::get_internals_pp_from_capsule(internals_obj);
} }
// Local internals contains data managed by the current interpreter, so we must clear them to // Local internals contains data managed by the current interpreter, so we must clear them to
// avoid undefined behaviors when initializing another interpreter // avoid undefined behaviors when initializing another interpreter
@ -259,6 +289,15 @@ public:
initialize_interpreter(init_signal_handlers, argc, argv, add_program_dir_to_path); initialize_interpreter(init_signal_handlers, argc, argv, add_program_dir_to_path);
} }
#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX
explicit scoped_interpreter(PyConfig *config,
int argc = 0,
const char *const *argv = nullptr,
bool add_program_dir_to_path = true) {
initialize_interpreter(config, argc, argv, add_program_dir_to_path);
}
#endif
scoped_interpreter(const scoped_interpreter &) = delete; scoped_interpreter(const scoped_interpreter &) = delete;
scoped_interpreter(scoped_interpreter &&other) noexcept { other.is_valid = false; } scoped_interpreter(scoped_interpreter &&other) noexcept { other.is_valid = false; }
scoped_interpreter &operator=(const scoped_interpreter &) = delete; scoped_interpreter &operator=(const scoped_interpreter &) = delete;

View File

@ -48,9 +48,16 @@ public:
*/ */
if (auto cfunc = func.cpp_function()) { if (auto cfunc = func.cpp_function()) {
auto *cfunc_self = PyCFunction_GET_SELF(cfunc.ptr()); auto *cfunc_self = PyCFunction_GET_SELF(cfunc.ptr());
if (isinstance<capsule>(cfunc_self)) { if (cfunc_self == nullptr) {
PyErr_Clear();
} else if (isinstance<capsule>(cfunc_self)) {
auto c = reinterpret_borrow<capsule>(cfunc_self); auto c = reinterpret_borrow<capsule>(cfunc_self);
auto *rec = (function_record *) c;
function_record *rec = nullptr;
// Check that we can safely reinterpret the capsule into a function_record
if (detail::is_function_record_capsule(c)) {
rec = c.get_pointer<function_record>();
}
while (rec != nullptr) { while (rec != nullptr) {
if (rec->is_stateless if (rec->is_stateless
@ -110,7 +117,7 @@ public:
template <typename Func> template <typename Func>
static handle cast(Func &&f_, return_value_policy policy, handle /* parent */) { static handle cast(Func &&f_, return_value_policy policy, handle /* parent */) {
if (!f_) { if (!f_) {
return none().inc_ref(); return none().release();
} }
auto result = f_.template target<function_type>(); auto result = f_.template target<function_type>();

View File

@ -10,7 +10,10 @@
#pragma once #pragma once
#include "detail/common.h" #include "detail/common.h"
#include "detail/internals.h"
#if defined(WITH_THREAD) && !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT)
# include "detail/internals.h"
#endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
@ -21,7 +24,9 @@ PyThreadState *get_thread_state_unchecked();
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
#if defined(WITH_THREAD) && !defined(PYPY_VERSION) #if defined(WITH_THREAD)
# if !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT)
/* The functions below essentially reproduce the PyGILState_* API using a RAII /* The functions below essentially reproduce the PyGILState_* API using a RAII
* pattern, but there are a few important differences: * pattern, but there are a few important differences:
@ -62,11 +67,11 @@ public:
if (!tstate) { if (!tstate) {
tstate = PyThreadState_New(internals.istate); tstate = PyThreadState_New(internals.istate);
# if defined(PYBIND11_DETAILED_ERROR_MESSAGES) # if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
if (!tstate) { if (!tstate) {
pybind11_fail("scoped_acquire: could not create thread state!"); pybind11_fail("scoped_acquire: could not create thread state!");
} }
# endif # endif
tstate->gilstate_counter = 0; tstate->gilstate_counter = 0;
PYBIND11_TLS_REPLACE_VALUE(internals.tstate, tstate); PYBIND11_TLS_REPLACE_VALUE(internals.tstate, tstate);
} else { } else {
@ -80,24 +85,27 @@ public:
inc_ref(); inc_ref();
} }
gil_scoped_acquire(const gil_scoped_acquire &) = delete;
gil_scoped_acquire &operator=(const gil_scoped_acquire &) = delete;
void inc_ref() { ++tstate->gilstate_counter; } void inc_ref() { ++tstate->gilstate_counter; }
PYBIND11_NOINLINE void dec_ref() { PYBIND11_NOINLINE void dec_ref() {
--tstate->gilstate_counter; --tstate->gilstate_counter;
# if defined(PYBIND11_DETAILED_ERROR_MESSAGES) # if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
if (detail::get_thread_state_unchecked() != tstate) { if (detail::get_thread_state_unchecked() != tstate) {
pybind11_fail("scoped_acquire::dec_ref(): thread state must be current!"); pybind11_fail("scoped_acquire::dec_ref(): thread state must be current!");
} }
if (tstate->gilstate_counter < 0) { if (tstate->gilstate_counter < 0) {
pybind11_fail("scoped_acquire::dec_ref(): reference count underflow!"); pybind11_fail("scoped_acquire::dec_ref(): reference count underflow!");
} }
# endif # endif
if (tstate->gilstate_counter == 0) { if (tstate->gilstate_counter == 0) {
# if defined(PYBIND11_DETAILED_ERROR_MESSAGES) # if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
if (!release) { if (!release) {
pybind11_fail("scoped_acquire::dec_ref(): internal error!"); pybind11_fail("scoped_acquire::dec_ref(): internal error!");
} }
# endif # endif
PyThreadState_Clear(tstate); PyThreadState_Clear(tstate);
if (active) { if (active) {
PyThreadState_DeleteCurrent(); PyThreadState_DeleteCurrent();
@ -144,6 +152,9 @@ public:
} }
} }
gil_scoped_release(const gil_scoped_release &) = delete;
gil_scoped_release &operator=(const gil_scoped_release &) = delete;
/// This method will disable the PyThreadState_DeleteCurrent call and the /// This method will disable the PyThreadState_DeleteCurrent call and the
/// GIL won't be acquired. This method should be used if the interpreter /// GIL won't be acquired. This method should be used if the interpreter
/// could be shutting down when this is called, as thread deletion is not /// could be shutting down when this is called, as thread deletion is not
@ -172,12 +183,16 @@ private:
bool disassoc; bool disassoc;
bool active = true; bool active = true;
}; };
#elif defined(PYPY_VERSION)
# else // PYBIND11_SIMPLE_GIL_MANAGEMENT
class gil_scoped_acquire { class gil_scoped_acquire {
PyGILState_STATE state; PyGILState_STATE state;
public: public:
gil_scoped_acquire() { state = PyGILState_Ensure(); } gil_scoped_acquire() : state{PyGILState_Ensure()} {}
gil_scoped_acquire(const gil_scoped_acquire &) = delete;
gil_scoped_acquire &operator=(const gil_scoped_acquire &) = delete;
~gil_scoped_acquire() { PyGILState_Release(state); } ~gil_scoped_acquire() { PyGILState_Release(state); }
void disarm() {} void disarm() {}
}; };
@ -186,17 +201,39 @@ class gil_scoped_release {
PyThreadState *state; PyThreadState *state;
public: public:
gil_scoped_release() { state = PyEval_SaveThread(); } gil_scoped_release() : state{PyEval_SaveThread()} {}
gil_scoped_release(const gil_scoped_release &) = delete;
gil_scoped_release &operator=(const gil_scoped_release &) = delete;
~gil_scoped_release() { PyEval_RestoreThread(state); } ~gil_scoped_release() { PyEval_RestoreThread(state); }
void disarm() {} void disarm() {}
}; };
#else
# endif // PYBIND11_SIMPLE_GIL_MANAGEMENT
#else // WITH_THREAD
class gil_scoped_acquire { class gil_scoped_acquire {
public:
gil_scoped_acquire() {
// Trick to suppress `unused variable` error messages (at call sites).
(void) (this != (this + 1));
}
gil_scoped_acquire(const gil_scoped_acquire &) = delete;
gil_scoped_acquire &operator=(const gil_scoped_acquire &) = delete;
void disarm() {} void disarm() {}
}; };
class gil_scoped_release { class gil_scoped_release {
public:
gil_scoped_release() {
// Trick to suppress `unused variable` error messages (at call sites).
(void) (this != (this + 1));
}
gil_scoped_release(const gil_scoped_release &) = delete;
gil_scoped_release &operator=(const gil_scoped_release &) = delete;
void disarm() {} void disarm() {}
}; };
#endif
#endif // WITH_THREAD
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@ -36,6 +36,8 @@ static_assert(std::is_signed<Py_intptr_t>::value, "Py_intptr_t must be signed");
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_WARNING_DISABLE_MSVC(4127)
class array; // Forward declaration class array; // Forward declaration
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
@ -537,7 +539,7 @@ PYBIND11_NAMESPACE_END(detail)
class dtype : public object { class dtype : public object {
public: public:
PYBIND11_OBJECT_DEFAULT(dtype, object, detail::npy_api::get().PyArrayDescr_Check_); PYBIND11_OBJECT_DEFAULT(dtype, object, detail::npy_api::get().PyArrayDescr_Check_)
explicit dtype(const buffer_info &info) { explicit dtype(const buffer_info &info) {
dtype descr(_dtype_from_pep3118()(pybind11::str(info.format))); dtype descr(_dtype_from_pep3118()(pybind11::str(info.format)));
@ -562,6 +564,8 @@ public:
m_ptr = from_args(args).release().ptr(); m_ptr = from_args(args).release().ptr();
} }
/// Return dtype for the given typenum (one of the NPY_TYPES).
/// https://numpy.org/devdocs/reference/c-api/array.html#c.PyArray_DescrFromType
explicit dtype(int typenum) explicit dtype(int typenum)
: object(detail::npy_api::get().PyArray_DescrFromType_(typenum), stolen_t{}) { : object(detail::npy_api::get().PyArray_DescrFromType_(typenum), stolen_t{}) {
if (m_ptr == nullptr) { if (m_ptr == nullptr) {
@ -875,7 +879,7 @@ public:
*/ */
template <typename T, ssize_t Dims = -1> template <typename T, ssize_t Dims = -1>
detail::unchecked_mutable_reference<T, Dims> mutable_unchecked() & { detail::unchecked_mutable_reference<T, Dims> mutable_unchecked() & {
if (PYBIND11_SILENCE_MSVC_C4127(Dims >= 0) && ndim() != Dims) { if (Dims >= 0 && ndim() != Dims) {
throw std::domain_error("array has incorrect number of dimensions: " throw std::domain_error("array has incorrect number of dimensions: "
+ std::to_string(ndim()) + "; expected " + std::to_string(ndim()) + "; expected "
+ std::to_string(Dims)); + std::to_string(Dims));
@ -893,7 +897,7 @@ public:
*/ */
template <typename T, ssize_t Dims = -1> template <typename T, ssize_t Dims = -1>
detail::unchecked_reference<T, Dims> unchecked() const & { detail::unchecked_reference<T, Dims> unchecked() const & {
if (PYBIND11_SILENCE_MSVC_C4127(Dims >= 0) && ndim() != Dims) { if (Dims >= 0 && ndim() != Dims) {
throw std::domain_error("array has incorrect number of dimensions: " throw std::domain_error("array has incorrect number of dimensions: "
+ std::to_string(ndim()) + "; expected " + std::to_string(ndim()) + "; expected "
+ std::to_string(Dims)); + std::to_string(Dims));
@ -1119,10 +1123,10 @@ public:
/** /**
* Returns a proxy object that provides const access to the array's data without bounds or * Returns a proxy object that provides const access to the array's data without bounds or
* dimensionality checking. Unlike `unchecked()`, this does not require that the underlying * dimensionality checking. Unlike `mutable_unchecked()`, this does not require that the
* array have the `writable` flag. Use with care: the array must not be destroyed or reshaped * underlying array have the `writable` flag. Use with care: the array must not be destroyed
* for the duration of the returned object, and the caller must take care not to access invalid * or reshaped for the duration of the returned object, and the caller must take care not to
* dimensions or dimension indices. * access invalid dimensions or dimension indices.
*/ */
template <ssize_t Dims = -1> template <ssize_t Dims = -1>
detail::unchecked_reference<T, Dims> unchecked() const & { detail::unchecked_reference<T, Dims> unchecked() const & {
@ -1281,12 +1285,16 @@ private:
public: public:
static constexpr int value = values[detail::is_fmt_numeric<T>::index]; static constexpr int value = values[detail::is_fmt_numeric<T>::index];
static pybind11::dtype dtype() { static pybind11::dtype dtype() { return pybind11::dtype(/*typenum*/ value); }
if (auto *ptr = npy_api::get().PyArray_DescrFromType_(value)) { };
return reinterpret_steal<pybind11::dtype>(ptr);
} template <typename T>
pybind11_fail("Unsupported buffer format!"); 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_;
static pybind11::dtype dtype() { return pybind11::dtype(/*typenum*/ value); }
}; };
#define PYBIND11_DECL_CHAR_FMT \ #define PYBIND11_DECL_CHAR_FMT \
@ -1401,7 +1409,7 @@ PYBIND11_NOINLINE void register_structured_dtype(any_container<field_descriptor>
oss << '}'; oss << '}';
auto format_str = oss.str(); auto format_str = oss.str();
// Sanity check: verify that NumPy properly parses our buffer format string // Smoke test: verify that NumPy properly parses our buffer format string
auto &api = npy_api::get(); auto &api = npy_api::get();
auto arr = array(buffer_info(nullptr, itemsize, format_str, 1)); auto arr = array(buffer_info(nullptr, itemsize, format_str, 1));
if (!api.PyArray_EquivTypes_(dtype_ptr, arr.dtype().ptr())) { if (!api.PyArray_EquivTypes_(dtype_ptr, arr.dtype().ptr())) {
@ -1469,7 +1477,7 @@ private:
} }
// Extract name, offset and format descriptor for a struct field // Extract name, offset and format descriptor for a struct field
# define PYBIND11_FIELD_DESCRIPTOR(T, Field) PYBIND11_FIELD_DESCRIPTOR_EX(T, Field, # Field) # define PYBIND11_FIELD_DESCRIPTOR(T, Field) PYBIND11_FIELD_DESCRIPTOR_EX(T, Field, #Field)
// The main idea of this macro is borrowed from https://github.com/swansontec/map-macro // The main idea of this macro is borrowed from https://github.com/swansontec/map-macro
// (C) William Swanson, Paul Fultz // (C) William Swanson, Paul Fultz
@ -1866,8 +1874,13 @@ private:
auto result = returned_array::create(trivial, shape); auto result = returned_array::create(trivial, shape);
PYBIND11_WARNING_PUSH
#ifdef PYBIND11_DETECTED_CLANG_WITH_MISLEADING_CALL_STD_MOVE_EXPLICITLY_WARNING
PYBIND11_WARNING_DISABLE_CLANG("-Wreturn-std-move")
#endif
if (size == 0) { if (size == 0) {
return std::move(result); return result;
} }
/* Call the function */ /* Call the function */
@ -1878,7 +1891,8 @@ private:
apply_trivial(buffers, params, mutable_data, size, i_seq, vi_seq, bi_seq); apply_trivial(buffers, params, mutable_data, size, i_seq, vi_seq, bi_seq);
} }
return std::move(result); return result;
PYBIND11_WARNING_POP
} }
template <size_t... Index, size_t... VIndex, size_t... BIndex> template <size_t... Index, size_t... VIndex, size_t... BIndex>

View File

@ -84,6 +84,7 @@ struct op_impl {};
/// Operator implementation generator /// Operator implementation generator
template <op_id id, op_type ot, typename L, typename R> template <op_id id, op_type ot, typename L, typename R>
struct op_ { struct op_ {
static constexpr bool op_enable_if_hook = true;
template <typename Class, typename... Extra> template <typename Class, typename... Extra>
void execute(Class &cl, const Extra &...extra) const { void execute(Class &cl, const Extra &...extra) const {
using Base = typename Class::type; using Base = typename Class::type;

View File

@ -47,6 +47,16 @@ public:
return *this; return *this;
} }
options &disable_enum_members_docstring() & {
global_state().show_enum_members_docstring = false;
return *this;
}
options &enable_enum_members_docstring() & {
global_state().show_enum_members_docstring = true;
return *this;
}
// Getter methods (return the global state): // Getter methods (return the global state):
static bool show_user_defined_docstrings() { static bool show_user_defined_docstrings() {
@ -55,6 +65,10 @@ public:
static bool show_function_signatures() { return global_state().show_function_signatures; } static bool show_function_signatures() { return global_state().show_function_signatures; }
static bool show_enum_members_docstring() {
return global_state().show_enum_members_docstring;
}
// This type is not meant to be allocated on the heap. // This type is not meant to be allocated on the heap.
void *operator new(size_t) = delete; void *operator new(size_t) = delete;
@ -63,6 +77,8 @@ private:
bool show_user_defined_docstrings = true; //< Include user-supplied texts in docstrings. bool show_user_defined_docstrings = true; //< Include user-supplied texts in docstrings.
bool show_function_signatures = true; //< Include auto-generated function signatures bool show_function_signatures = true; //< Include auto-generated function signatures
// in docstrings. // in docstrings.
bool show_enum_members_docstring = true; //< Include auto-generated member list in enum
// docstrings.
}; };
static state &global_state() { static state &global_state() {

View File

@ -35,6 +35,8 @@
# include <cxxabi.h> # include <cxxabi.h>
#endif #endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
/* https://stackoverflow.com/questions/46798456/handling-gccs-noexcept-type-warning /* https://stackoverflow.com/questions/46798456/handling-gccs-noexcept-type-warning
This warning is about ABI compatibility, not code health. This warning is about ABI compatibility, not code health.
It is only actually needed in a couple places, but apparently GCC 7 "generates this warning if It is only actually needed in a couple places, but apparently GCC 7 "generates this warning if
@ -43,11 +45,10 @@
No other GCC version generates this warning. No other GCC version generates this warning.
*/ */
#if defined(__GNUC__) && __GNUC__ == 7 #if defined(__GNUC__) && __GNUC__ == 7
# pragma GCC diagnostic push PYBIND11_WARNING_DISABLE_GCC("-Wnoexcept-type")
# pragma GCC diagnostic ignored "-Wnoexcept-type"
#endif #endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_WARNING_DISABLE_MSVC(4127)
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
@ -83,6 +84,7 @@ public:
cpp_function() = default; cpp_function() = default;
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
cpp_function(std::nullptr_t) {} cpp_function(std::nullptr_t) {}
cpp_function(std::nullptr_t, const is_setter &) {}
/// Construct a cpp_function from a vanilla function pointer /// Construct a cpp_function from a vanilla function pointer
template <typename Return, typename... Args, typename... Extra> template <typename Return, typename... Args, typename... Extra>
@ -177,22 +179,22 @@ protected:
auto *rec = unique_rec.get(); auto *rec = unique_rec.get();
/* Store the capture object directly in the function record if there is enough space */ /* Store the capture object directly in the function record if there is enough space */
if (PYBIND11_SILENCE_MSVC_C4127(sizeof(capture) <= sizeof(rec->data))) { if (sizeof(capture) <= sizeof(rec->data)) {
/* Without these pragmas, GCC warns that there might not be /* Without these pragmas, GCC warns that there might not be
enough space to use the placement new operator. However, the enough space to use the placement new operator. However, the
'if' statement above ensures that this is the case. */ 'if' statement above ensures that this is the case. */
#if defined(__GNUG__) && __GNUC__ >= 6 && !defined(__clang__) && !defined(__INTEL_COMPILER) PYBIND11_WARNING_PUSH
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wplacement-new" #if defined(__GNUG__) && __GNUC__ >= 6
PYBIND11_WARNING_DISABLE_GCC("-Wplacement-new")
#endif #endif
new ((capture *) &rec->data) capture{std::forward<Func>(f)}; new ((capture *) &rec->data) capture{std::forward<Func>(f)};
#if defined(__GNUG__) && __GNUC__ >= 6 && !defined(__clang__) && !defined(__INTEL_COMPILER)
# pragma GCC diagnostic pop #if !PYBIND11_HAS_STD_LAUNDER
#endif PYBIND11_WARNING_DISABLE_GCC("-Wstrict-aliasing")
#if defined(__GNUG__) && !PYBIND11_HAS_STD_LAUNDER && !defined(__INTEL_COMPILER)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wstrict-aliasing"
#endif #endif
// UB without std::launder, but without breaking ABI and/or // UB without std::launder, but without breaking ABI and/or
// a significant refactoring it's "impossible" to solve. // a significant refactoring it's "impossible" to solve.
if (!std::is_trivially_destructible<capture>::value) { if (!std::is_trivially_destructible<capture>::value) {
@ -202,9 +204,7 @@ protected:
data->~capture(); data->~capture();
}; };
} }
#if defined(__GNUG__) && !PYBIND11_HAS_STD_LAUNDER && !defined(__INTEL_COMPILER) PYBIND11_WARNING_POP
# pragma GCC diagnostic pop
#endif
} else { } else {
rec->data[0] = new capture{std::forward<Func>(f)}; rec->data[0] = new capture{std::forward<Func>(f)};
rec->free_data = [](function_record *r) { delete ((capture *) r->data[0]); }; rec->free_data = [](function_record *r) { delete ((capture *) r->data[0]); };
@ -245,10 +245,16 @@ protected:
using Guard = extract_guard_t<Extra...>; using Guard = extract_guard_t<Extra...>;
/* Perform the function call */ /* Perform the function call */
handle result handle result;
= cast_out::cast(std::move(args_converter).template call<Return, Guard>(cap->f), if (call.func.is_setter) {
policy, (void) std::move(args_converter).template call<Return, Guard>(cap->f);
call.parent); result = none().release();
} else {
result = cast_out::cast(
std::move(args_converter).template call<Return, Guard>(cap->f),
policy,
call.parent);
}
/* Invoke call policy post-call hook */ /* Invoke call policy post-call hook */
process_attributes<Extra...>::postcall(call, result); process_attributes<Extra...>::postcall(call, result);
@ -468,13 +474,20 @@ protected:
if (rec->sibling) { if (rec->sibling) {
if (PyCFunction_Check(rec->sibling.ptr())) { if (PyCFunction_Check(rec->sibling.ptr())) {
auto *self = PyCFunction_GET_SELF(rec->sibling.ptr()); auto *self = PyCFunction_GET_SELF(rec->sibling.ptr());
capsule rec_capsule = isinstance<capsule>(self) ? reinterpret_borrow<capsule>(self) if (!isinstance<capsule>(self)) {
: capsule(self);
chain = (detail::function_record *) rec_capsule;
/* Never append a method to an overload chain of a parent class;
instead, hide the parent's overloads in this case */
if (!chain->scope.is(rec->scope)) {
chain = nullptr; chain = nullptr;
} else {
auto rec_capsule = reinterpret_borrow<capsule>(self);
if (detail::is_function_record_capsule(rec_capsule)) {
chain = rec_capsule.get_pointer<detail::function_record>();
/* Never append a method to an overload chain of a parent class;
instead, hide the parent's overloads in this case */
if (!chain->scope.is(rec->scope)) {
chain = nullptr;
}
} else {
chain = nullptr;
}
} }
} }
// Don't trigger for things like the default __init__, which are wrapper_descriptors // Don't trigger for things like the default __init__, which are wrapper_descriptors
@ -495,6 +508,7 @@ protected:
rec->def->ml_flags = METH_VARARGS | METH_KEYWORDS; rec->def->ml_flags = METH_VARARGS | METH_KEYWORDS;
capsule rec_capsule(unique_rec.release(), capsule rec_capsule(unique_rec.release(),
detail::get_function_record_capsule_name(),
[](void *ptr) { destruct((detail::function_record *) ptr); }); [](void *ptr) { destruct((detail::function_record *) ptr); });
guarded_strdup.release(); guarded_strdup.release();
@ -661,10 +675,13 @@ protected:
/// Main dispatch logic for calls to functions bound using pybind11 /// Main dispatch logic for calls to functions bound using pybind11
static PyObject *dispatcher(PyObject *self, PyObject *args_in, PyObject *kwargs_in) { static PyObject *dispatcher(PyObject *self, PyObject *args_in, PyObject *kwargs_in) {
using namespace detail; using namespace detail;
assert(isinstance<capsule>(self));
/* Iterator over the list of potentially admissible overloads */ /* Iterator over the list of potentially admissible overloads */
const function_record *overloads = (function_record *) PyCapsule_GetPointer(self, nullptr), const function_record *overloads = reinterpret_cast<function_record *>(
PyCapsule_GetPointer(self, get_function_record_capsule_name())),
*it = overloads; *it = overloads;
assert(overloads != nullptr);
/* Need to know how many arguments + keyword arguments there are to pick the right /* Need to know how many arguments + keyword arguments there are to pick the right
overload */ overload */
@ -1416,9 +1433,9 @@ template <typename T, enable_if_t<has_operator_delete<T>::value, int> = 0>
void call_operator_delete(T *p, size_t, size_t) { void call_operator_delete(T *p, size_t, size_t) {
T::operator delete(p); T::operator delete(p);
} }
template < template <typename T,
typename T, enable_if_t<!has_operator_delete<T>::value && has_operator_delete_size<T>::value, int>
enable_if_t<!has_operator_delete<T>::value && has_operator_delete_size<T>::value, int> = 0> = 0>
void call_operator_delete(T *p, size_t s, size_t) { void call_operator_delete(T *p, size_t s, size_t) {
T::operator delete(p, s); T::operator delete(p, s);
} }
@ -1578,14 +1595,14 @@ public:
return *this; return *this;
} }
template <detail::op_id id, detail::op_type ot, typename L, typename R, typename... Extra> template <typename T, typename... Extra, detail::enable_if_t<T::op_enable_if_hook, int> = 0>
class_ &def(const detail::op_<id, ot, L, R> &op, const Extra &...extra) { class_ &def(const T &op, const Extra &...extra) {
op.execute(*this, extra...); op.execute(*this, extra...);
return *this; return *this;
} }
template <detail::op_id id, detail::op_type ot, typename L, typename R, typename... Extra> template <typename T, typename... Extra, detail::enable_if_t<T::op_enable_if_hook, int> = 0>
class_ &def_cast(const detail::op_<id, ot, L, R> &op, const Extra &...extra) { class_ &def_cast(const T &op, const Extra &...extra) {
op.execute_cast(*this, extra...); op.execute_cast(*this, extra...);
return *this; return *this;
} }
@ -1719,7 +1736,8 @@ public:
template <typename Getter, typename Setter, typename... Extra> template <typename Getter, typename Setter, typename... Extra>
class_ & class_ &
def_property(const char *name, const Getter &fget, const Setter &fset, const Extra &...extra) { def_property(const char *name, const Getter &fget, const Setter &fset, const Extra &...extra) {
return def_property(name, fget, cpp_function(method_adaptor<type>(fset)), extra...); return def_property(
name, fget, cpp_function(method_adaptor<type>(fset), is_setter()), extra...);
} }
template <typename Getter, typename... Extra> template <typename Getter, typename... Extra>
class_ &def_property(const char *name, class_ &def_property(const char *name,
@ -1830,8 +1848,7 @@ private:
if (holder_ptr) { if (holder_ptr) {
init_holder_from_existing(v_h, holder_ptr, std::is_copy_constructible<holder_type>()); init_holder_from_existing(v_h, holder_ptr, std::is_copy_constructible<holder_type>());
v_h.set_holder_constructed(); v_h.set_holder_constructed();
} else if (PYBIND11_SILENCE_MSVC_C4127(detail::always_construct_holder<holder_type>::value) } else if (detail::always_construct_holder<holder_type>::value || inst->owned) {
|| inst->owned) {
new (std::addressof(v_h.holder<holder_type>())) holder_type(v_h.value_ptr<type>()); new (std::addressof(v_h.holder<holder_type>())) holder_type(v_h.value_ptr<type>());
v_h.set_holder_constructed(); v_h.set_holder_constructed();
} }
@ -1871,9 +1888,22 @@ private:
static detail::function_record *get_function_record(handle h) { static detail::function_record *get_function_record(handle h) {
h = detail::get_function(h); h = detail::get_function(h);
return h ? (detail::function_record *) reinterpret_borrow<capsule>( if (!h) {
PyCFunction_GET_SELF(h.ptr())) return nullptr;
: nullptr; }
handle func_self = PyCFunction_GET_SELF(h.ptr());
if (!func_self) {
throw error_already_set();
}
if (!isinstance<capsule>(func_self)) {
return nullptr;
}
auto cap = reinterpret_borrow<capsule>(func_self);
if (!detail::is_function_record_capsule(cap)) {
return nullptr;
}
return cap.get_pointer<detail::function_record>();
} }
}; };
@ -1950,29 +1980,35 @@ struct enum_base {
name("name"), name("name"),
is_method(m_base)); is_method(m_base));
m_base.attr("__doc__") = static_property( if (options::show_enum_members_docstring()) {
cpp_function( m_base.attr("__doc__") = static_property(
[](handle arg) -> std::string { cpp_function(
std::string docstring; [](handle arg) -> std::string {
dict entries = arg.attr("__entries"); std::string docstring;
if (((PyTypeObject *) arg.ptr())->tp_doc) { dict entries = arg.attr("__entries");
docstring += std::string(((PyTypeObject *) arg.ptr())->tp_doc) + "\n\n"; if (((PyTypeObject *) arg.ptr())->tp_doc) {
} docstring += std::string(
docstring += "Members:"; reinterpret_cast<PyTypeObject *>(arg.ptr())->tp_doc);
for (auto kv : entries) { docstring += "\n\n";
auto key = std::string(pybind11::str(kv.first));
auto comment = kv.second[int_(1)];
docstring += "\n\n " + key;
if (!comment.is_none()) {
docstring += " : " + (std::string) pybind11::str(comment);
} }
} docstring += "Members:";
return docstring; for (auto kv : entries) {
}, auto key = std::string(pybind11::str(kv.first));
name("__doc__")), auto comment = kv.second[int_(1)];
none(), docstring += "\n\n ";
none(), docstring += key;
""); if (!comment.is_none()) {
docstring += " : ";
docstring += pybind11::str(comment).cast<std::string>();
}
}
return docstring;
},
name("__doc__")),
none(),
none(),
"");
}
m_base.attr("__members__") = static_property(cpp_function( m_base.attr("__members__") = static_property(cpp_function(
[](handle arg) -> dict { [](handle arg) -> dict {
@ -2073,7 +2109,7 @@ struct enum_base {
+ "\" already exists!"); + "\" already exists!");
} }
entries[name] = std::make_pair(value, doc); entries[name] = pybind11::make_tuple(value, doc);
m_base.attr(std::move(name)) = std::move(value); m_base.attr(std::move(name)) = std::move(value);
} }
@ -2333,7 +2369,7 @@ template <typename Access,
typename Sentinel, typename Sentinel,
typename ValueType, typename ValueType,
typename... Extra> typename... Extra>
iterator make_iterator_impl(Iterator &&first, Sentinel &&last, Extra &&...extra) { iterator make_iterator_impl(Iterator first, Sentinel last, Extra &&...extra) {
using state = detail::iterator_state<Access, Policy, Iterator, Sentinel, ValueType, Extra...>; using state = detail::iterator_state<Access, Policy, Iterator, Sentinel, ValueType, Extra...>;
// TODO: state captures only the types of Extra, not the values // TODO: state captures only the types of Extra, not the values
@ -2359,7 +2395,7 @@ iterator make_iterator_impl(Iterator &&first, Sentinel &&last, Extra &&...extra)
Policy); Policy);
} }
return cast(state{std::forward<Iterator>(first), std::forward<Sentinel>(last), true}); return cast(state{first, last, true});
} }
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
@ -2370,15 +2406,13 @@ template <return_value_policy Policy = return_value_policy::reference_internal,
typename Sentinel, typename Sentinel,
typename ValueType = typename detail::iterator_access<Iterator>::result_type, typename ValueType = typename detail::iterator_access<Iterator>::result_type,
typename... Extra> typename... Extra>
iterator make_iterator(Iterator &&first, Sentinel &&last, Extra &&...extra) { iterator make_iterator(Iterator first, Sentinel last, Extra &&...extra) {
return detail::make_iterator_impl<detail::iterator_access<Iterator>, return detail::make_iterator_impl<detail::iterator_access<Iterator>,
Policy, Policy,
Iterator, Iterator,
Sentinel, Sentinel,
ValueType, ValueType,
Extra...>(std::forward<Iterator>(first), Extra...>(first, last, std::forward<Extra>(extra)...);
std::forward<Sentinel>(last),
std::forward<Extra>(extra)...);
} }
/// Makes a python iterator over the keys (`.first`) of a iterator over pairs from a /// Makes a python iterator over the keys (`.first`) of a iterator over pairs from a
@ -2388,15 +2422,13 @@ template <return_value_policy Policy = return_value_policy::reference_internal,
typename Sentinel, typename Sentinel,
typename KeyType = typename detail::iterator_key_access<Iterator>::result_type, typename KeyType = typename detail::iterator_key_access<Iterator>::result_type,
typename... Extra> typename... Extra>
iterator make_key_iterator(Iterator &&first, Sentinel &&last, Extra &&...extra) { iterator make_key_iterator(Iterator first, Sentinel last, Extra &&...extra) {
return detail::make_iterator_impl<detail::iterator_key_access<Iterator>, return detail::make_iterator_impl<detail::iterator_key_access<Iterator>,
Policy, Policy,
Iterator, Iterator,
Sentinel, Sentinel,
KeyType, KeyType,
Extra...>(std::forward<Iterator>(first), Extra...>(first, last, std::forward<Extra>(extra)...);
std::forward<Sentinel>(last),
std::forward<Extra>(extra)...);
} }
/// Makes a python iterator over the values (`.second`) of a iterator over pairs from a /// Makes a python iterator over the values (`.second`) of a iterator over pairs from a
@ -2406,15 +2438,13 @@ template <return_value_policy Policy = return_value_policy::reference_internal,
typename Sentinel, typename Sentinel,
typename ValueType = typename detail::iterator_value_access<Iterator>::result_type, typename ValueType = typename detail::iterator_value_access<Iterator>::result_type,
typename... Extra> typename... Extra>
iterator make_value_iterator(Iterator &&first, Sentinel &&last, Extra &&...extra) { iterator make_value_iterator(Iterator first, Sentinel last, Extra &&...extra) {
return detail::make_iterator_impl<detail::iterator_value_access<Iterator>, return detail::make_iterator_impl<detail::iterator_value_access<Iterator>,
Policy, Policy,
Iterator, Iterator,
Sentinel, Sentinel,
ValueType, ValueType,
Extra...>(std::forward<Iterator>(first), Extra...>(first, last, std::forward<Extra>(extra)...);
std::forward<Sentinel>(last),
std::forward<Extra>(extra)...);
} }
/// Makes an iterator over values of an stl container or other container supporting /// Makes an iterator over values of an stl container or other container supporting
@ -2858,7 +2888,3 @@ inline function get_overload(const T *this_ptr, const char *name) {
PYBIND11_OVERRIDE_PURE(PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), fn, __VA_ARGS__); PYBIND11_OVERRIDE_PURE(PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), fn, __VA_ARGS__);
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
#if defined(__GNUC__) && __GNUC__ == 7
# pragma GCC diagnostic pop // -Wnoexcept-type
#endif

View File

@ -33,6 +33,8 @@
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_WARNING_DISABLE_MSVC(4127)
/* A few forward declarations */ /* A few forward declarations */
class handle; class handle;
class object; class object;
@ -155,23 +157,23 @@ public:
object operator-() const; object operator-() const;
object operator~() const; object operator~() const;
object operator+(object_api const &other) const; object operator+(object_api const &other) const;
object operator+=(object_api const &other) const; object operator+=(object_api const &other);
object operator-(object_api const &other) const; object operator-(object_api const &other) const;
object operator-=(object_api const &other) const; object operator-=(object_api const &other);
object operator*(object_api const &other) const; object operator*(object_api const &other) const;
object operator*=(object_api const &other) const; object operator*=(object_api const &other);
object operator/(object_api const &other) const; object operator/(object_api const &other) const;
object operator/=(object_api const &other) const; object operator/=(object_api const &other);
object operator|(object_api const &other) const; object operator|(object_api const &other) const;
object operator|=(object_api const &other) const; object operator|=(object_api const &other);
object operator&(object_api const &other) const; object operator&(object_api const &other) const;
object operator&=(object_api const &other) const; object operator&=(object_api const &other);
object operator^(object_api const &other) const; object operator^(object_api const &other) const;
object operator^=(object_api const &other) const; object operator^=(object_api const &other);
object operator<<(object_api const &other) const; object operator<<(object_api const &other) const;
object operator<<=(object_api const &other) const; object operator<<=(object_api const &other);
object operator>>(object_api const &other) const; object operator>>(object_api const &other) const;
object operator>>=(object_api const &other) const; object operator>>=(object_api const &other);
PYBIND11_DEPRECATED("Use py::str(obj) instead") PYBIND11_DEPRECATED("Use py::str(obj) instead")
pybind11::str str() const; pybind11::str str() const;
@ -230,7 +232,8 @@ public:
detail::enable_if_t<detail::all_of<detail::none_of<std::is_base_of<handle, T>, detail::enable_if_t<detail::all_of<detail::none_of<std::is_base_of<handle, T>,
detail::is_pyobj_ptr_or_nullptr_t<T>>, detail::is_pyobj_ptr_or_nullptr_t<T>>,
std::is_convertible<T, PyObject *>>::value, std::is_convertible<T, PyObject *>>::value,
int> = 0> int>
= 0>
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
handle(T &obj) : m_ptr(obj) {} handle(T &obj) : m_ptr(obj) {}
@ -246,6 +249,11 @@ public:
const handle &inc_ref() const & { const handle &inc_ref() const & {
#ifdef PYBIND11_HANDLE_REF_DEBUG #ifdef PYBIND11_HANDLE_REF_DEBUG
inc_ref_counter(1); inc_ref_counter(1);
#endif
#ifdef PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF
if (m_ptr != nullptr && !PyGILState_Check()) {
throw_gilstate_error("pybind11::handle::inc_ref()");
}
#endif #endif
Py_XINCREF(m_ptr); Py_XINCREF(m_ptr);
return *this; return *this;
@ -257,6 +265,11 @@ public:
this function automatically. Returns a reference to itself. this function automatically. Returns a reference to itself.
\endrst */ \endrst */
const handle &dec_ref() const & { const handle &dec_ref() const & {
#ifdef PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF
if (m_ptr != nullptr && !PyGILState_Check()) {
throw_gilstate_error("pybind11::handle::dec_ref()");
}
#endif
Py_XDECREF(m_ptr); Py_XDECREF(m_ptr);
return *this; return *this;
} }
@ -283,8 +296,33 @@ public:
protected: protected:
PyObject *m_ptr = nullptr; PyObject *m_ptr = nullptr;
#ifdef PYBIND11_HANDLE_REF_DEBUG
private: private:
#ifdef PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF
void throw_gilstate_error(const std::string &function_name) const {
fprintf(
stderr,
"%s is being called while the GIL is either not held or invalid. Please see "
"https://pybind11.readthedocs.io/en/stable/advanced/"
"misc.html#common-sources-of-global-interpreter-lock-errors for debugging advice.\n"
"If you are convinced there is no bug in your code, you can #define "
"PYBIND11_NO_ASSERT_GIL_HELD_INCREF_DECREF"
"to disable this check. In that case you have to ensure this #define is consistently "
"used for all translation units linked into a given pybind11 extension, otherwise "
"there will be ODR violations.",
function_name.c_str());
fflush(stderr);
if (Py_TYPE(m_ptr)->tp_name != nullptr) {
fprintf(stderr,
"The failing %s call was triggered on a %s object.\n",
function_name.c_str(),
Py_TYPE(m_ptr)->tp_name);
fflush(stderr);
}
throw std::runtime_error(function_name + " PyGILState_Check() failure.");
}
#endif
#ifdef PYBIND11_HANDLE_REF_DEBUG
static std::size_t inc_ref_counter(std::size_t add) { static std::size_t inc_ref_counter(std::size_t add) {
thread_local std::size_t counter = 0; thread_local std::size_t counter = 0;
counter += add; counter += add;
@ -334,12 +372,15 @@ public:
} }
object &operator=(const object &other) { object &operator=(const object &other) {
other.inc_ref(); // Skip inc_ref and dec_ref if both objects are the same
// Use temporary variable to ensure `*this` remains valid while if (!this->is(other)) {
// `Py_XDECREF` executes, in case `*this` is accessible from Python. other.inc_ref();
handle temp(m_ptr); // Use temporary variable to ensure `*this` remains valid while
m_ptr = other.m_ptr; // `Py_XDECREF` executes, in case `*this` is accessible from Python.
temp.dec_ref(); handle temp(m_ptr);
m_ptr = other.m_ptr;
temp.dec_ref();
}
return *this; return *this;
} }
@ -353,6 +394,20 @@ public:
return *this; return *this;
} }
#define PYBIND11_INPLACE_OP(iop) \
object iop(object_api const &other) { return operator=(handle::iop(other)); }
PYBIND11_INPLACE_OP(operator+=)
PYBIND11_INPLACE_OP(operator-=)
PYBIND11_INPLACE_OP(operator*=)
PYBIND11_INPLACE_OP(operator/=)
PYBIND11_INPLACE_OP(operator|=)
PYBIND11_INPLACE_OP(operator&=)
PYBIND11_INPLACE_OP(operator^=)
PYBIND11_INPLACE_OP(operator<<=)
PYBIND11_INPLACE_OP(operator>>=)
#undef PYBIND11_INPLACE_OP
// Calling cast() on an object lvalue just copies (via handle::cast) // Calling cast() on an object lvalue just copies (via handle::cast)
template <typename T> template <typename T>
T cast() const &; T cast() const &;
@ -413,7 +468,7 @@ PYBIND11_NAMESPACE_BEGIN(detail)
// Equivalent to obj.__class__.__name__ (or obj.__name__ if obj is a class). // Equivalent to obj.__class__.__name__ (or obj.__name__ if obj is a class).
inline const char *obj_class_name(PyObject *obj) { inline const char *obj_class_name(PyObject *obj) {
if (Py_TYPE(obj) == &PyType_Type) { if (PyType_Check(obj)) {
return reinterpret_cast<PyTypeObject *>(obj)->tp_name; return reinterpret_cast<PyTypeObject *>(obj)->tp_name;
} }
return Py_TYPE(obj)->tp_name; return Py_TYPE(obj)->tp_name;
@ -421,13 +476,24 @@ inline const char *obj_class_name(PyObject *obj) {
std::string error_string(); std::string error_string();
// The code in this struct is very unusual, to minimize the chances of
// masking bugs (elsewhere) by errors during the error handling (here).
// This is meant to be a lifeline for troubleshooting long-running processes
// that crash under conditions that are virtually impossible to reproduce.
// Low-level implementation alternatives are preferred to higher-level ones
// that might raise cascading exceptions. Last-ditch-kind-of attempts are made
// to report as much of the original error as possible, even if there are
// secondary issues obtaining some of the details.
struct error_fetch_and_normalize { struct error_fetch_and_normalize {
// Immediate normalization is long-established behavior (starting with // This comment only applies to Python <= 3.11:
// https://github.com/pybind/pybind11/commit/135ba8deafb8bf64a15b24d1513899eb600e2011 // Immediate normalization is long-established behavior (starting with
// from Sep 2016) and safest. Normalization could be deferred, but this could mask // https://github.com/pybind/pybind11/commit/135ba8deafb8bf64a15b24d1513899eb600e2011
// errors elsewhere, the performance gain is very minor in typical situations // from Sep 2016) and safest. Normalization could be deferred, but this could mask
// (usually the dominant bottleneck is EH unwinding), and the implementation here // errors elsewhere, the performance gain is very minor in typical situations
// would be more complex. // (usually the dominant bottleneck is EH unwinding), and the implementation here
// would be more complex.
// Starting with Python 3.12, PyErr_Fetch() normalizes exceptions immediately.
// Any errors during normalization are tracked under __notes__.
explicit error_fetch_and_normalize(const char *called) { explicit error_fetch_and_normalize(const char *called) {
PyErr_Fetch(&m_type.ptr(), &m_value.ptr(), &m_trace.ptr()); PyErr_Fetch(&m_type.ptr(), &m_value.ptr(), &m_trace.ptr());
if (!m_type) { if (!m_type) {
@ -442,6 +508,14 @@ struct error_fetch_and_normalize {
"of the original active exception type."); "of the original active exception type.");
} }
m_lazy_error_string = exc_type_name_orig; m_lazy_error_string = exc_type_name_orig;
#if PY_VERSION_HEX >= 0x030C0000
// The presence of __notes__ is likely due to exception normalization
// errors, although that is not necessarily true, therefore insert a
// hint only:
if (PyObject_HasAttrString(m_value.ptr(), "__notes__")) {
m_lazy_error_string += "[WITH __notes__]";
}
#else
// PyErr_NormalizeException() may change the exception type if there are cascading // PyErr_NormalizeException() may change the exception type if there are cascading
// failures. This can potentially be extremely confusing. // failures. This can potentially be extremely confusing.
PyErr_NormalizeException(&m_type.ptr(), &m_value.ptr(), &m_trace.ptr()); PyErr_NormalizeException(&m_type.ptr(), &m_value.ptr(), &m_trace.ptr());
@ -451,11 +525,17 @@ struct error_fetch_and_normalize {
"active exception."); "active exception.");
} }
const char *exc_type_name_norm = detail::obj_class_name(m_type.ptr()); const char *exc_type_name_norm = detail::obj_class_name(m_type.ptr());
if (exc_type_name_orig == nullptr) { if (exc_type_name_norm == nullptr) {
pybind11_fail("Internal error: " + std::string(called) pybind11_fail("Internal error: " + std::string(called)
+ " failed to obtain the name " + " failed to obtain the name "
"of the normalized active exception type."); "of the normalized active exception type.");
} }
# if defined(PYPY_VERSION_NUM) && PYPY_VERSION_NUM < 0x07030a00
// This behavior runs the risk of masking errors in the error handling, but avoids a
// conflict with PyPy, which relies on the normalization here to change OSError to
// FileNotFoundError (https://github.com/pybind/pybind11/issues/4075).
m_lazy_error_string = exc_type_name_norm;
# else
if (exc_type_name_norm != m_lazy_error_string) { if (exc_type_name_norm != m_lazy_error_string) {
std::string msg = std::string(called) std::string msg = std::string(called)
+ ": MISMATCH of original and normalized " + ": MISMATCH of original and normalized "
@ -467,6 +547,8 @@ struct error_fetch_and_normalize {
msg += ": " + format_value_and_trace(); msg += ": " + format_value_and_trace();
pybind11_fail(msg); pybind11_fail(msg);
} }
# endif
#endif
} }
error_fetch_and_normalize(const error_fetch_and_normalize &) = delete; error_fetch_and_normalize(const error_fetch_and_normalize &) = delete;
@ -477,12 +559,64 @@ struct error_fetch_and_normalize {
std::string message_error_string; std::string message_error_string;
if (m_value) { if (m_value) {
auto value_str = reinterpret_steal<object>(PyObject_Str(m_value.ptr())); auto value_str = reinterpret_steal<object>(PyObject_Str(m_value.ptr()));
constexpr const char *message_unavailable_exc
= "<MESSAGE UNAVAILABLE DUE TO ANOTHER EXCEPTION>";
if (!value_str) { if (!value_str) {
message_error_string = detail::error_string(); message_error_string = detail::error_string();
result = "<MESSAGE UNAVAILABLE DUE TO ANOTHER EXCEPTION>"; result = message_unavailable_exc;
} else { } else {
result = value_str.cast<std::string>(); // Not using `value_str.cast<std::string>()`, to not potentially throw a secondary
// error_already_set that will then result in process termination (#4288).
auto value_bytes = reinterpret_steal<object>(
PyUnicode_AsEncodedString(value_str.ptr(), "utf-8", "backslashreplace"));
if (!value_bytes) {
message_error_string = detail::error_string();
result = message_unavailable_exc;
} else {
char *buffer = nullptr;
Py_ssize_t length = 0;
if (PyBytes_AsStringAndSize(value_bytes.ptr(), &buffer, &length) == -1) {
message_error_string = detail::error_string();
result = message_unavailable_exc;
} else {
result = std::string(buffer, static_cast<std::size_t>(length));
}
}
} }
#if PY_VERSION_HEX >= 0x030B0000
auto notes
= reinterpret_steal<object>(PyObject_GetAttrString(m_value.ptr(), "__notes__"));
if (!notes) {
PyErr_Clear(); // No notes is good news.
} else {
auto len_notes = PyList_Size(notes.ptr());
if (len_notes < 0) {
result += "\nFAILURE obtaining len(__notes__): " + detail::error_string();
} else {
result += "\n__notes__ (len=" + std::to_string(len_notes) + "):";
for (ssize_t i = 0; i < len_notes; i++) {
PyObject *note = PyList_GET_ITEM(notes.ptr(), i);
auto note_bytes = reinterpret_steal<object>(
PyUnicode_AsEncodedString(note, "utf-8", "backslashreplace"));
if (!note_bytes) {
result += "\nFAILURE obtaining __notes__[" + std::to_string(i)
+ "]: " + detail::error_string();
} else {
char *buffer = nullptr;
Py_ssize_t length = 0;
if (PyBytes_AsStringAndSize(note_bytes.ptr(), &buffer, &length)
== -1) {
result += "\nFAILURE formatting __notes__[" + std::to_string(i)
+ "]: " + detail::error_string();
} else {
result += '\n';
result += std::string(buffer, static_cast<std::size_t>(length));
}
}
}
}
}
#endif
} else { } else {
result = "<MESSAGE UNAVAILABLE>"; result = "<MESSAGE UNAVAILABLE>";
} }
@ -581,12 +715,6 @@ inline std::string error_string() {
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
#if defined(_MSC_VER)
# pragma warning(push)
# pragma warning(disable : 4275 4251)
// warning C4275: An exported class was derived from a class that wasn't exported.
// Can be ignored when derived from a STL class.
#endif
/// Fetch and hold an error which was already set in Python. An instance of this is typically /// Fetch and hold an error which was already set in Python. An instance of this is typically
/// thrown to propagate python-side errors back through C++ which can either be caught manually or /// thrown to propagate python-side errors back through C++ which can either be caught manually or
/// else falls back to the function dispatcher (which then raises the captured error back to /// else falls back to the function dispatcher (which then raises the captured error back to
@ -646,9 +774,6 @@ private:
/// crashes (undefined behavior) if the Python interpreter is finalizing. /// crashes (undefined behavior) if the Python interpreter is finalizing.
static void m_fetched_error_deleter(detail::error_fetch_and_normalize *raw_ptr); static void m_fetched_error_deleter(detail::error_fetch_and_normalize *raw_ptr);
}; };
#if defined(_MSC_VER)
# pragma warning(pop)
#endif
/// Replaces the current Python error indicator with the chosen error, performing a /// Replaces the current Python error indicator with the chosen error, performing a
/// 'raise from' to indicate that the chosen error was caused by the original error. /// 'raise from' to indicate that the chosen error was caused by the original error.
@ -851,10 +976,8 @@ object object_or_cast(T &&o);
// Match a PyObject*, which we want to convert directly to handle via its converting constructor // Match a PyObject*, which we want to convert directly to handle via its converting constructor
inline handle object_or_cast(PyObject *ptr) { return ptr; } inline handle object_or_cast(PyObject *ptr) { return ptr; }
#if defined(_MSC_VER) && _MSC_VER < 1920 PYBIND11_WARNING_PUSH
# pragma warning(push) PYBIND11_WARNING_DISABLE_MSVC(4522) // warning C4522: multiple assignment operators specified
# pragma warning(disable : 4522) // warning C4522: multiple assignment operators specified
#endif
template <typename Policy> template <typename Policy>
class accessor : public object_api<accessor<Policy>> { class accessor : public object_api<accessor<Policy>> {
using key_type = typename Policy::key_type; using key_type = typename Policy::key_type;
@ -918,9 +1041,7 @@ private:
key_type key; key_type key;
mutable object cache; mutable object cache;
}; };
#if defined(_MSC_VER) && _MSC_VER < 1920 PYBIND11_WARNING_POP
# pragma warning(pop)
#endif
PYBIND11_NAMESPACE_BEGIN(accessor_policies) PYBIND11_NAMESPACE_BEGIN(accessor_policies)
struct obj_attr { struct obj_attr {
@ -1266,7 +1387,7 @@ public:
#define PYBIND11_OBJECT_CVT_DEFAULT(Name, Parent, CheckFun, ConvertFun) \ #define PYBIND11_OBJECT_CVT_DEFAULT(Name, Parent, CheckFun, ConvertFun) \
PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, ConvertFun) \ PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, ConvertFun) \
Name() : Parent() {} Name() = default;
#define PYBIND11_OBJECT_CHECK_FAILED(Name, o_ptr) \ #define PYBIND11_OBJECT_CHECK_FAILED(Name, o_ptr) \
::pybind11::type_error("Object of type '" \ ::pybind11::type_error("Object of type '" \
@ -1289,7 +1410,7 @@ public:
#define PYBIND11_OBJECT_DEFAULT(Name, Parent, CheckFun) \ #define PYBIND11_OBJECT_DEFAULT(Name, Parent, CheckFun) \
PYBIND11_OBJECT(Name, Parent, CheckFun) \ PYBIND11_OBJECT(Name, Parent, CheckFun) \
Name() : Parent() {} Name() = default;
/// \addtogroup pytypes /// \addtogroup pytypes
/// @{ /// @{
@ -1358,7 +1479,7 @@ public:
private: private:
void advance() { void advance() {
value = reinterpret_steal<object>(PyIter_Next(m_ptr)); value = reinterpret_steal<object>(PyIter_Next(m_ptr));
if (PyErr_Occurred()) { if (value.ptr() == nullptr && PyErr_Occurred()) {
throw error_already_set(); throw error_already_set();
} }
} }
@ -1408,6 +1529,9 @@ public:
str(const char *c, const SzType &n) str(const char *c, const SzType &n)
: object(PyUnicode_FromStringAndSize(c, ssize_t_cast(n)), stolen_t{}) { : object(PyUnicode_FromStringAndSize(c, ssize_t_cast(n)), stolen_t{}) {
if (!m_ptr) { if (!m_ptr) {
if (PyErr_Occurred()) {
throw error_already_set();
}
pybind11_fail("Could not allocate string object!"); pybind11_fail("Could not allocate string object!");
} }
} }
@ -1417,6 +1541,9 @@ public:
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
str(const char *c = "") : object(PyUnicode_FromString(c), stolen_t{}) { str(const char *c = "") : object(PyUnicode_FromString(c), stolen_t{}) {
if (!m_ptr) { if (!m_ptr) {
if (PyErr_Occurred()) {
throw error_already_set();
}
pybind11_fail("Could not allocate string object!"); pybind11_fail("Could not allocate string object!");
} }
} }
@ -1574,6 +1701,9 @@ inline str::str(const bytes &b) {
} }
auto obj = reinterpret_steal<object>(PyUnicode_FromStringAndSize(buffer, length)); auto obj = reinterpret_steal<object>(PyUnicode_FromStringAndSize(buffer, length));
if (!obj) { if (!obj) {
if (PyErr_Occurred()) {
throw error_already_set();
}
pybind11_fail("Could not allocate string object!"); pybind11_fail("Could not allocate string object!");
} }
m_ptr = obj.release().ptr(); m_ptr = obj.release().ptr();
@ -1651,7 +1781,7 @@ PYBIND11_NAMESPACE_BEGIN(detail)
// unsigned type: (A)-1 != (B)-1 when A and B are unsigned types of different sizes). // unsigned type: (A)-1 != (B)-1 when A and B are unsigned types of different sizes).
template <typename Unsigned> template <typename Unsigned>
Unsigned as_unsigned(PyObject *o) { Unsigned as_unsigned(PyObject *o) {
if (PYBIND11_SILENCE_MSVC_C4127(sizeof(Unsigned) <= sizeof(unsigned long))) { if (sizeof(Unsigned) <= sizeof(unsigned long)) {
unsigned long v = PyLong_AsUnsignedLong(o); unsigned long v = PyLong_AsUnsignedLong(o);
return v == (unsigned long) -1 && PyErr_Occurred() ? (Unsigned) -1 : (Unsigned) v; return v == (unsigned long) -1 && PyErr_Occurred() ? (Unsigned) -1 : (Unsigned) v;
} }
@ -1668,7 +1798,7 @@ public:
template <typename T, detail::enable_if_t<std::is_integral<T>::value, int> = 0> template <typename T, detail::enable_if_t<std::is_integral<T>::value, int> = 0>
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
int_(T value) { int_(T value) {
if (PYBIND11_SILENCE_MSVC_C4127(sizeof(T) <= sizeof(long))) { if (sizeof(T) <= sizeof(long)) {
if (std::is_signed<T>::value) { if (std::is_signed<T>::value) {
m_ptr = PyLong_FromLong((long) value); m_ptr = PyLong_FromLong((long) value);
} else { } else {
@ -1785,43 +1915,28 @@ public:
explicit capsule(const void *value, explicit capsule(const void *value,
const char *name = nullptr, const char *name = nullptr,
void (*destructor)(PyObject *) = nullptr) PyCapsule_Destructor destructor = nullptr)
: object(PyCapsule_New(const_cast<void *>(value), name, destructor), stolen_t{}) { : object(PyCapsule_New(const_cast<void *>(value), name, destructor), stolen_t{}) {
if (!m_ptr) { if (!m_ptr) {
throw error_already_set(); throw error_already_set();
} }
} }
PYBIND11_DEPRECATED("Please pass a destructor that takes a void pointer as input") PYBIND11_DEPRECATED("Please use the ctor with value, name, destructor args")
capsule(const void *value, void (*destruct)(PyObject *)) capsule(const void *value, PyCapsule_Destructor destructor)
: object(PyCapsule_New(const_cast<void *>(value), nullptr, destruct), stolen_t{}) { : object(PyCapsule_New(const_cast<void *>(value), nullptr, destructor), stolen_t{}) {
if (!m_ptr) { if (!m_ptr) {
throw error_already_set(); throw error_already_set();
} }
} }
/// Capsule name is nullptr.
capsule(const void *value, void (*destructor)(void *)) { capsule(const void *value, void (*destructor)(void *)) {
m_ptr = PyCapsule_New(const_cast<void *>(value), nullptr, [](PyObject *o) { initialize_with_void_ptr_destructor(value, nullptr, destructor);
// guard if destructor called while err indicator is set }
error_scope error_guard;
auto destructor = reinterpret_cast<void (*)(void *)>(PyCapsule_GetContext(o));
if (destructor == nullptr) {
if (PyErr_Occurred()) {
throw error_already_set();
}
pybind11_fail("Unable to get capsule context");
}
const char *name = get_name_in_error_scope(o);
void *ptr = PyCapsule_GetPointer(o, name);
if (ptr == nullptr) {
throw error_already_set();
}
destructor(ptr);
});
if (!m_ptr || PyCapsule_SetContext(m_ptr, (void *) destructor) != 0) { capsule(const void *value, const char *name, void (*destructor)(void *)) {
throw error_already_set(); initialize_with_void_ptr_destructor(value, name, destructor);
}
} }
explicit capsule(void (*destructor)()) { explicit capsule(void (*destructor)()) {
@ -1889,6 +2004,32 @@ private:
return name; return name;
} }
void initialize_with_void_ptr_destructor(const void *value,
const char *name,
void (*destructor)(void *)) {
m_ptr = PyCapsule_New(const_cast<void *>(value), name, [](PyObject *o) {
// guard if destructor called while err indicator is set
error_scope error_guard;
auto destructor = reinterpret_cast<void (*)(void *)>(PyCapsule_GetContext(o));
if (destructor == nullptr && PyErr_Occurred()) {
throw error_already_set();
}
const char *name = get_name_in_error_scope(o);
void *ptr = PyCapsule_GetPointer(o, name);
if (ptr == nullptr) {
throw error_already_set();
}
if (destructor != nullptr) {
destructor(ptr);
}
});
if (!m_ptr || PyCapsule_SetContext(m_ptr, reinterpret_cast<void *>(destructor)) != 0) {
throw error_already_set();
}
}
}; };
class tuple : public object { class tuple : public object {
@ -1943,7 +2084,11 @@ public:
void clear() /* py-non-const */ { PyDict_Clear(ptr()); } void clear() /* py-non-const */ { PyDict_Clear(ptr()); }
template <typename T> template <typename T>
bool contains(T &&key) const { bool contains(T &&key) const {
return PyDict_Contains(m_ptr, detail::object_or_cast(std::forward<T>(key)).ptr()) == 1; auto result = PyDict_Contains(m_ptr, detail::object_or_cast(std::forward<T>(key)).ptr());
if (result == -1) {
throw error_already_set();
}
return result == 1;
} }
private: private:
@ -1998,14 +2143,20 @@ public:
detail::list_iterator end() const { return {*this, PyList_GET_SIZE(m_ptr)}; } detail::list_iterator end() const { return {*this, PyList_GET_SIZE(m_ptr)}; }
template <typename T> template <typename T>
void append(T &&val) /* py-non-const */ { void append(T &&val) /* py-non-const */ {
PyList_Append(m_ptr, detail::object_or_cast(std::forward<T>(val)).ptr()); if (PyList_Append(m_ptr, detail::object_or_cast(std::forward<T>(val)).ptr()) != 0) {
throw error_already_set();
}
} }
template <typename IdxType, template <typename IdxType,
typename ValType, typename ValType,
detail::enable_if_t<std::is_integral<IdxType>::value, int> = 0> detail::enable_if_t<std::is_integral<IdxType>::value, int> = 0>
void insert(const IdxType &index, ValType &&val) /* py-non-const */ { void insert(const IdxType &index, ValType &&val) /* py-non-const */ {
PyList_Insert( if (PyList_Insert(m_ptr,
m_ptr, ssize_t_cast(index), detail::object_or_cast(std::forward<ValType>(val)).ptr()); ssize_t_cast(index),
detail::object_or_cast(std::forward<ValType>(val)).ptr())
!= 0) {
throw error_already_set();
}
} }
}; };
@ -2023,7 +2174,11 @@ public:
bool empty() const { return size() == 0; } bool empty() const { return size() == 0; }
template <typename T> template <typename T>
bool contains(T &&val) const { bool contains(T &&val) const {
return PySet_Contains(m_ptr, detail::object_or_cast(std::forward<T>(val)).ptr()) == 1; auto result = PySet_Contains(m_ptr, detail::object_or_cast(std::forward<T>(val)).ptr());
if (result == -1) {
throw error_already_set();
}
return result == 1;
} }
}; };
@ -2364,29 +2519,39 @@ bool object_api<D>::rich_compare(object_api const &other, int value) const {
return result; \ return result; \
} }
#define PYBIND11_MATH_OPERATOR_BINARY_INPLACE(iop, fn) \
template <typename D> \
object object_api<D>::iop(object_api const &other) { \
object result = reinterpret_steal<object>(fn(derived().ptr(), other.derived().ptr())); \
if (!result.ptr()) \
throw error_already_set(); \
return result; \
}
PYBIND11_MATH_OPERATOR_UNARY(operator~, PyNumber_Invert) PYBIND11_MATH_OPERATOR_UNARY(operator~, PyNumber_Invert)
PYBIND11_MATH_OPERATOR_UNARY(operator-, PyNumber_Negative) PYBIND11_MATH_OPERATOR_UNARY(operator-, PyNumber_Negative)
PYBIND11_MATH_OPERATOR_BINARY(operator+, PyNumber_Add) PYBIND11_MATH_OPERATOR_BINARY(operator+, PyNumber_Add)
PYBIND11_MATH_OPERATOR_BINARY(operator+=, PyNumber_InPlaceAdd) PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator+=, PyNumber_InPlaceAdd)
PYBIND11_MATH_OPERATOR_BINARY(operator-, PyNumber_Subtract) PYBIND11_MATH_OPERATOR_BINARY(operator-, PyNumber_Subtract)
PYBIND11_MATH_OPERATOR_BINARY(operator-=, PyNumber_InPlaceSubtract) PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator-=, PyNumber_InPlaceSubtract)
PYBIND11_MATH_OPERATOR_BINARY(operator*, PyNumber_Multiply) PYBIND11_MATH_OPERATOR_BINARY(operator*, PyNumber_Multiply)
PYBIND11_MATH_OPERATOR_BINARY(operator*=, PyNumber_InPlaceMultiply) PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator*=, PyNumber_InPlaceMultiply)
PYBIND11_MATH_OPERATOR_BINARY(operator/, PyNumber_TrueDivide) PYBIND11_MATH_OPERATOR_BINARY(operator/, PyNumber_TrueDivide)
PYBIND11_MATH_OPERATOR_BINARY(operator/=, PyNumber_InPlaceTrueDivide) PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator/=, PyNumber_InPlaceTrueDivide)
PYBIND11_MATH_OPERATOR_BINARY(operator|, PyNumber_Or) PYBIND11_MATH_OPERATOR_BINARY(operator|, PyNumber_Or)
PYBIND11_MATH_OPERATOR_BINARY(operator|=, PyNumber_InPlaceOr) PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator|=, PyNumber_InPlaceOr)
PYBIND11_MATH_OPERATOR_BINARY(operator&, PyNumber_And) PYBIND11_MATH_OPERATOR_BINARY(operator&, PyNumber_And)
PYBIND11_MATH_OPERATOR_BINARY(operator&=, PyNumber_InPlaceAnd) PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator&=, PyNumber_InPlaceAnd)
PYBIND11_MATH_OPERATOR_BINARY(operator^, PyNumber_Xor) PYBIND11_MATH_OPERATOR_BINARY(operator^, PyNumber_Xor)
PYBIND11_MATH_OPERATOR_BINARY(operator^=, PyNumber_InPlaceXor) PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator^=, PyNumber_InPlaceXor)
PYBIND11_MATH_OPERATOR_BINARY(operator<<, PyNumber_Lshift) PYBIND11_MATH_OPERATOR_BINARY(operator<<, PyNumber_Lshift)
PYBIND11_MATH_OPERATOR_BINARY(operator<<=, PyNumber_InPlaceLshift) PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator<<=, PyNumber_InPlaceLshift)
PYBIND11_MATH_OPERATOR_BINARY(operator>>, PyNumber_Rshift) PYBIND11_MATH_OPERATOR_BINARY(operator>>, PyNumber_Rshift)
PYBIND11_MATH_OPERATOR_BINARY(operator>>=, PyNumber_InPlaceRshift) PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator>>=, PyNumber_InPlaceRshift)
#undef PYBIND11_MATH_OPERATOR_UNARY #undef PYBIND11_MATH_OPERATOR_UNARY
#undef PYBIND11_MATH_OPERATOR_BINARY #undef PYBIND11_MATH_OPERATOR_BINARY
#undef PYBIND11_MATH_OPERATOR_BINARY_INPLACE
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@ -45,21 +45,35 @@ using forwarded_type = conditional_t<std::is_lvalue_reference<T>::value,
/// Forwards a value U as rvalue or lvalue according to whether T is rvalue or lvalue; typically /// Forwards a value U as rvalue or lvalue according to whether T is rvalue or lvalue; typically
/// used for forwarding a container's elements. /// used for forwarding a container's elements.
template <typename T, typename U> template <typename T, typename U>
forwarded_type<T, U> forward_like(U &&u) { constexpr forwarded_type<T, U> forward_like(U &&u) {
return std::forward<detail::forwarded_type<T, U>>(std::forward<U>(u)); return std::forward<detail::forwarded_type<T, U>>(std::forward<U>(u));
} }
// Checks if a container has a STL style reserve method.
// This will only return true for a `reserve()` with a `void` return.
template <typename C>
using has_reserve_method = std::is_same<decltype(std::declval<C>().reserve(0)), void>;
template <typename Type, typename Key> template <typename Type, typename Key>
struct set_caster { struct set_caster {
using type = Type; using type = Type;
using key_conv = make_caster<Key>; using key_conv = make_caster<Key>;
private:
template <typename T = Type, enable_if_t<has_reserve_method<T>::value, int> = 0>
void reserve_maybe(const anyset &s, Type *) {
value.reserve(s.size());
}
void reserve_maybe(const anyset &, void *) {}
public:
bool load(handle src, bool convert) { bool load(handle src, bool convert) {
if (!isinstance<anyset>(src)) { if (!isinstance<anyset>(src)) {
return false; return false;
} }
auto s = reinterpret_borrow<anyset>(src); auto s = reinterpret_borrow<anyset>(src);
value.clear(); value.clear();
reserve_maybe(s, &value);
for (auto entry : s) { for (auto entry : s) {
key_conv conv; key_conv conv;
if (!conv.load(entry, convert)) { if (!conv.load(entry, convert)) {
@ -78,7 +92,7 @@ struct set_caster {
pybind11::set s; pybind11::set s;
for (auto &&value : src) { for (auto &&value : src) {
auto value_ = reinterpret_steal<object>( auto value_ = reinterpret_steal<object>(
key_conv::cast(forward_like<T>(value), policy, parent)); key_conv::cast(detail::forward_like<T>(value), policy, parent));
if (!value_ || !s.add(std::move(value_))) { if (!value_ || !s.add(std::move(value_))) {
return handle(); return handle();
} }
@ -94,12 +108,21 @@ struct map_caster {
using key_conv = make_caster<Key>; using key_conv = make_caster<Key>;
using value_conv = make_caster<Value>; using value_conv = make_caster<Value>;
private:
template <typename T = Type, enable_if_t<has_reserve_method<T>::value, int> = 0>
void reserve_maybe(const dict &d, Type *) {
value.reserve(d.size());
}
void reserve_maybe(const dict &, void *) {}
public:
bool load(handle src, bool convert) { bool load(handle src, bool convert) {
if (!isinstance<dict>(src)) { if (!isinstance<dict>(src)) {
return false; return false;
} }
auto d = reinterpret_borrow<dict>(src); auto d = reinterpret_borrow<dict>(src);
value.clear(); value.clear();
reserve_maybe(d, &value);
for (auto it : d) { for (auto it : d) {
key_conv kconv; key_conv kconv;
value_conv vconv; value_conv vconv;
@ -122,9 +145,9 @@ struct map_caster {
} }
for (auto &&kv : src) { for (auto &&kv : src) {
auto key = reinterpret_steal<object>( auto key = reinterpret_steal<object>(
key_conv::cast(forward_like<T>(kv.first), policy_key, parent)); key_conv::cast(detail::forward_like<T>(kv.first), policy_key, parent));
auto value = reinterpret_steal<object>( auto value = reinterpret_steal<object>(
value_conv::cast(forward_like<T>(kv.second), policy_value, parent)); value_conv::cast(detail::forward_like<T>(kv.second), policy_value, parent));
if (!key || !value) { if (!key || !value) {
return handle(); return handle();
} }
@ -160,9 +183,7 @@ struct list_caster {
} }
private: private:
template < template <typename T = Type, enable_if_t<has_reserve_method<T>::value, int> = 0>
typename T = Type,
enable_if_t<std::is_same<decltype(std::declval<T>().reserve(0)), void>::value, int> = 0>
void reserve_maybe(const sequence &s, Type *) { void reserve_maybe(const sequence &s, Type *) {
value.reserve(s.size()); value.reserve(s.size());
} }
@ -178,7 +199,7 @@ public:
ssize_t index = 0; ssize_t index = 0;
for (auto &&value : src) { for (auto &&value : src) {
auto value_ = reinterpret_steal<object>( auto value_ = reinterpret_steal<object>(
value_conv::cast(forward_like<T>(value), policy, parent)); value_conv::cast(detail::forward_like<T>(value), policy, parent));
if (!value_) { if (!value_) {
return handle(); return handle();
} }
@ -242,7 +263,7 @@ public:
ssize_t index = 0; ssize_t index = 0;
for (auto &&value : src) { for (auto &&value : src) {
auto value_ = reinterpret_steal<object>( auto value_ = reinterpret_steal<object>(
value_conv::cast(forward_like<T>(value), policy, parent)); value_conv::cast(detail::forward_like<T>(value), policy, parent));
if (!value_) { if (!value_) {
return handle(); return handle();
} }
@ -252,11 +273,11 @@ public:
} }
PYBIND11_TYPE_CASTER(ArrayType, PYBIND11_TYPE_CASTER(ArrayType,
const_name("List[") + value_conv::name const_name<Resizable>(const_name(""), const_name("Annotated["))
+ const_name("List[") + value_conv::name + const_name("]")
+ const_name<Resizable>(const_name(""), + const_name<Resizable>(const_name(""),
const_name("[") + const_name<Size>() const_name(", FixedSize(")
+ const_name("]")) + const_name<Size>() + const_name(")]")));
+ const_name("]"));
}; };
template <typename Type, size_t Size> template <typename Type, size_t Size>
@ -290,11 +311,12 @@ struct optional_caster {
template <typename T> template <typename T>
static handle cast(T &&src, return_value_policy policy, handle parent) { static handle cast(T &&src, return_value_policy policy, handle parent) {
if (!src) { if (!src) {
return none().inc_ref(); return none().release();
} }
if (!std::is_lvalue_reference<T>::value) { if (!std::is_lvalue_reference<T>::value) {
policy = return_value_policy_override<Value>::policy(policy); policy = return_value_policy_override<Value>::policy(policy);
} }
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
return value_conv::cast(*std::forward<T>(src), policy, parent); return value_conv::cast(*std::forward<T>(src), policy, parent);
} }

View File

@ -10,10 +10,13 @@
#pragma once #pragma once
#include "detail/common.h" #include "detail/common.h"
#include "detail/type_caster_base.h"
#include "cast.h"
#include "operators.h" #include "operators.h"
#include <algorithm> #include <algorithm>
#include <sstream> #include <sstream>
#include <type_traits>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
@ -58,9 +61,11 @@ struct is_comparable<
/* For a vector/map data structure, recursively check the value type /* For a vector/map data structure, recursively check the value type
(which is std::pair for maps) */ (which is std::pair for maps) */
template <typename T> template <typename T>
struct is_comparable<T, enable_if_t<container_traits<T>::is_vector>> { struct is_comparable<T, enable_if_t<container_traits<T>::is_vector>>
static constexpr const bool value = is_comparable<typename T::value_type>::value; : is_comparable<typename recursive_container_traits<T>::type_to_check_recursively> {};
};
template <>
struct is_comparable<recursive_bottom> : std::true_type {};
/* For pairs, recursively check the two data types */ /* For pairs, recursively check the two data types */
template <typename T> template <typename T>
@ -352,13 +357,17 @@ void vector_accessor(enable_if_t<vector_needs_copy<Vector>::value, Class_> &cl)
using DiffType = typename Vector::difference_type; using DiffType = typename Vector::difference_type;
using ItType = typename Vector::iterator; using ItType = typename Vector::iterator;
cl.def("__getitem__", [](const Vector &v, DiffType i) -> T { cl.def("__getitem__", [](const Vector &v, DiffType i) -> T {
if (i < 0 && (i += v.size()) < 0) { if (i < 0) {
i += v.size();
if (i < 0) {
throw index_error();
}
}
auto i_st = static_cast<SizeType>(i);
if (i_st >= v.size()) {
throw index_error(); throw index_error();
} }
if ((SizeType) i >= v.size()) { return v[i_st];
throw index_error();
}
return v[(SizeType) i];
}); });
cl.def( cl.def(
@ -636,18 +645,52 @@ auto map_if_insertion_operator(Class_ &cl, std::string const &name)
"Return the canonical string representation of this map."); "Return the canonical string representation of this map.");
} }
template <typename Map> template <typename KeyType>
struct keys_view { struct keys_view {
Map &map; virtual size_t len() = 0;
virtual iterator iter() = 0;
virtual bool contains(const KeyType &k) = 0;
virtual bool contains(const object &k) = 0;
virtual ~keys_view() = default;
}; };
template <typename Map> template <typename MappedType>
struct values_view { struct values_view {
virtual size_t len() = 0;
virtual iterator iter() = 0;
virtual ~values_view() = default;
};
template <typename KeyType, typename MappedType>
struct items_view {
virtual size_t len() = 0;
virtual iterator iter() = 0;
virtual ~items_view() = default;
};
template <typename Map, typename KeysView>
struct KeysViewImpl : public KeysView {
explicit KeysViewImpl(Map &map) : map(map) {}
size_t len() override { return map.size(); }
iterator iter() override { return make_key_iterator(map.begin(), map.end()); }
bool contains(const typename Map::key_type &k) override { return map.find(k) != map.end(); }
bool contains(const object &) override { return false; }
Map &map; Map &map;
}; };
template <typename Map> template <typename Map, typename ValuesView>
struct items_view { struct ValuesViewImpl : public ValuesView {
explicit ValuesViewImpl(Map &map) : map(map) {}
size_t len() override { return map.size(); }
iterator iter() override { return make_value_iterator(map.begin(), map.end()); }
Map &map;
};
template <typename Map, typename ItemsView>
struct ItemsViewImpl : public ItemsView {
explicit ItemsViewImpl(Map &map) : map(map) {}
size_t len() override { return map.size(); }
iterator iter() override { return make_iterator(map.begin(), map.end()); }
Map &map; Map &map;
}; };
@ -657,9 +700,11 @@ template <typename Map, typename holder_type = std::unique_ptr<Map>, typename...
class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&...args) { class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&...args) {
using KeyType = typename Map::key_type; using KeyType = typename Map::key_type;
using MappedType = typename Map::mapped_type; using MappedType = typename Map::mapped_type;
using KeysView = detail::keys_view<Map>; using StrippedKeyType = detail::remove_cvref_t<KeyType>;
using ValuesView = detail::values_view<Map>; using StrippedMappedType = detail::remove_cvref_t<MappedType>;
using ItemsView = detail::items_view<Map>; using KeysView = detail::keys_view<StrippedKeyType>;
using ValuesView = detail::values_view<StrippedMappedType>;
using ItemsView = detail::items_view<StrippedKeyType, StrippedMappedType>;
using Class_ = class_<Map, holder_type>; using Class_ = class_<Map, holder_type>;
// If either type is a non-module-local bound type then make the map binding non-local as well; // If either type is a non-module-local bound type then make the map binding non-local as well;
@ -673,12 +718,57 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&
} }
Class_ cl(scope, name.c_str(), pybind11::module_local(local), std::forward<Args>(args)...); Class_ cl(scope, name.c_str(), pybind11::module_local(local), std::forward<Args>(args)...);
class_<KeysView> keys_view( static constexpr auto key_type_descr = detail::make_caster<KeyType>::name;
scope, ("KeysView[" + name + "]").c_str(), pybind11::module_local(local)); static constexpr auto mapped_type_descr = detail::make_caster<MappedType>::name;
class_<ValuesView> values_view( std::string key_type_name(key_type_descr.text), mapped_type_name(mapped_type_descr.text);
scope, ("ValuesView[" + name + "]").c_str(), pybind11::module_local(local));
class_<ItemsView> items_view( // If key type isn't properly wrapped, fall back to C++ names
scope, ("ItemsView[" + name + "]").c_str(), pybind11::module_local(local)); if (key_type_name == "%") {
key_type_name = detail::type_info_description(typeid(KeyType));
}
// Similarly for value type:
if (mapped_type_name == "%") {
mapped_type_name = detail::type_info_description(typeid(MappedType));
}
// Wrap KeysView[KeyType] if it wasn't already wrapped
if (!detail::get_type_info(typeid(KeysView))) {
class_<KeysView> keys_view(
scope, ("KeysView[" + key_type_name + "]").c_str(), pybind11::module_local(local));
keys_view.def("__len__", &KeysView::len);
keys_view.def("__iter__",
&KeysView::iter,
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);
keys_view.def("__contains__",
static_cast<bool (KeysView::*)(const KeyType &)>(&KeysView::contains));
// Fallback for when the object is not of the key type
keys_view.def("__contains__",
static_cast<bool (KeysView::*)(const object &)>(&KeysView::contains));
}
// Similarly for ValuesView:
if (!detail::get_type_info(typeid(ValuesView))) {
class_<ValuesView> values_view(scope,
("ValuesView[" + mapped_type_name + "]").c_str(),
pybind11::module_local(local));
values_view.def("__len__", &ValuesView::len);
values_view.def("__iter__",
&ValuesView::iter,
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);
}
// Similarly for ItemsView:
if (!detail::get_type_info(typeid(ItemsView))) {
class_<ItemsView> items_view(
scope,
("ItemsView[" + key_type_name + ", ").append(mapped_type_name + "]").c_str(),
pybind11::module_local(local));
items_view.def("__len__", &ItemsView::len);
items_view.def("__iter__",
&ItemsView::iter,
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);
}
cl.def(init<>()); cl.def(init<>());
@ -698,19 +788,25 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&
cl.def( cl.def(
"keys", "keys",
[](Map &m) { return KeysView{m}; }, [](Map &m) {
return std::unique_ptr<KeysView>(new detail::KeysViewImpl<Map, KeysView>(m));
},
keep_alive<0, 1>() /* Essential: keep map alive while view exists */ keep_alive<0, 1>() /* Essential: keep map alive while view exists */
); );
cl.def( cl.def(
"values", "values",
[](Map &m) { return ValuesView{m}; }, [](Map &m) {
return std::unique_ptr<ValuesView>(new detail::ValuesViewImpl<Map, ValuesView>(m));
},
keep_alive<0, 1>() /* Essential: keep map alive while view exists */ keep_alive<0, 1>() /* Essential: keep map alive while view exists */
); );
cl.def( cl.def(
"items", "items",
[](Map &m) { return ItemsView{m}; }, [](Map &m) {
return std::unique_ptr<ItemsView>(new detail::ItemsViewImpl<Map, ItemsView>(m));
},
keep_alive<0, 1>() /* Essential: keep map alive while view exists */ keep_alive<0, 1>() /* Essential: keep map alive while view exists */
); );
@ -749,36 +845,6 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&
cl.def("__len__", &Map::size); cl.def("__len__", &Map::size);
keys_view.def("__len__", [](KeysView &view) { return view.map.size(); });
keys_view.def(
"__iter__",
[](KeysView &view) { return make_key_iterator(view.map.begin(), view.map.end()); },
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);
keys_view.def("__contains__", [](KeysView &view, const KeyType &k) -> bool {
auto it = view.map.find(k);
if (it == view.map.end()) {
return false;
}
return true;
});
// Fallback for when the object is not of the key type
keys_view.def("__contains__", [](KeysView &, const object &) -> bool { return false; });
values_view.def("__len__", [](ValuesView &view) { return view.map.size(); });
values_view.def(
"__iter__",
[](ValuesView &view) { return make_value_iterator(view.map.begin(), view.map.end()); },
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);
items_view.def("__len__", [](ItemsView &view) { return view.map.size(); });
items_view.def(
"__iter__",
[](ItemsView &view) { return make_iterator(view.map.begin(), view.map.end()); },
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);
return cl; return cl;
} }

View File

@ -0,0 +1,61 @@
// Copyright (c) 2023 The pybind Community.
#pragma once
#include "detail/common.h"
#include "detail/descr.h"
#include "cast.h"
#include "pytypes.h"
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
template <>
class type_caster<PyObject> {
public:
static constexpr auto name = const_name("object"); // See discussion under PR #4601.
// This overload is purely to guard against accidents.
template <typename T,
detail::enable_if_t<!is_same_ignoring_cvref<T, PyObject *>::value, int> = 0>
static handle cast(T &&, return_value_policy, handle /*parent*/) {
static_assert(is_same_ignoring_cvref<T, PyObject *>::value,
"Invalid C++ type T for to-Python conversion (type_caster<PyObject>).");
return nullptr; // Unreachable.
}
static handle cast(PyObject *src, return_value_policy policy, handle /*parent*/) {
if (src == nullptr) {
throw error_already_set();
}
if (PyErr_Occurred()) {
raise_from(PyExc_SystemError, "src != nullptr but PyErr_Occurred()");
throw error_already_set();
}
if (policy == return_value_policy::take_ownership) {
return src;
}
if (policy == return_value_policy::reference
|| policy == return_value_policy::automatic_reference) {
return handle(src).inc_ref();
}
pybind11_fail("type_caster<PyObject>::cast(): unsupported return_value_policy: "
+ std::to_string(static_cast<int>(policy)));
}
bool load(handle src, bool) {
value = reinterpret_borrow<object>(src);
return true;
}
template <typename T>
using cast_op_type = PyObject *;
explicit operator PyObject *() { return value.ptr(); }
private:
object value;
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@ -5,7 +5,17 @@ import nox
nox.needs_version = ">=2022.1.7" nox.needs_version = ">=2022.1.7"
nox.options.sessions = ["lint", "tests", "tests_packaging"] nox.options.sessions = ["lint", "tests", "tests_packaging"]
PYTHON_VERISONS = ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "pypy3.7", "pypy3.8"] PYTHON_VERSIONS = [
"3.6",
"3.7",
"3.8",
"3.9",
"3.10",
"3.11",
"pypy3.7",
"pypy3.8",
"pypy3.9",
]
if os.environ.get("CI", None): if os.environ.get("CI", None):
nox.options.error_on_missing_interpreters = True nox.options.error_on_missing_interpreters = True
@ -17,10 +27,10 @@ def lint(session: nox.Session) -> None:
Lint the codebase (except for clang-format/tidy). Lint the codebase (except for clang-format/tidy).
""" """
session.install("pre-commit") session.install("pre-commit")
session.run("pre-commit", "run", "-a") session.run("pre-commit", "run", "-a", *session.posargs)
@nox.session(python=PYTHON_VERISONS) @nox.session(python=PYTHON_VERSIONS)
def tests(session: nox.Session) -> None: def tests(session: nox.Session) -> None:
""" """
Run the tests (requires a compiler). Run the tests (requires a compiler).
@ -48,7 +58,7 @@ def tests_packaging(session: nox.Session) -> None:
""" """
session.install("-r", "tests/requirements.txt", "--prefer-binary") session.install("-r", "tests/requirements.txt", "--prefer-binary")
session.run("pytest", "tests/extra_python_package") session.run("pytest", "tests/extra_python_package", *session.posargs)
@nox.session(reuse_venv=True) @nox.session(reuse_venv=True)

View File

@ -1,16 +1,17 @@
import sys import sys
if sys.version_info < (3, 6): if sys.version_info < (3, 6): # noqa: UP036
msg = "pybind11 does not support Python < 3.6. 2.9 was the last release supporting Python 2.7 and 3.5." msg = "pybind11 does not support Python < 3.6. 2.9 was the last release supporting Python 2.7 and 3.5."
raise ImportError(msg) raise ImportError(msg)
from ._version import __version__, version_info from ._version import __version__, version_info
from .commands import get_cmake_dir, get_include from .commands import get_cmake_dir, get_include, get_pkgconfig_dir
__all__ = ( __all__ = (
"version_info", "version_info",
"__version__", "__version__",
"get_include", "get_include",
"get_cmake_dir", "get_cmake_dir",
"get_pkgconfig_dir",
) )

View File

@ -4,7 +4,8 @@ import argparse
import sys import sys
import sysconfig import sysconfig
from .commands import get_cmake_dir, get_include from ._version import __version__
from .commands import get_cmake_dir, get_include, get_pkgconfig_dir
def print_includes() -> None: def print_includes() -> None:
@ -24,8 +25,13 @@ def print_includes() -> None:
def main() -> None: def main() -> None:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument(
"--version",
action="version",
version=__version__,
help="Print the version and exit.",
)
parser.add_argument( parser.add_argument(
"--includes", "--includes",
action="store_true", action="store_true",
@ -36,6 +42,11 @@ def main() -> None:
action="store_true", action="store_true",
help="Print the CMake module directory, ideal for setting -Dpybind11_ROOT in CMake.", help="Print the CMake module directory, ideal for setting -Dpybind11_ROOT in CMake.",
) )
parser.add_argument(
"--pkgconfigdir",
action="store_true",
help="Print the pkgconfig directory, ideal for setting $PKG_CONFIG_PATH.",
)
args = parser.parse_args() args = parser.parse_args()
if not sys.argv[1:]: if not sys.argv[1:]:
parser.print_help() parser.print_help()
@ -43,6 +54,8 @@ def main() -> None:
print_includes() print_includes()
if args.cmakedir: if args.cmakedir:
print(get_cmake_dir()) print(get_cmake_dir())
if args.pkgconfigdir:
print(get_pkgconfig_dir())
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -8,5 +8,5 @@ def _to_int(s: str) -> Union[int, str]:
return s return s
__version__ = "2.10.0" __version__ = "2.11.1"
version_info = tuple(_to_int(s) for s in __version__.split(".")) version_info = tuple(_to_int(s) for s in __version__.split("."))

View File

@ -3,7 +3,7 @@ import os
DIR = os.path.abspath(os.path.dirname(__file__)) DIR = os.path.abspath(os.path.dirname(__file__))
def get_include(user: bool = False) -> str: # pylint: disable=unused-argument def get_include(user: bool = False) -> str: # noqa: ARG001
""" """
Return the path to the pybind11 include directory. The historical "user" Return the path to the pybind11 include directory. The historical "user"
argument is unused, and may be removed. argument is unused, and may be removed.
@ -23,3 +23,15 @@ def get_cmake_dir() -> str:
msg = "pybind11 not installed, installation required to access the CMake files" msg = "pybind11 not installed, installation required to access the CMake files"
raise ImportError(msg) raise ImportError(msg)
def get_pkgconfig_dir() -> str:
"""
Return the path to the pybind11 pkgconfig directory.
"""
pkgconfig_installed_path = os.path.join(DIR, "share", "pkgconfig")
if os.path.exists(pkgconfig_installed_path):
return pkgconfig_installed_path
msg = "pybind11 not installed, installation required to access the pkgconfig files"
raise ImportError(msg)

View File

@ -66,8 +66,8 @@ try:
from setuptools import Extension as _Extension from setuptools import Extension as _Extension
from setuptools.command.build_ext import build_ext as _build_ext from setuptools.command.build_ext import build_ext as _build_ext
except ImportError: except ImportError:
from distutils.command.build_ext import build_ext as _build_ext from distutils.command.build_ext import build_ext as _build_ext # type: ignore[assignment]
from distutils.extension import Extension as _Extension from distutils.extension import Extension as _Extension # type: ignore[assignment]
import distutils.ccompiler import distutils.ccompiler
import distutils.errors import distutils.errors
@ -84,7 +84,7 @@ STD_TMPL = "/std:c++{}" if WIN else "-std=c++{}"
# directory into your path if it sits beside your setup.py. # directory into your path if it sits beside your setup.py.
class Pybind11Extension(_Extension): # type: ignore[misc] class Pybind11Extension(_Extension):
""" """
Build a C++11+ Extension module with pybind11. This automatically adds the Build a C++11+ Extension module with pybind11. This automatically adds the
recommended flags when you init the extension and assumes C++ sources - you recommended flags when you init the extension and assumes C++ sources - you
@ -118,7 +118,6 @@ class Pybind11Extension(_Extension): # type: ignore[misc]
self.extra_link_args[:0] = flags self.extra_link_args[:0] = flags
def __init__(self, *args: Any, **kwargs: Any) -> None: def __init__(self, *args: Any, **kwargs: Any) -> None:
self._cxx_level = 0 self._cxx_level = 0
cxx_std = kwargs.pop("cxx_std", 0) cxx_std = kwargs.pop("cxx_std", 0)
@ -145,7 +144,6 @@ class Pybind11Extension(_Extension): # type: ignore[misc]
self.cxx_std = cxx_std self.cxx_std = cxx_std
cflags = [] cflags = []
ldflags = []
if WIN: if WIN:
cflags += ["/EHsc", "/bigobj"] cflags += ["/EHsc", "/bigobj"]
else: else:
@ -155,11 +153,7 @@ class Pybind11Extension(_Extension): # type: ignore[misc]
c_cpp_flags = shlex.split(env_cflags) + shlex.split(env_cppflags) c_cpp_flags = shlex.split(env_cflags) + shlex.split(env_cppflags)
if not any(opt.startswith("-g") for opt in c_cpp_flags): if not any(opt.startswith("-g") for opt in c_cpp_flags):
cflags += ["-g0"] cflags += ["-g0"]
if MACOS:
cflags += ["-stdlib=libc++"]
ldflags += ["-stdlib=libc++"]
self._add_cflags(cflags) self._add_cflags(cflags)
self._add_ldflags(ldflags)
@property @property
def cxx_std(self) -> int: def cxx_std(self) -> int:
@ -174,9 +168,10 @@ class Pybind11Extension(_Extension): # type: ignore[misc]
@cxx_std.setter @cxx_std.setter
def cxx_std(self, level: int) -> None: def cxx_std(self, level: int) -> None:
if self._cxx_level: if self._cxx_level:
warnings.warn("You cannot safely change the cxx_level after setting it!") warnings.warn(
"You cannot safely change the cxx_level after setting it!", stacklevel=2
)
# MSVC 2015 Update 3 and later only have 14 (and later 17) modes, so # MSVC 2015 Update 3 and later only have 14 (and later 17) modes, so
# force a valid flag here. # force a valid flag here.
@ -271,7 +266,7 @@ def auto_cpp_level(compiler: Any) -> Union[str, int]:
raise RuntimeError(msg) raise RuntimeError(msg)
class build_ext(_build_ext): # type: ignore[misc] # noqa: N801 class build_ext(_build_ext): # noqa: N801
""" """
Customized build_ext that allows an auto-search for the highest supported Customized build_ext that allows an auto-search for the highest supported
C++ level for Pybind11Extension. This is only needed for the auto-search C++ level for Pybind11Extension. This is only needed for the auto-search
@ -341,7 +336,7 @@ def naive_recompile(obj: str, src: str) -> bool:
return os.stat(obj).st_mtime < os.stat(src).st_mtime return os.stat(obj).st_mtime < os.stat(src).st_mtime
def no_recompile(obg: str, src: str) -> bool: # pylint: disable=unused-argument def no_recompile(obg: str, src: str) -> bool: # noqa: ARG001
""" """
This is the safest but slowest choice (and is the default) - will always This is the safest but slowest choice (and is the default) - will always
recompile sources. recompile sources.
@ -439,7 +434,6 @@ class ParallelCompile:
extra_postargs: Optional[List[str]] = None, extra_postargs: Optional[List[str]] = None,
depends: Optional[List[str]] = None, depends: Optional[List[str]] = None,
) -> Any: ) -> Any:
# These lines are directly from distutils.ccompiler.CCompiler # These lines are directly from distutils.ccompiler.CCompiler
macros, objects, extra_postargs, pp_opts, build = compiler._setup_compile( # type: ignore[attr-defined] macros, objects, extra_postargs, pp_opts, build = compiler._setup_compile( # type: ignore[attr-defined]
output_dir, macros, include_dirs, sources, depends, extra_postargs output_dir, macros, include_dirs, sources, depends, extra_postargs

View File

@ -2,6 +2,7 @@
requires = ["setuptools>=42", "cmake>=3.18", "ninja"] requires = ["setuptools>=42", "cmake>=3.18", "ninja"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[tool.check-manifest] [tool.check-manifest]
ignore = [ ignore = [
"tests/**", "tests/**",
@ -15,11 +16,6 @@ ignore = [
"noxfile.py", "noxfile.py",
] ]
[tool.isort]
# Needs the compiled .so modules and env.py from tests
known_first_party = "env,pybind11_cross_module_tests,pybind11_tests,"
# For black compatibility
profile = "black"
[tool.mypy] [tool.mypy]
files = ["pybind11"] files = ["pybind11"]
@ -30,7 +26,7 @@ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
warn_unreachable = true warn_unreachable = true
[[tool.mypy.overrides]] [[tool.mypy.overrides]]
module = ["ghapi.*", "setuptools.*"] module = ["ghapi.*"]
ignore_missing_imports = true ignore_missing_imports = true
@ -58,4 +54,45 @@ messages_control.disable = [
"invalid-name", "invalid-name",
"protected-access", "protected-access",
"missing-module-docstring", "missing-module-docstring",
"unused-argument", # covered by Ruff ARG
] ]
[tool.ruff]
select = [
"E", "F", "W", # flake8
"B", # flake8-bugbear
"I", # isort
"N", # pep8-naming
"ARG", # flake8-unused-arguments
"C4", # flake8-comprehensions
"EM", # flake8-errmsg
"ICN", # flake8-import-conventions
"ISC", # flake8-implicit-str-concat
"PGH", # pygrep-hooks
"PIE", # flake8-pie
"PL", # pylint
"PT", # flake8-pytest-style
"RET", # flake8-return
"RUF100", # Ruff-specific
"SIM", # flake8-simplify
"UP", # pyupgrade
"YTT", # flake8-2020
]
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())
]
target-version = "py37"
src = ["src"]
unfixable = ["T20"]
exclude = []
line-length = 120
isort.known-first-party = ["env", "pybind11_cross_module_tests", "pybind11_tests"]
[tool.ruff.per-file-ignores]
"tests/**" = ["EM", "N"]
"tests/test_call_policies.py" = ["PLC1901"]

View File

@ -20,6 +20,7 @@ classifiers =
Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
License :: OSI Approved :: BSD License License :: OSI Approved :: BSD License
Programming Language :: Python :: Implementation :: PyPy Programming Language :: Python :: Implementation :: PyPy
Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: CPython
@ -40,11 +41,3 @@ project_urls =
[options] [options]
python_requires = >=3.6 python_requires = >=3.6
zip_safe = False zip_safe = False
[flake8]
max-line-length = 120
show_source = True
exclude = .git, __pycache__, build, dist, docs, tools, venv
extend-ignore = E203, E722, B950
extend-select = B9

View File

@ -96,7 +96,7 @@ def get_and_replace(
# Use our input files instead when making the SDist (and anything that depends # Use our input files instead when making the SDist (and anything that depends
# on it, like a wheel) # on it, like a wheel)
class SDist(setuptools.command.sdist.sdist): # type: ignore[misc] class SDist(setuptools.command.sdist.sdist):
def make_release_tree(self, base_dir: str, files: List[str]) -> None: def make_release_tree(self, base_dir: str, files: List[str]) -> None:
super().make_release_tree(base_dir, files) super().make_release_tree(base_dir, files)
@ -127,6 +127,7 @@ with remove_output("pybind11/include", "pybind11/share"):
"-DCMAKE_INSTALL_PREFIX=pybind11", "-DCMAKE_INSTALL_PREFIX=pybind11",
"-DBUILD_TESTING=OFF", "-DBUILD_TESTING=OFF",
"-DPYBIND11_NOPYTHON=ON", "-DPYBIND11_NOPYTHON=ON",
"-Dprefix_for_pc_file=${pcfiledir}/../../",
] ]
if "CMAKE_ARGS" in os.environ: if "CMAKE_ARGS" in os.environ:
fcommand = [ fcommand = [

View File

@ -5,20 +5,17 @@
# All rights reserved. Use of this source code is governed by a # All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file. # BSD-style license that can be found in the LICENSE file.
cmake_minimum_required(VERSION 3.4) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate # some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround: # the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.21) if(${CMAKE_VERSION} VERSION_LESS 3.26)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.21) cmake_policy(VERSION 3.26)
endif() endif()
# Only needed for CMake < 3.5 support
include(CMakeParseArguments)
# Filter out items; print an optional message if any items filtered. This ignores extensions. # Filter out items; print an optional message if any items filtered. This ignores extensions.
# #
# Usage: # Usage:
@ -128,7 +125,8 @@ set(PYBIND11_TEST_FILES
test_custom_type_casters test_custom_type_casters
test_custom_type_setup test_custom_type_setup
test_docstring_options test_docstring_options
test_eigen test_eigen_matrix
test_eigen_tensor
test_enum test_enum
test_eval test_eval
test_exceptions test_exceptions
@ -153,7 +151,11 @@ set(PYBIND11_TEST_FILES
test_stl_binders test_stl_binders
test_tagbased_polymorphic test_tagbased_polymorphic
test_thread test_thread
test_type_caster_pyobject_ptr
test_union test_union
test_unnamed_namespace_a
test_unnamed_namespace_b
test_vector_unique_ptr_member
test_virtual_functions) test_virtual_functions)
# Invoking cmake with something like: # Invoking cmake with something like:
@ -168,7 +170,7 @@ if(PYBIND11_TEST_OVERRIDE)
# This allows the override to be done with extensions, preserving backwards compatibility. # This allows the override to be done with extensions, preserving backwards compatibility.
foreach(test_name ${TEST_FILES_NO_EXT}) foreach(test_name ${TEST_FILES_NO_EXT})
if(NOT ${test_name} IN_LIST TEST_OVERRIDE_NO_EXT if(NOT ${test_name} IN_LIST TEST_OVERRIDE_NO_EXT
)# If not in the whitelist, add to be filtered out. )# If not in the allowlist, add to be filtered out.
list(APPEND PYBIND11_TEST_FILTER ${test_name}) list(APPEND PYBIND11_TEST_FILTER ${test_name})
endif() endif()
endforeach() endforeach()
@ -233,7 +235,10 @@ list(GET PYBIND11_EIGEN_VERSION_AND_HASH 1 PYBIND11_EIGEN_VERSION_HASH)
# Check if Eigen is available; if not, remove from PYBIND11_TEST_FILES (but # Check if Eigen is available; if not, remove from PYBIND11_TEST_FILES (but
# keep it in PYBIND11_PYTEST_FILES, so that we get the "eigen is not installed" # keep it in PYBIND11_PYTEST_FILES, so that we get the "eigen is not installed"
# skip message). # skip message).
list(FIND PYBIND11_TEST_FILES test_eigen.cpp PYBIND11_TEST_FILES_EIGEN_I) list(FIND PYBIND11_TEST_FILES test_eigen_matrix.cpp PYBIND11_TEST_FILES_EIGEN_I)
if(PYBIND11_TEST_FILES_EIGEN_I EQUAL -1)
list(FIND PYBIND11_TEST_FILES test_eigen_tensor.cpp PYBIND11_TEST_FILES_EIGEN_I)
endif()
if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
# Try loading via newer Eigen's Eigen3Config first (bypassing tools/FindEigen3.cmake). # 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 # Eigen 3.3.1+ exports a cmake 3.0+ target for handling dependency requirements, but also
@ -288,13 +293,34 @@ if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
set(EIGEN3_VERSION ${EIGEN3_VERSION_STRING}) set(EIGEN3_VERSION ${EIGEN3_VERSION_STRING})
endif() endif()
message(STATUS "Building tests with Eigen v${EIGEN3_VERSION}") message(STATUS "Building tests with Eigen v${EIGEN3_VERSION}")
if(NOT (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0))
tests_extra_targets("test_eigen_tensor.py" "eigen_tensor_avoid_stl_array")
endif()
else() else()
list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I}) list(FIND PYBIND11_TEST_FILES test_eigen_matrix.cpp PYBIND11_TEST_FILES_EIGEN_I)
if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I})
endif()
list(FIND PYBIND11_TEST_FILES test_eigen_tensor.cpp PYBIND11_TEST_FILES_EIGEN_I)
if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I})
endif()
message( message(
STATUS "Building tests WITHOUT Eigen, use -DDOWNLOAD_EIGEN=ON on CMake 3.11+ to download") STATUS "Building tests WITHOUT Eigen, use -DDOWNLOAD_EIGEN=ON on CMake 3.11+ to download")
endif() endif()
endif() endif()
# Some code doesn't support gcc 4
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0)
list(FIND PYBIND11_TEST_FILES test_eigen_tensor.cpp PYBIND11_TEST_FILES_EIGEN_I)
if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I})
endif()
endif()
# Optional dependency for some tests (boost::variant is only supported with version >= 1.56) # Optional dependency for some tests (boost::variant is only supported with version >= 1.56)
find_package(Boost 1.56) find_package(Boost 1.56)

View File

@ -7,13 +7,36 @@ Adds docstring and exceptions message sanitizers.
import contextlib import contextlib
import difflib import difflib
import gc import gc
import multiprocessing
import re import re
import sys
import textwrap import textwrap
import traceback
import pytest import pytest
# Early diagnostic for failed imports # Early diagnostic for failed imports
import pybind11_tests try:
import pybind11_tests
except Exception:
# pytest does not show the traceback without this.
traceback.print_exc()
raise
@pytest.fixture(scope="session", autouse=True)
def use_multiprocessing_forkserver_on_linux():
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
# In a nutshell: fork() after starting threads == flakiness in the form of deadlocks.
# It is actually a well-known pitfall, unfortunately without guard rails.
# "forkserver" is more performant than "spawn" (~9s vs ~13s for tests/test_gil_scoped.py,
# visit the issuecomment link above for details).
multiprocessing.set_start_method("forkserver")
_long_marker = re.compile(r"([0-9])L") _long_marker = re.compile(r"([0-9])L")
_hexadecimal = re.compile(r"0x[0-9a-fA-F]+") _hexadecimal = re.compile(r"0x[0-9a-fA-F]+")
@ -59,9 +82,8 @@ class Output:
b = _strip_and_dedent(other).splitlines() b = _strip_and_dedent(other).splitlines()
if a == b: if a == b:
return True return True
else: self.explanation = _make_explanation(a, b)
self.explanation = _make_explanation(a, b) return False
return False
class Unordered(Output): class Unordered(Output):
@ -72,9 +94,8 @@ class Unordered(Output):
b = _split_and_sort(other) b = _split_and_sort(other)
if a == b: if a == b:
return True return True
else: self.explanation = _make_explanation(a, b)
self.explanation = _make_explanation(a, b) return False
return False
class Capture: class Capture:
@ -95,9 +116,8 @@ class Capture:
b = other b = other
if a == b: if a == b:
return True return True
else: self.explanation = a.explanation
self.explanation = a.explanation return False
return False
def __str__(self): def __str__(self):
return self.out return self.out
@ -114,7 +134,7 @@ class Capture:
return Output(self.err) return Output(self.err)
@pytest.fixture @pytest.fixture()
def capture(capsys): def capture(capsys):
"""Extended `capsys` with context manager and custom equality operators""" """Extended `capsys` with context manager and custom equality operators"""
return Capture(capsys) return Capture(capsys)
@ -135,25 +155,22 @@ class SanitizedString:
b = _strip_and_dedent(other) b = _strip_and_dedent(other)
if a == b: if a == b:
return True return True
else: self.explanation = _make_explanation(a.splitlines(), b.splitlines())
self.explanation = _make_explanation(a.splitlines(), b.splitlines()) return False
return False
def _sanitize_general(s): def _sanitize_general(s):
s = s.strip() s = s.strip()
s = s.replace("pybind11_tests.", "m.") s = s.replace("pybind11_tests.", "m.")
s = _long_marker.sub(r"\1", s) return _long_marker.sub(r"\1", s)
return s
def _sanitize_docstring(thing): def _sanitize_docstring(thing):
s = thing.__doc__ s = thing.__doc__
s = _sanitize_general(s) return _sanitize_general(s)
return s
@pytest.fixture @pytest.fixture()
def doc(): def doc():
"""Sanitize docstrings and add custom failure explanation""" """Sanitize docstrings and add custom failure explanation"""
return SanitizedString(_sanitize_docstring) return SanitizedString(_sanitize_docstring)
@ -162,30 +179,20 @@ def doc():
def _sanitize_message(thing): def _sanitize_message(thing):
s = str(thing) s = str(thing)
s = _sanitize_general(s) s = _sanitize_general(s)
s = _hexadecimal.sub("0", s) return _hexadecimal.sub("0", s)
return s
@pytest.fixture @pytest.fixture()
def msg(): def msg():
"""Sanitize messages and add custom failure explanation""" """Sanitize messages and add custom failure explanation"""
return SanitizedString(_sanitize_message) return SanitizedString(_sanitize_message)
# noinspection PyUnusedLocal def pytest_assertrepr_compare(op, left, right): # noqa: ARG001
def pytest_assertrepr_compare(op, left, right):
"""Hook to insert custom failure explanation""" """Hook to insert custom failure explanation"""
if hasattr(left, "explanation"): if hasattr(left, "explanation"):
return left.explanation return left.explanation
return None
@contextlib.contextmanager
def suppress(exception):
"""Suppress the desired exception"""
try:
yield
except exception:
pass
def gc_collect(): def gc_collect():
@ -196,7 +203,7 @@ def gc_collect():
def pytest_configure(): def pytest_configure():
pytest.suppress = suppress pytest.suppress = contextlib.suppress
pytest.gc_collect = gc_collect pytest.gc_collect = gc_collect
@ -210,4 +217,5 @@ def pytest_report_header(config):
f" {pybind11_tests.compiler_info}" f" {pybind11_tests.compiler_info}"
f" {pybind11_tests.cpp_std}" f" {pybind11_tests.cpp_std}"
f" {pybind11_tests.PYBIND11_INTERNALS_ID}" f" {pybind11_tests.PYBIND11_INTERNALS_ID}"
f" PYBIND11_SIMPLE_GIL_MANAGEMENT={pybind11_tests.PYBIND11_SIMPLE_GIL_MANAGEMENT}"
) )

View File

@ -115,7 +115,7 @@ public:
#if defined(PYPY_VERSION) #if defined(PYPY_VERSION)
PyObject *globals = PyEval_GetGlobals(); PyObject *globals = PyEval_GetGlobals();
PyObject *result = PyRun_String("import gc\n" PyObject *result = PyRun_String("import gc\n"
"for i in range(2):" "for i in range(2):\n"
" gc.collect()\n", " gc.collect()\n",
Py_file_input, Py_file_input,
globals, globals,

View File

@ -6,9 +6,15 @@
All rights reserved. Use of this source code is governed by a All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file. BSD-style license that can be found in the LICENSE file.
*/ */
#if defined(PYBIND11_INTERNALS_VERSION)
# undef PYBIND11_INTERNALS_VERSION
#endif
#define PYBIND11_INTERNALS_VERSION 21814642 // Ensure this module has its own `internals` instance.
#include <pybind11/pybind11.h> #include <pybind11/pybind11.h>
#include <cstdint> #include <cstdint>
#include <string>
#include <thread>
// This file mimics a DSO that makes pybind11 calls but does not define a // This file mimics a DSO that makes pybind11 calls but does not define a
// PYBIND11_MODULE. The purpose is to test that such a DSO can create a // PYBIND11_MODULE. The purpose is to test that such a DSO can create a
@ -21,8 +27,54 @@
namespace { namespace {
namespace py = pybind11; namespace py = pybind11;
void gil_acquire() { py::gil_scoped_acquire gil; } void gil_acquire() { py::gil_scoped_acquire gil; }
std::string gil_multi_acquire_release(unsigned bits) {
if ((bits & 0x1u) != 0u) {
py::gil_scoped_acquire gil;
}
if ((bits & 0x2u) != 0u) {
py::gil_scoped_release gil;
}
if ((bits & 0x4u) != 0u) {
py::gil_scoped_acquire gil;
}
if ((bits & 0x8u) != 0u) {
py::gil_scoped_release gil;
}
return PYBIND11_INTERNALS_ID;
}
struct CustomAutoGIL {
CustomAutoGIL() : gstate(PyGILState_Ensure()) {}
~CustomAutoGIL() { PyGILState_Release(gstate); }
PyGILState_STATE gstate;
};
struct CustomAutoNoGIL {
CustomAutoNoGIL() : save(PyEval_SaveThread()) {}
~CustomAutoNoGIL() { PyEval_RestoreThread(save); }
PyThreadState *save;
};
template <typename Acquire, typename Release>
void gil_acquire_inner() {
Acquire acquire_outer;
Acquire acquire_inner;
Release release;
}
template <typename Acquire, typename Release>
void gil_acquire_nested() {
Acquire acquire_outer;
Acquire acquire_inner;
Release release;
auto thread = std::thread(&gil_acquire_inner<Acquire, Release>);
thread.join();
}
constexpr char kModuleName[] = "cross_module_gil_utils"; constexpr char kModuleName[] = "cross_module_gil_utils";
struct PyModuleDef moduledef = { struct PyModuleDef moduledef = {
@ -30,6 +82,9 @@ struct PyModuleDef moduledef = {
} // namespace } // namespace
#define ADD_FUNCTION(Name, ...) \
PyModule_AddObject(m, Name, PyLong_FromVoidPtr(reinterpret_cast<void *>(&__VA_ARGS__)));
extern "C" PYBIND11_EXPORT PyObject *PyInit_cross_module_gil_utils() { extern "C" PYBIND11_EXPORT PyObject *PyInit_cross_module_gil_utils() {
PyObject *m = PyModule_Create(&moduledef); PyObject *m = PyModule_Create(&moduledef);
@ -37,8 +92,16 @@ extern "C" PYBIND11_EXPORT PyObject *PyInit_cross_module_gil_utils() {
if (m != nullptr) { if (m != nullptr) {
static_assert(sizeof(&gil_acquire) == sizeof(void *), static_assert(sizeof(&gil_acquire) == sizeof(void *),
"Function pointer must have the same size as void*"); "Function pointer must have the same size as void*");
PyModule_AddObject( ADD_FUNCTION("gil_acquire_funcaddr", gil_acquire)
m, "gil_acquire_funcaddr", PyLong_FromVoidPtr(reinterpret_cast<void *>(&gil_acquire))); ADD_FUNCTION("gil_multi_acquire_release_funcaddr", gil_multi_acquire_release)
ADD_FUNCTION("gil_acquire_inner_custom_funcaddr",
gil_acquire_inner<CustomAutoGIL, CustomAutoNoGIL>)
ADD_FUNCTION("gil_acquire_nested_custom_funcaddr",
gil_acquire_nested<CustomAutoGIL, CustomAutoNoGIL>)
ADD_FUNCTION("gil_acquire_inner_pybind11_funcaddr",
gil_acquire_inner<py::gil_scoped_acquire, py::gil_scoped_release>)
ADD_FUNCTION("gil_acquire_nested_pybind11_funcaddr",
gil_acquire_nested<py::gil_scoped_acquire, py::gil_scoped_release>)
} }
return m; return m;

View File

@ -0,0 +1,14 @@
/*
tests/eigen_tensor.cpp -- automatic conversion of Eigen Tensor
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#ifndef EIGEN_AVOID_STL_ARRAY
# define EIGEN_AVOID_STL_ARRAY
#endif
#include "test_eigen_tensor.inl"
PYBIND11_MODULE(eigen_tensor_avoid_stl_array, m) { eigen_tensor_test::test_module(m); }

View File

@ -24,5 +24,4 @@ def deprecated_call():
pytest_major_minor = (int(pieces[0]), int(pieces[1])) pytest_major_minor = (int(pieces[0]), int(pieces[1]))
if pytest_major_minor < (3, 9): if pytest_major_minor < (3, 9):
return pytest.warns((DeprecationWarning, PendingDeprecationWarning)) return pytest.warns((DeprecationWarning, PendingDeprecationWarning))
else: return pytest.deprecated_call()
return pytest.deprecated_call()

View File

@ -12,6 +12,16 @@ import zipfile
DIR = os.path.abspath(os.path.dirname(__file__)) DIR = os.path.abspath(os.path.dirname(__file__))
MAIN_DIR = os.path.dirname(os.path.dirname(DIR)) MAIN_DIR = os.path.dirname(os.path.dirname(DIR))
PKGCONFIG = """\
prefix=${{pcfiledir}}/../../
includedir=${{prefix}}/include
Name: pybind11
Description: Seamless operability between C++11 and Python
Version: {VERSION}
Cflags: -I${{includedir}}
"""
main_headers = { main_headers = {
"include/pybind11/attr.h", "include/pybind11/attr.h",
@ -33,6 +43,7 @@ main_headers = {
"include/pybind11/pytypes.h", "include/pybind11/pytypes.h",
"include/pybind11/stl.h", "include/pybind11/stl.h",
"include/pybind11/stl_bind.h", "include/pybind11/stl_bind.h",
"include/pybind11/type_caster_pyobject_ptr.h",
} }
detail_headers = { detail_headers = {
@ -45,6 +56,12 @@ detail_headers = {
"include/pybind11/detail/typeid.h", "include/pybind11/detail/typeid.h",
} }
eigen_headers = {
"include/pybind11/eigen/common.h",
"include/pybind11/eigen/matrix.h",
"include/pybind11/eigen/tensor.h",
}
stl_headers = { stl_headers = {
"include/pybind11/stl/filesystem.h", "include/pybind11/stl/filesystem.h",
} }
@ -59,6 +76,10 @@ cmake_files = {
"share/cmake/pybind11/pybind11Tools.cmake", "share/cmake/pybind11/pybind11Tools.cmake",
} }
pkgconfig_files = {
"share/pkgconfig/pybind11.pc",
}
py_files = { py_files = {
"__init__.py", "__init__.py",
"__main__.py", "__main__.py",
@ -68,8 +89,8 @@ py_files = {
"setup_helpers.py", "setup_helpers.py",
} }
headers = main_headers | detail_headers | stl_headers headers = main_headers | detail_headers | eigen_headers | stl_headers
src_files = headers | cmake_files src_files = headers | cmake_files | pkgconfig_files
all_files = src_files | py_files all_files = src_files | py_files
@ -78,10 +99,12 @@ sdist_files = {
"pybind11/include", "pybind11/include",
"pybind11/include/pybind11", "pybind11/include/pybind11",
"pybind11/include/pybind11/detail", "pybind11/include/pybind11/detail",
"pybind11/include/pybind11/eigen",
"pybind11/include/pybind11/stl", "pybind11/include/pybind11/stl",
"pybind11/share", "pybind11/share",
"pybind11/share/cmake", "pybind11/share/cmake",
"pybind11/share/cmake/pybind11", "pybind11/share/cmake/pybind11",
"pybind11/share/pkgconfig",
"pyproject.toml", "pyproject.toml",
"setup.cfg", "setup.cfg",
"setup.py", "setup.py",
@ -89,6 +112,7 @@ sdist_files = {
"MANIFEST.in", "MANIFEST.in",
"README.rst", "README.rst",
"PKG-INFO", "PKG-INFO",
"SECURITY.md",
} }
local_sdist_files = { local_sdist_files = {
@ -101,22 +125,24 @@ local_sdist_files = {
} }
def test_build_sdist(monkeypatch, tmpdir): def read_tz_file(tar: tarfile.TarFile, name: str) -> bytes:
start = tar.getnames()[0] + "/"
inner_file = tar.extractfile(tar.getmember(f"{start}{name}"))
assert inner_file
with contextlib.closing(inner_file) as f:
return f.read()
def normalize_line_endings(value: bytes) -> bytes:
return value.replace(os.linesep.encode("utf-8"), b"\n")
def test_build_sdist(monkeypatch, tmpdir):
monkeypatch.chdir(MAIN_DIR) monkeypatch.chdir(MAIN_DIR)
out = subprocess.check_output( subprocess.run(
[ [sys.executable, "-m", "build", "--sdist", f"--outdir={tmpdir}"], check=True
sys.executable,
"-m",
"build",
"--sdist",
"--outdir",
str(tmpdir),
]
) )
if hasattr(out, "decode"):
out = out.decode()
(sdist,) = tmpdir.visit("*.tar.gz") (sdist,) = tmpdir.visit("*.tar.gz")
@ -125,25 +151,17 @@ def test_build_sdist(monkeypatch, tmpdir):
version = start[9:-1] version = start[9:-1]
simpler = {n.split("/", 1)[-1] for n in tar.getnames()[1:]} simpler = {n.split("/", 1)[-1] for n in tar.getnames()[1:]}
with contextlib.closing( setup_py = read_tz_file(tar, "setup.py")
tar.extractfile(tar.getmember(start + "setup.py")) pyproject_toml = read_tz_file(tar, "pyproject.toml")
) as f: pkgconfig = read_tz_file(tar, "pybind11/share/pkgconfig/pybind11.pc")
setup_py = f.read() cmake_cfg = read_tz_file(
tar, "pybind11/share/cmake/pybind11/pybind11Config.cmake"
)
with contextlib.closing( assert (
tar.extractfile(tar.getmember(start + "pyproject.toml")) 'set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")'
) as f: in cmake_cfg.decode("utf-8")
pyproject_toml = f.read() )
with contextlib.closing(
tar.extractfile(
tar.getmember(
start + "pybind11/share/cmake/pybind11/pybind11Config.cmake"
)
)
) as f:
contents = f.read().decode("utf8")
assert 'set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")' in contents
files = {f"pybind11/{n}" for n in all_files} files = {f"pybind11/{n}" for n in all_files}
files |= sdist_files files |= sdist_files
@ -154,9 +172,9 @@ def test_build_sdist(monkeypatch, tmpdir):
with open(os.path.join(MAIN_DIR, "tools", "setup_main.py.in"), "rb") as f: with open(os.path.join(MAIN_DIR, "tools", "setup_main.py.in"), "rb") as f:
contents = ( contents = (
string.Template(f.read().decode()) string.Template(f.read().decode("utf-8"))
.substitute(version=version, extra_cmd="") .substitute(version=version, extra_cmd="")
.encode() .encode("utf-8")
) )
assert setup_py == contents assert setup_py == contents
@ -164,25 +182,18 @@ def test_build_sdist(monkeypatch, tmpdir):
contents = f.read() contents = f.read()
assert pyproject_toml == contents assert pyproject_toml == contents
simple_version = ".".join(version.split(".")[:3])
pkgconfig_expected = PKGCONFIG.format(VERSION=simple_version).encode("utf-8")
assert normalize_line_endings(pkgconfig) == pkgconfig_expected
def test_build_global_dist(monkeypatch, tmpdir): def test_build_global_dist(monkeypatch, tmpdir):
monkeypatch.chdir(MAIN_DIR) monkeypatch.chdir(MAIN_DIR)
monkeypatch.setenv("PYBIND11_GLOBAL_SDIST", "1") monkeypatch.setenv("PYBIND11_GLOBAL_SDIST", "1")
out = subprocess.check_output( subprocess.run(
[ [sys.executable, "-m", "build", "--sdist", "--outdir", str(tmpdir)], check=True
sys.executable,
"-m",
"build",
"--sdist",
"--outdir",
str(tmpdir),
]
) )
if hasattr(out, "decode"):
out = out.decode()
(sdist,) = tmpdir.visit("*.tar.gz") (sdist,) = tmpdir.visit("*.tar.gz")
with tarfile.open(str(sdist), "r:gz") as tar: with tarfile.open(str(sdist), "r:gz") as tar:
@ -190,15 +201,17 @@ def test_build_global_dist(monkeypatch, tmpdir):
version = start[16:-1] version = start[16:-1]
simpler = {n.split("/", 1)[-1] for n in tar.getnames()[1:]} simpler = {n.split("/", 1)[-1] for n in tar.getnames()[1:]}
with contextlib.closing( setup_py = read_tz_file(tar, "setup.py")
tar.extractfile(tar.getmember(start + "setup.py")) pyproject_toml = read_tz_file(tar, "pyproject.toml")
) as f: pkgconfig = read_tz_file(tar, "pybind11/share/pkgconfig/pybind11.pc")
setup_py = f.read() cmake_cfg = read_tz_file(
tar, "pybind11/share/cmake/pybind11/pybind11Config.cmake"
)
with contextlib.closing( assert (
tar.extractfile(tar.getmember(start + "pyproject.toml")) 'set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")'
) as f: in cmake_cfg.decode("utf-8")
pyproject_toml = f.read() )
files = {f"pybind11/{n}" for n in all_files} files = {f"pybind11/{n}" for n in all_files}
files |= sdist_files files |= sdist_files
@ -209,7 +222,7 @@ def test_build_global_dist(monkeypatch, tmpdir):
contents = ( contents = (
string.Template(f.read().decode()) string.Template(f.read().decode())
.substitute(version=version, extra_cmd="") .substitute(version=version, extra_cmd="")
.encode() .encode("utf-8")
) )
assert setup_py == contents assert setup_py == contents
@ -217,12 +230,16 @@ def test_build_global_dist(monkeypatch, tmpdir):
contents = f.read() contents = f.read()
assert pyproject_toml == contents assert pyproject_toml == contents
simple_version = ".".join(version.split(".")[:3])
pkgconfig_expected = PKGCONFIG.format(VERSION=simple_version).encode("utf-8")
assert normalize_line_endings(pkgconfig) == pkgconfig_expected
def tests_build_wheel(monkeypatch, tmpdir): def tests_build_wheel(monkeypatch, tmpdir):
monkeypatch.chdir(MAIN_DIR) monkeypatch.chdir(MAIN_DIR)
subprocess.check_output( subprocess.run(
[sys.executable, "-m", "pip", "wheel", ".", "-w", str(tmpdir)] [sys.executable, "-m", "pip", "wheel", ".", "-w", str(tmpdir)], check=True
) )
(wheel,) = tmpdir.visit("*.whl") (wheel,) = tmpdir.visit("*.whl")
@ -249,8 +266,8 @@ def tests_build_global_wheel(monkeypatch, tmpdir):
monkeypatch.chdir(MAIN_DIR) monkeypatch.chdir(MAIN_DIR)
monkeypatch.setenv("PYBIND11_GLOBAL_SDIST", "1") monkeypatch.setenv("PYBIND11_GLOBAL_SDIST", "1")
subprocess.check_output( subprocess.run(
[sys.executable, "-m", "pip", "wheel", ".", "-w", str(tmpdir)] [sys.executable, "-m", "pip", "wheel", ".", "-w", str(tmpdir)], check=True
) )
(wheel,) = tmpdir.visit("*.whl") (wheel,) = tmpdir.visit("*.whl")

View File

@ -89,6 +89,12 @@ PYBIND11_MODULE(pybind11_tests, m) {
#endif #endif
m.attr("cpp_std") = cpp_std(); m.attr("cpp_std") = cpp_std();
m.attr("PYBIND11_INTERNALS_ID") = PYBIND11_INTERNALS_ID; m.attr("PYBIND11_INTERNALS_ID") = PYBIND11_INTERNALS_ID;
m.attr("PYBIND11_SIMPLE_GIL_MANAGEMENT") =
#if defined(PYBIND11_SIMPLE_GIL_MANAGEMENT)
true;
#else
false;
#endif
bind_ConstructorStats(m); bind_ConstructorStats(m);

View File

@ -6,4 +6,4 @@ numpy==1.22.2; platform_python_implementation!="PyPy" and python_version>="3.10"
pytest==7.0.0 pytest==7.0.0
pytest-timeout pytest-timeout
scipy==1.5.4; platform_python_implementation!="PyPy" and python_version<"3.10" 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" scipy==1.10.0; platform_python_implementation!="PyPy" and python_version=="3.10"

View File

@ -4,7 +4,7 @@ asyncio = pytest.importorskip("asyncio")
m = pytest.importorskip("pybind11_tests.async_module") m = pytest.importorskip("pybind11_tests.async_module")
@pytest.fixture @pytest.fixture()
def event_loop(): def event_loop():
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
yield loop yield loop
@ -16,7 +16,7 @@ async def get_await_result(x):
def test_await(event_loop): def test_await(event_loop):
assert 5 == event_loop.run_until_complete(get_await_result(m.SupportsAsync())) assert event_loop.run_until_complete(get_await_result(m.SupportsAsync())) == 5
def test_await_missing(event_loop): def test_await_missing(event_loop):

View File

@ -7,12 +7,47 @@
BSD-style license that can be found in the LICENSE file. BSD-style license that can be found in the LICENSE file.
*/ */
#include <pybind11/complex.h>
#include <pybind11/stl.h> #include <pybind11/stl.h>
#include "constructor_stats.h" #include "constructor_stats.h"
#include "pybind11_tests.h" #include "pybind11_tests.h"
TEST_SUBMODULE(buffers, m) { TEST_SUBMODULE(buffers, m) {
m.attr("long_double_and_double_have_same_size") = (sizeof(long double) == sizeof(double));
m.def("format_descriptor_format_buffer_info_equiv",
[](const std::string &cpp_name, const py::buffer &buffer) {
// https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables
static auto *format_table = new std::map<std::string, std::string>;
static auto *equiv_table
= new std::map<std::string, bool (py::buffer_info::*)() const>;
if (format_table->empty()) {
#define PYBIND11_ASSIGN_HELPER(...) \
(*format_table)[#__VA_ARGS__] = py::format_descriptor<__VA_ARGS__>::format(); \
(*equiv_table)[#__VA_ARGS__] = &py::buffer_info::item_type_is_equivalent_to<__VA_ARGS__>;
PYBIND11_ASSIGN_HELPER(PyObject *)
PYBIND11_ASSIGN_HELPER(bool)
PYBIND11_ASSIGN_HELPER(std::int8_t)
PYBIND11_ASSIGN_HELPER(std::uint8_t)
PYBIND11_ASSIGN_HELPER(std::int16_t)
PYBIND11_ASSIGN_HELPER(std::uint16_t)
PYBIND11_ASSIGN_HELPER(std::int32_t)
PYBIND11_ASSIGN_HELPER(std::uint32_t)
PYBIND11_ASSIGN_HELPER(std::int64_t)
PYBIND11_ASSIGN_HELPER(std::uint64_t)
PYBIND11_ASSIGN_HELPER(float)
PYBIND11_ASSIGN_HELPER(double)
PYBIND11_ASSIGN_HELPER(long double)
PYBIND11_ASSIGN_HELPER(std::complex<float>)
PYBIND11_ASSIGN_HELPER(std::complex<double>)
PYBIND11_ASSIGN_HELPER(std::complex<long double>)
#undef PYBIND11_ASSIGN_HELPER
}
return std::pair<std::string, bool>(
(*format_table)[cpp_name], (buffer.request().*((*equiv_table)[cpp_name]))());
});
// test_from_python / test_to_python: // test_from_python / test_to_python:
class Matrix { class Matrix {
public: public:

View File

@ -10,6 +10,63 @@ from pybind11_tests import buffers as m
np = pytest.importorskip("numpy") np = pytest.importorskip("numpy")
if m.long_double_and_double_have_same_size:
# Determined by the compiler used to build the pybind11 tests
# (e.g. MSVC gets here, but MinGW might not).
np_float128 = None
np_complex256 = None
else:
# Determined by the compiler used to build numpy (e.g. MinGW).
np_float128 = getattr(np, *["float128"] * 2)
np_complex256 = getattr(np, *["complex256"] * 2)
CPP_NAME_FORMAT_NP_DTYPE_TABLE = [
("PyObject *", "O", object),
("bool", "?", np.bool_),
("std::int8_t", "b", np.int8),
("std::uint8_t", "B", np.uint8),
("std::int16_t", "h", np.int16),
("std::uint16_t", "H", np.uint16),
("std::int32_t", "i", np.int32),
("std::uint32_t", "I", np.uint32),
("std::int64_t", "q", np.int64),
("std::uint64_t", "Q", np.uint64),
("float", "f", np.float32),
("double", "d", np.float64),
("long double", "g", np_float128),
("std::complex<float>", "Zf", np.complex64),
("std::complex<double>", "Zd", np.complex128),
("std::complex<long double>", "Zg", np_complex256),
]
CPP_NAME_FORMAT_TABLE = [
(cpp_name, format)
for cpp_name, format, np_dtype in CPP_NAME_FORMAT_NP_DTYPE_TABLE
if np_dtype is not None
]
CPP_NAME_NP_DTYPE_TABLE = [
(cpp_name, np_dtype) for cpp_name, _, np_dtype in CPP_NAME_FORMAT_NP_DTYPE_TABLE
]
@pytest.mark.parametrize(("cpp_name", "np_dtype"), CPP_NAME_NP_DTYPE_TABLE)
def test_format_descriptor_format_buffer_info_equiv(cpp_name, np_dtype):
if np_dtype is None:
pytest.skip(
f"cpp_name=`{cpp_name}`: `long double` and `double` have same size."
)
if isinstance(np_dtype, str):
pytest.skip(f"np.{np_dtype} does not exist.")
np_array = np.array([], dtype=np_dtype)
for other_cpp_name, expected_format in CPP_NAME_FORMAT_TABLE:
format, np_array_is_matching = m.format_descriptor_format_buffer_info_equiv(
other_cpp_name, np_array
)
assert format == expected_format
if other_cpp_name == cpp_name:
assert np_array_is_matching
else:
assert not np_array_is_matching
def test_from_python(): def test_from_python():
with pytest.raises(RuntimeError) as excinfo: with pytest.raises(RuntimeError) as excinfo:
@ -54,7 +111,8 @@ def test_to_python():
mat2 = np.array(mat, copy=False) mat2 = np.array(mat, copy=False)
assert mat2.shape == (5, 4) assert mat2.shape == (5, 4)
assert abs(mat2).sum() == 11 assert abs(mat2).sum() == 11
assert mat2[2, 3] == 4 and mat2[3, 2] == 7 assert mat2[2, 3] == 4
assert mat2[3, 2] == 7
mat2[2, 3] = 5 mat2[2, 3] = 5
assert mat2[2, 3] == 5 assert mat2[2, 3] == 5

View File

@ -73,6 +73,9 @@ PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(pybind11) PYBIND11_NAMESPACE_END(pybind11)
TEST_SUBMODULE(builtin_casters, m) { TEST_SUBMODULE(builtin_casters, m) {
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_MSVC(4127)
// test_simple_string // test_simple_string
m.def("string_roundtrip", [](const char *s) { return s; }); m.def("string_roundtrip", [](const char *s) { return s; });
@ -86,7 +89,7 @@ TEST_SUBMODULE(builtin_casters, m) {
std::wstring wstr; std::wstring wstr;
wstr.push_back(0x61); // a wstr.push_back(0x61); // a
wstr.push_back(0x2e18); // ⸘ wstr.push_back(0x2e18); // ⸘
if (PYBIND11_SILENCE_MSVC_C4127(sizeof(wchar_t) == 2)) { if (sizeof(wchar_t) == 2) {
wstr.push_back(mathbfA16_1); wstr.push_back(mathbfA16_1);
wstr.push_back(mathbfA16_2); wstr.push_back(mathbfA16_2);
} // 𝐀, utf16 } // 𝐀, utf16
@ -113,7 +116,7 @@ TEST_SUBMODULE(builtin_casters, m) {
// Under Python 2.7, invalid unicode UTF-32 characters didn't appear to trigger // Under Python 2.7, invalid unicode UTF-32 characters didn't appear to trigger
// UnicodeDecodeError // UnicodeDecodeError
m.def("bad_utf32_string", [=]() { return std::u32string({a32, char32_t(0xd800), z32}); }); m.def("bad_utf32_string", [=]() { return std::u32string({a32, char32_t(0xd800), z32}); });
if (PYBIND11_SILENCE_MSVC_C4127(sizeof(wchar_t) == 2)) { if (sizeof(wchar_t) == 2) {
m.def("bad_wchar_string", [=]() { m.def("bad_wchar_string", [=]() {
return std::wstring({wchar_t(0x61), wchar_t(0xd800)}); return std::wstring({wchar_t(0x61), wchar_t(0xd800)});
}); });
@ -266,9 +269,14 @@ TEST_SUBMODULE(builtin_casters, m) {
}); });
m.def("lvalue_nested", []() -> const decltype(lvnested) & { return lvnested; }); m.def("lvalue_nested", []() -> const decltype(lvnested) & { return lvnested; });
static std::pair<int, std::string> int_string_pair{2, "items"};
m.def( m.def(
"int_string_pair", []() { return &int_string_pair; }, py::return_value_policy::reference); "int_string_pair",
[]() {
// Using no-destructor idiom to side-step warnings from overzealous compilers.
static auto *int_string_pair = new std::pair<int, std::string>{2, "items"};
return int_string_pair;
},
py::return_value_policy::reference);
// test_builtins_cast_return_none // test_builtins_cast_return_none
m.def("return_none_string", []() -> std::string * { return nullptr; }); m.def("return_none_string", []() -> std::string * { return nullptr; });
@ -379,4 +387,6 @@ TEST_SUBMODULE(builtin_casters, m) {
m.def("takes_const_ref", [](const ConstRefCasted &x) { return x.tag; }); m.def("takes_const_ref", [](const ConstRefCasted &x) { return x.tag; });
m.def("takes_const_ref_wrap", m.def("takes_const_ref_wrap",
[](std::reference_wrapper<const ConstRefCasted> x) { return x.get().tag; }); [](std::reference_wrapper<const ConstRefCasted> x) { return x.get().tag; });
PYBIND11_WARNING_POP
} }

View File

@ -126,8 +126,8 @@ def test_bytes_to_string():
assert m.strlen(b"hi") == 2 assert m.strlen(b"hi") == 2
assert m.string_length(b"world") == 5 assert m.string_length(b"world") == 5
assert m.string_length("a\x00b".encode()) == 3 assert m.string_length(b"a\x00b") == 3
assert m.strlen("a\x00b".encode()) == 1 # C-string limitation assert m.strlen(b"a\x00b") == 1 # C-string limitation
# passing in a utf8 encoded string should work # passing in a utf8 encoded string should work
assert m.string_length("💩".encode()) == 4 assert m.string_length("💩".encode()) == 4
@ -421,13 +421,15 @@ def test_reference_wrapper():
a2 = m.refwrap_list(copy=True) a2 = m.refwrap_list(copy=True)
assert [x.value for x in a1] == [2, 3] assert [x.value for x in a1] == [2, 3]
assert [x.value for x in a2] == [2, 3] assert [x.value for x in a2] == [2, 3]
assert not a1[0] is a2[0] and not a1[1] is a2[1] assert a1[0] is not a2[0]
assert a1[1] is not a2[1]
b1 = m.refwrap_list(copy=False) b1 = m.refwrap_list(copy=False)
b2 = m.refwrap_list(copy=False) b2 = m.refwrap_list(copy=False)
assert [x.value for x in b1] == [1, 2] assert [x.value for x in b1] == [1, 2]
assert [x.value for x in b2] == [1, 2] assert [x.value for x in b2] == [1, 2]
assert b1[0] is b2[0] and b1[1] is b2[1] assert b1[0] is b2[0]
assert b1[1] is b2[1]
assert m.refwrap_iiw(IncType(5)) == 5 assert m.refwrap_iiw(IncType(5)) == 5
assert m.refwrap_call_iiw(IncType(10), m.refwrap_iiw) == [10, 10, 10, 10] assert m.refwrap_call_iiw(IncType(10), m.refwrap_iiw) == [10, 10, 10, 10]

View File

@ -240,4 +240,41 @@ TEST_SUBMODULE(callbacks, m) {
f(); f();
} }
}); });
auto *custom_def = []() {
static PyMethodDef def;
def.ml_name = "example_name";
def.ml_doc = "Example doc";
def.ml_meth = [](PyObject *, PyObject *args) -> PyObject * {
if (PyTuple_Size(args) != 1) {
throw std::runtime_error("Invalid number of arguments for example_name");
}
PyObject *first = PyTuple_GetItem(args, 0);
if (!PyLong_Check(first)) {
throw std::runtime_error("Invalid argument to example_name");
}
auto result = py::cast(PyLong_AsLong(first) * 9);
return result.release().ptr();
};
def.ml_flags = METH_VARARGS;
return &def;
}();
// rec_capsule with name that has the same value (but not pointer) as our internal one
// This capsule should be detected by our code as foreign and not inspected as the pointers
// shouldn't match
constexpr const char *rec_capsule_name
= pybind11::detail::internals_function_record_capsule_name;
py::capsule rec_capsule(std::malloc(1), [](void *data) { std::free(data); });
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
} }

View File

@ -5,6 +5,7 @@ import pytest
import env # noqa: F401 import env # noqa: F401
from pybind11_tests import callbacks as m from pybind11_tests import callbacks as m
from pybind11_tests import detailed_error_messages_enabled
def test_callbacks(): def test_callbacks():
@ -70,11 +71,20 @@ def test_keyword_args_and_generalized_unpacking():
with pytest.raises(RuntimeError) as excinfo: with pytest.raises(RuntimeError) as excinfo:
m.test_arg_conversion_error1(f) m.test_arg_conversion_error1(f)
assert "Unable to convert call argument" in str(excinfo.value) assert str(excinfo.value) == "Unable to convert call argument " + (
"'1' of type 'UnregisteredType' to Python object"
if detailed_error_messages_enabled
else "'1' to Python object (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"
)
with pytest.raises(RuntimeError) as excinfo: with pytest.raises(RuntimeError) as excinfo:
m.test_arg_conversion_error2(f) m.test_arg_conversion_error2(f)
assert "Unable to convert call argument" in str(excinfo.value) assert str(excinfo.value) == "Unable to convert call argument " + (
"'expected_name' of type 'UnregisteredType' to Python object"
if detailed_error_messages_enabled
else "'expected_name' to Python object "
"(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"
)
def test_lambda_closure_cleanup(): def test_lambda_closure_cleanup():
@ -193,3 +203,16 @@ def test_callback_num_times():
if len(rates) > 1: if len(rates) > 1:
print("Min Mean Max") print("Min Mean Max")
print(f"{min(rates):6.3f} {sum(rates) / len(rates):6.3f} {max(rates):6.3f}") print(f"{min(rates):6.3f} {sum(rates) / len(rates):6.3f} {max(rates):6.3f}")
def test_custom_func():
assert m.custom_function(4) == 36
assert m.roundtrip(m.custom_function)(4) == 36
@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

View File

@ -7,7 +7,6 @@ from pybind11_tests import chrono as m
def test_chrono_system_clock(): def test_chrono_system_clock():
# Get the time from both c++ and datetime # Get the time from both c++ and datetime
date0 = datetime.datetime.today() date0 = datetime.datetime.today()
date1 = m.test_chrono1() date1 = m.test_chrono1()
@ -122,7 +121,6 @@ def test_chrono_system_clock_roundtrip_time(time1, tz, monkeypatch):
def test_chrono_duration_roundtrip(): def test_chrono_duration_roundtrip():
# Get the difference between two times (a timedelta) # Get the difference between two times (a timedelta)
date1 = datetime.datetime.today() date1 = datetime.datetime.today()
date2 = datetime.datetime.today() date2 = datetime.datetime.today()
@ -143,7 +141,6 @@ def test_chrono_duration_roundtrip():
def test_chrono_duration_subtraction_equivalence(): def test_chrono_duration_subtraction_equivalence():
date1 = datetime.datetime.today() date1 = datetime.datetime.today()
date2 = datetime.datetime.today() date2 = datetime.datetime.today()
@ -154,7 +151,6 @@ def test_chrono_duration_subtraction_equivalence():
def test_chrono_duration_subtraction_equivalence_date(): def test_chrono_duration_subtraction_equivalence_date():
date1 = datetime.date.today() date1 = datetime.date.today()
date2 = datetime.date.today() date2 = datetime.date.today()

View File

@ -22,10 +22,8 @@
#include <utility> #include <utility>
#if defined(_MSC_VER) PYBIND11_WARNING_DISABLE_MSVC(4324)
# pragma warning(disable : 4324)
// warning C4324: structure was padded due to alignment specifier // warning C4324: structure was padded due to alignment specifier
#endif
// test_brace_initialization // test_brace_initialization
struct NoBraceInitialization { struct NoBraceInitialization {
@ -36,7 +34,29 @@ struct NoBraceInitialization {
std::vector<int> vec; std::vector<int> vec;
}; };
namespace test_class {
namespace pr4220_tripped_over_this { // PR #4227
template <int>
struct SoEmpty {};
template <typename T>
std::string get_msg(const T &) {
return "This is really only meant to exercise successful compilation.";
}
using Empty0 = SoEmpty<0x0>;
void bind_empty0(py::module_ &m) {
py::class_<Empty0>(m, "Empty0").def(py::init<>()).def("get_msg", get_msg<Empty0>);
}
} // namespace pr4220_tripped_over_this
} // namespace test_class
TEST_SUBMODULE(class_, m) { TEST_SUBMODULE(class_, m) {
m.def("obj_class_name", [](py::handle obj) { return py::detail::obj_class_name(obj.ptr()); });
// test_instance // test_instance
struct NoConstructor { struct NoConstructor {
NoConstructor() = default; NoConstructor() = default;
@ -65,7 +85,7 @@ TEST_SUBMODULE(class_, m) {
.def_static("new_instance", &NoConstructor::new_instance, "Return an instance"); .def_static("new_instance", &NoConstructor::new_instance, "Return an instance");
py::class_<NoConstructorNew>(m, "NoConstructorNew") py::class_<NoConstructorNew>(m, "NoConstructorNew")
.def(py::init([](const NoConstructorNew &self) { return self; })) // Need a NOOP __init__ .def(py::init([]() { return nullptr; })) // Need a NOOP __init__
.def_static("__new__", .def_static("__new__",
[](const py::object &) { return NoConstructorNew::new_instance(); }); [](const py::object &) { return NoConstructorNew::new_instance(); });
@ -364,6 +384,8 @@ TEST_SUBMODULE(class_, m) {
protected: protected:
virtual int foo() const { return value; } virtual int foo() const { return value; }
virtual void *void_foo() { return static_cast<void *>(&value); }
virtual void *get_self() { return static_cast<void *>(this); }
private: private:
int value = 42; int value = 42;
@ -372,6 +394,8 @@ TEST_SUBMODULE(class_, m) {
class TrampolineB : public ProtectedB { class TrampolineB : public ProtectedB {
public: public:
int foo() const override { PYBIND11_OVERRIDE(int, ProtectedB, foo, ); } int foo() const override { PYBIND11_OVERRIDE(int, ProtectedB, foo, ); }
void *void_foo() override { PYBIND11_OVERRIDE(void *, ProtectedB, void_foo, ); }
void *get_self() override { PYBIND11_OVERRIDE(void *, ProtectedB, get_self, ); }
}; };
class PublicistB : public ProtectedB { class PublicistB : public ProtectedB {
@ -381,11 +405,23 @@ TEST_SUBMODULE(class_, m) {
// (in Debug builds only, tested with icpc (ICC) 2021.1 Beta 20200827) // (in Debug builds only, tested with icpc (ICC) 2021.1 Beta 20200827)
~PublicistB() override{}; // NOLINT(modernize-use-equals-default) ~PublicistB() override{}; // NOLINT(modernize-use-equals-default)
using ProtectedB::foo; using ProtectedB::foo;
using ProtectedB::get_self;
using ProtectedB::void_foo;
}; };
m.def("read_foo", [](const void *original) {
const int *ptr = reinterpret_cast<const int *>(original);
return *ptr;
});
m.def("pointers_equal",
[](const void *original, const void *comparison) { return original == comparison; });
py::class_<ProtectedB, TrampolineB>(m, "ProtectedB") py::class_<ProtectedB, TrampolineB>(m, "ProtectedB")
.def(py::init<>()) .def(py::init<>())
.def("foo", &PublicistB::foo); .def("foo", &PublicistB::foo)
.def("void_foo", &PublicistB::void_foo)
.def("get_self", &PublicistB::get_self);
// test_brace_initialization // test_brace_initialization
struct BraceInitialization { struct BraceInitialization {
@ -517,6 +553,8 @@ TEST_SUBMODULE(class_, m) {
py::class_<OtherDuplicateNested>(gt, "OtherDuplicateNested"); py::class_<OtherDuplicateNested>(gt, "OtherDuplicateNested");
py::class_<OtherDuplicateNested>(gt, "YetAnotherDuplicateNested"); py::class_<OtherDuplicateNested>(gt, "YetAnotherDuplicateNested");
}); });
test_class::pr4220_tripped_over_this::bind_empty0(m);
} }
template <int N> template <int N>

View File

@ -1,10 +1,16 @@
import pytest import pytest
import env # noqa: F401 import env
from pybind11_tests import ConstructorStats, UserType from pybind11_tests import ConstructorStats, UserType
from pybind11_tests import class_ as m from pybind11_tests import class_ as m
def test_obj_class_name():
expected_name = "UserType" if env.PYPY else "pybind11_tests.UserType"
assert m.obj_class_name(UserType(1)) == expected_name
assert m.obj_class_name(UserType) == expected_name
def test_repr(): def test_repr():
assert "pybind11_type" in repr(type(UserType)) assert "pybind11_type" in repr(type(UserType))
assert "UserType" in repr(UserType) assert "UserType" in repr(UserType)
@ -23,7 +29,7 @@ def test_instance(msg):
assert cstats.alive() == 0 assert cstats.alive() == 0
def test_instance_new(msg): def test_instance_new():
instance = m.NoConstructorNew() # .__new__(m.NoConstructor.__class__) instance = m.NoConstructorNew() # .__new__(m.NoConstructor.__class__)
cstats = ConstructorStats.get(m.NoConstructorNew) cstats = ConstructorStats.get(m.NoConstructorNew)
assert cstats.alive() == 1 assert cstats.alive() == 1
@ -176,7 +182,6 @@ def test_inheritance(msg):
def test_inheritance_init(msg): def test_inheritance_init(msg):
# Single base # Single base
class Python(m.Pet): class Python(m.Pet):
def __init__(self): def __init__(self):
@ -213,7 +218,7 @@ def test_automatic_upcasting():
def test_isinstance(): def test_isinstance():
objects = [tuple(), dict(), m.Pet("Polly", "parrot")] + [m.Dog("Molly")] * 4 objects = [(), {}, m.Pet("Polly", "parrot")] + [m.Dog("Molly")] * 4
expected = (True, True, True, True, True, False, False) expected = (True, True, True, True, True, False, False)
assert m.check_instances(objects) == expected assert m.check_instances(objects) == expected
@ -313,6 +318,8 @@ def test_bind_protected_functions():
b = m.ProtectedB() b = m.ProtectedB()
assert b.foo() == 42 assert b.foo() == 42
assert m.read_foo(b.void_foo()) == 42
assert m.pointers_equal(b.get_self(), b)
class C(m.ProtectedB): class C(m.ProtectedB):
def __init__(self): def __init__(self):
@ -417,7 +424,7 @@ def test_exception_rvalue_abort():
# https://github.com/pybind/pybind11/issues/1568 # https://github.com/pybind/pybind11/issues/1568
def test_multiple_instances_with_same_pointer(capture): def test_multiple_instances_with_same_pointer():
n = 100 n = 100
instances = [m.SamePointer() for _ in range(n)] instances = [m.SamePointer() for _ in range(n)]
for i in range(n): for i in range(n):
@ -469,3 +476,10 @@ def test_register_duplicate_class():
m.register_duplicate_nested_class_type(ClassScope) m.register_duplicate_nested_class_type(ClassScope)
expected = 'generic_type: type "YetAnotherDuplicateNested" is already registered!' expected = 'generic_type: type "YetAnotherDuplicateNested" is already registered!'
assert str(exc_info.value) == expected assert str(exc_info.value) == expected
def test_pr4220_tripped_over_this():
assert (
m.Empty0().get_msg()
== "This is really only meant to exercise successful compilation."
)

View File

@ -1,6 +1,3 @@
# Built-in in CMake 3.5+
include(CMakeParseArguments)
add_custom_target(test_cmake_build) add_custom_target(test_cmake_build)
function(pybind11_add_build_test name) function(pybind11_add_build_test name)

View File

@ -1,12 +1,12 @@
cmake_minimum_required(VERSION 3.4) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate # some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround: # the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.18) if(${CMAKE_VERSION} VERSION_LESS 3.26)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.18) cmake_policy(VERSION 3.26)
endif() endif()
project(test_installed_embed CXX) project(test_installed_embed CXX)

View File

@ -1,13 +1,13 @@
cmake_minimum_required(VERSION 3.4) cmake_minimum_required(VERSION 3.5)
project(test_installed_module CXX) project(test_installed_module CXX)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate # some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround: # the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.18) if(${CMAKE_VERSION} VERSION_LESS 3.26)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.18) cmake_policy(VERSION 3.26)
endif() endif()
project(test_installed_function CXX) project(test_installed_function CXX)

View File

@ -1,12 +1,12 @@
cmake_minimum_required(VERSION 3.4) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate # some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround: # the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.18) if(${CMAKE_VERSION} VERSION_LESS 3.26)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.18) cmake_policy(VERSION 3.26)
endif() endif()
project(test_installed_target CXX) project(test_installed_target CXX)

View File

@ -1,12 +1,12 @@
cmake_minimum_required(VERSION 3.4) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate # some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround: # the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.18) if(${CMAKE_VERSION} VERSION_LESS 3.26)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.18) cmake_policy(VERSION 3.26)
endif() endif()
project(test_subdirectory_embed CXX) project(test_subdirectory_embed CXX)

View File

@ -1,12 +1,12 @@
cmake_minimum_required(VERSION 3.4) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate # some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround: # the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.18) if(${CMAKE_VERSION} VERSION_LESS 3.26)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.18) cmake_policy(VERSION 3.26)
endif() endif()
project(test_subdirectory_function CXX) project(test_subdirectory_function CXX)

View File

@ -1,12 +1,12 @@
cmake_minimum_required(VERSION 3.4) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate # some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround: # the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.18) if(${CMAKE_VERSION} VERSION_LESS 3.26)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.18) cmake_policy(VERSION 3.26)
endif() endif()
project(test_subdirectory_target CXX) project(test_subdirectory_target CXX)

View File

@ -3,9 +3,9 @@ import pytest
from pybind11_tests import const_name as m from pybind11_tests import const_name as m
@pytest.mark.parametrize("func", (m.const_name_tests, m.underscore_tests)) @pytest.mark.parametrize("func", [m.const_name_tests, m.underscore_tests])
@pytest.mark.parametrize( @pytest.mark.parametrize(
"selector, expected", ("selector", "expected"),
enumerate( enumerate(
( (
"", "",

View File

@ -52,15 +52,12 @@ int f1(int x) noexcept { return x + 1; }
#endif #endif
int f2(int x) noexcept(true) { return x + 2; } int f2(int x) noexcept(true) { return x + 2; }
int f3(int x) noexcept(false) { return x + 3; } int f3(int x) noexcept(false) { return x + 3; }
#if defined(__GNUG__) && !defined(__INTEL_COMPILER) PYBIND11_WARNING_PUSH
# pragma GCC diagnostic push PYBIND11_WARNING_DISABLE_GCC("-Wdeprecated")
# pragma GCC diagnostic ignored "-Wdeprecated" PYBIND11_WARNING_DISABLE_CLANG("-Wdeprecated")
#endif
// NOLINTNEXTLINE(modernize-use-noexcept) // NOLINTNEXTLINE(modernize-use-noexcept)
int f4(int x) throw() { return x + 4; } // Deprecated equivalent to noexcept(true) int f4(int x) throw() { return x + 4; } // Deprecated equivalent to noexcept(true)
#if defined(__GNUG__) && !defined(__INTEL_COMPILER) PYBIND11_WARNING_POP
# pragma GCC diagnostic pop
#endif
struct C { struct C {
int m1(int x) noexcept { return x - 1; } int m1(int x) noexcept { return x - 1; }
int m2(int x) const noexcept { return x - 2; } int m2(int x) const noexcept { return x - 2; }
@ -68,17 +65,14 @@ struct C {
int m4(int x) const noexcept(true) { return x - 4; } int m4(int x) const noexcept(true) { return x - 4; }
int m5(int x) noexcept(false) { return x - 5; } int m5(int x) noexcept(false) { return x - 5; }
int m6(int x) const noexcept(false) { return x - 6; } int m6(int x) const noexcept(false) { return x - 6; }
#if defined(__GNUG__) && !defined(__INTEL_COMPILER) PYBIND11_WARNING_PUSH
# pragma GCC diagnostic push PYBIND11_WARNING_DISABLE_GCC("-Wdeprecated")
# pragma GCC diagnostic ignored "-Wdeprecated" PYBIND11_WARNING_DISABLE_CLANG("-Wdeprecated")
#endif
// NOLINTNEXTLINE(modernize-use-noexcept) // NOLINTNEXTLINE(modernize-use-noexcept)
int m7(int x) throw() { return x - 7; } int m7(int x) throw() { return x - 7; }
// NOLINTNEXTLINE(modernize-use-noexcept) // NOLINTNEXTLINE(modernize-use-noexcept)
int m8(int x) const throw() { return x - 8; } int m8(int x) const throw() { return x - 8; }
#if defined(__GNUG__) && !defined(__INTEL_COMPILER) PYBIND11_WARNING_POP
# pragma GCC diagnostic pop
#endif
}; };
} // namespace test_exc_sp } // namespace test_exc_sp
@ -126,14 +120,12 @@ TEST_SUBMODULE(constants_and_functions, m) {
.def("m8", &C::m8); .def("m8", &C::m8);
m.def("f1", f1); m.def("f1", f1);
m.def("f2", f2); m.def("f2", f2);
#if defined(__INTEL_COMPILER)
# pragma warning push PYBIND11_WARNING_PUSH
# pragma warning disable 878 // incompatible exception specifications PYBIND11_WARNING_DISABLE_INTEL(878) // incompatible exception specifications
#endif
m.def("f3", f3); m.def("f3", f3);
#if defined(__INTEL_COMPILER) PYBIND11_WARNING_POP
# pragma warning pop
#endif
m.def("f4", f4); m.def("f4", f4);
// test_function_record_leaks // test_function_record_leaks
@ -156,4 +148,7 @@ TEST_SUBMODULE(constants_and_functions, m) {
py::arg_v("y", 42, "<the answer>"), py::arg_v("y", 42, "<the answer>"),
py::arg_v("z", default_value)); py::arg_v("z", default_value));
}); });
// test noexcept(true) lambda (#4565)
m.def("l1", []() noexcept(true) { return 0; });
} }

View File

@ -50,3 +50,7 @@ def test_function_record_leaks():
m.register_large_capture_with_invalid_arguments(m) m.register_large_capture_with_invalid_arguments(m)
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
m.register_with_raising_repr(m, RaisingRepr()) m.register_with_raising_repr(m, RaisingRepr())
def test_noexcept_lambda():
assert m.l1() == 0

View File

@ -13,6 +13,8 @@
#include "constructor_stats.h" #include "constructor_stats.h"
#include "pybind11_tests.h" #include "pybind11_tests.h"
#include <type_traits>
template <typename derived> template <typename derived>
struct empty { struct empty {
static const derived &get_one() { return instance_; } static const derived &get_one() { return instance_; }
@ -293,3 +295,239 @@ TEST_SUBMODULE(copy_move_policies, m) {
// Make sure that cast from pytype rvalue to other pytype works // Make sure that cast from pytype rvalue to other pytype works
m.def("get_pytype_rvalue_castissue", [](double i) { return py::float_(i).cast<py::int_>(); }); m.def("get_pytype_rvalue_castissue", [](double i) { return py::float_(i).cast<py::int_>(); });
} }
/*
* Rest of the file:
* static_assert based tests for pybind11 adaptations of
* std::is_move_constructible, std::is_copy_constructible and
* std::is_copy_assignable (no adaptation of std::is_move_assignable).
* Difference between pybind11 and std traits: pybind11 traits will also check
* the contained value_types.
*/
struct NotMovable {
NotMovable() = default;
NotMovable(NotMovable const &) = default;
NotMovable(NotMovable &&) = delete;
NotMovable &operator=(NotMovable const &) = default;
NotMovable &operator=(NotMovable &&) = delete;
};
static_assert(!std::is_move_constructible<NotMovable>::value,
"!std::is_move_constructible<NotMovable>::value");
static_assert(std::is_copy_constructible<NotMovable>::value,
"std::is_copy_constructible<NotMovable>::value");
static_assert(!pybind11::detail::is_move_constructible<NotMovable>::value,
"!pybind11::detail::is_move_constructible<NotMovable>::value");
static_assert(pybind11::detail::is_copy_constructible<NotMovable>::value,
"pybind11::detail::is_copy_constructible<NotMovable>::value");
static_assert(!std::is_move_assignable<NotMovable>::value,
"!std::is_move_assignable<NotMovable>::value");
static_assert(std::is_copy_assignable<NotMovable>::value,
"std::is_copy_assignable<NotMovable>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<NotMovable>::value,
// "!pybind11::detail::is_move_assignable<NotMovable>::value");
static_assert(pybind11::detail::is_copy_assignable<NotMovable>::value,
"pybind11::detail::is_copy_assignable<NotMovable>::value");
struct NotCopyable {
NotCopyable() = default;
NotCopyable(NotCopyable const &) = delete;
NotCopyable(NotCopyable &&) = default;
NotCopyable &operator=(NotCopyable const &) = delete;
NotCopyable &operator=(NotCopyable &&) = default;
};
static_assert(std::is_move_constructible<NotCopyable>::value,
"std::is_move_constructible<NotCopyable>::value");
static_assert(!std::is_copy_constructible<NotCopyable>::value,
"!std::is_copy_constructible<NotCopyable>::value");
static_assert(pybind11::detail::is_move_constructible<NotCopyable>::value,
"pybind11::detail::is_move_constructible<NotCopyable>::value");
static_assert(!pybind11::detail::is_copy_constructible<NotCopyable>::value,
"!pybind11::detail::is_copy_constructible<NotCopyable>::value");
static_assert(std::is_move_assignable<NotCopyable>::value,
"std::is_move_assignable<NotCopyable>::value");
static_assert(!std::is_copy_assignable<NotCopyable>::value,
"!std::is_copy_assignable<NotCopyable>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<NotCopyable>::value,
// "!pybind11::detail::is_move_assignable<NotCopyable>::value");
static_assert(!pybind11::detail::is_copy_assignable<NotCopyable>::value,
"!pybind11::detail::is_copy_assignable<NotCopyable>::value");
struct NotCopyableNotMovable {
NotCopyableNotMovable() = default;
NotCopyableNotMovable(NotCopyableNotMovable const &) = delete;
NotCopyableNotMovable(NotCopyableNotMovable &&) = delete;
NotCopyableNotMovable &operator=(NotCopyableNotMovable const &) = delete;
NotCopyableNotMovable &operator=(NotCopyableNotMovable &&) = delete;
};
static_assert(!std::is_move_constructible<NotCopyableNotMovable>::value,
"!std::is_move_constructible<NotCopyableNotMovable>::value");
static_assert(!std::is_copy_constructible<NotCopyableNotMovable>::value,
"!std::is_copy_constructible<NotCopyableNotMovable>::value");
static_assert(!pybind11::detail::is_move_constructible<NotCopyableNotMovable>::value,
"!pybind11::detail::is_move_constructible<NotCopyableNotMovable>::value");
static_assert(!pybind11::detail::is_copy_constructible<NotCopyableNotMovable>::value,
"!pybind11::detail::is_copy_constructible<NotCopyableNotMovable>::value");
static_assert(!std::is_move_assignable<NotCopyableNotMovable>::value,
"!std::is_move_assignable<NotCopyableNotMovable>::value");
static_assert(!std::is_copy_assignable<NotCopyableNotMovable>::value,
"!std::is_copy_assignable<NotCopyableNotMovable>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<NotCopyableNotMovable>::value,
// "!pybind11::detail::is_move_assignable<NotCopyableNotMovable>::value");
static_assert(!pybind11::detail::is_copy_assignable<NotCopyableNotMovable>::value,
"!pybind11::detail::is_copy_assignable<NotCopyableNotMovable>::value");
struct NotMovableVector : std::vector<NotMovable> {};
static_assert(std::is_move_constructible<NotMovableVector>::value,
"std::is_move_constructible<NotMovableVector>::value");
static_assert(std::is_copy_constructible<NotMovableVector>::value,
"std::is_copy_constructible<NotMovableVector>::value");
static_assert(!pybind11::detail::is_move_constructible<NotMovableVector>::value,
"!pybind11::detail::is_move_constructible<NotMovableVector>::value");
static_assert(pybind11::detail::is_copy_constructible<NotMovableVector>::value,
"pybind11::detail::is_copy_constructible<NotMovableVector>::value");
static_assert(std::is_move_assignable<NotMovableVector>::value,
"std::is_move_assignable<NotMovableVector>::value");
static_assert(std::is_copy_assignable<NotMovableVector>::value,
"std::is_copy_assignable<NotMovableVector>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<NotMovableVector>::value,
// "!pybind11::detail::is_move_assignable<NotMovableVector>::value");
static_assert(pybind11::detail::is_copy_assignable<NotMovableVector>::value,
"pybind11::detail::is_copy_assignable<NotMovableVector>::value");
struct NotCopyableVector : std::vector<NotCopyable> {};
static_assert(std::is_move_constructible<NotCopyableVector>::value,
"std::is_move_constructible<NotCopyableVector>::value");
static_assert(std::is_copy_constructible<NotCopyableVector>::value,
"std::is_copy_constructible<NotCopyableVector>::value");
static_assert(pybind11::detail::is_move_constructible<NotCopyableVector>::value,
"pybind11::detail::is_move_constructible<NotCopyableVector>::value");
static_assert(!pybind11::detail::is_copy_constructible<NotCopyableVector>::value,
"!pybind11::detail::is_copy_constructible<NotCopyableVector>::value");
static_assert(std::is_move_assignable<NotCopyableVector>::value,
"std::is_move_assignable<NotCopyableVector>::value");
static_assert(std::is_copy_assignable<NotCopyableVector>::value,
"std::is_copy_assignable<NotCopyableVector>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<NotCopyableVector>::value,
// "!pybind11::detail::is_move_assignable<NotCopyableVector>::value");
static_assert(!pybind11::detail::is_copy_assignable<NotCopyableVector>::value,
"!pybind11::detail::is_copy_assignable<NotCopyableVector>::value");
struct NotCopyableNotMovableVector : std::vector<NotCopyableNotMovable> {};
static_assert(std::is_move_constructible<NotCopyableNotMovableVector>::value,
"std::is_move_constructible<NotCopyableNotMovableVector>::value");
static_assert(std::is_copy_constructible<NotCopyableNotMovableVector>::value,
"std::is_copy_constructible<NotCopyableNotMovableVector>::value");
static_assert(!pybind11::detail::is_move_constructible<NotCopyableNotMovableVector>::value,
"!pybind11::detail::is_move_constructible<NotCopyableNotMovableVector>::value");
static_assert(!pybind11::detail::is_copy_constructible<NotCopyableNotMovableVector>::value,
"!pybind11::detail::is_copy_constructible<NotCopyableNotMovableVector>::value");
static_assert(std::is_move_assignable<NotCopyableNotMovableVector>::value,
"std::is_move_assignable<NotCopyableNotMovableVector>::value");
static_assert(std::is_copy_assignable<NotCopyableNotMovableVector>::value,
"std::is_copy_assignable<NotCopyableNotMovableVector>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<NotCopyableNotMovableVector>::value,
// "!pybind11::detail::is_move_assignable<NotCopyableNotMovableVector>::value");
static_assert(!pybind11::detail::is_copy_assignable<NotCopyableNotMovableVector>::value,
"!pybind11::detail::is_copy_assignable<NotCopyableNotMovableVector>::value");
struct NotMovableMap : std::map<int, NotMovable> {};
static_assert(std::is_move_constructible<NotMovableMap>::value,
"std::is_move_constructible<NotMovableMap>::value");
static_assert(std::is_copy_constructible<NotMovableMap>::value,
"std::is_copy_constructible<NotMovableMap>::value");
static_assert(!pybind11::detail::is_move_constructible<NotMovableMap>::value,
"!pybind11::detail::is_move_constructible<NotMovableMap>::value");
static_assert(pybind11::detail::is_copy_constructible<NotMovableMap>::value,
"pybind11::detail::is_copy_constructible<NotMovableMap>::value");
static_assert(std::is_move_assignable<NotMovableMap>::value,
"std::is_move_assignable<NotMovableMap>::value");
static_assert(std::is_copy_assignable<NotMovableMap>::value,
"std::is_copy_assignable<NotMovableMap>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<NotMovableMap>::value,
// "!pybind11::detail::is_move_assignable<NotMovableMap>::value");
static_assert(pybind11::detail::is_copy_assignable<NotMovableMap>::value,
"pybind11::detail::is_copy_assignable<NotMovableMap>::value");
struct NotCopyableMap : std::map<int, NotCopyable> {};
static_assert(std::is_move_constructible<NotCopyableMap>::value,
"std::is_move_constructible<NotCopyableMap>::value");
static_assert(std::is_copy_constructible<NotCopyableMap>::value,
"std::is_copy_constructible<NotCopyableMap>::value");
static_assert(pybind11::detail::is_move_constructible<NotCopyableMap>::value,
"pybind11::detail::is_move_constructible<NotCopyableMap>::value");
static_assert(!pybind11::detail::is_copy_constructible<NotCopyableMap>::value,
"!pybind11::detail::is_copy_constructible<NotCopyableMap>::value");
static_assert(std::is_move_assignable<NotCopyableMap>::value,
"std::is_move_assignable<NotCopyableMap>::value");
static_assert(std::is_copy_assignable<NotCopyableMap>::value,
"std::is_copy_assignable<NotCopyableMap>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<NotCopyableMap>::value,
// "!pybind11::detail::is_move_assignable<NotCopyableMap>::value");
static_assert(!pybind11::detail::is_copy_assignable<NotCopyableMap>::value,
"!pybind11::detail::is_copy_assignable<NotCopyableMap>::value");
struct NotCopyableNotMovableMap : std::map<int, NotCopyableNotMovable> {};
static_assert(std::is_move_constructible<NotCopyableNotMovableMap>::value,
"std::is_move_constructible<NotCopyableNotMovableMap>::value");
static_assert(std::is_copy_constructible<NotCopyableNotMovableMap>::value,
"std::is_copy_constructible<NotCopyableNotMovableMap>::value");
static_assert(!pybind11::detail::is_move_constructible<NotCopyableNotMovableMap>::value,
"!pybind11::detail::is_move_constructible<NotCopyableNotMovableMap>::value");
static_assert(!pybind11::detail::is_copy_constructible<NotCopyableNotMovableMap>::value,
"!pybind11::detail::is_copy_constructible<NotCopyableNotMovableMap>::value");
static_assert(std::is_move_assignable<NotCopyableNotMovableMap>::value,
"std::is_move_assignable<NotCopyableNotMovableMap>::value");
static_assert(std::is_copy_assignable<NotCopyableNotMovableMap>::value,
"std::is_copy_assignable<NotCopyableNotMovableMap>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<NotCopyableNotMovableMap>::value,
// "!pybind11::detail::is_move_assignable<NotCopyableNotMovableMap>::value");
static_assert(!pybind11::detail::is_copy_assignable<NotCopyableNotMovableMap>::value,
"!pybind11::detail::is_copy_assignable<NotCopyableNotMovableMap>::value");
struct RecursiveVector : std::vector<RecursiveVector> {};
static_assert(std::is_move_constructible<RecursiveVector>::value,
"std::is_move_constructible<RecursiveVector>::value");
static_assert(std::is_copy_constructible<RecursiveVector>::value,
"std::is_copy_constructible<RecursiveVector>::value");
static_assert(pybind11::detail::is_move_constructible<RecursiveVector>::value,
"pybind11::detail::is_move_constructible<RecursiveVector>::value");
static_assert(pybind11::detail::is_copy_constructible<RecursiveVector>::value,
"pybind11::detail::is_copy_constructible<RecursiveVector>::value");
static_assert(std::is_move_assignable<RecursiveVector>::value,
"std::is_move_assignable<RecursiveVector>::value");
static_assert(std::is_copy_assignable<RecursiveVector>::value,
"std::is_copy_assignable<RecursiveVector>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<RecursiveVector>::value,
// "!pybind11::detail::is_move_assignable<RecursiveVector>::value");
static_assert(pybind11::detail::is_copy_assignable<RecursiveVector>::value,
"pybind11::detail::is_copy_assignable<RecursiveVector>::value");
struct RecursiveMap : std::map<int, RecursiveMap> {};
static_assert(std::is_move_constructible<RecursiveMap>::value,
"std::is_move_constructible<RecursiveMap>::value");
static_assert(std::is_copy_constructible<RecursiveMap>::value,
"std::is_copy_constructible<RecursiveMap>::value");
static_assert(pybind11::detail::is_move_constructible<RecursiveMap>::value,
"pybind11::detail::is_move_constructible<RecursiveMap>::value");
static_assert(pybind11::detail::is_copy_constructible<RecursiveMap>::value,
"pybind11::detail::is_copy_constructible<RecursiveMap>::value");
static_assert(std::is_move_assignable<RecursiveMap>::value,
"std::is_move_assignable<RecursiveMap>::value");
static_assert(std::is_copy_assignable<RecursiveMap>::value,
"std::is_copy_assignable<RecursiveMap>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<RecursiveMap>::value,
// "!pybind11::detail::is_move_assignable<RecursiveMap>::value");
static_assert(pybind11::detail::is_copy_assignable<RecursiveMap>::value,
"pybind11::detail::is_copy_assignable<RecursiveMap>::value");

View File

@ -21,7 +21,7 @@ public:
}; };
class ArgAlwaysConverts {}; class ArgAlwaysConverts {};
namespace pybind11 { namespace PYBIND11_NAMESPACE {
namespace detail { namespace detail {
template <> template <>
struct type_caster<ArgInspector1> { struct type_caster<ArgInspector1> {
@ -74,7 +74,7 @@ public:
} }
}; };
} // namespace detail } // namespace detail
} // namespace pybind11 } // namespace PYBIND11_NAMESPACE
// test_custom_caster_destruction // test_custom_caster_destruction
class DestructionTester { class DestructionTester {
@ -92,7 +92,7 @@ public:
return *this; return *this;
} }
}; };
namespace pybind11 { namespace PYBIND11_NAMESPACE {
namespace detail { namespace detail {
template <> template <>
struct type_caster<DestructionTester> { struct type_caster<DestructionTester> {
@ -104,7 +104,7 @@ struct type_caster<DestructionTester> {
} }
}; };
} // namespace detail } // namespace detail
} // namespace pybind11 } // namespace PYBIND11_NAMESPACE
// Define type caster outside of `pybind11::detail` and then alias it. // Define type caster outside of `pybind11::detail` and then alias it.
namespace other_lib { namespace other_lib {
@ -112,7 +112,7 @@ struct MyType {};
// Corrupt `py` shorthand alias for surrounding context. // Corrupt `py` shorthand alias for surrounding context.
namespace py {} namespace py {}
// Corrupt unqualified relative `pybind11` namespace. // Corrupt unqualified relative `pybind11` namespace.
namespace pybind11 {} namespace PYBIND11_NAMESPACE {}
// Correct alias. // Correct alias.
namespace py_ = ::pybind11; namespace py_ = ::pybind11;
// Define caster. This is effectively no-op, we only ensure it compiles and we // Define caster. This is effectively no-op, we only ensure it compiles and we
@ -127,12 +127,12 @@ struct my_caster {
}; };
} // namespace other_lib } // namespace other_lib
// Effectively "alias" it into correct namespace (via inheritance). // Effectively "alias" it into correct namespace (via inheritance).
namespace pybind11 { namespace PYBIND11_NAMESPACE {
namespace detail { namespace detail {
template <> template <>
struct type_caster<other_lib::MyType> : public other_lib::my_caster {}; struct type_caster<other_lib::MyType> : public other_lib::my_caster {};
} // namespace detail } // namespace detail
} // namespace pybind11 } // namespace PYBIND11_NAMESPACE
TEST_SUBMODULE(custom_type_casters, m) { TEST_SUBMODULE(custom_type_casters, m) {
// test_custom_type_casters // test_custom_type_casters

View File

@ -94,12 +94,14 @@ def test_noconvert_args(msg):
def test_custom_caster_destruction(): def test_custom_caster_destruction():
"""Tests that returning a pointer to a type that gets converted with a custom type caster gets """Tests that returning a pointer to a type that gets converted with a custom type caster gets
destroyed when the function has py::return_value_policy::take_ownership policy applied.""" destroyed when the function has py::return_value_policy::take_ownership policy applied.
"""
cstats = m.destruction_tester_cstats() cstats = m.destruction_tester_cstats()
# This one *doesn't* have take_ownership: the pointer should be used but not destroyed: # This one *doesn't* have take_ownership: the pointer should be used but not destroyed:
z = m.custom_caster_no_destroy() z = m.custom_caster_no_destroy()
assert cstats.alive() == 1 and cstats.default_constructions == 1 assert cstats.alive() == 1
assert cstats.default_constructions == 1
assert z assert z
# take_ownership applied: this constructs a new object, casts it, then destroys it: # take_ownership applied: this constructs a new object, casts it, then destroys it:

Some files were not shown because too many files have changed in this diff Show More