diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2bfbd7d9..daf5c2c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,7 +70,7 @@ jobs: runs-on: ${{ matrix.runs-on }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python }} uses: actions/setup-python@v4 @@ -206,7 +206,7 @@ jobs: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python-version }} (deadsnakes) uses: deadsnakes/action@v3.0.1 @@ -303,12 +303,15 @@ jobs: - clang: 15 std: 20 container_suffix: "-bullseye" + - clang: 16 + std: 20 + container_suffix: "-bullseye" name: "🐍 3 • Clang ${{ matrix.clang }} • C++${{ matrix.std }} • x64" container: "silkeh/clang:${{ matrix.clang }}${{ matrix.container_suffix }}" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Add wget and python3 run: apt-get update && apt-get install -y python3-dev python3-numpy python3-pytest libeigen3-dev @@ -342,7 +345,7 @@ jobs: container: nvidia/cuda:12.2.0-devel-ubuntu22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # tzdata will try to ask for the timezone, so set the DEBIAN_FRONTEND - name: Install 🐍 3 @@ -366,7 +369,7 @@ jobs: # container: centos:8 # # steps: -# - uses: actions/checkout@v3 +# - uses: actions/checkout@v4 # # - name: Add Python 3 and a few requirements # run: yum update -y && yum install -y git python3-devel python3-numpy python3-pytest make environment-modules @@ -402,54 +405,55 @@ jobs: # run: cmake --build build --target test_cmake_build - # Testing on CentOS 7 + PGI compilers, which seems to require more workarounds - centos-nvhpc7: - if: ${{ false }} # JOB DISABLED (NEEDS WORK): https://github.com/pybind/pybind11/issues/4690 - runs-on: ubuntu-latest - name: "🐍 3 • CentOS7 / PGI 22.9 • x64" - container: centos:7 + # Testing on Ubuntu + NVHPC (previous PGI) compilers, which seems to require more workarounds + ubuntu-nvhpc7: + runs-on: ubuntu-20.04 + name: "🐍 3 • NVHPC 23.5 • C++17 • x64" + env: + # tzdata will try to ask for the timezone, so set the DEBIAN_FRONTEND + DEBIAN_FRONTEND: 'noninteractive' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - name: Add Python 3 and a few requirements - run: yum update -y && yum install -y epel-release && yum install -y git python3-devel make environment-modules cmake3 yum-utils + - name: Add NVHPC Repo + run: | + echo 'deb [trusted=yes] https://developer.download.nvidia.com/hpc-sdk/ubuntu/amd64 /' | \ + sudo tee /etc/apt/sources.list.d/nvhpc.list - - 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.9 + - name: Install 🐍 3 & NVHPC + run: | + sudo apt-get update -y && \ + sudo apt-get install -y cmake environment-modules git python3-dev python3-pip python3-numpy && \ + sudo apt-get install -y --no-install-recommends nvhpc-23-5 && \ + sudo rm -rf /var/lib/apt/lists/* + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade pytest - # 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 - # standard library). On some systems, you many need further workarounds: + # On some systems, you many need further workarounds: # https://github.com/pybind/pybind11/pull/2475 - name: Configure shell: bash run: | source /etc/profile.d/modules.sh - module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/22.9 - cmake3 -S . -B build -DDOWNLOAD_CATCH=ON \ - -DCMAKE_CXX_STANDARD=11 \ + module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/23.5 + cmake -S . -B build -DDOWNLOAD_CATCH=ON \ + -DCMAKE_CXX_STANDARD=17 \ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \ -DCMAKE_CXX_FLAGS="-Wc,--pending_instantiations=0" \ -DPYBIND11_TEST_FILTER="test_smart_ptr.cpp" - # Building before installing Pip should produce a warning but not an error - name: Build - run: cmake3 --build build -j 2 --verbose - - - name: Install CMake with pip - run: | - python3 -m pip install --upgrade pip - python3 -m pip install pytest + run: cmake --build build -j 2 --verbose - name: Python tests - run: cmake3 --build build --target pytest + run: cmake --build build --target pytest - name: C++ tests - run: cmake3 --build build --target cpptest + run: cmake --build build --target cpptest - name: Interface test - run: cmake3 --build build --target test_cmake_build + run: cmake --build build --target test_cmake_build # Testing on GCC using the GCC docker images (only recent images supported) @@ -466,12 +470,13 @@ jobs: - { gcc: 10, std: 17 } - { gcc: 11, std: 20 } - { gcc: 12, std: 20 } + - { gcc: 13, std: 20 } name: "🐍 3 • GCC ${{ matrix.gcc }} • C++${{ matrix.std }}• x64" container: "gcc:${{ matrix.gcc }}" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Add Python 3 run: apt-get update; apt-get install -y python3-dev python3-numpy python3-pytest python3-pip libeigen3-dev @@ -531,7 +536,7 @@ jobs: name: "🐍 3 • ICC latest • x64" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Add apt repo run: | @@ -635,7 +640,13 @@ jobs: container: "${{ matrix.container }}" steps: - - uses: actions/checkout@v3 + - name: Latest actions/checkout + uses: actions/checkout@v4 + if: matrix.container != 'centos:7' + + - name: Pin actions/checkout as required for centos:7 + uses: actions/checkout@v3 + if: matrix.container == 'centos:7' - name: Add Python 3 (RHEL 7) if: matrix.container == 'centos:7' @@ -683,7 +694,7 @@ jobs: container: i386/debian:buster steps: - - uses: actions/checkout@v1 # Required to run inside docker + - uses: actions/checkout@v1 # v1 is required to run inside docker - name: Install requirements run: | @@ -726,7 +737,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: @@ -778,7 +789,7 @@ jobs: runs-on: windows-2019 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python }} uses: actions/setup-python@v4 @@ -831,7 +842,7 @@ jobs: runs-on: windows-2019 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python }} uses: actions/setup-python@v4 @@ -879,7 +890,7 @@ jobs: runs-on: windows-2022 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python }} uses: actions/setup-python@v4 @@ -957,7 +968,7 @@ jobs: mingw-w64-${{matrix.env}}-boost mingw-w64-${{matrix.env}}-catch - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Configure C++11 # LTO leads to many undefined reference like @@ -1028,7 +1039,7 @@ jobs: run: env - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Clang uses: egor-tensin/setup-clang@v1 @@ -1097,7 +1108,7 @@ jobs: run: env - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Show Clang++ version before brew install llvm run: clang++ --version diff --git a/.github/workflows/ci_sh_def.yml b/.github/workflows/ci_sh_def.yml index a8608864..ddf4b065 100644 --- a/.github/workflows/ci_sh_def.yml +++ b/.github/workflows/ci_sh_def.yml @@ -82,7 +82,7 @@ jobs: runs-on: ${{ matrix.runs-on }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python }} uses: actions/setup-python@v4 @@ -221,7 +221,7 @@ jobs: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python-version }} (deadsnakes) uses: deadsnakes/action@v3.0.1 @@ -319,12 +319,15 @@ jobs: - clang: 15 std: 20 container_suffix: "-bullseye" + - clang: 16 + std: 20 + container_suffix: "-bullseye" name: "🐍 3 • Clang ${{ matrix.clang }} • C++${{ matrix.std }} • x64" container: "silkeh/clang:${{ matrix.clang }}${{ matrix.container_suffix }}" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Add wget and python3 run: apt-get update && apt-get install -y python3-dev python3-numpy python3-pytest libeigen3-dev @@ -359,7 +362,7 @@ jobs: container: nvidia/cuda:12.2.0-devel-ubuntu22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # tzdata will try to ask for the timezone, so set the DEBIAN_FRONTEND - name: Install 🐍 3 @@ -383,7 +386,7 @@ jobs: # container: centos:8 # # steps: -# - uses: actions/checkout@v3 +# - uses: actions/checkout@v4 # # - name: Add Python 3 and a few requirements # run: yum update -y && yum install -y git python3-devel python3-numpy python3-pytest make environment-modules @@ -419,54 +422,55 @@ jobs: # run: cmake --build build --target test_cmake_build - # Testing on CentOS 7 + PGI compilers, which seems to require more workarounds - centos-nvhpc7: - if: ${{ false }} # JOB DISABLED (NEEDS WORK): https://github.com/pybind/pybind11/issues/4690 - runs-on: ubuntu-latest - name: "🐍 3 • CentOS7 / PGI 22.9 • x64" - container: centos:7 + # Testing on Ubuntu + NVHPC (previous PGI) compilers, which seems to require more workarounds + ubuntu-nvhpc7: + runs-on: ubuntu-20.04 + name: "🐍 3 • NVHPC 23.5 • C++17 • x64" + env: + # tzdata will try to ask for the timezone, so set the DEBIAN_FRONTEND + DEBIAN_FRONTEND: 'noninteractive' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - name: Add Python 3 and a few requirements - run: yum update -y && yum install -y epel-release && yum install -y git python3-devel make environment-modules cmake3 yum-utils + - name: Add NVHPC Repo + run: | + echo 'deb [trusted=yes] https://developer.download.nvidia.com/hpc-sdk/ubuntu/amd64 /' | \ + sudo tee /etc/apt/sources.list.d/nvhpc.list - - 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.9 + - name: Install 🐍 3 & NVHPC + run: | + sudo apt-get update -y && \ + sudo apt-get install -y cmake environment-modules git python3-dev python3-pip python3-numpy && \ + sudo apt-get install -y --no-install-recommends nvhpc-23-5 && \ + sudo rm -rf /var/lib/apt/lists/* + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade pytest - # 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 - # standard library). On some systems, you many need further workarounds: + # On some systems, you many need further workarounds: # https://github.com/pybind/pybind11/pull/2475 - name: Configure shell: bash run: | source /etc/profile.d/modules.sh - module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/22.9 - cmake3 -S . -B build -DDOWNLOAD_CATCH=ON \ - -DCMAKE_CXX_STANDARD=11 \ + module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/23.5 + cmake -S . -B build -DDOWNLOAD_CATCH=ON \ + -DCMAKE_CXX_STANDARD=17 \ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \ -DCMAKE_CXX_FLAGS="-Wc,--pending_instantiations=0 -DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" \ -DPYBIND11_TEST_FILTER="test_smart_ptr.cpp" - # Building before installing Pip should produce a warning but not an error - name: Build - run: cmake3 --build build -j 2 --verbose - - - name: Install CMake with pip - run: | - python3 -m pip install --upgrade pip - python3 -m pip install pytest + run: cmake --build build -j 2 --verbose - name: Python tests - run: cmake3 --build build --target pytest + run: cmake --build build --target pytest - name: C++ tests - run: cmake3 --build build --target cpptest + run: cmake --build build --target cpptest - name: Interface test - run: cmake3 --build build --target test_cmake_build + run: cmake --build build --target test_cmake_build # Testing on GCC using the GCC docker images (only recent images supported) @@ -483,12 +487,13 @@ jobs: - { gcc: 10, std: 17 } - { gcc: 11, std: 20 } - { gcc: 12, std: 20 } + - { gcc: 13, std: 20 } name: "🐍 3 • GCC ${{ matrix.gcc }} • C++${{ matrix.std }}• x64" container: "gcc:${{ matrix.gcc }}" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Add Python 3 run: apt-get update; apt-get install -y python3-dev python3-numpy python3-pytest python3-pip libeigen3-dev @@ -550,7 +555,7 @@ jobs: name: "🐍 3 • ICC latest • x64" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Add apt repo run: | @@ -656,7 +661,13 @@ jobs: container: "${{ matrix.container }}" steps: - - uses: actions/checkout@v3 + - name: Latest actions/checkout + uses: actions/checkout@v4 + if: matrix.container != 'centos:7' + + - name: Pin actions/checkout as required for centos:7 + uses: actions/checkout@v3 + if: matrix.container == 'centos:7' - name: Add Python 3 (RHEL 7) if: matrix.container == 'centos:7' @@ -705,7 +716,7 @@ jobs: container: i386/debian:buster steps: - - uses: actions/checkout@v1 # Required to run inside docker + - uses: actions/checkout@v1 # v1 is required to run inside docker - name: Install requirements run: | @@ -749,7 +760,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: @@ -801,7 +812,7 @@ jobs: runs-on: windows-2019 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python }} uses: actions/setup-python@v4 @@ -855,7 +866,7 @@ jobs: runs-on: windows-2019 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python }} uses: actions/setup-python@v4 @@ -904,7 +915,7 @@ jobs: runs-on: windows-2022 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python }} uses: actions/setup-python@v4 @@ -984,7 +995,7 @@ jobs: mingw-w64-${{matrix.env}}-boost mingw-w64-${{matrix.env}}-catch - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Configure C++11 # LTO leads to many undefined reference like @@ -1055,7 +1066,7 @@ jobs: run: env - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Clang uses: egor-tensin/setup-clang@v1 @@ -1125,7 +1136,7 @@ jobs: run: env - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Show Clang++ version before brew install llvm run: clang++ --version diff --git a/.github/workflows/ci_sh_def.yml.patch b/.github/workflows/ci_sh_def.yml.patch index e62ebe36..c70e8b9f 100644 --- a/.github/workflows/ci_sh_def.yml.patch +++ b/.github/workflows/ci_sh_def.yml.patch @@ -1,5 +1,5 @@ ---- ci.yml 2023-07-12 13:23:42.051641241 -0700 -+++ ci_sh_def.yml 2023-07-12 13:24:17.295586495 -0700 +--- ci.yml 2023-09-10 10:49:38.655616237 -0700 ++++ ci_sh_def.yml 2023-09-10 10:50:17.855597833 -0700 @@ -1,4 +1,16 @@ -name: CI +# PLEASE KEEP THIS GROUP OF FILES IN SYNC AT ALL TIMES: @@ -59,7 +59,7 @@ - name: Build run: cmake --build build -j 2 -@@ -320,6 +336,7 @@ +@@ -323,6 +339,7 @@ -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DCMAKE_CXX_STANDARD=${{ matrix.std }} @@ -67,7 +67,7 @@ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - name: Build -@@ -349,7 +366,7 @@ +@@ -352,7 +369,7 @@ run: apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y cmake git python3-dev python3-pytest python3-numpy - name: Configure @@ -76,16 +76,16 @@ - name: Build run: cmake --build build -j2 --verbose -@@ -430,7 +447,7 @@ - cmake3 -S . -B build -DDOWNLOAD_CATCH=ON \ - -DCMAKE_CXX_STANDARD=11 \ +@@ -440,7 +457,7 @@ + cmake -S . -B build -DDOWNLOAD_CATCH=ON \ + -DCMAKE_CXX_STANDARD=17 \ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \ - -DCMAKE_CXX_FLAGS="-Wc,--pending_instantiations=0" \ + -DCMAKE_CXX_FLAGS="-Wc,--pending_instantiations=0 -DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" \ -DPYBIND11_TEST_FILTER="test_smart_ptr.cpp" - # Building before installing Pip should produce a warning but not an error -@@ -489,6 +506,7 @@ + - name: Build +@@ -494,6 +511,7 @@ -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DCMAKE_CXX_STANDARD=${{ matrix.std }} @@ -93,7 +93,7 @@ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - name: Build -@@ -511,6 +529,7 @@ +@@ -516,6 +534,7 @@ -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DCMAKE_CXX_STANDARD=${{ matrix.std }} @@ -101,7 +101,7 @@ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" -@@ -562,6 +581,7 @@ +@@ -567,6 +586,7 @@ -DDOWNLOAD_CATCH=ON \ -DDOWNLOAD_EIGEN=OFF \ -DCMAKE_CXX_STANDARD=11 \ @@ -109,7 +109,7 @@ -DCMAKE_CXX_COMPILER=$(which icpc) \ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") -@@ -594,6 +614,7 @@ +@@ -599,6 +619,7 @@ -DDOWNLOAD_CATCH=ON \ -DDOWNLOAD_EIGEN=OFF \ -DCMAKE_CXX_STANDARD=17 \ @@ -117,7 +117,7 @@ -DCMAKE_CXX_COMPILER=$(which icpc) \ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") -@@ -661,6 +682,7 @@ +@@ -672,6 +693,7 @@ -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=11 @@ -125,7 +125,7 @@ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - name: Build -@@ -711,6 +733,7 @@ +@@ -722,6 +744,7 @@ cmake ../pybind11-tests -DDOWNLOAD_CATCH=ON -DPYBIND11_WERROR=ON @@ -133,7 +133,7 @@ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") working-directory: /build-tests -@@ -806,6 +829,7 @@ +@@ -817,6 +840,7 @@ -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON @@ -141,7 +141,7 @@ ${{ matrix.args }} - name: Build C++11 run: cmake --build build -j 2 -@@ -860,6 +884,7 @@ +@@ -871,6 +895,7 @@ -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON @@ -149,7 +149,7 @@ ${{ matrix.args }} - name: Build C++11 run: cmake --build build --config Debug -j 2 -@@ -900,6 +925,7 @@ +@@ -911,6 +936,7 @@ -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=20 @@ -157,7 +157,7 @@ - name: Build C++20 run: cmake --build build -j 2 -@@ -920,6 +946,7 @@ +@@ -931,6 +957,7 @@ -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=20 @@ -165,7 +165,7 @@ "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" - name: Build C++20 - Exercise cmake -DPYBIND11_TEST_OVERRIDE -@@ -962,7 +989,7 @@ +@@ -973,7 +1000,7 @@ - name: Configure C++11 # LTO leads to many undefined reference like # `pybind11::detail::function_call::function_call(pybind11::detail::function_call&&) @@ -174,7 +174,7 @@ - name: Build C++11 run: cmake --build build -j 2 -@@ -980,7 +1007,7 @@ +@@ -991,7 +1018,7 @@ run: git clean -fdx - name: Configure C++14 @@ -183,7 +183,7 @@ - name: Build C++14 run: cmake --build build2 -j 2 -@@ -998,7 +1025,7 @@ +@@ -1009,7 +1036,7 @@ run: git clean -fdx - name: Configure C++17 @@ -192,7 +192,7 @@ - name: Build C++17 run: cmake --build build3 -j 2 -@@ -1065,6 +1092,7 @@ +@@ -1076,6 +1103,7 @@ -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_STANDARD=17 @@ -200,7 +200,7 @@ - name: Build run: cmake --build . -j 2 -@@ -1130,6 +1158,7 @@ +@@ -1141,6 +1169,7 @@ -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_STANDARD=17 @@ -208,7 +208,7 @@ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - name: Build -@@ -1153,6 +1182,7 @@ +@@ -1164,6 +1193,7 @@ -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_STANDARD=17 diff --git a/.github/workflows/configure.yml b/.github/workflows/configure.yml index 360d999f..1c215b1c 100644 --- a/.github/workflows/configure.yml +++ b/.github/workflows/configure.yml @@ -50,7 +50,7 @@ jobs: runs-on: ${{ matrix.runs-on }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python 3.7 uses: actions/setup-python@v4 diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index ce666cda..0d31f1f0 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -26,7 +26,7 @@ jobs: name: Format runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: "3.x" @@ -44,7 +44,7 @@ jobs: runs-on: ubuntu-latest container: silkeh/clang:15-bullseye steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install requirements run: apt-get update && apt-get install -y git python3-dev python3-pytest diff --git a/.github/workflows/python312dev.yml b/.github/workflows/python312dev.yml index ea7780c0..816c9379 100644 --- a/.github/workflows/python312dev.yml +++ b/.github/workflows/python312dev.yml @@ -26,7 +26,7 @@ jobs: - name: Show env run: env - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python 3.12-dev uses: actions/setup-python@v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2668fc91..f4f392c8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,21 +31,21 @@ repos: 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 +- repo: https://github.com/psf/black-pre-commit-mirror + rev: "23.7.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 + rev: v0.0.287 hooks: - id: ruff args: ["--fix", "--show-fixes"] # Check static types with mypy - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.4.1" + rev: "v1.5.1" hooks: - id: mypy args: [] @@ -85,7 +85,7 @@ repos: # Also code format the docs - repo: https://github.com/asottile/blacken-docs - rev: "1.14.0" + rev: "1.16.0" hooks: - id: blacken-docs additional_dependencies: @@ -93,7 +93,7 @@ repos: # Changes tabs to spaces - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: "v1.5.1" + rev: "v1.5.4" hooks: - id: remove-tabs exclude: (^docs/.*|\.patch)?$ @@ -149,7 +149,7 @@ repos: # PyLint has native support - not always usable, but works for us - repo: https://github.com/PyCQA/pylint - rev: "v3.0.0a6" + rev: "v3.0.0a7" hooks: - id: pylint files: ^pybind11 diff --git a/.readthedocs.yml b/.readthedocs.yml index c9c61617..a2b802f7 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,3 +1,20 @@ +# https://blog.readthedocs.com/migrate-configuration-v2/ + +version: 2 + +build: + os: ubuntu-22.04 + apt_packages: + - librsvg2-bin + tools: + python: "3.11" + +sphinx: + configuration: docs/conf.py + python: - version: 3 -requirements_file: docs/requirements.txt + install: + - requirements: docs/requirements.txt + +formats: + - pdf diff --git a/CMakeLists.txt b/CMakeLists.txt index 6627ea15..7b349508 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,7 +153,8 @@ set(PYBIND11_HEADERS include/pybind11/stl_bind.h include/pybind11/stl/filesystem.h include/pybind11/trampoline_self_life_support.h - include/pybind11/type_caster_pyobject_ptr.h) + include/pybind11/type_caster_pyobject_ptr.h + include/pybind11/typing.h) # Compare with grep and warn if mismatched if(PYBIND11_MASTER_PROJECT AND NOT CMAKE_VERSION VERSION_LESS 3.12) diff --git a/docs/advanced/exceptions.rst b/docs/advanced/exceptions.rst index 53981dc0..616e2d77 100644 --- a/docs/advanced/exceptions.rst +++ b/docs/advanced/exceptions.rst @@ -127,8 +127,7 @@ before a global translator is tried. Inside the translator, ``std::rethrow_exception`` should be used within a try block to re-throw the exception. One or more catch clauses to catch the appropriate exceptions should then be used with each clause using -``PyErr_SetString`` to set a Python exception or ``ex(string)`` to set -the python exception to a custom exception type (see below). +``py::set_error()`` (see below). To declare a custom Python exception type, declare a ``py::exception`` variable and use this in the associated exception translator (note: it is often useful @@ -142,14 +141,17 @@ standard python RuntimeError: .. code-block:: cpp - static py::exception exc(m, "MyCustomError"); + // This is a static object, so we must leak the Python reference: + // It is undefined when the destructor will run, possibly only after the + // Python interpreter is finalized already. + static py::handle exc = py::exception(m, "MyCustomError").release(); py::register_exception_translator([](std::exception_ptr p) { try { if (p) std::rethrow_exception(p); } catch (const MyCustomException &e) { - exc(e.what()); + py::set_error(exc, e.what()); } catch (const OtherException &e) { - PyErr_SetString(PyExc_RuntimeError, e.what()); + py::set_error(PyExc_RuntimeError, e.what()); } }); @@ -168,8 +170,7 @@ section. .. note:: - Call either ``PyErr_SetString`` or a custom exception's call - operator (``exc(string)``) for every exception caught in a custom exception + Call ``py::set_error()`` for every exception caught in a custom exception translator. Failure to do so will cause Python to crash with ``SystemError: error return without exception set``. @@ -200,7 +201,7 @@ If module1 has the following translator: try { if (p) std::rethrow_exception(p); } catch (const std::invalid_argument &e) { - PyErr_SetString("module1 handled this") + py::set_error(PyExc_ArgumentError, "module1 handled this"); } } @@ -212,7 +213,7 @@ and module2 has the following similar translator: try { if (p) std::rethrow_exception(p); } catch (const std::invalid_argument &e) { - PyErr_SetString("module2 handled this") + py::set_error(PyExc_ArgumentError, "module2 handled this"); } } @@ -312,11 +313,11 @@ error protocol, which is outlined here. After calling the Python C API, if Python returns an error, ``throw py::error_already_set();``, which allows pybind11 to deal with the exception and pass it back to the Python interpreter. This includes calls to -the error setting functions such as ``PyErr_SetString``. +the error setting functions such as ``py::set_error()``. .. code-block:: cpp - PyErr_SetString(PyExc_TypeError, "C API type error demo"); + py::set_error(PyExc_TypeError, "C API type error demo"); throw py::error_already_set(); // But it would be easier to simply... diff --git a/docs/advanced/functions.rst b/docs/advanced/functions.rst index 69e3d8a1..372934b0 100644 --- a/docs/advanced/functions.rst +++ b/docs/advanced/functions.rst @@ -16,7 +16,7 @@ lifetime of objects managed by them. This can lead to issues when creating bindings for functions that return a non-trivial type. Just by looking at the type information, it is not clear whether Python should take charge of the returned value and eventually free its resources, or if this is handled on the -C++ side. For this reason, pybind11 provides a several *return value policy* +C++ side. For this reason, pybind11 provides several *return value policy* annotations that can be passed to the :func:`module_::def` and :func:`class_::def` functions. The default policy is :enum:`return_value_policy::automatic`. diff --git a/docs/advanced/misc.rst b/docs/advanced/misc.rst index 805ec838..ddd7f393 100644 --- a/docs/advanced/misc.rst +++ b/docs/advanced/misc.rst @@ -398,3 +398,32 @@ before they are used as a parameter or return type of a function: pyFoo.def(py::init()); pyBar.def(py::init()); } + +Setting inner type hints in docstrings +====================================== + +When you use pybind11 wrappers for ``list``, ``dict``, and other generic python +types, the docstring will just display the generic type. You can convey the +inner types in the docstring by using a special 'typed' version of the generic +type. + +.. code-block:: cpp + + PYBIND11_MODULE(example, m) { + m.def("pass_list_of_str", [](py::typing::List arg) { + // arg can be used just like py::list + )); + } + +The resulting docstring will be ``pass_list_of_str(arg0: list[str]) -> None``. + +The following special types are available in ``pybind11/typing.h``: + +* ``py::Tuple`` +* ``py::Dict`` +* ``py::List`` +* ``py::Set`` +* ``py::Callable`` + +.. warning:: Just like in python, these are merely hints. They don't actually + enforce the types of their contents at runtime or compile time. diff --git a/docs/benchmark.py b/docs/benchmark.py index 2150b6ca..fb49fd04 100644 --- a/docs/benchmark.py +++ b/docs/benchmark.py @@ -70,7 +70,7 @@ def generate_dummy_code_boost(nclasses=10): for codegen in [generate_dummy_code_pybind11, generate_dummy_code_boost]: print("{") - for i in range(0, 10): + for i in range(10): nclasses = 2**i with open("test.cpp", "w") as f: f.write(codegen(nclasses)) diff --git a/docs/compiling.rst b/docs/compiling.rst index 1fd098be..9d53904c 100644 --- a/docs/compiling.rst +++ b/docs/compiling.rst @@ -143,7 +143,7 @@ Your ``pyproject.toml`` file will likely look something like this: .. code-block:: toml [build-system] - requires = ["setuptools>=42", "wheel", "pybind11~=2.6.1"] + requires = ["setuptools>=42", "pybind11>=2.6.1"] build-backend = "setuptools.build_meta" .. note:: diff --git a/docs/release.rst b/docs/release.rst index 4d390a07..20b53a35 100644 --- a/docs/release.rst +++ b/docs/release.rst @@ -15,8 +15,8 @@ For example: For beta, ``PYBIND11_VERSION_PATCH`` should be ``Z.b1``. RC's can be ``Z.rc1``. Always include the dot (even though PEP 440 allows it to be dropped). For a -final release, this must be a simple integer. There is also a HEX version of -the version just below. +final release, this must be a simple integer. There is also +``PYBIND11_VERSION_HEX`` just below that needs to be updated. To release a new version of pybind11: @@ -26,56 +26,93 @@ If you don't have nox, you should either use ``pipx run nox`` instead, or use ``pipx install nox`` or ``brew install nox`` (Unix). - Update the version number - - Update ``PYBIND11_VERSION_MAJOR`` etc. in - ``include/pybind11/detail/common.h``. PATCH should be a simple integer. - - Update the version HEX just below, as well. - - Update ``pybind11/_version.py`` (match above) - - Run ``nox -s tests_packaging`` to ensure this was done correctly. - - Ensure that all the information in ``setup.cfg`` is up-to-date, like - supported Python versions. - - Add release date in ``docs/changelog.rst`` and integrate the output of - ``nox -s make_changelog``. - - Note that the ``make_changelog`` command inspects - `needs changelog `_. - - 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 - 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 - - New branch: ``git checkout -b vX.Y``, ``git push -u origin vX.Y`` - - Update branch: ``git checkout vX.Y``, ``git merge ``, ``git push`` + + - Update ``PYBIND11_VERSION_MAJOR`` etc. in + ``include/pybind11/detail/common.h``. PATCH should be a simple integer. + + - Update ``PYBIND11_VERSION_HEX`` just below as well. + + - Update ``pybind11/_version.py`` (match above). + + - Run ``nox -s tests_packaging`` to ensure this was done correctly. + + - Ensure that all the information in ``setup.cfg`` is up-to-date, like + supported Python versions. + + - Add release date in ``docs/changelog.rst`` and integrate the output of + ``nox -s make_changelog``. + + - Note that the ``make_changelog`` command inspects + `needs changelog `_. + + - 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 + 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 + + - New branch: ``git checkout -b vX.Y``, ``git push -u origin vX.Y`` + + - Update branch: ``git checkout vX.Y``, ``git merge ``, ``git push`` + - Update tags (optional; if you skip this, the GitHub release makes a - non-annotated tag for you) - - ``git tag -a vX.Y.Z -m 'vX.Y.Z release'``. - - ``git push --tags``. + non-annotated tag for you) + + - ``git tag -a vX.Y.Z -m 'vX.Y.Z release'`` + + - ``grep ^__version__ pybind11/_version.py`` + + - Last-minute consistency check: same as tag? + + - ``git push --tags`` + - Update stable - - ``git checkout stable`` - - ``git merge -X theirs vX.Y.Z`` - - ``git diff vX.Y.Z`` - - Carefully review and reconcile any diffs. There should be none. - - ``git push`` + + - ``git checkout stable`` + + - ``git merge -X theirs vX.Y.Z`` + + - ``git diff vX.Y.Z`` + + - Carefully review and reconcile any diffs. There should be none. + + - ``git push`` + - Make a GitHub release (this shows up in the UI, sends new release notifications to users watching releases, and also uploads PyPI packages). (Note: if you do not use an existing tag, this creates a new lightweight tag for you, so you could skip the above step.) - - GUI method: Under `releases `_ - click "Draft a new release" on the far right, fill in the tag name - (if you didn't tag above, it will be made here), fill in a release name - like "Version X.Y.Z", and copy-and-paste the markdown-formatted (!) changelog - into the description (usually ``cat docs/changelog.rst | pandoc -f rst -t gfm``). - Check "pre-release" if this is a beta/RC. - - CLI method: with ``gh`` installed, run ``gh release create vX.Y.Z -t "Version X.Y.Z"`` - If this is a pre-release, add ``-p``. + + - GUI method: Under `releases `_ + click "Draft a new release" on the far right, fill in the tag name + (if you didn't tag above, it will be made here), fill in a release name + like "Version X.Y.Z", and copy-and-paste the markdown-formatted (!) changelog + into the description. You can use ``cat docs/changelog.rst | pandoc -f rst -t gfm``, + then manually remove line breaks and strip links to PRs and issues, + e.g. to a bare ``#1234``, without the surrounding ``<...>_`` hyperlink markup. + Check "pre-release" if this is a beta/RC. + + - CLI method: with ``gh`` installed, run ``gh release create vX.Y.Z -t "Version X.Y.Z"`` + If this is a pre-release, add ``-p``. - Get back to work - - Make sure you are on master, not somewhere else: ``git checkout master`` - - Update version macros in ``include/pybind11/detail/common.h`` (set PATCH to - ``0.dev1`` and increment MINOR). - - Update ``pybind11/_version.py`` to match - - Run ``nox -s tests_packaging`` to ensure this was done correctly. - - If the release was a new MINOR version, add a new `IN DEVELOPMENT` - section in ``docs/changelog.rst``. - - ``git add``, ``git commit``, ``git push`` + + - Make sure you are on master, not somewhere else: ``git checkout master`` + + - Update version macros in ``include/pybind11/detail/common.h`` (set PATCH to + ``0.dev1`` and increment MINOR). + + - Update ``pybind11/_version.py`` to match. + + - Run ``nox -s tests_packaging`` to ensure this was done correctly. + + - If the release was a new MINOR version, add a new ``IN DEVELOPMENT`` + section in ``docs/changelog.rst``. + + - ``git add``, ``git commit``, ``git push`` If a version branch is updated, remember to set PATCH to ``1.dev1``. @@ -92,7 +129,11 @@ merge it if there are no issues. Manual packaging ^^^^^^^^^^^^^^^^ -If you need to manually upload releases, you can download the releases from the job artifacts and upload them with twine. You can also make the files locally (not recommended in general, as your local directory is more likely to be "dirty" and SDists love picking up random unrelated/hidden files); this is the procedure: +If you need to manually upload releases, you can download the releases from +the job artifacts and upload them with twine. You can also make the files +locally (not recommended in general, as your local directory is more likely +to be "dirty" and SDists love picking up random unrelated/hidden files); +this is the procedure: .. code-block:: bash diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index a857d955..f6ab98e4 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1060,6 +1060,10 @@ struct handle_type_name { static constexpr auto name = const_name("float"); }; template <> +struct handle_type_name { + static constexpr auto name = const_name("Callable"); +}; +template <> struct handle_type_name { static constexpr auto name = const_name("None"); }; @@ -1171,6 +1175,7 @@ struct return_value_policy_override< static return_value_policy policy(return_value_policy p) { return !std::is_lvalue_reference::value && !std::is_pointer::value && p != return_value_policy::_clif_automatic + && p != return_value_policy::_return_as_bytes ? return_value_policy::move : p; } diff --git a/include/pybind11/detail/abi_platform_id.h b/include/pybind11/detail/abi_platform_id.h index 355cabbf..5467322f 100644 --- a/include/pybind11/detail/abi_platform_id.h +++ b/include/pybind11/detail/abi_platform_id.h @@ -48,9 +48,12 @@ #endif /// On Linux/OSX, changes in __GXX_ABI_VERSION__ indicate ABI incompatibility. +/// On MSVC, changes in _MSC_VER may indicate ABI incompatibility (#2898). #ifndef PYBIND11_BUILD_ABI # if defined(__GXX_ABI_VERSION) # define PYBIND11_BUILD_ABI "_cxxabi" PYBIND11_TOSTRING(__GXX_ABI_VERSION) +# elif defined(_MSC_VER) +# define PYBIND11_BUILD_ABI "_mscver" # else # define PYBIND11_BUILD_ABI "" # endif @@ -99,8 +102,9 @@ /// further ABI-incompatible changes may be made before the ABI is officially /// changed to the new version. #ifndef PYBIND11_INTERNALS_VERSION -# if PY_VERSION_HEX >= 0x030C0000 +# if PY_VERSION_HEX >= 0x030C0000 || defined(_MSC_VER) // Version bump for Python 3.12+, before first 3.12 beta release. +// Version bump for MSVC piggy-backed on PR #4779. See comments there. # define PYBIND11_INTERNALS_VERSION 5 # else # define PYBIND11_INTERNALS_VERSION 4 diff --git a/include/pybind11/detail/class.h b/include/pybind11/detail/class.h index 2a60a8f4..94825a88 100644 --- a/include/pybind11/detail/class.h +++ b/include/pybind11/detail/class.h @@ -373,7 +373,7 @@ extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject *) { PyTypeObject *type = Py_TYPE(self); std::string msg = get_fully_qualified_tp_name(type) + ": No constructor defined!"; - PyErr_SetString(PyExc_TypeError, msg.c_str()); + set_error(PyExc_TypeError, msg.c_str()); return -1; } @@ -579,7 +579,7 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla if (view) { view->obj = nullptr; } - PyErr_SetString(PyExc_BufferError, "pybind11_getbuffer(): Internal error"); + set_error(PyExc_BufferError, "pybind11_getbuffer(): Internal error"); return -1; } std::memset(view, 0, sizeof(Py_buffer)); @@ -587,7 +587,7 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE && info->readonly) { delete info; // view->obj = nullptr; // Was just memset to 0, so not necessary - PyErr_SetString(PyExc_BufferError, "Writable buffer requested for readonly storage"); + set_error(PyExc_BufferError, "Writable buffer requested for readonly storage"); return -1; } view->obj = obj; diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 44d9705f..d31454c2 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -399,7 +399,7 @@ PYBIND11_WARNING_POP return nullptr; \ } \ catch (const std::exception &e) { \ - PyErr_SetString(PyExc_ImportError, e.what()); \ + ::pybind11::set_error(PyExc_ImportError, e.what()); \ return nullptr; \ } diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index 0c4d6643..9be2878a 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -237,7 +237,7 @@ inline bool raise_err(PyObject *exc_type, const char *msg) { raise_from(exc_type, msg); return true; } - PyErr_SetString(exc_type, msg); + set_error(exc_type, msg); return false; } diff --git a/include/pybind11/detail/smart_holder_type_casters.h b/include/pybind11/detail/smart_holder_type_casters.h index 67d7c4f1..bd9483a0 100644 --- a/include/pybind11/detail/smart_holder_type_casters.h +++ b/include/pybind11/detail/smart_holder_type_casters.h @@ -965,6 +965,7 @@ struct smart_holder_type_caster> : smart_holder_type_caste static handle cast(std::unique_ptr &&src, return_value_policy policy, handle parent) { if (policy != return_value_policy::automatic + && policy != return_value_policy::automatic_reference && policy != return_value_policy::reference_internal && policy != return_value_policy::move && policy != return_value_policy::_clif_automatic) { diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index 09c851c5..d446fe1b 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -825,7 +825,7 @@ class type_caster_generic { std::string tname = rtti_type ? rtti_type->name() : cast_type.name(); detail::clean_type_id(tname); std::string msg = "Unregistered type : " + tname; - PyErr_SetString(PyExc_TypeError, msg.c_str()); + set_error(PyExc_TypeError, msg.c_str()); return {nullptr, nullptr}; } diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 36077ec0..02f65d74 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -1008,7 +1008,7 @@ class array : public buffer { /// Create array from any object -- always returns a new reference static PyObject *raw_array(PyObject *ptr, int ExtraFlags = 0) { if (ptr == nullptr) { - PyErr_SetString(PyExc_ValueError, "cannot create a pybind11::array from a nullptr"); + set_error(PyExc_ValueError, "cannot create a pybind11::array from a nullptr"); return nullptr; } return detail::npy_api::get().PyArray_FromAny_( @@ -1155,7 +1155,7 @@ class array_t : public array { /// Create array from any object -- always returns a new reference static PyObject *raw_array_t(PyObject *ptr) { if (ptr == nullptr) { - PyErr_SetString(PyExc_ValueError, "cannot create a pybind11::array_t from a nullptr"); + set_error(PyExc_ValueError, "cannot create a pybind11::array_t from a nullptr"); return nullptr; } return detail::npy_api::get().PyArray_FromAny_(ptr, diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 2dcf8efe..68d3d36e 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -54,6 +54,45 @@ PYBIND11_WARNING_DISABLE_MSVC(4127) PYBIND11_NAMESPACE_BEGIN(detail) +inline std::string replace_newlines_and_squash(const char *text) { + const char *whitespaces = " \t\n\r\f\v"; + std::string result(text); + bool previous_is_whitespace = false; + + // Do not modify string representations + char first_char = result[0]; + char last_char = result[result.size() - 1]; + if (first_char == last_char && first_char == '\'') { + return result; + } + result.clear(); + + // Replace characters in whitespaces array with spaces and squash consecutive spaces + while (*text != '\0') { + if (std::strchr(whitespaces, *text)) { + if (!previous_is_whitespace) { + result += ' '; + previous_is_whitespace = true; + } + } else { + result += *text; + previous_is_whitespace = false; + } + ++text; + } + + // Strip leading and trailing whitespaces + const size_t str_begin = result.find_first_not_of(whitespaces); + if (str_begin == std::string::npos) { + return ""; + } + + const size_t str_end = result.find_last_not_of(whitespaces); + const size_t str_range = str_end - str_begin + 1; + + return result.substr(str_begin, str_range); +} + // Apply all the extensions translators from a list // Return true if one of the translators completed without raising an exception // itself. Return of false indicates that if there are other translators @@ -426,7 +465,7 @@ class cpp_function : public function { // Write default value if available. if (!is_starred && arg_index < rec->args.size() && rec->args[arg_index].descr) { signature += " = "; - signature += rec->args[arg_index].descr; + signature += detail::replace_newlines_and_squash(rec->args[arg_index].descr); } // Separator for positional-only arguments (placed after the // argument, rather than before like * @@ -682,7 +721,7 @@ class cpp_function : public function { /* Iterator over the list of potentially admissible overloads */ const function_record *overloads = reinterpret_cast( PyCapsule_GetPointer(self, get_function_record_capsule_name())), - *it = overloads; + *current_overload = overloads; assert(overloads != nullptr); /* Need to know how many arguments + keyword arguments there are to pick the right @@ -696,9 +735,8 @@ class cpp_function : public function { if (overloads->is_constructor) { if (!parent || !PyObject_TypeCheck(parent.ptr(), (PyTypeObject *) overloads->scope.ptr())) { - PyErr_SetString( - PyExc_TypeError, - "__init__(self, ...) called with invalid or missing `self` argument"); + set_error(PyExc_TypeError, + "__init__(self, ...) called with invalid or missing `self` argument"); return nullptr; } @@ -721,9 +759,10 @@ class cpp_function : public function { std::vector second_pass; // However, if there are no overloads, we can just skip the no-convert pass entirely - const bool overloaded = it != nullptr && it->next != nullptr; + const bool overloaded + = current_overload != nullptr && current_overload->next != nullptr; - for (; it != nullptr; it = it->next) { + for (; current_overload != nullptr; current_overload = current_overload->next) { /* For each overload: 1. Copy all positional arguments we were given, also checking to make sure that @@ -744,7 +783,7 @@ class cpp_function : public function { a result other than PYBIND11_TRY_NEXT_OVERLOAD. */ - const function_record &func = *it; + const function_record &func = *current_overload; size_t num_args = func.nargs; // Number of positional arguments that we need if (func.has_args) { --num_args; // (but don't count py::args @@ -986,10 +1025,10 @@ class cpp_function : public function { } if (result.ptr() != PYBIND11_TRY_NEXT_OVERLOAD) { - // The error reporting logic below expects 'it' to be valid, as it would be - // if we'd encountered this failure in the first-pass loop. + // The error reporting logic below expects 'current_overload' to be valid, + // as it would be if we'd encountered this failure in the first-pass loop. if (!result) { - it = &call.func; + current_overload = &call.func; } break; } @@ -1013,7 +1052,7 @@ class cpp_function : public function { A translator may choose to do one of the following: - - catch the exception and call PyErr_SetString or PyErr_SetObject + - catch the exception and call py::set_error() to set a standard (or custom) Python exception, or - do nothing and let the exception fall through to the next translator, or - delegate translation to the next translator by throwing a new type of exception. @@ -1029,8 +1068,7 @@ class cpp_function : public function { return nullptr; } - PyErr_SetString(PyExc_SystemError, - "Exception escaped from default exception translator!"); + set_error(PyExc_SystemError, "Exception escaped from default exception translator!"); return nullptr; } @@ -1131,20 +1169,21 @@ class cpp_function : public function { raise_from(PyExc_TypeError, msg.c_str()); return nullptr; } - PyErr_SetString(PyExc_TypeError, msg.c_str()); + set_error(PyExc_TypeError, msg.c_str()); return nullptr; } if (!result) { std::string msg = "Unable to convert function return value to a " "Python type! The signature was\n\t"; - msg += it->signature; + assert(current_overload != nullptr); + msg += current_overload->signature; append_note_if_missing_header_is_suspected(msg); // Attach additional error info to the exception if supported if (PyErr_Occurred()) { raise_from(PyExc_TypeError, msg.c_str()); return nullptr; } - PyErr_SetString(PyExc_TypeError, msg.c_str()); + set_error(PyExc_TypeError, msg.c_str()); return nullptr; } if (overloads->is_constructor && !self_value_and_holder.holder_constructed()) { @@ -2260,7 +2299,7 @@ struct enum_base { object type_name = type::handle_of(arg).attr("__name__"); return pybind11::str("{}.{}").format(std::move(type_name), enum_name(arg)); }, - name("name"), + name("__str__"), is_method(m_base)); if (options::show_enum_members_docstring()) { @@ -2687,7 +2726,7 @@ iterator make_iterator_impl(Iterator first, Sentinel last, Extra &&...extra) { Policy); } - return cast(state{first, last, true}); + return cast(state{std::forward(first), std::forward(last), true}); } PYBIND11_NAMESPACE_END(detail) @@ -2704,7 +2743,9 @@ iterator make_iterator(Iterator first, Sentinel last, Extra &&...extra) { Iterator, Sentinel, ValueType, - Extra...>(first, last, std::forward(extra)...); + Extra...>(std::forward(first), + std::forward(last), + std::forward(extra)...); } /// Makes a python iterator over the keys (`.first`) of a iterator over pairs from a @@ -2720,7 +2761,9 @@ iterator make_key_iterator(Iterator first, Sentinel last, Extra &&...extra) { Iterator, Sentinel, KeyType, - Extra...>(first, last, std::forward(extra)...); + Extra...>(std::forward(first), + std::forward(last), + std::forward(extra)...); } /// Makes a python iterator over the values (`.second`) of a iterator over pairs from a @@ -2736,7 +2779,9 @@ iterator make_value_iterator(Iterator first, Sentinel last, Extra &&...extra) { Iterator, Sentinel, ValueType, - Extra...>(first, last, std::forward(extra)...); + Extra...>(std::forward(first), + std::forward(last), + std::forward(extra)...); } /// Makes an iterator over values of an stl container or other container supporting @@ -2820,7 +2865,7 @@ inline void register_local_exception_translator(ExceptionTranslator &&translator /** * Wrapper to generate a new Python exception type. * - * This should only be used with PyErr_SetString for now. + * This should only be used with py::set_error() for now. * It is not (yet) possible to use as a py::base. * Template type argument is reserved for future use. */ @@ -2841,7 +2886,9 @@ class exception : public object { } // Sets the current python exception to this exception object with the given message - void operator()(const char *message) { PyErr_SetString(m_ptr, message); } + PYBIND11_DEPRECATED("Please use py::set_error() instead " + "(https://github.com/pybind/pybind11/pull/4772)") + void operator()(const char *message) const { set_error(*this, message); } }; PYBIND11_NAMESPACE_BEGIN(detail) @@ -2873,7 +2920,7 @@ register_exception_impl(handle scope, const char *name, handle base, bool isLoca try { std::rethrow_exception(p); } catch (const CppException &e) { - detail::get_exception_object()(e.what()); + set_error(detail::get_exception_object(), e.what()); } }); return ex; diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 21eb91a9..29d03919 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -341,6 +341,14 @@ class handle : public detail::object_api { #endif }; +inline void set_error(const handle &type, const char *message) { + PyErr_SetString(type.ptr(), message); +} + +inline void set_error(const handle &type, const handle &value) { + PyErr_SetObject(type.ptr(), value.ptr()); +} + /** \rst Holds a reference to a Python object (with reference counting) diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index ae81a70e..804d9617 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -187,9 +187,9 @@ struct set_caster { template static handle cast(T &&src, const return_value_policy_pack &rvpp, handle parent) { - return_value_policy_pack rvpp_local = rvpp; + return_value_policy_pack rvpp_local = rvpp.get(0); if (!std::is_lvalue_reference::value) { - rvpp_local = rvpp.override_policy(return_value_policy_override::policy); + rvpp_local = rvpp_local.override_policy(return_value_policy_override::policy); } pybind11::set s; for (auto &&value : src) { @@ -323,9 +323,9 @@ struct list_caster { public: template static handle cast(T &&src, const return_value_policy_pack &rvpp, handle parent) { - return_value_policy_pack rvpp_local = rvpp; + return_value_policy_pack rvpp_local = rvpp.get(0); if (!std::is_lvalue_reference::value) { - rvpp_local = rvpp.override_policy(return_value_policy_override::policy); + rvpp_local = rvpp_local.override_policy(return_value_policy_override::policy); } list l(src.size()); ssize_t index = 0; @@ -406,11 +406,12 @@ struct array_caster { template static handle cast(T &&src, const return_value_policy_pack &rvpp, handle parent) { + return_value_policy_pack rvpp_local = rvpp.get(0); list l(src.size()); ssize_t index = 0; for (auto &&value : src) { auto value_ = reinterpret_steal( - value_conv::cast(detail::forward_like(value), rvpp, parent)); + value_conv::cast(detail::forward_like(value), rvpp_local, parent)); if (!value_) { return handle(); } @@ -461,9 +462,9 @@ struct optional_caster { if (!src) { return none().release(); } - return_value_policy_pack rvpp_local = rvpp; + return_value_policy_pack rvpp_local = rvpp.get(0); if (!std::is_lvalue_reference::value) { - rvpp_local = rvpp.override_policy(return_value_policy_override::policy); + rvpp_local = rvpp_local.override_policy(return_value_policy_override::policy); } // NOLINTNEXTLINE(bugprone-unchecked-optional-access) return value_conv::cast(*std::forward(src), rvpp_local, parent); diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h new file mode 100644 index 00000000..74fd82ea --- /dev/null +++ b/include/pybind11/typing.h @@ -0,0 +1,97 @@ +/* + pybind11/typing.h: Convenience wrapper classes for basic Python types + with more explicit annotations. + + Copyright (c) 2023 Dustin Spicuzza + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#pragma once + +#include "detail/common.h" +#include "cast.h" +#include "pytypes.h" + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(typing) + +/* + The following types can be used to direct pybind11-generated docstrings + to have have more explicit types (e.g., `list[str]` instead of `list`). + Just use these in place of existing types. + + There is no additional enforcement of types at runtime. +*/ + +template +class Tuple : public tuple { + using tuple::tuple; +}; + +template +class Dict : public dict { + using dict::dict; +}; + +template +class List : public list { + using list::list; +}; + +template +class Set : public set { + using set::set; +}; + +template +class Callable; + +template +class Callable : public function { + using function::function; +}; + +PYBIND11_NAMESPACE_END(typing) + +PYBIND11_NAMESPACE_BEGIN(detail) + +template +struct handle_type_name> { + static constexpr auto name + = const_name("tuple[") + concat(make_caster::name...) + const_name("]"); +}; + +template <> +struct handle_type_name> { + // PEP 484 specifies this syntax for an empty tuple + static constexpr auto name = const_name("tuple[()]"); +}; + +template +struct handle_type_name> { + static constexpr auto name = const_name("dict[") + make_caster::name + const_name(", ") + + make_caster::name + const_name("]"); +}; + +template +struct handle_type_name> { + static constexpr auto name = const_name("list[") + make_caster::name + const_name("]"); +}; + +template +struct handle_type_name> { + static constexpr auto name = const_name("set[") + make_caster::name + const_name("]"); +}; + +template +struct handle_type_name> { + using retval_type = conditional_t::value, void_type, Return>; + static constexpr auto name = const_name("Callable[[") + concat(make_caster::name...) + + const_name("], ") + make_caster::name + + const_name("]"); +}; + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/pyproject.toml b/pyproject.toml index 59c15ea6..ef2a8736 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,5 +94,5 @@ line-length = 120 isort.known-first-party = ["env", "pybind11_cross_module_tests", "pybind11_tests"] [tool.ruff.per-file-ignores] -"tests/**" = ["EM", "N"] +"tests/**" = ["EM", "N", "E721"] "tests/test_call_policies.py" = ["PLC1901"] diff --git a/tests/cross_module_interleaved_error_already_set.cpp b/tests/cross_module_interleaved_error_already_set.cpp index fdd9939e..3493a7e6 100644 --- a/tests/cross_module_interleaved_error_already_set.cpp +++ b/tests/cross_module_interleaved_error_already_set.cpp @@ -16,12 +16,12 @@ namespace { namespace py = pybind11; void interleaved_error_already_set() { - PyErr_SetString(PyExc_RuntimeError, "1st error."); + py::set_error(PyExc_RuntimeError, "1st error."); try { throw py::error_already_set(); } catch (const py::error_already_set &) { // The 2nd error could be conditional in a real application. - PyErr_SetString(PyExc_RuntimeError, "2nd error."); + py::set_error(PyExc_RuntimeError, "2nd error."); } // Here the 1st error is destroyed before the 2nd error is fetched. // The error_already_set dtor triggers a pybind11::detail::get_internals() // call via pybind11::gil_scoped_acquire. diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py index a0645618..24359343 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -47,6 +47,7 @@ "include/pybind11/stl_bind.h", "include/pybind11/trampoline_self_life_support.h", "include/pybind11/type_caster_pyobject_ptr.h", + "include/pybind11/typing.h", } detail_headers = { diff --git a/tests/pybind11_cross_module_tests.cpp b/tests/pybind11_cross_module_tests.cpp index 9379f3f2..ad68e9a5 100644 --- a/tests/pybind11_cross_module_tests.cpp +++ b/tests/pybind11_cross_module_tests.cpp @@ -31,11 +31,11 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) { // test_exceptions.py py::register_local_exception(m, "LocalSimpleException"); m.def("raise_runtime_error", []() { - PyErr_SetString(PyExc_RuntimeError, "My runtime error"); + py::set_error(PyExc_RuntimeError, "My runtime error"); throw py::error_already_set(); }); m.def("raise_value_error", []() { - PyErr_SetString(PyExc_ValueError, "My value error"); + py::set_error(PyExc_ValueError, "My value error"); throw py::error_already_set(); }); m.def("throw_pybind_value_error", []() { throw py::value_error("pybind11 value error"); }); @@ -49,7 +49,7 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) { std::rethrow_exception(p); } } catch (const shared_exception &e) { - PyErr_SetString(PyExc_KeyError, e.what()); + py::set_error(PyExc_KeyError, e.what()); } }); @@ -60,7 +60,7 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) { std::rethrow_exception(p); } } catch (const LocalException &e) { - PyErr_SetString(PyExc_KeyError, e.what()); + py::set_error(PyExc_KeyError, e.what()); } }); diff --git a/tests/test_callbacks.py b/tests/test_callbacks.py index 4a652f53..86c76745 100644 --- a/tests/test_callbacks.py +++ b/tests/test_callbacks.py @@ -216,3 +216,10 @@ def test_custom_func(): def test_custom_func2(): assert m.custom_function2(3) == 27 assert m.roundtrip(m.custom_function2)(3) == 27 + + +def test_callback_docstring(): + assert ( + m.test_tuple_unpacking.__doc__.strip() + == "test_tuple_unpacking(arg0: Callable) -> object" + ) diff --git a/tests/test_class_sh_basic.cpp b/tests/test_class_sh_basic.cpp index 502f48a2..7582cfa0 100644 --- a/tests/test_class_sh_basic.cpp +++ b/tests/test_class_sh_basic.cpp @@ -160,6 +160,12 @@ TEST_SUBMODULE(class_sh_basic, m) { m.def("args_shared_ptr_const", [](std::shared_ptr p) { return p; }); m.def("args_unique_ptr", [](std::unique_ptr p) { return p; }); m.def("args_unique_ptr_const", [](std::unique_ptr p) { return p; }); + + // Make sure unique_ptr type caster accept automatic_reference return value policy. + m.def( + "rtrn_uq_automatic_reference", + []() { return std::unique_ptr(new atyp("rtrn_uq_automatic_reference")); }, + pybind11::return_value_policy::automatic_reference); } } // namespace class_sh_basic diff --git a/tests/test_class_sh_basic.py b/tests/test_class_sh_basic.py index 95460d9d..268e7939 100644 --- a/tests/test_class_sh_basic.py +++ b/tests/test_class_sh_basic.py @@ -181,3 +181,7 @@ def test_function_signatures(doc): doc(m.args_unique_ptr_const) == "args_unique_ptr_const(arg0: m.class_sh_basic.atyp) -> m.class_sh_basic.atyp" ) + + +def test_unique_ptr_return_value_policy_automatic_reference(): + assert m.get_mtxt(m.rtrn_uq_automatic_reference()) == "rtrn_uq_automatic_reference" diff --git a/tests/test_eigen_matrix.cpp b/tests/test_eigen_matrix.cpp index 554cc4d7..0c003d05 100644 --- a/tests/test_eigen_matrix.cpp +++ b/tests/test_eigen_matrix.cpp @@ -330,6 +330,23 @@ TEST_SUBMODULE(eigen_matrix, m) { m.def("dense_c", [mat]() -> DenseMatrixC { return DenseMatrixC(mat); }); m.def("dense_copy_r", [](const DenseMatrixR &m) -> DenseMatrixR { return m; }); m.def("dense_copy_c", [](const DenseMatrixC &m) -> DenseMatrixC { return m; }); + // test_defaults + bool have_numpy = true; + try { + py::module_::import("numpy"); + } catch (const py::error_already_set &) { + have_numpy = false; + } + if (have_numpy) { + py::module_::import("numpy"); + Eigen::Matrix defaultMatrix = Eigen::Matrix3d::Identity(); + m.def( + "defaults_mat", [](const Eigen::Matrix3d &) {}, py::arg("mat") = defaultMatrix); + + Eigen::VectorXd defaultVector = Eigen::VectorXd::Ones(32); + m.def( + "defaults_vec", [](const Eigen::VectorXd &) {}, py::arg("vec") = defaultMatrix); + } // test_sparse, test_sparse_signature m.def("sparse_r", [mat]() -> SparseMatrixR { // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) diff --git a/tests/test_eigen_matrix.py b/tests/test_eigen_matrix.py index b2e76740..a486c2f9 100644 --- a/tests/test_eigen_matrix.py +++ b/tests/test_eigen_matrix.py @@ -716,6 +716,11 @@ def test_dense_signature(doc): ) +def test_defaults(doc): + assert "\n" not in str(doc(m.defaults_mat)) + assert "\n" not in str(doc(m.defaults_vec)) + + def test_named_arguments(): a = np.array([[1.0, 2], [3, 4], [5, 6]]) b = np.ones((2, 1)) diff --git a/tests/test_enum.py b/tests/test_enum.py index 997ac6c5..106ca50d 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -266,6 +266,11 @@ def test_docstring_signatures(): assert "arg0" not in (attr.__doc__ or "") +def test_str_signature(): + for enum_type in [m.ScopedEnum, m.UnscopedEnum]: + assert enum_type.__str__.__doc__.startswith("__str__") + + @pytest.mark.skipif( isinstance(m.obj_cast_UnscopedEnum_ptr, str), reason=m.obj_cast_UnscopedEnum_ptr ) diff --git a/tests/test_exceptions.cpp b/tests/test_exceptions.cpp index 854c7e6f..f6d9c215 100644 --- a/tests/test_exceptions.cpp +++ b/tests/test_exceptions.cpp @@ -25,6 +25,10 @@ class MyException : public std::exception { std::string message = ""; }; +class MyExceptionUseDeprecatedOperatorCall : public MyException { + using MyException::MyException; +}; + // A type that should be translated to a standard Python exception class MyException2 : public std::exception { public: @@ -109,8 +113,8 @@ TEST_SUBMODULE(exceptions, m) { m.def("throw_std_exception", []() { throw std::runtime_error("This exception was intentionally thrown."); }); - // make a new custom exception and use it as a translation target - static py::exception ex(m, "MyException"); + // PLEASE KEEP IN SYNC with docs/advanced/exceptions.rst + static py::handle ex = py::exception(m, "MyException").release(); py::register_exception_translator([](std::exception_ptr p) { try { if (p) { @@ -118,7 +122,32 @@ TEST_SUBMODULE(exceptions, m) { } } catch (const MyException &e) { // Set MyException as the active python error - ex(e.what()); + py::set_error(ex, e.what()); + } + }); + + // Same as above, but using the deprecated `py::exception<>::operator()` + // We want to be sure it still works, until it's removed. + static const auto *const exd = new py::exception( + m, "MyExceptionUseDeprecatedOperatorCall"); + py::register_exception_translator([](std::exception_ptr p) { + try { + if (p) { + std::rethrow_exception(p); + } + } catch (const MyExceptionUseDeprecatedOperatorCall &e) { +#if defined(__INTEL_COMPILER) || defined(__NVCOMPILER) + // It is not worth the trouble dealing with warning suppressions for these compilers. + // Falling back to the recommended approach to keep the test code simple. + py::set_error(*exd, e.what()); +#else + PYBIND11_WARNING_PUSH + PYBIND11_WARNING_DISABLE_CLANG("-Wdeprecated-declarations") + PYBIND11_WARNING_DISABLE_GCC("-Wdeprecated-declarations") + PYBIND11_WARNING_DISABLE_MSVC(4996) + (*exd)(e.what()); + PYBIND11_WARNING_POP +#endif } }); @@ -132,7 +161,7 @@ TEST_SUBMODULE(exceptions, m) { } } catch (const MyException2 &e) { // Translate this exception to a standard RuntimeError - PyErr_SetString(PyExc_RuntimeError, e.what()); + py::set_error(PyExc_RuntimeError, e.what()); } }); @@ -162,11 +191,16 @@ TEST_SUBMODULE(exceptions, m) { std::rethrow_exception(p); } } catch (const MyException6 &e) { - PyErr_SetString(PyExc_RuntimeError, e.what()); + py::set_error(PyExc_RuntimeError, e.what()); } }); - m.def("throws1", []() { throw MyException("this error should go to a custom type"); }); + m.def("throws1", + []() { throw MyException("this error should go to py::exception"); }); + m.def("throws1d", []() { + throw MyExceptionUseDeprecatedOperatorCall( + "this error should go to py::exception"); + }); m.def("throws2", []() { throw MyException2("this error should go to a standard Python exception"); }); m.def("throws3", []() { throw MyException3("this error cannot be translated"); }); @@ -222,7 +256,7 @@ TEST_SUBMODULE(exceptions, m) { m.def("throw_already_set", [](bool err) { if (err) { - PyErr_SetString(PyExc_ValueError, "foo"); + py::set_error(PyExc_ValueError, "foo"); } try { throw py::error_already_set(); @@ -238,7 +272,7 @@ TEST_SUBMODULE(exceptions, m) { } PyErr_Clear(); if (err) { - PyErr_SetString(PyExc_ValueError, "foo"); + py::set_error(PyExc_ValueError, "foo"); } throw py::error_already_set(); }); @@ -247,7 +281,7 @@ TEST_SUBMODULE(exceptions, m) { bool retval = false; try { PythonCallInDestructor set_dict_in_destructor(d); - PyErr_SetString(PyExc_ValueError, "foo"); + py::set_error(PyExc_ValueError, "foo"); throw py::error_already_set(); } catch (const py::error_already_set &) { retval = true; @@ -282,14 +316,14 @@ TEST_SUBMODULE(exceptions, m) { m.def("throw_should_be_translated_to_key_error", []() { throw shared_exception(); }); m.def("raise_from", []() { - PyErr_SetString(PyExc_ValueError, "inner"); + py::set_error(PyExc_ValueError, "inner"); py::raise_from(PyExc_ValueError, "outer"); throw py::error_already_set(); }); m.def("raise_from_already_set", []() { try { - PyErr_SetString(PyExc_ValueError, "inner"); + py::set_error(PyExc_ValueError, "inner"); throw py::error_already_set(); } catch (py::error_already_set &e) { py::raise_from(e, PyExc_ValueError, "outer"); @@ -306,7 +340,7 @@ TEST_SUBMODULE(exceptions, m) { }); m.def("error_already_set_what", [](const py::object &exc_type, const py::object &exc_value) { - PyErr_SetObject(exc_type.ptr(), exc_value.ptr()); + py::set_error(exc_type, exc_value); std::string what = py::error_already_set().what(); bool py_err_set_after_what = (PyErr_Occurred() != nullptr); PyErr_Clear(); @@ -321,7 +355,7 @@ TEST_SUBMODULE(exceptions, m) { }); m.def("test_error_already_set_double_restore", [](bool dry_run) { - PyErr_SetString(PyExc_ValueError, "Random error."); + py::set_error(PyExc_ValueError, "Random error."); py::error_already_set e; e.restore(); PyErr_Clear(); diff --git a/tests/test_exceptions.h b/tests/test_exceptions.h index 03684b89..2eaa3d3d 100644 --- a/tests/test_exceptions.h +++ b/tests/test_exceptions.h @@ -9,5 +9,5 @@ class PYBIND11_EXPORT_EXCEPTION shared_exception : public pybind11::builtin_exce public: using builtin_exception::builtin_exception; explicit shared_exception() : shared_exception("") {} - void set_error() const override { PyErr_SetString(PyExc_RuntimeError, what()); } + void set_error() const override { py::set_error(PyExc_RuntimeError, what()); } }; diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index ccac4536..5d8e6292 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -139,7 +139,15 @@ def test_custom(msg): # Can we catch a MyException? with pytest.raises(m.MyException) as excinfo: m.throws1() - assert msg(excinfo.value) == "this error should go to a custom type" + assert msg(excinfo.value) == "this error should go to py::exception" + + # Can we catch a MyExceptionUseDeprecatedOperatorCall? + with pytest.raises(m.MyExceptionUseDeprecatedOperatorCall) as excinfo: + m.throws1d() + assert ( + msg(excinfo.value) + == "this error should go to py::exception" + ) # Can we translate to standard Python exceptions? with pytest.raises(RuntimeError) as excinfo: diff --git a/tests/test_kwargs_and_defaults.cpp b/tests/test_kwargs_and_defaults.cpp index 77e72c0c..614c826d 100644 --- a/tests/test_kwargs_and_defaults.cpp +++ b/tests/test_kwargs_and_defaults.cpp @@ -42,6 +42,50 @@ TEST_SUBMODULE(kwargs_and_defaults, m) { m.def("kw_func_udl", kw_func, "x"_a, "y"_a = 300); m.def("kw_func_udl_z", kw_func, "x"_a, "y"_a = 0); + // test line breaks in default argument representation + struct CustomRepr { + std::string repr_string; + + explicit CustomRepr(const std::string &repr) : repr_string(repr) {} + + std::string __repr__() const { return repr_string; } + }; + + py::class_(m, "CustomRepr") + .def(py::init()) + .def("__repr__", &CustomRepr::__repr__); + + m.def( + "kw_lb_func0", + [](const CustomRepr &) {}, + py::arg("custom") = CustomRepr(" array([[A, B], [C, D]]) ")); + m.def( + "kw_lb_func1", + [](const CustomRepr &) {}, + py::arg("custom") = CustomRepr(" array([[A, B],\n[C, D]]) ")); + m.def( + "kw_lb_func2", + [](const CustomRepr &) {}, + py::arg("custom") = CustomRepr("\v\n array([[A, B], [C, D]])")); + m.def( + "kw_lb_func3", + [](const CustomRepr &) {}, + py::arg("custom") = CustomRepr("array([[A, B], [C, D]]) \f\n")); + m.def( + "kw_lb_func4", + [](const CustomRepr &) {}, + py::arg("custom") = CustomRepr("array([[A, B],\n\f\n[C, D]])")); + m.def( + "kw_lb_func5", + [](const CustomRepr &) {}, + py::arg("custom") = CustomRepr("array([[A, B],\r [C, D]])")); + m.def( + "kw_lb_func6", [](const CustomRepr &) {}, py::arg("custom") = CustomRepr(" \v\t ")); + m.def( + "kw_lb_func7", + [](const std::string &) {}, + py::arg("str_arg") = "First line.\n Second line."); + // test_args_and_kwargs m.def("args_function", [](py::args args) -> py::tuple { PYBIND11_WARNING_PUSH diff --git a/tests/test_kwargs_and_defaults.py b/tests/test_kwargs_and_defaults.py index 7174726f..b5770019 100644 --- a/tests/test_kwargs_and_defaults.py +++ b/tests/test_kwargs_and_defaults.py @@ -23,6 +23,38 @@ def test_function_signatures(doc): doc(m.KWClass.foo1) == "foo1(self: m.kwargs_and_defaults.KWClass, x: int, y: float) -> None" ) + assert ( + doc(m.kw_lb_func0) + == "kw_lb_func0(custom: m.kwargs_and_defaults.CustomRepr = array([[A, B], [C, D]])) -> None" + ) + assert ( + doc(m.kw_lb_func1) + == "kw_lb_func1(custom: m.kwargs_and_defaults.CustomRepr = array([[A, B], [C, D]])) -> None" + ) + assert ( + doc(m.kw_lb_func2) + == "kw_lb_func2(custom: m.kwargs_and_defaults.CustomRepr = array([[A, B], [C, D]])) -> None" + ) + assert ( + doc(m.kw_lb_func3) + == "kw_lb_func3(custom: m.kwargs_and_defaults.CustomRepr = array([[A, B], [C, D]])) -> None" + ) + assert ( + doc(m.kw_lb_func4) + == "kw_lb_func4(custom: m.kwargs_and_defaults.CustomRepr = array([[A, B], [C, D]])) -> None" + ) + assert ( + doc(m.kw_lb_func5) + == "kw_lb_func5(custom: m.kwargs_and_defaults.CustomRepr = array([[A, B], [C, D]])) -> None" + ) + assert ( + doc(m.kw_lb_func6) + == "kw_lb_func6(custom: m.kwargs_and_defaults.CustomRepr = ) -> None" + ) + assert ( + doc(m.kw_lb_func7) + == "kw_lb_func7(str_arg: str = 'First line.\\n Second line.') -> None" + ) def test_named_arguments(): diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index b4ee6428..b03f5624 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -7,6 +7,8 @@ BSD-style license that can be found in the LICENSE file. */ +#include + #include "pybind11_tests.h" #include @@ -23,7 +25,7 @@ PyObject *conv(PyObject *o) { ret = PyFloat_FromDouble(v); } } else { - PyErr_SetString(PyExc_TypeError, "Unexpected type"); + py::set_error(PyExc_TypeError, "Unexpected type"); } return ret; } @@ -820,4 +822,12 @@ TEST_SUBMODULE(pytypes, m) { a >>= b; return a; }); + + m.def("annotate_tuple_float_str", [](const py::typing::Tuple &) {}); + m.def("annotate_tuple_empty", [](const py::typing::Tuple<> &) {}); + m.def("annotate_dict_str_int", [](const py::typing::Dict &) {}); + m.def("annotate_list_int", [](const py::typing::List &) {}); + m.def("annotate_set_str", [](const py::typing::Set &) {}); + m.def("annotate_fn", + [](const py::typing::Callable, py::str)> &) {}); } diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index eda7a20a..e88a9328 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -896,3 +896,38 @@ def test_inplace_lshift(a, b): def test_inplace_rshift(a, b): expected = a >> b assert m.inplace_rshift(a, b) == expected + + +def test_tuple_nonempty_annotations(doc): + assert ( + doc(m.annotate_tuple_float_str) + == "annotate_tuple_float_str(arg0: tuple[float, str]) -> None" + ) + + +def test_tuple_empty_annotations(doc): + assert ( + doc(m.annotate_tuple_empty) == "annotate_tuple_empty(arg0: tuple[()]) -> None" + ) + + +def test_dict_annotations(doc): + assert ( + doc(m.annotate_dict_str_int) + == "annotate_dict_str_int(arg0: dict[str, int]) -> None" + ) + + +def test_list_annotations(doc): + assert doc(m.annotate_list_int) == "annotate_list_int(arg0: list[int]) -> None" + + +def test_set_annotations(doc): + assert doc(m.annotate_set_str) == "annotate_set_str(arg0: set[str]) -> None" + + +def test_fn_annotations(doc): + assert ( + doc(m.annotate_fn) + == "annotate_fn(arg0: Callable[[list[str], str], int]) -> None" + ) diff --git a/tests/test_return_value_policy_pack.cpp b/tests/test_return_value_policy_pack.cpp index 6fbaed68..4e257fe9 100644 --- a/tests/test_return_value_policy_pack.cpp +++ b/tests/test_return_value_policy_pack.cpp @@ -311,7 +311,7 @@ TEST_SUBMODULE(return_value_policy_pack, m) { m.def( "return_set_sb", []() { return return_set_pair_string(); }, - py::return_value_policy_pack({rvpc, rvpb})); + py::return_value_policy_pack({{rvpc, rvpb}})); m.def( "return_set_bs", []() { @@ -319,12 +319,12 @@ TEST_SUBMODULE(return_value_policy_pack, m) { static auto *s = new SetPairString(return_set_pair_string()); return s; }, - py::return_value_policy_pack({rvpb, rvpc})); + py::return_value_policy_pack({{rvpb, rvpc}})); m.def( "return_vector_sb", []() { return return_vector_pair_string(); }, - py::return_value_policy_pack({rvpc, rvpb})); + py::return_value_policy_pack({{rvpc, rvpb}})); m.def( "return_vector_bs", []() { @@ -332,12 +332,12 @@ TEST_SUBMODULE(return_value_policy_pack, m) { static auto *v = new VectorPairString(return_vector_pair_string()); return v; }, - py::return_value_policy_pack({rvpb, rvpc})); + py::return_value_policy_pack({{rvpb, rvpc}})); m.def( "return_array_sb", []() { return return_array_pair_string(); }, - py::return_value_policy_pack({rvpc, rvpb})); + py::return_value_policy_pack({{rvpc, rvpb}})); m.def( "return_array_bs", []() { @@ -345,7 +345,7 @@ TEST_SUBMODULE(return_value_policy_pack, m) { static auto *a = new ArrayPairString(return_array_pair_string()); return a; }, - py::return_value_policy_pack({rvpb, rvpc})); + py::return_value_policy_pack({{rvpb, rvpc}})); m.attr("PYBIND11_HAS_OPTIONAL") = #if !defined(PYBIND11_HAS_OPTIONAL) @@ -355,7 +355,7 @@ TEST_SUBMODULE(return_value_policy_pack, m) { m.def( "return_optional_sb", []() { return return_optional_pair_string(); }, - py::return_value_policy_pack({rvpc, rvpb})); + py::return_value_policy_pack({{rvpc, rvpb}})); m.def( "return_optional_bs", []() { @@ -363,7 +363,7 @@ TEST_SUBMODULE(return_value_policy_pack, m) { static auto *o = new OptionalPairString(return_optional_pair_string()); return o; }, - py::return_value_policy_pack({rvpb, rvpc})); + py::return_value_policy_pack({{rvpb, rvpc}})); #endif m.attr("PYBIND11_HAS_VARIANT") = diff --git a/tests/test_sequences_and_iterators.cpp b/tests/test_sequences_and_iterators.cpp index 1de65edb..4a1d37f4 100644 --- a/tests/test_sequences_and_iterators.cpp +++ b/tests/test_sequences_and_iterators.cpp @@ -28,6 +28,13 @@ class NonZeroIterator { public: explicit NonZeroIterator(const T *ptr) : ptr_(ptr) {} + + // Make the iterator non-copyable and movable + NonZeroIterator(const NonZeroIterator &) = delete; + NonZeroIterator(NonZeroIterator &&) noexcept = default; + NonZeroIterator &operator=(const NonZeroIterator &) = delete; + NonZeroIterator &operator=(NonZeroIterator &&) noexcept = default; + const T &operator*() const { return *ptr_; } NonZeroIterator &operator++() { ++ptr_; @@ -78,6 +85,7 @@ class NonCopyableInt { int value_; }; using NonCopyableIntPair = std::pair; + PYBIND11_MAKE_OPAQUE(std::vector); PYBIND11_MAKE_OPAQUE(std::vector); @@ -375,6 +383,17 @@ TEST_SUBMODULE(sequences_and_iterators, m) { private: std::vector> data_; }; + + { + // #4383 : Make sure `py::make_*iterator` functions work with move-only iterators + using iterator_t = NonZeroIterator>; + + static_assert(std::is_move_assignable::value, ""); + static_assert(std::is_move_constructible::value, ""); + static_assert(!std::is_copy_assignable::value, ""); + static_assert(!std::is_copy_constructible::value, ""); + } + py::class_(m, "IntPairs") .def(py::init>>()) .def( diff --git a/tests/test_stl_binders.py b/tests/test_stl_binders.py index e002f5b6..79923f48 100644 --- a/tests/test_stl_binders.py +++ b/tests/test_stl_binders.py @@ -209,7 +209,7 @@ def test_map_string_double_const(): def test_noncopyable_containers(): # std::vector vnc = m.get_vnc(5) - for i in range(0, 5): + for i in range(5): assert vnc[i].value == i + 1 for i, j in enumerate(vnc, start=1): @@ -217,7 +217,7 @@ def test_noncopyable_containers(): # std::deque dnc = m.get_dnc(5) - for i in range(0, 5): + for i in range(5): assert dnc[i].value == i + 1 i = 1 @@ -252,7 +252,7 @@ def test_noncopyable_containers(): # nested std::map nvnc = m.get_nvnc(5) for i in range(1, 6): - for j in range(0, 5): + for j in range(5): assert nvnc[i][j].value == j + 1 # Note: maps do not have .values() diff --git a/tests/test_type_caster_odr_guard_1.cpp b/tests/test_type_caster_odr_guard_1.cpp index 80deb047..13a533c8 100644 --- a/tests/test_type_caster_odr_guard_1.cpp +++ b/tests/test_type_caster_odr_guard_1.cpp @@ -89,5 +89,12 @@ TEST_SUBMODULE(type_caster_odr_guard_1, m) { __CUDACC_VER_MINOR__) "." PYBIND11_TOSTRING(__CUDACC_VER_BUILD__); #else py::none(); +#endif + m.attr("NVCOMPILER") = +#if defined(__NVCOMPILER_MAJOR__) + PYBIND11_TOSTRING(__NVCOMPILER_MAJOR__) "." PYBIND11_TOSTRING( + __NVCOMPILER_MINOR__) "." PYBIND11_TOSTRING(__NVCOMPILER_PATCHLEVEL__); +#else + py::none(); #endif } diff --git a/tests/test_type_caster_odr_guard_1.py b/tests/test_type_caster_odr_guard_1.py index dd25b35b..8b624904 100644 --- a/tests/test_type_caster_odr_guard_1.py +++ b/tests/test_type_caster_odr_guard_1.py @@ -32,21 +32,22 @@ def test_type_caster_odr_registry_values(): assert "test_type_caster_odr_guard_" in "\n".join(reg_values) +def _count_0_message(tail): + return ( + "type_caster_odr_violation_detected_count() == 0:" + f" {pybind11_tests.compiler_info}, {pybind11_tests.cpp_std}, " + tail + ) + + def test_type_caster_odr_violation_detected_counter(): num_violations = m.type_caster_odr_violation_detected_count() if num_violations is None: pytest.skip("type_caster_odr_violation_detected_count() is None") if num_violations == 0: if m.if_defined__NO_INLINE__: - pytest.skip( - f"type_caster_odr_violation_detected_count() == 0:" - f" {pybind11_tests.compiler_info}, {pybind11_tests.cpp_std}," - " __NO_INLINE__" - ) + pytest.skip(_count_0_message("__NO_INLINE__")) if m.CUDACC is not None: - pytest.skip( - f"type_caster_odr_violation_detected_count() == 0:" - f" {pybind11_tests.compiler_info}, {pybind11_tests.cpp_std}," - " CUDACC = {m.CUDACC}" - ) + pytest.skip(_count_0_message(f"CUDACC = {m.CUDACC}")) + if m.NVCOMPILER is not None: + pytest.skip(_count_0_message(f"NVCOMPILER = {m.NVCOMPILER}")) assert num_violations == 1 diff --git a/tests/test_type_caster_pyobject_ptr.cpp b/tests/test_type_caster_pyobject_ptr.cpp index 1667ea12..8069f7dc 100644 --- a/tests/test_type_caster_pyobject_ptr.cpp +++ b/tests/test_type_caster_pyobject_ptr.cpp @@ -78,14 +78,14 @@ TEST_SUBMODULE(type_caster_pyobject_ptr, m) { m.def("cast_to_pyobject_ptr_nullptr", [](bool set_error) { if (set_error) { - PyErr_SetString(PyExc_RuntimeError, "Reflective of healthy error handling."); + py::set_error(PyExc_RuntimeError, "Reflective of healthy error handling."); } PyObject *ptr = nullptr; py::cast(ptr); }); m.def("cast_to_pyobject_ptr_non_nullptr_with_error_set", []() { - PyErr_SetString(PyExc_RuntimeError, "Reflective of unhealthy error handling."); + py::set_error(PyExc_RuntimeError, "Reflective of unhealthy error handling."); py::cast(Py_None); }); diff --git a/tools/pybind11NewTools.cmake b/tools/pybind11NewTools.cmake index 7d7424a7..3a35a738 100644 --- a/tools/pybind11NewTools.cmake +++ b/tools/pybind11NewTools.cmake @@ -233,11 +233,13 @@ function(pybind11_add_module target_name) endif() endif() - # Use case-insensitive comparison to match the result of $ - string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) - if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO) - # Strip unnecessary sections of the binary on Linux/macOS - pybind11_strip(${target_name}) + if(DEFINED CMAKE_BUILD_TYPE) # see https://github.com/pybind/pybind11/issues/4454 + # Use case-insensitive comparison to match the result of $ + string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) + if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO) + # Strip unnecessary sections of the binary on Linux/macOS + pybind11_strip(${target_name}) + endif() endif() if(MSVC) diff --git a/tools/pybind11Tools.cmake b/tools/pybind11Tools.cmake index 66ad00a4..67e710bd 100644 --- a/tools/pybind11Tools.cmake +++ b/tools/pybind11Tools.cmake @@ -212,10 +212,12 @@ function(pybind11_add_module target_name) endif() endif() - # Use case-insensitive comparison to match the result of $ - string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) - if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO) - pybind11_strip(${target_name}) + if(DEFINED CMAKE_BUILD_TYPE) # see https://github.com/pybind/pybind11/issues/4454 + # Use case-insensitive comparison to match the result of $ + string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) + if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO) + pybind11_strip(${target_name}) + endif() endif() if(MSVC)