diff --git a/.github/actions/build-emscripten/action.yml b/.github/actions/build-emscripten/action.yml new file mode 100644 index 00000000000..1a454801ebf --- /dev/null +++ b/.github/actions/build-emscripten/action.yml @@ -0,0 +1,51 @@ +name: Build Emscripten +description: Build the cached CPython Emscripten environment +inputs: + save-cache: + description: Whether to save build caches + required: false + default: "false" +runs: + using: composite + steps: + - name: Select Python + shell: bash + run: echo "UV_PYTHON=3.15" >> "$GITHUB_ENV" + - uses: astral-sh/setup-uv@v7 + with: + save-cache: ${{ inputs.save-cache }} + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-emscripten + components: rust-src + - uses: actions/setup-node@v7 + with: + node-version: 24 + - name: Resolve CPython version + id: python + shell: bash + run: | + version=$(uv run --no-project --python "$UV_PYTHON" python -c 'import sys; print(sys.version.split()[0])') + echo "version=$version" >> "$GITHUB_OUTPUT" + - name: Restore build cache + if: inputs.save-cache != 'true' + id: cache-restore + uses: actions/cache/restore@v6 + with: + path: .nox/emscripten + key: emscripten-${{ hashFiles('wasm/emscripten/*', 'wasm/common.mk') }}-${{ hashFiles('noxfile.py') }}-${{ steps.python.outputs.version }} + - name: Restore and save build cache + if: inputs.save-cache == 'true' + id: cache + uses: actions/cache@v6 + with: + path: .nox/emscripten + key: emscripten-${{ hashFiles('wasm/emscripten/*', 'wasm/common.mk') }}-${{ hashFiles('noxfile.py') }}-${{ steps.python.outputs.version }} + - uses: Swatinem/rust-cache@v2 + with: + save-if: ${{ inputs.save-cache }} + - name: Build + if: steps.cache.outputs.cache-hit != 'true' && steps.cache-restore.outputs.cache-hit != 'true' + shell: bash + run: uvx nox -s build-emscripten diff --git a/.github/actions/build-wasi/action.yml b/.github/actions/build-wasi/action.yml new file mode 100644 index 00000000000..8b3d0c11af1 --- /dev/null +++ b/.github/actions/build-wasi/action.yml @@ -0,0 +1,59 @@ +name: Build WASI +description: Build the cached CPython WASI environment +inputs: + save-cache: + description: Whether to save caches + required: false + default: "false" +runs: + using: composite + steps: + - name: Select Python + shell: bash + run: echo "UV_PYTHON=3.15" >> "$GITHUB_ENV" + - uses: astral-sh/setup-uv@v7 + with: + save-cache: ${{ inputs.save-cache }} + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-wasip1 + components: rust-src + - name: Install wasmtime + uses: bytecodealliance/actions/wasmtime/setup@v1 + - name: Resolve CPython version + id: python + shell: bash + run: | + version=$(uv run --no-project --python "$UV_PYTHON" python -c 'import sys; print(sys.version.split()[0])') + echo "version=$version" >> "$GITHUB_OUTPUT" + - name: Restore build cache + if: inputs.save-cache != 'true' + id: cache-restore + uses: actions/cache/restore@v6 + with: + path: .nox/wasi + key: wasi-${{ hashFiles('wasm/wasi/*', 'wasm/common.mk') }}-${{ hashFiles('noxfile.py') }}-${{ steps.python.outputs.version }} + - name: Restore and save build cache + if: inputs.save-cache == 'true' + id: cache + uses: actions/cache@v6 + with: + path: .nox/wasi + key: wasi-${{ hashFiles('wasm/wasi/*', 'wasm/common.mk') }}-${{ hashFiles('noxfile.py') }}-${{ steps.python.outputs.version }} + - uses: Swatinem/rust-cache@v2 + with: + save-if: ${{ inputs.save-cache }} + - name: Prepare CPython source + id: prepare + shell: bash + run: uvx nox -s prepare-wasm + - name: Install WASI SDK + uses: bytecodealliance/setup-wasi-sdk-action@v1 + with: + version: ${{ steps.prepare.outputs.wasi-sdk-version }} + add-to-path: false + - name: Build + if: steps.cache.outputs.cache-hit != 'true' && steps.cache-restore.outputs.cache-hit != 'true' + shell: bash + run: uvx nox -s build-wasm diff --git a/.github/workflows/benches.yml b/.github/workflows/benches.yml index 6dfb55b08a9..4a2fa4915e8 100644 --- a/.github/workflows/benches.yml +++ b/.github/workflows/benches.yml @@ -18,14 +18,14 @@ jobs: benchmarks: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: astral-sh/setup-uv@v7 with: save-cache: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} # Using this action is still necessary for CodSpeed to build flamegraphs correctly, # see note about setup-python in https://codspeed.io/docs/benchmarks/python#recipes - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: ${{ env.UV_PYTHON }} @@ -47,7 +47,7 @@ jobs: tool: cargo-codspeed - name: Run the benchmarks - uses: CodSpeedHQ/action@v5.0.1 + uses: CodSpeedHQ/action@v5.2.1 with: run: uvx nox -s codspeed token: ${{ secrets.CODSPEED_TOKEN }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e5482361ea..7329e246834 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,7 +36,7 @@ jobs: runs-on: ${{ inputs.os }} if: ${{ !(startsWith(inputs.python-version, 'graalpy') && startsWith(inputs.os, 'windows')) }} steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 with: ref: ${{ inputs.sha }} @@ -44,12 +44,11 @@ jobs: - if: ${{ !(inputs.os == 'macos-latest' && contains(fromJSON('["3.8", "3.9"]'), inputs.python-version) && inputs.python-architecture == 'x64') }} name: Set up Python ${{ inputs.python-version }} id: setup-python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: ${{ inputs.python-version }} architecture: ${{ inputs.python-architecture }} - # PyPy can have FFI changes within Python versions, which creates pain in CI - check-latest: ${{ startsWith(inputs.python-version, 'pypy') }} + check-latest: true - name: Install zoneinfo backport for Python 3.8 id: zoneinfo-backport diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 2938aa128b1..ad46e947379 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -9,8 +9,8 @@ jobs: name: Check changelog entry runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - uses: actions/checkout@v7.0.1 + - uses: actions/setup-python@v7 with: python-version: '3.14' - uses: astral-sh/setup-uv@v7 diff --git a/.github/workflows/ci-cache-warmup.yml b/.github/workflows/ci-cache-warmup.yml index 6bf9426595b..5e386b7a960 100644 --- a/.github/workflows/ci-cache-warmup.yml +++ b/.github/workflows/ci-cache-warmup.yml @@ -9,8 +9,8 @@ jobs: cross-compilation-windows: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - uses: actions/checkout@v7.0.1 + - uses: actions/setup-python@v7 with: python-version: "3.14" - uses: dtolnay/rust-toolchain@stable @@ -32,3 +32,19 @@ jobs: with: path: ~/.cache/cargo-xwin key: cargo-xwin-cache + + emscripten: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7.0.1 + - uses: ./.github/actions/build-emscripten + with: + save-cache: true + + wasm32-wasip1: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7.0.1 + - uses: ./.github/actions/build-wasi + with: + save-cache: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3e1d250dd1..e15fa31ed8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,8 +20,8 @@ jobs: fmt: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - uses: actions/checkout@v7.0.1 + - uses: actions/setup-python@v7 with: python-version: "3.14" - uses: astral-sh/setup-uv@v7 @@ -54,8 +54,8 @@ jobs: # with the commit diff, because the merge may affect line numbers. coverage-sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} steps: - - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - uses: actions/checkout@v7.0.1 + - uses: actions/setup-python@v7 with: python-version: "3.14" - name: resolve MSRV @@ -67,8 +67,8 @@ jobs: needs: [fmt] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - uses: actions/checkout@v7.0.1 + - uses: actions/setup-python@v7 with: python-version: "3.14" - name: Fetch merge base @@ -85,12 +85,12 @@ jobs: needs: [fmt, resolve] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ needs.resolve.outputs.MSRV }} components: rust-src - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: "3.14" - uses: Swatinem/rust-cache@v2 @@ -133,7 +133,7 @@ jobs: name: clippy/${{ matrix.target }}/${{ matrix.rust }} continue-on-error: ${{ matrix.rust != 'stable' }} steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} @@ -157,7 +157,7 @@ jobs: name: check-nightly/${{ matrix.target }}/${{ matrix.rust }} continue-on-error: true steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: dtolnay/rust-toolchain@nightly with: targets: ${{ matrix.target }} @@ -439,8 +439,8 @@ jobs: needs: [fmt] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - uses: actions/checkout@v7.0.1 + - uses: actions/setup-python@v7 with: python-version: "3.14" - uses: Swatinem/rust-cache@v2 @@ -460,15 +460,18 @@ jobs: needs: [fmt] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - uses: actions/checkout@v7.0.1 + - uses: actions/setup-python@v7 with: python-version: "3.14" - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - - uses: dtolnay/rust-toolchain@nightly + # TODO: unpin to rust-toolchain@nightly once the below is fixed + # https://github.com/RalfJung/cargo-careful/issues/55 + - uses: dtolnay/rust-toolchain@master with: + toolchain: nightly-2026-08-06 components: rust-src - uses: taiki-e/install-action@cargo-careful - uses: astral-sh/setup-uv@v7 @@ -482,8 +485,8 @@ jobs: needs: [fmt] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - uses: actions/checkout@v7.0.1 + - uses: actions/setup-python@v7 with: python-version: "3.14" - uses: Swatinem/rust-cache@v2 @@ -497,93 +500,35 @@ jobs: emscripten: name: emscripten if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} - needs: [fmt] + needs: [fmt, resolve] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 - - uses: astral-sh/setup-uv@v7 + - uses: actions/checkout@v7.0.1 + - uses: ./.github/actions/build-emscripten with: save-cache: ${{ needs.resolve.outputs.save-cache }} - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32-unknown-emscripten - components: rust-src - - uses: actions/setup-node@v6 - with: - node-version: 24 - - uses: actions/cache/restore@v6 - id: cache - with: - path: | - .nox/emscripten - key: emscripten-${{ hashFiles('wasm/emscripten/*', 'wasm/common.mk') }}-${{ hashFiles('noxfile.py') }}-${{ env.UV_PYTHON }} - - uses: Swatinem/rust-cache@v2 - with: - save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - - name: Build - if: steps.cache.outputs.cache-hit != 'true' - run: uvx nox -s build-emscripten - name: Test run: uvx nox -s test-emscripten - - uses: actions/cache/save@v6 - if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - with: - path: | - .nox/emscripten - key: emscripten-${{ hashFiles('wasm/emscripten/*', 'wasm/common.mk') }}-${{ hashFiles('noxfile.py') }}-${{ env.UV_PYTHON }} wasm32-wasip1: name: wasm32-wasip1 if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} - needs: [fmt] + needs: [fmt, resolve] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 - - uses: astral-sh/setup-uv@v7 + - uses: actions/checkout@v7.0.1 + - uses: ./.github/actions/build-wasi with: save-cache: ${{ needs.resolve.outputs.save-cache }} - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32-wasip1 - components: rust-src - - name: "Install wasmtime" - uses: bytecodealliance/actions/wasmtime/setup@v1 - - name: "Install WASI SDK" - uses: bytecodealliance/setup-wasi-sdk-action@main - with: - version: "24" - # wasi sdk sets CC variables which break Python's configure script - # (it also sets WASI_SDK_PATH even without `add-to-path`, which is sufficient) - add-to-path: false - - uses: actions/cache/restore@v6 - id: cache - with: - path: | - .nox/wasi - key: wasi-${{ hashFiles('wasm/wasi/*', 'wasm/common.mk') }}-${{ hashFiles('noxfile.py') }}-${{ env.UV_PYTHON }} - - uses: Swatinem/rust-cache@v2 - with: - save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - - name: Build - if: steps.cache.outputs.cache-hit != 'true' - run: uvx nox -s build-wasm - name: Test run: uvx nox -s test-wasm - - uses: actions/cache/save@v6 - if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - with: - path: | - .nox/wasi - key: wasi-${{ hashFiles('wasm/wasi/*', 'wasm/common.mk') }}-${{ hashFiles('noxfile.py') }}-${{ env.UV_PYTHON }} test-debug: if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} needs: [fmt] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} @@ -601,7 +546,7 @@ jobs: needs: [fmt] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} @@ -619,7 +564,7 @@ jobs: if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 with: ref: ${{ needs.resolve.outputs.coverage-sha }} - uses: astral-sh/setup-uv@v7 @@ -649,8 +594,8 @@ jobs: include: - rust: ${{ needs.resolve.outputs.MSRV }} steps: - - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - uses: actions/checkout@v7.0.1 + - uses: actions/setup-python@v7 with: python-version: "3.15-dev" - uses: Swatinem/rust-cache@v2 @@ -695,13 +640,13 @@ jobs: target: "aarch64-pc-windows-msvc" flags: "-i python3.13" steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 with: ref: ${{ needs.resolve.outputs.coverage-sha }} - uses: astral-sh/setup-uv@v7 with: save-cache: ${{ needs.resolve.outputs.save-cache }} - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: ${{ env.UV_PYTHON }} - uses: Swatinem/rust-cache@v2 @@ -749,7 +694,7 @@ jobs: if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 with: ref: ${{ needs.resolve.outputs.coverage-sha }} - uses: astral-sh/setup-uv@v7 @@ -806,11 +751,11 @@ jobs: ] runs-on: ${{ matrix.platform.os }} steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.platform.rust-target }} - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: "3.14" architecture: ${{ matrix.platform.python-architecture }} @@ -827,9 +772,9 @@ jobs: if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-build-full') && github.event_name == 'pull_request' }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: dtolnay/rust-toolchain@stable - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: "3.14" - uses: astral-sh/setup-uv@v7 @@ -844,11 +789,11 @@ jobs: matrix: checker: [mypy, pyrefly] steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: dtolnay/rust-toolchain@stable with: components: rust-src - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: "3.14" - uses: astral-sh/setup-uv@v7 @@ -867,6 +812,7 @@ jobs: - careful - docsrs - emscripten + - wasm32-wasip1 - test-debug - test-version-limits - check-feature-powerset diff --git a/.github/workflows/coverage-pr-base.yml b/.github/workflows/coverage-pr-base.yml index 6d45a0312a1..8b531d89360 100644 --- a/.github/workflows/coverage-pr-base.yml +++ b/.github/workflows/coverage-pr-base.yml @@ -12,8 +12,8 @@ jobs: coverage-pr-base: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - uses: actions/checkout@v7.0.1 + - uses: actions/setup-python@v7 with: python-version: '3.14' - name: Fetch merge base diff --git a/.github/workflows/netlify-build.yml b/.github/workflows/netlify-build.yml index bafeeff0382..42fbf3023b5 100644 --- a/.github/workflows/netlify-build.yml +++ b/.github/workflows/netlify-build.yml @@ -19,8 +19,8 @@ jobs: guide-build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - uses: actions/checkout@v7.0.1 + - uses: actions/setup-python@v7 with: python-version: "3.14" - uses: astral-sh/setup-uv@v7 diff --git a/.github/workflows/python-wheel.yml b/.github/workflows/python-wheel.yml index bd08ed50c5f..5ed664185e3 100644 --- a/.github/workflows/python-wheel.yml +++ b/.github/workflows/python-wheel.yml @@ -19,7 +19,7 @@ jobs: matrix: target: [x86_64, x86, aarch64, armv7, s390x, ppc64le] steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} @@ -45,7 +45,7 @@ jobs: - runner: windows-11-arm target: aarch64 steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - name: Build wheels uses: PyO3/maturin-action@v1 with: @@ -69,7 +69,7 @@ jobs: - runner: macos-latest target: aarch64 steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: PyO3/maturin-action@v1 with: target: ${{ matrix.platform.target }} @@ -85,7 +85,7 @@ jobs: pypi_sdist: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: PyO3/maturin-action@v1 with: command: sdist diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4886fca7cb7..40fac6907d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest environment: release steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # The tag to build or the tag received by the tag event ref: ${{ github.event.inputs.version || github.ref }} @@ -34,6 +34,6 @@ jobs: id: auth - name: Publish to crates.io - run: uvx nox -s publish + run: cargo publish --workspace env: CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} diff --git a/Cargo.toml b/Cargo.toml index 3f0f05432e3..3653605d13f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -262,10 +262,6 @@ workspace = true # CI is marginally more efficient if `required-feature` is specified to avoid # building and launching empty test suites. -[[test]] -name = "test_anyhow" -required-features = ["anyhow"] - [[test]] name = "test_append_to_inittab" required-features = ["macros"] @@ -282,10 +278,6 @@ required-features = ["macros"] name = "test_buffer_protocol" required-features = ["macros"] -[[test]] -name = "test_bytes" -required-features = ["macros"] - [[test]] name = "test_class_attributes" required-features = ["macros"] @@ -407,18 +399,10 @@ required-features = ["macros"] name = "test_sequence" required-features = ["macros"] -[[test]] -name = "test_serde" -required-features = ["serde"] - [[test]] name = "test_static_slots" required-features = ["macros"] -[[test]] -name = "test_string" -required-features = ["macros"] - [[test]] name = "test_super" required-features = ["macros"] diff --git a/README.md b/README.md index c4b6e39544b..22420c1a9ea 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![benchmark](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/PyO3/pyo3) [![codecov](https://img.shields.io/codecov/c/gh/PyO3/pyo3?logo=codecov)](https://codecov.io/gh/PyO3/pyo3) [![crates.io](https://img.shields.io/crates/v/pyo3?logo=rust)](https://crates.io/crates/pyo3) -[![minimum rustc 1.83](https://img.shields.io/badge/rustc-1.83+-blue?logo=rust)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html) +[![minimum rustc](https://img.shields.io/badge/dynamic/json?url=https://crates.io/api/v1/crates/pyo3&query=$.versions[0].rust_version&label=rustc&suffix=%2B&color=blue&logo=rust)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html) [![discord server](https://img.shields.io/discord/1209263839632424990?logo=discord)](https://discord.gg/33kcChzH7f) [![contributing notes](https://img.shields.io/badge/contribute-on%20github-Green?logo=github)](https://github.com/PyO3/pyo3/blob/main/Contributing.md) diff --git a/guide/src/class/protocols.md b/guide/src/class/protocols.md index a0d73fb37c0..1e626c6e953 100644 --- a/guide/src/class/protocols.md +++ b/guide/src/class/protocols.md @@ -178,7 +178,7 @@ The given signatures should be interpreted as follows: Iterators can be defined using these methods: - `__iter__() -> object` -- `__next__() -> Option or IterNextOutput` ([see details](#returning-a-value-from-iteration)) +- `__next__() -> Option` ([see details](#returning-a-value-from-iteration)) Returning `None` from `__next__` indicates that that there are no further items. diff --git a/guide/src/free-threading.md b/guide/src/free-threading.md index c802d409004..cc355ff9cc4 100644 --- a/guide/src/free-threading.md +++ b/guide/src/free-threading.md @@ -169,7 +169,7 @@ For now you should explicitly add locking, possibly using conditional compilatio ### Cannot build extension modules using the limited API The free-threaded build uses a completely new ABI and there is not yet an equivalent to the limited API for the free-threaded ABI. -That means if your crate depends on PyO3 using the `abi3` feature or an an `abi3-pyxx` feature, PyO3 will print a warning and ignore that setting when building extension modules using the free-threaded interpreter. +That means if your crate depends on PyO3 using the `abi3` feature or an `abi3-pyxx` feature, PyO3 will print a warning and ignore that setting when building extension modules using the free-threaded interpreter. This means that if your package makes use of the ABI forward compatibility provided by the limited API to upload only one wheel for each release of your package, you will need to update your release procedure to also upload a version-specific free-threaded wheel. diff --git a/guide/src/performance.md b/guide/src/performance.md index 8b5d91fd7ed..763ec999018 100644 --- a/guide/src/performance.md +++ b/guide/src/performance.md @@ -56,6 +56,47 @@ fn frobnicate<'py>(value: &Bound<'py, PyAny>) -> PyResult> { } ``` +## String interning + +Every time a Rust `&str` is converted into a Python string, for example via `PyString::new` or through an `IntoPyObject` implementation, PyO3 allocates a new `PyString` object. +For strings that are reused repeatedly at the same call site, e.g. as dictionary keys or attribute names, this repeated allocation is unnecessary overhead. + +The [`intern!`] macro caches a `PyString` in static storage the first time it is evaluated for a given call site, and returns a reference to that same object on every subsequent call, avoiding the repeated allocation. + +For example, instead of writing + +```rust,no_run +# #![allow(dead_code)] +# use pyo3::prelude::*; +# use pyo3::types::PyDict; + +#[pyfunction] +fn create_dict(py: Python<'_>) -> PyResult> { + let dict = PyDict::new(py); + // A new `PyString` is created for every call of this function. + dict.set_item("foo", 42)?; + Ok(dict) +} +``` + +use the more efficient + +```rust,no_run +# #![allow(dead_code)] +# use pyo3::prelude::*; +# use pyo3::{intern, types::PyDict}; + +#[pyfunction] +fn create_dict(py: Python<'_>) -> PyResult> { + let dict = PyDict::new(py); + // A `PyString` is created once and reused for the lifetime of the program. + dict.set_item(intern!(py, "foo"), 42)?; + Ok(dict) +} +``` + +[`intern!`]: {{#PYO3_DOCS_URL}}/pyo3/macro.intern.html + ## Access to Bound implies access to Python token Calling `Python::attach` is effectively a no-op when we're already attached to the interpreter, but checking that this is the case still has a cost. @@ -105,7 +146,7 @@ impl PartialEq for FooBound<'_> { CPython support multiple calling protocols: [`tp_call`] and [`vectorcall`]. [`vectorcall`] is a more efficient protocol unlocking faster calls. -PyO3 will try to dispatch Python `call`s using the [`vectorcall`] calling convention to archive maximum performance if possible and falling back to [`tp_call`] otherwise. +PyO3 will try to dispatch Python `call`s using the [`vectorcall`] calling convention to achieve maximum performance if possible and falling back to [`tp_call`] otherwise. This is implemented using the (internal) `PyCallArgs` trait. It defines how Rust types can be used as Python `call` arguments. This trait is currently implemented for diff --git a/newsfragments/6160.added.md b/newsfragments/6160.added.md new file mode 100644 index 00000000000..f948bceff25 --- /dev/null +++ b/newsfragments/6160.added.md @@ -0,0 +1 @@ +Enable `PyLong(Writer|Export)` api on abi3 from 3.15+ for fast u128/i128 conversions diff --git a/newsfragments/6195.added.md b/newsfragments/6195.added.md new file mode 100644 index 00000000000..92b32e3fad8 --- /dev/null +++ b/newsfragments/6195.added.md @@ -0,0 +1 @@ +Added FFI bindings for CPython eval-frame get/set API diff --git a/newsfragments/6195.fixed.md b/newsfragments/6195.fixed.md new file mode 100644 index 00000000000..836c5e711a5 --- /dev/null +++ b/newsfragments/6195.fixed.md @@ -0,0 +1 @@ +Fix the PyUnstable_Eval_RequestCodeExtraIndex FFI binding to link to the private CPython symbol on Python versions before 3.12. diff --git a/newsfragments/6225.changed.md b/newsfragments/6225.changed.md new file mode 100644 index 00000000000..c3c8a2ae251 --- /dev/null +++ b/newsfragments/6225.changed.md @@ -0,0 +1 @@ +Reduce allocation traffic when extracting Python `set` and `frozenset` values into Rust hash sets by preallocating the destination set. diff --git a/newsfragments/6230.fixed.md b/newsfragments/6230.fixed.md new file mode 100644 index 00000000000..5ec4c840dde --- /dev/null +++ b/newsfragments/6230.fixed.md @@ -0,0 +1 @@ +Fix FFI definitions `PyByteArray_GET_SIZE`, `PyList_GET_SIZE`, and `PySet_GET_SIZE` to use an atomic load for free-threaded Python. diff --git a/newsfragments/6273.fixed.md b/newsfragments/6273.fixed.md new file mode 100644 index 00000000000..77d550d4c83 --- /dev/null +++ b/newsfragments/6273.fixed.md @@ -0,0 +1 @@ +`experimental-inspect`: deduplicate repeated members of a type union in the generated stubs, and put a space on both sides of the `|` rather than only before it. diff --git a/newsfragments/6274.fixed.md b/newsfragments/6274.fixed.md new file mode 100644 index 00000000000..a28fe7c3f3f --- /dev/null +++ b/newsfragments/6274.fixed.md @@ -0,0 +1 @@ +`experimental-inspect`: `__next__` and `__anext__` returning `Option` (or `PyResult>`) are now introspected as returning `T`, since `None` stops the iteration instead of being yielded. diff --git a/newsfragments/6309.fixed.md b/newsfragments/6309.fixed.md new file mode 100644 index 00000000000..a4bf373a22d --- /dev/null +++ b/newsfragments/6309.fixed.md @@ -0,0 +1 @@ +Fix `clippy::clone_on_copy` warnings triggered on nightly Rust by `#[pyclass(from_py_object)]` on classes which implement `Copy`. diff --git a/newsfragments/6363.fixed.md b/newsfragments/6363.fixed.md new file mode 100644 index 00000000000..90818189d2c --- /dev/null +++ b/newsfragments/6363.fixed.md @@ -0,0 +1 @@ +`experimental-inspect`: `__pow__`, `__rpow__` and `__get__` now introspect their trailing argument as defaulting to `None`, matching the CPython slot wrappers which substitute `None` when it is omitted. diff --git a/newsfragments/6365.fixed.md b/newsfragments/6365.fixed.md new file mode 100644 index 00000000000..325c50e7db3 --- /dev/null +++ b/newsfragments/6365.fixed.md @@ -0,0 +1 @@ +`experimental-inspect`: fix the stubs of a package nested inside another package being written into a directory named after its parent instead of its own name. diff --git a/newsfragments/6377.changed.md b/newsfragments/6377.changed.md new file mode 100644 index 00000000000..d3159ad375f --- /dev/null +++ b/newsfragments/6377.changed.md @@ -0,0 +1 @@ +Optimize PyBytes::as_bytes() in the unlimited API diff --git a/newsfragments/6389.fixed.md b/newsfragments/6389.fixed.md new file mode 100644 index 00000000000..eeb3b5af576 --- /dev/null +++ b/newsfragments/6389.fixed.md @@ -0,0 +1 @@ +Fix many unresolved symbols when linking for PyPy due to incorrect link names in `pyo3-ffi`. diff --git a/newsfragments/6393.fixed.md b/newsfragments/6393.fixed.md new file mode 100644 index 00000000000..c89b80a7191 --- /dev/null +++ b/newsfragments/6393.fixed.md @@ -0,0 +1 @@ +Fix empty tuple type hints being rendered as invalid `tuple[]` instead of `tuple[()]` in generated stubs and `PyStaticExpr` display. diff --git a/newsfragments/6394.fixed.md b/newsfragments/6394.fixed.md new file mode 100644 index 00000000000..89b087507ed --- /dev/null +++ b/newsfragments/6394.fixed.md @@ -0,0 +1 @@ +`experimental-inspect`: escape stub docstrings to prevent invalid Python syntax. diff --git a/newsfragments/6395.fixed.md b/newsfragments/6395.fixed.md new file mode 100644 index 00000000000..2d34c8d17ff --- /dev/null +++ b/newsfragments/6395.fixed.md @@ -0,0 +1 @@ +`experimental-inspect`: fix the generated type annotation for `PyBuffer` on `python <3.12` by using `typing_extensions.Buffer` instead of `collections.abc.Buffer`. diff --git a/newsfragments/6397.fixed.md b/newsfragments/6397.fixed.md new file mode 100644 index 00000000000..ea71b5f7e12 --- /dev/null +++ b/newsfragments/6397.fixed.md @@ -0,0 +1 @@ +Fix string constants containing control characters rendering as invalid Python in `Display for PyStaticExpr`. diff --git a/newsfragments/6404.fixed.md b/newsfragments/6404.fixed.md new file mode 100644 index 00000000000..9f85271d790 --- /dev/null +++ b/newsfragments/6404.fixed.md @@ -0,0 +1 @@ +Fix crash when a detached thread is terminated while trying to reattach during interpreter finalization. diff --git a/newsfragments/6410.fixed.2.md b/newsfragments/6410.fixed.2.md new file mode 100644 index 00000000000..8483a926d2e --- /dev/null +++ b/newsfragments/6410.fixed.2.md @@ -0,0 +1 @@ +Fix FFI definition `PyVectorcall_Call` failing to link on Python 3.11 and older. diff --git a/newsfragments/6410.fixed.3.md b/newsfragments/6410.fixed.3.md new file mode 100644 index 00000000000..0c9fb3d9452 --- /dev/null +++ b/newsfragments/6410.fixed.3.md @@ -0,0 +1 @@ +Fix DLL load failures on Windows with PyPy when `raw-dylib` linking is disabled. diff --git a/newsfragments/6410.fixed.md b/newsfragments/6410.fixed.md new file mode 100644 index 00000000000..fc5828db6db --- /dev/null +++ b/newsfragments/6410.fixed.md @@ -0,0 +1 @@ +Fix link failures on 32-bit Windows when `raw-dylib` linking is disabled. diff --git a/newsfragments/6419.added.md b/newsfragments/6419.added.md new file mode 100644 index 00000000000..b4817c93fd9 --- /dev/null +++ b/newsfragments/6419.added.md @@ -0,0 +1 @@ +Add FFI definitions `PyModule_GetState_DuringGC`, `PyModule_GetToken_DuringGC`, `PyObject_GetTypeData_DuringGC`, `PyType_GetModuleState_DuringGC`, `PyType_GetBaseByToken_DuringGC`, `PyType_GetModule_DuringGC`, and `PyType_GetModuleByToken_DuringGC` for Python 3.15 and up. diff --git a/newsfragments/6421.fixed.md b/newsfragments/6421.fixed.md new file mode 100644 index 00000000000..fece7a15765 --- /dev/null +++ b/newsfragments/6421.fixed.md @@ -0,0 +1 @@ +Fix many unresolved data symbols when linking for PyPy due to incorrect link names in `pyo3-ffi`. diff --git a/newsfragments/6421.removed.md b/newsfragments/6421.removed.md new file mode 100644 index 00000000000..b3740a11308 --- /dev/null +++ b/newsfragments/6421.removed.md @@ -0,0 +1 @@ +Remove FFI definitions `PyExc_RecursionErrorInst` and `Py_UseClassExceptionsFlag` (not present in supported Python versions). diff --git a/newsfragments/6422.fixed.md b/newsfragments/6422.fixed.md new file mode 100644 index 00000000000..9a438dd46ed --- /dev/null +++ b/newsfragments/6422.fixed.md @@ -0,0 +1 @@ +Fix linker errors on PyPy for outdated FFI definitions where PyPy moved from a function to a macro. diff --git a/noxfile.py b/noxfile.py index dff06ff6fdf..c8acb98e123 100644 --- a/noxfile.py +++ b/noxfile.py @@ -365,16 +365,6 @@ def _check(env: Dict[str, str], version: Tuple[int, int]) -> None: session.error("one or more jobs failed") -@nox.session(venv_backend="none") -def publish(session: nox.Session) -> None: - _run_cargo_publish(session, package="pyo3-build-config") - _run_cargo_publish(session, package="pyo3-macros-backend") - _run_cargo_publish(session, package="pyo3-macros") - _run_cargo_publish(session, package="pyo3-ffi") - _run_cargo_publish(session, package="pyo3") - _run_cargo_publish(session, package="pyo3-introspection") - - @nox.session(venv_backend="none") def contributors(session: nox.Session) -> None: import requests @@ -486,6 +476,8 @@ def test_emscripten(session: nox.Session): "-C link-arg=-sEXPORTED_FUNCTIONS=_main,__PyRuntime", "-C link-arg=-sALLOW_MEMORY_GROWTH=1", "-C link-arg=-sSTACK_SIZE=262144", + # https://github.com/python/cpython/issues/156780 + "-C link-arg=-sMAIN_MODULE=2", ] ) session.env["RUSTDOCFLAGS"] = session.env["RUSTFLAGS"] @@ -524,14 +516,13 @@ def __init__(self): self.libdir = crossbuild_dir / "build" / f"lib.wasi-wasm32-{self.pymajorminor}" -@nox.session(name="build-wasm", venv_backend="none") -def build_wasm(session: nox.Session): - info = WasiInfo() +def _make_wasm(session: nox.Session, info: WasiInfo, *targets: str): _run( session, "make", "-C", str(info.wasi_dir), + *targets, f"PYTHON={sys.executable}", f"BUILDROOT={info.builddir}", f"PYMAJORMINORMICRO={info.pyversion}", @@ -539,6 +530,27 @@ def build_wasm(session: nox.Session): ) +@nox.session(name="prepare-wasm", venv_backend="none") +def prepare_wasm(session: nox.Session): + import tomllib + + info = WasiInfo() + _make_wasm(session, info, "prepare") + + with (info.cpython_dir / "Platforms/WASI/config.toml").open("rb") as config_file: + wasi_sdk_version = tomllib.load(config_file)["targets"]["wasi-sdk"] + + session.log("CPython requires WASI SDK %s", wasi_sdk_version) + if github_output := os.environ.get("GITHUB_OUTPUT"): + with open(github_output, "a") as output_file: + print(f"wasi-sdk-version={wasi_sdk_version}", file=output_file) + + +@nox.session(name="build-wasm", venv_backend="none") +def build_wasm(session: nox.Session): + _make_wasm(session, WasiInfo()) + + @nox.session(name="test-wasm", venv_backend="none") def test_wasm(session: nox.Session): info = WasiInfo() @@ -552,8 +564,11 @@ def test_wasm(session: nox.Session): ) session.env["PYO3_CROSS_LIB_DIR"] = str(info.libdir) session.env["CARGO_BUILD_TARGET"] = target + # The checkout is mounted at `/`; point the embedded interpreter at the stdlib and + # the WASI build outputs. + build_lib_dir = info.libdir.relative_to(info.cpython_dir).as_posix() session.env["CARGO_TARGET_WASM32_WASIP1_RUNNER"] = ( - f"wasmtime run --dir {info.cpython_dir}::/ --env PYTHONPATH=/lib" + f"wasmtime run --dir {info.cpython_dir}::/ --env PYTHONPATH=/Lib:/{build_lib_dir}" ) session.env["RUSTFLAGS"] = " ".join( [ @@ -561,7 +576,12 @@ def test_wasm(session: nox.Session): "-C link-arg=-lwasi-emulated-signal", "-C link-arg=-lwasi-emulated-process-clocks", "-C link-arg=-lwasi-emulated-getpid", - "-C link-arg=-lmpdec", + "-C link-arg=-lpthread", + "-C link-arg=-lHacl_Hash_MD5", + "-C link-arg=-lHacl_Hash_SHA1", + "-C link-arg=-lHacl_Hash_SHA2", + "-C link-arg=-lHacl_Hash_SHA3", + "-C link-arg=-lHacl_Hash_BLAKE2", "-C link-arg=-lHacl_HMAC", "-C link-arg=-lexpat", ] @@ -1296,13 +1316,16 @@ def load_pkg_versions(): @nox.session(name="ffi-check") def ffi_check(session: nox.Session): - extra_args = [] - # This flag can be useful for debugging ffi-check errors, but overall the - # short message format is easier to read - if "--long-message-format" not in session.posargs: - extra_args.append("--message-format=short") - - _run_cargo(session, "run", _FFI_CHECK, *extra_args) + # on windows, missing symbols are reported best at link time against a + # proper import library, so running with raw dylib disabled gets the best + # feedback. Exercise both paths. + no_raw_dylib_env = {**os.environ, "PYO3_USE_RAW_DYLIB": "0"} + raw_dylib_env = {**os.environ, "PYO3_USE_RAW_DYLIB": "1"} + if sys.platform == "win32": + # only relevant to run this on windows; the env var is ignored on + # other platforms + _run_cargo(session, "run", _FFI_CHECK, env=no_raw_dylib_env) + _run_cargo(session, "run", _FFI_CHECK, env=raw_dylib_env) _check_raw_dylib_macro(session) @@ -1934,10 +1957,6 @@ def _run_cargo_test( _run(session, *command, external=True, env=test_env) -def _run_cargo_publish(session: nox.Session, *, package: str) -> None: - _run_cargo(session, "publish", f"--package={package}") - - def _run_cargo_set_package_version( session: nox.Session, pkg_id: str, diff --git a/pyo3-benches/benches/bench_bigint.rs b/pyo3-benches/benches/bench_bigint.rs index 6227e95e496..c3a2849b7d6 100644 --- a/pyo3-benches/benches/bench_bigint.rs +++ b/pyo3-benches/benches/bench_bigint.rs @@ -1,8 +1,9 @@ use std::hint::black_box; use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; -use num_bigint::BigInt; +use num_bigint::{BigInt, BigUint}; +use pyo3::conversion::IntoPyObject; use pyo3::prelude::*; use pyo3::types::PyDict; @@ -17,53 +18,78 @@ fn extract_bigint_extract_fail(bench: &mut Bencher<'_>) { }); } -fn extract_bigint_small(bench: &mut Bencher<'_>) { +fn extract_bigint(bench: &mut Bencher<'_>, value: &BigInt) { Python::attach(|py| { - let int = py.eval(c"-42", None, None).unwrap(); - + let int = value.into_pyobject(py).unwrap(); bench.iter_with_large_drop(|| black_box(&int).extract::().unwrap()); }); } -fn extract_bigint_big_negative(bench: &mut Bencher<'_>) { +fn extract_biguint(bench: &mut Bencher<'_>, value: &BigUint) { Python::attach(|py| { - let int = py.eval(c"-10**300", None, None).unwrap(); - - bench.iter_with_large_drop(|| black_box(&int).extract::().unwrap()); + let int = value.into_pyobject(py).unwrap(); + bench.iter_with_large_drop(|| black_box(&int).extract::().unwrap()); }); } -fn extract_bigint_big_positive(bench: &mut Bencher<'_>) { +fn extract_biguint_negative_fail(bench: &mut Bencher<'_>) { Python::attach(|py| { - let int = py.eval(c"10**300", None, None).unwrap(); + let int = py.eval(c"-10**300", None, None).unwrap(); - bench.iter_with_large_drop(|| black_box(&int).extract::().unwrap()); + bench.iter(|| match black_box(&int).extract::() { + Ok(v) => panic!("should err {}", v), + Err(e) => e, + }); }); } -fn extract_bigint_huge_negative(bench: &mut Bencher<'_>) { +fn into_bigint(bench: &mut Bencher<'_>, value: &BigInt) { Python::attach(|py| { - let int = py.eval(c"-10**3000", None, None).unwrap(); - - bench.iter_with_large_drop(|| black_box(&int).extract::().unwrap()); + bench.iter_with_large_drop(|| black_box(value).into_pyobject(py).unwrap()); }); } -fn extract_bigint_huge_positive(bench: &mut Bencher<'_>) { +fn into_biguint(bench: &mut Bencher<'_>, value: &BigUint) { Python::attach(|py| { - let int = py.eval(c"10**3000", None, None).unwrap(); - - bench.iter_with_large_drop(|| black_box(&int).extract::().unwrap()); + bench.iter_with_large_drop(|| black_box(value).into_pyobject(py).unwrap()); }); } fn criterion_benchmark(c: &mut Criterion) { + let bigint_cases = [ + ("small", BigInt::from(-42)), + ("big_negative", -(BigInt::from(10u8).pow(300))), + ("big_positive", BigInt::from(10u8).pow(300)), + ("huge_negative", -(BigInt::from(10u8).pow(3000))), + ("huge_positive", BigInt::from(10u8).pow(3000)), + ]; + + let biguint_cases = [ + ("zero", BigUint::from(0u8)), + ("small", BigUint::from(42u8)), + ("big", BigUint::from(10u8).pow(300)), + ("huge", BigUint::from(10u8).pow(3000)), + ]; + c.bench_function("extract_bigint_extract_fail", extract_bigint_extract_fail); - c.bench_function("extract_bigint_small", extract_bigint_small); - c.bench_function("extract_bigint_big_negative", extract_bigint_big_negative); - c.bench_function("extract_bigint_big_positive", extract_bigint_big_positive); - c.bench_function("extract_bigint_huge_negative", extract_bigint_huge_negative); - c.bench_function("extract_bigint_huge_positive", extract_bigint_huge_positive); + + for (name, value) in &bigint_cases { + c.bench_function(&format!("extract_bigint_{name}"), |b| extract_bigint(b, value)); + } + + c.bench_function("extract_biguint_negative_fail", extract_biguint_negative_fail); + + for (name, value) in &biguint_cases { + c.bench_function(&format!("extract_biguint_{name}"), |b| extract_biguint(b, value)); + } + + for (name, value) in &bigint_cases { + c.bench_function(&format!("into_bigint_{name}"), |b| into_bigint(b, value)); + } + + for (name, value) in &biguint_cases { + c.bench_function(&format!("into_biguint_{name}"), |b| into_biguint(b, value)); + } } criterion_group!(benches, criterion_benchmark); diff --git a/pyo3-build-config/src/impl_.rs b/pyo3-build-config/src/impl_.rs index 21eefad8331..2e3bd0c196a 100644 --- a/pyo3-build-config/src/impl_.rs +++ b/pyo3-build-config/src/impl_.rs @@ -1332,7 +1332,7 @@ impl InterpreterConfigBuilder { } pub fn finalize(self) -> Result { - let mut build_flags = self.build_flags.clone(); + let mut build_flags = self.build_flags; let py_gil_disabled = build_flags.0.contains(&BuildFlag::Py_GIL_DISABLED); let target_abi = match (self.target_abi, py_gil_disabled) { // No target ABI set, no Py_GIL_DISABLED: default to GIL-enabled version-specific. diff --git a/pyo3-ffi-check/README.md b/pyo3-ffi-check/README.md index ced60dceb6e..b0dba29f341 100644 --- a/pyo3-ffi-check/README.md +++ b/pyo3-ffi-check/README.md @@ -2,6 +2,6 @@ This is a simple program which compares ffi definitions from `pyo3-ffi` against those produced by `bindgen`. -If any differ in size, these are printed to stdout and a the process will exit nonzero. +It checks type layouts, function signatures, and the addresses of functions and statics. Any differences are printed to stdout and the process exits nonzero. The main purpose of this program is to be run as part of PyO3's continuous integration pipeline to catch possible errors in PyO3's ffi definitions. diff --git a/pyo3-ffi-check/definitions/Cargo.toml b/pyo3-ffi-check/definitions/Cargo.toml index 2cd1b7f6854..729969338bb 100644 --- a/pyo3-ffi-check/definitions/Cargo.toml +++ b/pyo3-ffi-check/definitions/Cargo.toml @@ -8,5 +8,6 @@ publish = false pyo3-ffi = { path = "../../pyo3-ffi" } [build-dependencies] -bindgen = "0.72" +bindgen = "0.73" +target-lexicon = "0.13" pyo3-build-config = { path = "../../pyo3-build-config" } diff --git a/pyo3-ffi-check/definitions/build.rs b/pyo3-ffi-check/definitions/build.rs index 992da8a360c..aca869f6ae7 100644 --- a/pyo3-ffi-check/definitions/build.rs +++ b/pyo3-ffi-check/definitions/build.rs @@ -1,7 +1,8 @@ use std::env; use std::path::PathBuf; -use bindgen::callbacks::ItemInfo; +use bindgen::callbacks::{ItemInfo, ItemKind}; +use target_lexicon::{Architecture, OperatingSystem, Triple}; #[derive(Debug)] struct ParseCallbacks; @@ -20,12 +21,14 @@ impl bindgen::callbacks::ParseCallbacks for ParseCallbacks { } #[derive(Debug)] -struct PyPyReplaceCallbacks; +struct WindowsX86RawDylibCallbacks; -impl bindgen::callbacks::ParseCallbacks for PyPyReplaceCallbacks { - fn item_name(&self, item_info: ItemInfo<'_>) -> Option { - if item_info.name.starts_with("PyPy") || item_info.name.starts_with("_PyPy") { - Some(item_info.name.replacen("PyPy", "Py", 1)) +// Matches the adjustment in `pyo3-ffi` to force the link name for functions starting +// with `_Py` (see `pyo3-ffi/src/impl_/macros.rs`) +impl bindgen::callbacks::ParseCallbacks for WindowsX86RawDylibCallbacks { + fn generated_link_name_override(&self, item: ItemInfo<'_>) -> Option { + if item.kind == ItemKind::Function && item.name.starts_with("_Py") { + Some(format!("_{}", item.name)) } else { None } @@ -34,12 +37,13 @@ impl bindgen::callbacks::ParseCallbacks for PyPyReplaceCallbacks { fn main() { let config = pyo3_build_config::get(); + let target: Triple = env::var("TARGET").unwrap().parse().unwrap(); let python_include_dir = config .run_python_script( "import sysconfig; print(sysconfig.get_config_var('INCLUDEPY'), end='');", ) - .expect("failed to get lib dir"); + .expect("failed to get include dir"); let gil_disabled_on_windows = config .run_python_script( "import sysconfig; import platform; print(sysconfig.get_config_var('Py_GIL_DISABLED') == 1 and platform.system() == 'Windows');", @@ -61,35 +65,32 @@ fn main() { .header("wrapper.h") .clang_args(clang_args) .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) - .parse_callbacks(Box::new(ParseCallbacks)); + .parse_callbacks(Box::new(ParseCallbacks)) + // Minimising bindgen output to `Py` symbols and their dependencies, avoiding + // system declarations etc which are not relevant to `pyo3-ffi-check`. + .allowlist_type("_?Py.*") + .allowlist_function("_?Py.*") + .allowlist_var("_?Py.*|PY.*"); - if matches!( - config.implementation(), - pyo3_build_config::PythonImplementation::PyPy - ) { - builder = builder.parse_callbacks(Box::new(PyPyReplaceCallbacks)); + // Match PyO3's choice to use raw-dylib linking on Windows for the bindgen symbols + // so that link resolution is done identically + if target.operating_system == OperatingSystem::Windows { + println!("cargo:rerun-if-env-changed=PYO3_USE_RAW_DYLIB"); + let lib_name = config.lib_name().expect("missing Python library name"); + if env::var("PYO3_USE_RAW_DYLIB").map_or(true, |value| value == "1") { + let import_name_type = if matches!(target.architecture, Architecture::X86_32(_)) { + builder = builder.parse_callbacks(Box::new(WindowsX86RawDylibCallbacks)); + ", import_name_type = \"undecorated\"" + } else { + "" + }; + builder = builder.extern_block_attrs(format!( + "#[link(name = \"{lib_name}\", kind = \"raw-dylib\"{import_name_type})]" + )); + } } - let bindings = builder - // blocklist some values which apparently have conflicting definitions on unix - .blocklist_item("FP_NORMAL") - .blocklist_item("FP_SUBNORMAL") - .blocklist_item("FP_NAN") - .blocklist_item("FP_INFINITE") - .blocklist_item("FP_INT_UPWARD") - .blocklist_item("FP_INT_DOWNWARD") - .blocklist_item("FP_INT_TOWARDZERO") - .blocklist_item("FP_INT_TONEARESTFROMZERO") - .blocklist_item("FP_INT_TONEAREST") - .blocklist_item("FP_ZERO") - // blocklist mingw specific types - .blocklist_type("__mingw_ldbl_type_t") - // ARM neon intrinsics cause issue on GitHub actions windows CI, also not relevant to - // what we're trying to check anyway. - .blocklist_file(r".*(\\|/)arm(64)?_neon\.h") - .blocklist_file(r".*(\\|/)arm_vector_types\.h") - .generate() - .expect("Unable to generate bindings"); + let bindings = builder.generate().expect("Unable to generate bindings"); let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); bindings diff --git a/pyo3-ffi-check/macro/src/lib.rs b/pyo3-ffi-check/macro/src/lib.rs index 2c448640362..4ce547a5851 100644 --- a/pyo3-ffi-check/macro/src/lib.rs +++ b/pyo3-ffi-check/macro/src/lib.rs @@ -6,7 +6,7 @@ use std::{ }; use proc_macro2::{Ident, Span, TokenStream, TokenTree}; -use pyo3_build_config::PythonVersion; +use pyo3_build_config::{PythonImplementation, PythonVersion}; use quote::quote; const PY_3_15: PythonVersion = PythonVersion { @@ -19,6 +19,11 @@ const PY_3_12: PythonVersion = PythonVersion { minor: 12, }; +const PY_3_11: PythonVersion = PythonVersion { + major: 3, + minor: 11, +}; + /// Macro which expands to multiple macro calls, one per pyo3-ffi struct. #[proc_macro] pub fn for_all_structs(input: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -70,15 +75,20 @@ pub fn for_all_structs(input: proc_macro::TokenStream) -> proc_macro::TokenStrea static DOC_DIR: LazyLock = LazyLock::new(|| PathBuf::from(env::var_os("PYO3_FFI_CHECK_DOC_DIR").unwrap())); -static BINDGEN_FUNCTION_NAMES: LazyLock> = LazyLock::new(|| { - // parse all the function names from the bindgen index file +static BINDGEN_FUNCTION_NAMES: LazyLock> = + LazyLock::new(|| get_bindgen_names("fn")); + +static BINDGEN_STATIC_NAMES: LazyLock> = + LazyLock::new(|| get_bindgen_names("static")); + +fn get_bindgen_names(kind: &str) -> HashSet { + // Parse names from the bindgen index file. let index_file = DOC_DIR.join("bindgen/index.html"); - // the functions are in `a` elements with class "fn", and the full path is in the - // `title` attribute + // The full path is in the `title` attribute of each item's link. let html = fs::read_to_string(index_file).unwrap(); let html = scraper::Html::parse_document(&html); - let selector = scraper::Selector::parse("a.fn").unwrap(); + let selector = scraper::Selector::parse(&format!("a.{kind}")).unwrap(); html.select(&selector) .map(|el| { @@ -91,7 +101,81 @@ static BINDGEN_FUNCTION_NAMES: LazyLock> = LazyLock::new(|| { .to_string() }) .collect() -}); +} + +fn get_bindgen_name(name: &str, names: &HashSet) -> String { + if pyo3_build_config::get().implementation() == PythonImplementation::PyPy + && (name.starts_with("Py") || name.starts_with("_Py")) + { + let prefixed_name = name.replacen("Py", "PyPy", 1); + if names.contains(&prefixed_name) { + return prefixed_name; + } + } + name.to_owned() +} + +/// Macro which expands to multiple macro calls, one per pyo3-ffi static. +#[proc_macro] +pub fn for_all_statics(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let macro_name = match get_macro_name_from_input("for_all_statics", input) { + Ok(name) => name, + Err(err) => return err.into(), + }; + + let statics_glob = format!("{}/pyo3_ffi/static.*.html", DOC_DIR.display()); + let mut output = TokenStream::new(); + + for entry in glob::glob(&statics_glob).expect("Failed to read glob pattern") { + let entry = entry.unwrap(); + let file_name = entry.file_name().unwrap().to_string_lossy().into_owned(); + let static_name = file_name + .strip_prefix("static.") + .unwrap() + .strip_suffix(".html") + .unwrap(); + + if static_name == "PyStructSequence_UnnamedField" + && pyo3_build_config::get().target_abi().version() < PY_3_11 + { + // Not marked PyAPI_DATA (and thus not exported reliably) before Python 3.11. + // https://github.com/python/cpython/issues/88386 + continue; + } + + let is_pypy = pyo3_build_config::get().implementation() == PythonImplementation::PyPy; + if is_pypy && static_name == "PySuper_Type" { + // PyPy declares this in its headers but does not export it. + continue; + } + + // PyPy uses a macro to define these aliases as the same static; CPython has three + // separate statics + let bindgen_name = get_bindgen_name( + if is_pypy + && matches!( + static_name, + "PyExc_EnvironmentError" | "PyExc_IOError" | "PyExc_WindowsError" + ) + { + "PyExc_OSError" + } else { + static_name + }, + &BINDGEN_STATIC_NAMES, + ); + if is_pypy && !BINDGEN_STATIC_NAMES.contains(&bindgen_name) { + // As with functions, PyPy may not yet offer all of the declared symbols. + continue; + } + + let static_ident = Ident::new(static_name, Span::call_site()); + let bindgen_ident = Ident::new(&bindgen_name, Span::call_site()); + output.extend(quote!(#macro_name!(#static_ident, #bindgen_ident);)); + } + + output.into() +} /// Macro which expands to multiple macro calls, one per field in a pyo3-ffi /// struct. @@ -195,6 +279,7 @@ pub fn for_all_fields(input: proc_macro::TokenStream) -> proc_macro::TokenStream let bindgen_field_ident = if (pyo3_build_config::get().target_abi().version() >= PY_3_12) && struct_name == "PyObject" && field_name == "ob_refcnt" + && pyo3_build_config::get().target_abi().implementation() != PythonImplementation::PyPy { // PyObject since 3.12 implements ob_refcnt as a union; bindgen creates // an anonymous name for the field @@ -247,14 +332,14 @@ const MACRO_EXCLUSIONS: &[(&str, &str)] = &[ // FIXME: for many of these `not(PyPy)` cases, // it seems that PyPy might actually offer symbols which PyO3 // should be using rather than implementing inline functions - ("PyAnySet_Check", "not(PyPy)"), - ("PyAnySet_CheckExact", "not(PyPy)"), + ("PyAnySet_Check", "any(not(PyPy), Py_3_12)"), + ("PyAnySet_CheckExact", "any(not(PyPy), Py_3_12)"), ("PyAsyncGen_CheckExact", ""), ("PyBool_Check", ""), ("PyByteArray_AS_STRING", ""), ("PyByteArray_GET_SIZE", ""), - ("PyByteArray_Check", "not(PyPy)"), - ("PyByteArray_CheckExact", "not(PyPy)"), + ("PyByteArray_Check", "any(not(PyPy), Py_3_12)"), + ("PyByteArray_CheckExact", "any(not(PyPy), Py_3_12)"), ("PyBytes_AS_STRING", "not(PyPy)"), ("PyBytes_Check", ""), ("PyBytes_CheckExact", ""), @@ -270,8 +355,8 @@ const MACRO_EXCLUSIONS: &[(&str, &str)] = &[ ("PyCapsule_CheckExact", ""), ("PyCell_Check", ""), ("PyCode_Check", "not(PyPy)"), - ("PyComplex_Check", "not(PyPy)"), - ("PyComplex_CheckExact", "not(PyPy)"), + ("PyComplex_Check", "any(not(PyPy), Py_3_12)"), + ("PyComplex_CheckExact", "any(not(PyPy), Py_3_12)"), ("PyContext_CheckExact", ""), ("PyContextToken_CheckExact", ""), ("PyContextVar_CheckExact", ""), @@ -320,12 +405,12 @@ const MACRO_EXCLUSIONS: &[(&str, &str)] = &[ ("PyExceptionInstance_Class", "not(PyPy)"), ("PyEval_CallObject", "not(Py_3_13)"), ("PyFloat_AS_DOUBLE", "not(PyPy)"), - ("PyFloat_Check", "not(PyPy)"), - ("PyFloat_CheckExact", "not(PyPy)"), + ("PyFloat_Check", "any(not(PyPy), Py_3_12)"), + ("PyFloat_CheckExact", "any(not(PyPy), Py_3_12)"), ("PyFrame_Check", ""), ("PyFrameLocalsProxy_Check", ""), - ("PyFrozenSet_Check", "not(PyPy)"), - ("PyFrozenSet_CheckExact", "not(PyPy)"), + ("PyFrozenSet_Check", "any(not(PyPy), Py_3_12)"), + ("PyFrozenSet_CheckExact", "any(not(PyPy), Py_3_12)"), ("PyFunction_Check", "not(PyPy)"), ("PyGen_Check", "not(PyPy)"), ("PyGen_CheckExact", "not(PyPy)"), @@ -340,9 +425,9 @@ const MACRO_EXCLUSIONS: &[(&str, &str)] = &[ ("PyLong_CheckExact", ""), ("PyMapping_DelItem", ""), ("PyMapping_DelItemString", ""), - ("PyMemoryView_Check", "not(PyPy)"), - ("PyModule_Check", "not(PyPy)"), - ("PyModule_CheckExact", "not(PyPy)"), + ("PyMemoryView_Check", "any(not(PyPy), Py_3_12)"), + ("PyModule_Check", "any(not(PyPy), Py_3_12)"), + ("PyModule_CheckExact", "any(not(PyPy), Py_3_12)"), ("PyModule_Create", ""), ("PyModule_FromDefAndSpec", "not(PyPy)"), ("PyObject_CallMethodNoArgs", ""), @@ -360,7 +445,6 @@ const MACRO_EXCLUSIONS: &[(&str, &str)] = &[ ("PyObject_GC_NewVar", ""), ("PyObject_GC_Resize", ""), ("PyObject_GET_WEAKREFS_LISTPTR", "not(Py_3_9)"), - ("PyObject_IS_GC", "not(Py_3_9)"), ("PyObject_New", ""), ("PyObject_NewVar", ""), ("PyObject_TypeCheck", ""), @@ -372,8 +456,8 @@ const MACRO_EXCLUSIONS: &[(&str, &str)] = &[ ("PySequence_Fast_GET_SIZE", ""), ("PySequence_Fast_ITEMS", ""), ("PySequence_ITEM", "not(PyPy)"), - ("PySet_Check", "not(PyPy)"), - ("PySet_CheckExact", "not(PyPy)"), + ("PySet_Check", "any(not(PyPy), Py_3_12)"), + ("PySet_CheckExact", "any(not(PyPy), Py_3_12)"), ("PySet_GET_SIZE", ""), ("PySlice_Check", ""), ("PySlot_DATA", ""), @@ -396,7 +480,7 @@ const MACRO_EXCLUSIONS: &[(&str, &str)] = &[ ("PyTime_FromTimeAndFold", ""), ("PyTimeZone_FromOffset", ""), ("PyTimeZone_FromOffsetAndName", ""), - ("PyTraceBack_Check", "not(PyPy)"), + ("PyTraceBack_Check", "any(not(PyPy), Py_3_12)"), ("PyTuple_Check", ""), ("PyTuple_CheckExact", ""), ("PyTuple_GET_ITEM", ""), @@ -407,7 +491,7 @@ const MACRO_EXCLUSIONS: &[(&str, &str)] = &[ ("PyType_FastSubclass", ""), ("PyType_HasFeature", ""), ("PyType_IS_GC", ""), - ("PyType_SUPPORTS_WEAKREFS", "not(Py_3_11)"), + ("PyType_SUPPORTS_WEAKREFS", "any(PyPy, not(Py_3_11))"), ("PyUnicode_1BYTE_DATA", ""), ("PyUnicode_2BYTE_DATA", ""), ("PyUnicode_4BYTE_DATA", ""), @@ -421,18 +505,21 @@ const MACRO_EXCLUSIONS: &[(&str, &str)] = &[ ("PyUnicode_IS_READY", ""), ("PyUnicode_KIND", "not(Py_3_14)"), ("PyUnicode_READY", ""), - ("PyWeakref_Check", "not(PyPy)"), - ("PyWeakref_CheckProxy", "not(PyPy)"), - ("PyWeakref_CheckRef", "not(PyPy)"), - ("PyWeakref_CheckRefExact", "not(PyPy)"), + ("PyWeakref_Check", "any(not(PyPy), Py_3_12)"), + ("PyWeakref_CheckProxy", "any(not(PyPy), Py_3_12)"), + ("PyWeakref_CheckRef", "any(not(PyPy), Py_3_12)"), + ("PyWeakref_CheckRefExact", "any(not(PyPy), Py_3_12)"), ("PyVectorcall_NARGS", "not(Py_3_12)"), ("Py_CLEAR", ""), - ("Py_CompileString", "not(Py_3_10)"), - ("Py_CompileStringFlags", "all(not(PyPy), not(Py_3_15))"), + ( + "Py_CompileString", + "any(not(Py_3_10), all(PyPy, not(Py_3_12)))", + ), + ("Py_CompileStringFlags", "all(not(PyPy), not(Py_3_13))"), ("Py_DECREF", ""), ("Py_Ellipsis", ""), ("Py_False", ""), - ("Py_GETENV", "not(Py_3_11)"), + ("Py_GETENV", "any(PyPy, not(Py_3_11))"), ("Py_INCREF", ""), ("Py_IS_TYPE", "not(Py_3_15)"), // symbol added for stable abi on 3.15 ("Py_None", ""), @@ -444,15 +531,12 @@ const MACRO_EXCLUSIONS: &[(&str, &str)] = &[ ("Py_UNICODE_TODECIMAL", ""), ("Py_XDECREF", ""), ("Py_XINCREF", ""), - ("_PyCode_GetExtra", "Py_3_12"), - ("_PyCode_SetExtra", "Py_3_12"), - ("_PyEval_RequestCodeExtraIndex", "Py_3_12"), // These functions were only added in 3.10, but pyo3-ffi defines them for // all versions. Technically not macros but the machinery happens to work // the same way. ("Py_Is", "not(Py_3_10)"), - ("Py_IsFalse", "not(Py_3_10)"), - ("Py_IsTrue", "not(Py_3_10)"), + ("Py_IsFalse", "any(not(Py_3_10), all(PyPy, not(Py_3_12)))"), + ("Py_IsTrue", "any(not(Py_3_10), all(PyPy, not(Py_3_12)))"), ("Py_IsNone", "not(Py_3_10)"), ]; @@ -486,6 +570,19 @@ const EXCLUDED_SYMBOLS: &[&str] = &[ "PyOS_BeforeFork", "PyOS_AfterFork_Parent", "PyOS_AfterFork_Child", + // TODO: PyPy 3.12 declares these symbols in its headers but does not implement them? + "PyMapping_Length", + "PyObject_IS_GC", + "PyObject_Length", + "PySequence_In", + "PySequence_Length", + "PyType_ClearCache", + // TODO: deprecated backwards compatibility aliases to be removed in PyO3 0.31 + "_PyCode_GetExtra", + "_PyCode_SetExtra", + "_PyEval_RequestCodeExtraIndex", + // Never implemented before 3.9, just exclude it on this patch release + "PyBuffer_SizeFromFormat", ]; // Assert at compile time that `MACRO_EXCLUSIONS` and `EXCLUDED_SYMBOLS` are disjoint @@ -539,16 +636,7 @@ pub fn for_all_functions(_input: proc_macro::TokenStream) -> proc_macro::TokenSt continue; } - if pyo3_build_config::get().implementation() - == pyo3_build_config::PythonImplementation::PyPy - { - // If the function doesn't exist in PyPy, for now we don't care: - // - For PyO3 inline functions it's probably fine to include anyway - // - For extern symbols - PyPy may add them in a future release - if !BINDGEN_FUNCTION_NAMES.contains(function_name) { - continue; - } - } + let bindgen_name = get_bindgen_name(function_name, &BINDGEN_FUNCTION_NAMES); let FunctionInfo { modifiers, @@ -595,6 +683,20 @@ pub fn for_all_functions(_input: proc_macro::TokenStream) -> proc_macro::TokenSt variadic: false, } } + ("PyDateTime_IMPORT", Err(FunctionNameMismatch(e))) if e == "PyDateTime_Import" => { + FunctionInfo { + modifiers: quote!(unsafe), + arg_count: 0, + variadic: false, + } + } + ("PyDateTime_Import", Err(FunctionNameMismatch(e))) if e == "PyDateTime_IMPORT" => { + FunctionInfo { + modifiers: quote!(unsafe extern "C"), + arg_count: 0, + variadic: false, + } + } (function_name, Err(FunctionNameMismatch(unexpected))) => { let error_message = format!( "parsed unexpected function declaration for `{function_name}`: {unexpected}", @@ -605,6 +707,7 @@ pub fn for_all_functions(_input: proc_macro::TokenStream) -> proc_macro::TokenSt }; let function_ident = Ident::new(function_name, Span::call_site()); + let bindgen_ident = Ident::new(&bindgen_name, Span::call_site()); let arg_types = std::iter::repeat_n(quote!(_), arg_count); @@ -631,7 +734,7 @@ pub fn for_all_functions(_input: proc_macro::TokenStream) -> proc_macro::TokenSt .map(|(_, cfg)| if cfg.is_empty() { "all()" } else { *cfg }) .map(|cfg| cfg.parse().expect("failed to parse macro exclusion cfg")); - let has_symbol = BINDGEN_FUNCTION_NAMES.contains(function_name); + let has_symbol = BINDGEN_FUNCTION_NAMES.contains(&bindgen_name); match (macro_exclusion_cfg, has_symbol) { (Some(cfg), true) => { // emit an error if checking within the cfgs where a macro is expected @@ -641,7 +744,7 @@ pub fn for_all_functions(_input: proc_macro::TokenStream) -> proc_macro::TokenSt output.extend(quote!(#[cfg(#cfg)] compile_error!(#error_message);)); // if not within the macro range, we found a symbol, this should be good output.extend( - quote!(#[cfg(not(#cfg))] #macro_name!(#inline #function_ident, #modifiers (#(#arg_types),* #vararg));), + quote!(#[cfg(not(#cfg))] #macro_name!(#inline #function_ident, #bindgen_ident, #modifiers (#(#arg_types),* #vararg));), ); } (Some(cfg), false) => { @@ -655,9 +758,16 @@ pub fn for_all_functions(_input: proc_macro::TokenStream) -> proc_macro::TokenSt (None, true) => { // emit the comparison macro to check that the argument count matches output.extend( - quote!(#macro_name!(#inline #function_ident, #modifiers (#(#arg_types),* #vararg));), + quote!(#macro_name!(#inline #function_ident, #bindgen_ident, #modifiers (#(#arg_types),* #vararg));), ); } + (None, false) + if pyo3_build_config::get().implementation() == PythonImplementation::PyPy => + { + // Without an explicit macro exclusion, tolerate missing PyPy symbols: + // - For PyO3 inline functions it's probably fine to include anyway + // - For extern symbols - PyPy may add them in a future release + } (None, false) => { // Not in MACRO_EXCLUSIONS, should have a symbol from bindgen let error_message = format!( diff --git a/pyo3-ffi-check/src/main.rs b/pyo3-ffi-check/src/main.rs index 444613db1a4..5cd0f85efeb 100644 --- a/pyo3-ffi-check/src/main.rs +++ b/pyo3-ffi-check/src/main.rs @@ -1,7 +1,16 @@ -use std::{ffi::CStr, process::exit}; +use std::{ + ffi::{c_void, CStr}, + process::exit, +}; use pyo3_ffi_check_definitions::{bindgen as bindings, pyo3_ffi}; +/// Functions which don't have equivalent addresses between pyo3-ffi and bindgen. +static SPECIAL_CASE_FUNCTIONS: &[&str] = &[ + "PyEval_RestoreThread", // PyO3 adds special handling for pthread_exit + "PyGILState_Ensure", // Similar to PyEval_RestoreThread +]; + fn main() { println!( "comparing pyo3-ffi against headers generated for {}", @@ -138,12 +147,30 @@ fn main() { }; } + // Check that the function signatures are compatible between pyo3-ffi and bindgen. + // + // Typically `name` == `bindgen_name`, but e.g. for PyPy this is not the case. macro_rules! check_function { - ($name:ident, [$($modifiers:tt)*] ($($arg_types:tt)*)) => {{ + ($name:ident, $bindgen_name:ident, [$($modifiers:tt)*] ($($arg_types:tt)*)) => {{ // Check functions have the same number of arguments #[allow(deprecated)] - { pyo3_ffi::$name as $($modifiers)* fn($($arg_types)*) -> _ }; - bindings::$name as $($modifiers)* fn($($arg_types)*) -> _; + let pyo3_ffi_fn = { pyo3_ffi::$name as $($modifiers)* fn($($arg_types)*) -> _ }; + let bindgen_fn = bindings::$bindgen_name as $($modifiers)* fn($($arg_types)*) -> _; + + // Check function addresses are the same (i.e. link is configured as expected). + // This will also trigger build errors if linker fails to find the symbol pyo3-ffi + // is expecting. + if !std::ptr::fn_addr_eq(pyo3_ffi_fn, bindgen_fn) + && !SPECIAL_CASE_FUNCTIONS.contains(&stringify!($name)) + { + failed = true; + println!( + "error: function address of {} differs between pyo3_ffi ({:p}) and bindgen ({:p})", + stringify!($name), + pyo3_ffi_fn, + bindgen_fn + ); + } // TODO: can probably sniff arg types by binding sniffers for each argument position and then passing // those inside `todo_args!` to use type inference for each argument. @@ -151,17 +178,17 @@ fn main() { // Check return types are compatible #[allow(deprecated)] let pyo3_ffi_return_type = ReturnTypeSniffer::new(|| unsafe { todo_args!((pyo3_ffi::$name)($($arg_types)*)) }); - let bindgen_return_type = ReturnTypeSniffer::new(|| unsafe { todo_args!((bindings::$name)($($arg_types)*)) }); + let bindgen_return_type = ReturnTypeSniffer::new(|| unsafe { todo_args!((bindings::$bindgen_name)($($arg_types)*)) }); failed |= !ReturnTypeSniffer::check_compatible(stringify!($name), &pyo3_ffi_return_type, &bindgen_return_type); }}; // case when the function is an inline function in the headers, in which case pyo3-ffi will use the // Rust abi and the extern symbol uses the C abi - (@inline $name:ident, ($($arg_types:tt)*)) => {{ + (@inline $name:ident, $bindgen_name:ident, ($($arg_types:tt)*)) => {{ // Check functions have the same number of arguments #[allow(deprecated)] { pyo3_ffi::$name as unsafe fn($($arg_types)*) -> _ }; - bindings::$name as unsafe extern "C" fn($($arg_types)*) -> _; + bindings::$bindgen_name as unsafe extern "C" fn($($arg_types)*) -> _; // TODO: can probably sniff arg types by binding sniffers for each argument position and then passing // those inside `todo_args!` to use type inference for each argument. @@ -169,7 +196,7 @@ fn main() { // Check return types are compatible #[allow(deprecated)] let pyo3_ffi_return_type = ReturnTypeSniffer::new(|| unsafe { todo_args!((pyo3_ffi::$name)($($arg_types)*)) }); - let bindgen_return_type = ReturnTypeSniffer::new(|| unsafe { todo_args!((bindings::$name)($($arg_types)*)) }); + let bindgen_return_type = ReturnTypeSniffer::new(|| unsafe { todo_args!((bindings::$bindgen_name)($($arg_types)*)) }); failed |= !ReturnTypeSniffer::check_compatible(stringify!($name), &pyo3_ffi_return_type, &bindgen_return_type); }}; @@ -177,6 +204,26 @@ fn main() { pyo3_ffi_check_macro::for_all_functions!(check_function); + macro_rules! check_static { + ($name:ident, $bindgen_name:ident) => {{ + #[allow(deprecated)] + let pyo3_ffi_ptr = (&raw const pyo3_ffi::$name).cast::(); + let bindgen_ptr = (&raw const bindings::$bindgen_name).cast::(); + + if pyo3_ffi_ptr != bindgen_ptr { + failed = true; + println!( + "error: static address of {} differs between pyo3_ffi ({:p}) and bindgen ({:p})", + stringify!($name), + pyo3_ffi_ptr, + bindgen_ptr + ); + } + }}; + } + + pyo3_ffi_check_macro::for_all_statics!(check_static); + if failed { exit(1); } else { diff --git a/pyo3-ffi/build.rs b/pyo3-ffi/build.rs index 7ec773cf33d..d8514ab363f 100644 --- a/pyo3-ffi/build.rs +++ b/pyo3-ffi/build.rs @@ -276,6 +276,30 @@ fn emit_link_config(build_config: &BuildConfig) -> Result<()> { return Ok(()); } + // Not using raw-dylib linking: PyPy dll needs to be the import library not the DLL name + let lib_name = if interpreter_config.target_abi().implementation() == PythonImplementation::PyPy + && target_os == "windows" + { + // FIXME: this should probably be done with better configuration in pyo3-build-config + // for `raw-dylib` in general, rather than as a patch here. + // + // Assert expected raw pypy dll name as a sanity check for now + assert_eq!( + lib_name, + format!( + "libpypy3.{}-c", + interpreter_config.target_abi().version().minor + ) + ); + format!( + "python{}{}", + interpreter_config.target_abi().version().major, + interpreter_config.target_abi().version().minor + ) + } else { + lib_name.to_string() + }; + println!( "cargo:rustc-link-lib={link_model}{alias}{lib_name}", link_model = if interpreter_config.shared() { diff --git a/pyo3-ffi/src/abstract_.rs b/pyo3-ffi/src/abstract_.rs index fe04b66e62f..e67a831efa1 100644 --- a/pyo3-ffi/src/abstract_.rs +++ b/pyo3-ffi/src/abstract_.rs @@ -29,24 +29,24 @@ extern_libpython! { ))] #[cfg_attr(PyPy, link_name = "PyPyObject_CallNoArgs")] pub fn PyObject_CallNoArgs(func: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_Call")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_Call")] pub fn PyObject_Call( callable_object: *mut PyObject, args: *mut PyObject, kw: *mut PyObject, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_CallObject")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_CallObject")] pub fn PyObject_CallObject( callable_object: *mut PyObject, args: *mut PyObject, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_CallFunction")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_CallFunction")] pub fn PyObject_CallFunction( callable_object: *mut PyObject, format: *const c_char, ... ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_CallMethod")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_CallMethod")] pub fn PyObject_CallMethod( o: *mut PyObject, method: *const c_char, @@ -55,28 +55,28 @@ extern_libpython! { ) -> *mut PyObject; #[cfg(all(PyPy, not(Py_3_13)))] // called internally in PyUnicodeDecodeError_Create on PyPy - #[cfg_attr(PyPy, link_name = "_PyPyObject_CallFunction_SizeT")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "_PyPyObject_CallFunction_SizeT")] pub(crate) fn _PyObject_CallFunction_SizeT( callable_object: *mut PyObject, format: *const c_char, ... ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_CallFunctionObjArgs")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_CallFunctionObjArgs")] pub fn PyObject_CallFunctionObjArgs(callable: *mut PyObject, ...) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_CallMethodObjArgs")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_CallMethodObjArgs")] pub fn PyObject_CallMethodObjArgs( o: *mut PyObject, method: *mut PyObject, ... ) -> *mut PyObject; - #[cfg(all(Py_3_12, Py_LIMITED_API))] // is an inline function in cpython/abstract.rs on version-specific ABI - #[cfg_attr(PyPy, link_name = "PyPyVectorcall_NARGS")] + #[cfg(all(Py_3_12, Py_LIMITED_API))] + // is an inline function in cpython/abstract.rs on version-specific ABI pub fn PyVectorcall_NARGS(nargsf: size_t) -> Py_ssize_t; - #[cfg_attr(not(any(Py_3_12, PyPy)), link_name = "_PyVectorcall_Call")] // symbol made public in 3.12 - #[cfg_attr(PyPy, link_name = "PyPyVectorcall_Call")] + #[cfg(any(Py_3_12, not(Py_LIMITED_API)))] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyVectorcall_Call")] pub fn PyVectorcall_Call( callable: *mut PyObject, tuple: *mut PyObject, @@ -106,43 +106,43 @@ extern_libpython! { nargsf: size_t, kwnames: *mut PyObject, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_Type")] pub fn PyObject_Type(o: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_Size")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_Size")] pub fn PyObject_Size(o: *mut PyObject) -> Py_ssize_t; // PyObject_Length is a direct alias for PyObject_Size - #[cfg_attr(not(PyPy), link_name = "PyObject_Size")] - #[cfg_attr(PyPy, link_name = "PyPyObject_Size")] + #[cfg_attr(any(not(PyPy), all(PyPy, Py_3_12)), link_name = "PyObject_Size")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_Size")] pub fn PyObject_Length(o: *mut PyObject) -> Py_ssize_t; - #[cfg_attr(PyPy, link_name = "PyPyObject_GetItem")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_GetItem")] pub fn PyObject_GetItem(o: *mut PyObject, key: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_SetItem")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_SetItem")] pub fn PyObject_SetItem(o: *mut PyObject, key: *mut PyObject, v: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyObject_DelItemString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_DelItemString")] pub fn PyObject_DelItemString(o: *mut PyObject, key: *const c_char) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyObject_DelItem")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_DelItem")] pub fn PyObject_DelItem(o: *mut PyObject, key: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyObject_Format")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_Format")] pub fn PyObject_Format(obj: *mut PyObject, format_spec: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_GetIter")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_GetIter")] pub fn PyObject_GetIter(arg1: *mut PyObject) -> *mut PyObject; #[cfg(Py_3_10)] - #[cfg_attr(PyPy, link_name = "PyPyObject_GetAIter")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_GetAIter")] pub fn PyObject_GetAIter(arg1: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyIter_Check")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyIter_Check")] pub fn PyIter_Check(obj: *mut PyObject) -> c_int; #[cfg(Py_3_10)] - #[cfg_attr(PyPy, link_name = "PyPyAIter_Check")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyAIter_Check")] pub fn PyAIter_Check(obj: *mut PyObject) -> c_int; #[cfg(Py_3_14)] #[cfg_attr(PyPy, link_name = "PyPyIter_NextItem")] pub fn PyIter_NextItem(iter: *mut PyObject, item: *mut *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyIter_Next")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyIter_Next")] pub fn PyIter_Next(arg1: *mut PyObject) -> *mut PyObject; #[cfg(all(not(PyPy), Py_3_10))] #[cfg_attr(PyPy, link_name = "PyPyIter_Send")] @@ -152,149 +152,153 @@ extern_libpython! { presult: *mut *mut PyObject, ) -> PySendResult; - #[cfg_attr(PyPy, link_name = "PyPyNumber_Check")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_Check")] pub fn PyNumber_Check(o: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyNumber_Add")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_Add")] pub fn PyNumber_Add(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_Subtract")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_Subtract")] pub fn PyNumber_Subtract(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_Multiply")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_Multiply")] pub fn PyNumber_Multiply(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_MatrixMultiply")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_MatrixMultiply")] pub fn PyNumber_MatrixMultiply(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_FloorDivide")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_FloorDivide")] pub fn PyNumber_FloorDivide(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_TrueDivide")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_TrueDivide")] pub fn PyNumber_TrueDivide(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_Remainder")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_Remainder")] pub fn PyNumber_Remainder(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_Divmod")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_Divmod")] pub fn PyNumber_Divmod(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_Power")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_Power")] pub fn PyNumber_Power(o1: *mut PyObject, o2: *mut PyObject, o3: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_Negative")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_Negative")] pub fn PyNumber_Negative(o: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_Positive")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_Positive")] pub fn PyNumber_Positive(o: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_Absolute")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_Absolute")] pub fn PyNumber_Absolute(o: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_Invert")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_Invert")] pub fn PyNumber_Invert(o: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_Lshift")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_Lshift")] pub fn PyNumber_Lshift(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_Rshift")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_Rshift")] pub fn PyNumber_Rshift(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_And")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_And")] pub fn PyNumber_And(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_Xor")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_Xor")] pub fn PyNumber_Xor(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_Or")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_Or")] pub fn PyNumber_Or(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyIndex_Check")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyIndex_Check")] pub fn PyIndex_Check(o: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyNumber_Index")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_Index")] pub fn PyNumber_Index(o: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_AsSsize_t")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_AsSsize_t")] pub fn PyNumber_AsSsize_t(o: *mut PyObject, exc: *mut PyObject) -> Py_ssize_t; - #[cfg_attr(PyPy, link_name = "PyPyNumber_Long")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_Long")] pub fn PyNumber_Long(o: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_Float")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_Float")] pub fn PyNumber_Float(o: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceAdd")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_InPlaceAdd")] pub fn PyNumber_InPlaceAdd(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceSubtract")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_InPlaceSubtract")] pub fn PyNumber_InPlaceSubtract(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceMultiply")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_InPlaceMultiply")] pub fn PyNumber_InPlaceMultiply(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceMatrixMultiply")] + #[cfg_attr( + all(PyPy, not(Py_3_12)), + link_name = "PyPyNumber_InPlaceMatrixMultiply" + )] pub fn PyNumber_InPlaceMatrixMultiply(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceFloorDivide")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_InPlaceFloorDivide")] pub fn PyNumber_InPlaceFloorDivide(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceTrueDivide")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_InPlaceTrueDivide")] pub fn PyNumber_InPlaceTrueDivide(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceRemainder")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_InPlaceRemainder")] pub fn PyNumber_InPlaceRemainder(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlacePower")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_InPlacePower")] pub fn PyNumber_InPlacePower( o1: *mut PyObject, o2: *mut PyObject, o3: *mut PyObject, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceLshift")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_InPlaceLshift")] pub fn PyNumber_InPlaceLshift(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceRshift")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_InPlaceRshift")] pub fn PyNumber_InPlaceRshift(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceAnd")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_InPlaceAnd")] pub fn PyNumber_InPlaceAnd(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceXor")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_InPlaceXor")] pub fn PyNumber_InPlaceXor(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceOr")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_InPlaceOr")] pub fn PyNumber_InPlaceOr(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyNumber_ToBase")] pub fn PyNumber_ToBase(n: *mut PyObject, base: c_int) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPySequence_Check")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySequence_Check")] pub fn PySequence_Check(o: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPySequence_Size")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySequence_Size")] pub fn PySequence_Size(o: *mut PyObject) -> Py_ssize_t; // PySequence_Length is a direct alias for PySequence_Size - #[cfg_attr(not(PyPy), link_name = "PySequence_Size")] - #[cfg_attr(PyPy, link_name = "PyPySequence_Size")] + #[cfg_attr(any(not(PyPy), all(PyPy, Py_3_12)), link_name = "PySequence_Size")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySequence_Size")] pub fn PySequence_Length(o: *mut PyObject) -> Py_ssize_t; - #[cfg_attr(PyPy, link_name = "PyPySequence_Concat")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySequence_Concat")] pub fn PySequence_Concat(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPySequence_Repeat")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySequence_Repeat")] pub fn PySequence_Repeat(o: *mut PyObject, count: Py_ssize_t) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPySequence_GetItem")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySequence_GetItem")] pub fn PySequence_GetItem(o: *mut PyObject, i: Py_ssize_t) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPySequence_GetSlice")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySequence_GetSlice")] pub fn PySequence_GetSlice(o: *mut PyObject, i1: Py_ssize_t, i2: Py_ssize_t) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPySequence_SetItem")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySequence_SetItem")] pub fn PySequence_SetItem(o: *mut PyObject, i: Py_ssize_t, v: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPySequence_DelItem")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySequence_DelItem")] pub fn PySequence_DelItem(o: *mut PyObject, i: Py_ssize_t) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPySequence_SetSlice")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySequence_SetSlice")] pub fn PySequence_SetSlice( o: *mut PyObject, i1: Py_ssize_t, i2: Py_ssize_t, v: *mut PyObject, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPySequence_DelSlice")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySequence_DelSlice")] pub fn PySequence_DelSlice(o: *mut PyObject, i1: Py_ssize_t, i2: Py_ssize_t) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPySequence_Tuple")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySequence_Tuple")] pub fn PySequence_Tuple(o: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPySequence_List")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySequence_List")] pub fn PySequence_List(o: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPySequence_Fast")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySequence_Fast")] pub fn PySequence_Fast(o: *mut PyObject, m: *const c_char) -> *mut PyObject; pub fn PySequence_Count(o: *mut PyObject, value: *mut PyObject) -> Py_ssize_t; - #[cfg_attr(PyPy, link_name = "PyPySequence_Contains")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySequence_Contains")] pub fn PySequence_Contains(seq: *mut PyObject, ob: *mut PyObject) -> c_int; // PySequence_In is a direct alias for PySequence_Contains - #[cfg_attr(not(PyPy), link_name = "PySequence_Contains")] - #[cfg_attr(PyPy, link_name = "PyPySequence_Contains")] + #[cfg_attr(any(not(PyPy), all(PyPy, Py_3_12)), link_name = "PySequence_Contains")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySequence_Contains")] pub fn PySequence_In(o: *mut PyObject, value: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPySequence_Index")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySequence_Index")] pub fn PySequence_Index(o: *mut PyObject, value: *mut PyObject) -> Py_ssize_t; - #[cfg_attr(PyPy, link_name = "PyPySequence_InPlaceConcat")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySequence_InPlaceConcat")] pub fn PySequence_InPlaceConcat(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPySequence_InPlaceRepeat")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySequence_InPlaceRepeat")] pub fn PySequence_InPlaceRepeat(o: *mut PyObject, count: Py_ssize_t) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyMapping_Check")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMapping_Check")] pub fn PyMapping_Check(o: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyMapping_Size")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMapping_Size")] pub fn PyMapping_Size(o: *mut PyObject) -> Py_ssize_t; // PyMapping_Length is a direct alias for PyMapping_Size - #[cfg_attr(not(PyPy), link_name = "PyMapping_Size")] - #[cfg_attr(PyPy, link_name = "PyPyMapping_Size")] + #[cfg_attr(any(not(PyPy), all(PyPy, Py_3_12)), link_name = "PyMapping_Size")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMapping_Size")] pub fn PyMapping_Length(o: *mut PyObject) -> Py_ssize_t; } @@ -309,9 +313,9 @@ pub unsafe fn PyMapping_DelItem(o: *mut PyObject, key: *mut PyObject) -> c_int { } extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyMapping_HasKeyString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMapping_HasKeyString")] pub fn PyMapping_HasKeyString(o: *mut PyObject, key: *const c_char) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyMapping_HasKey")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMapping_HasKey")] pub fn PyMapping_HasKey(o: *mut PyObject, key: *mut PyObject) -> c_int; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyMapping_HasKeyWithError")] @@ -319,13 +323,13 @@ extern_libpython! { #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyMapping_HasKeyStringWithError")] pub fn PyMapping_HasKeyStringWithError(o: *mut PyObject, key: *const c_char) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyMapping_Keys")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMapping_Keys")] pub fn PyMapping_Keys(o: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyMapping_Values")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMapping_Values")] pub fn PyMapping_Values(o: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyMapping_Items")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMapping_Items")] pub fn PyMapping_Items(o: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyMapping_GetItemString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMapping_GetItemString")] pub fn PyMapping_GetItemString(o: *mut PyObject, key: *const c_char) -> *mut PyObject; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyMapping_GetOptionalItem")] @@ -341,14 +345,14 @@ extern_libpython! { key: *const c_char, result: *mut *mut PyObject, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyMapping_SetItemString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMapping_SetItemString")] pub fn PyMapping_SetItemString( o: *mut PyObject, key: *const c_char, value: *mut PyObject, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyObject_IsInstance")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_IsInstance")] pub fn PyObject_IsInstance(object: *mut PyObject, typeorclass: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyObject_IsSubclass")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_IsSubclass")] pub fn PyObject_IsSubclass(object: *mut PyObject, typeorclass: *mut PyObject) -> c_int; } diff --git a/pyo3-ffi/src/boolobject.rs b/pyo3-ffi/src/boolobject.rs index bf55c1dedc6..f1beddd3a97 100644 --- a/pyo3-ffi/src/boolobject.rs +++ b/pyo3-ffi/src/boolobject.rs @@ -14,10 +14,10 @@ extern_libpython! { pub fn PyBool_Check(op: *mut PyObject) -> c_int; #[cfg(all(not(GraalPy), not(all(Py_3_13, Py_LIMITED_API))))] - #[cfg_attr(PyPy, link_name = "_PyPy_FalseStruct")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "_PyPy_FalseStruct")] static mut _Py_FalseStruct: PyLongObject; #[cfg(all(not(GraalPy), not(all(Py_3_13, Py_LIMITED_API))))] - #[cfg_attr(PyPy, link_name = "_PyPy_TrueStruct")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "_PyPy_TrueStruct")] static mut _Py_TrueStruct: PyLongObject; #[cfg(GraalPy)] @@ -64,6 +64,6 @@ pub unsafe fn Py_IsFalse(x: *mut PyObject) -> c_int { // skipped Py_RETURN_FALSE extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyBool_FromLong")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBool_FromLong")] pub fn PyBool_FromLong(arg1: c_long) -> *mut PyObject; } diff --git a/pyo3-ffi/src/bytearrayobject.rs b/pyo3-ffi/src/bytearrayobject.rs index 713a352c530..5bce28e4512 100644 --- a/pyo3-ffi/src/bytearrayobject.rs +++ b/pyo3-ffi/src/bytearrayobject.rs @@ -4,7 +4,7 @@ use core::ffi::{c_char, c_int}; #[cfg(not(RustPython))] extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyByteArray_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyByteArray_Type")] pub static mut PyByteArray_Type: PyTypeObject; pub static mut PyByteArrayIter_Type: PyTypeObject; @@ -28,16 +28,16 @@ extern_libpython! { #[cfg(RustPython)] pub fn PyByteArray_CheckExact(op: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyByteArray_FromObject")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyByteArray_FromObject")] pub fn PyByteArray_FromObject(o: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyByteArray_Concat")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyByteArray_Concat")] pub fn PyByteArray_Concat(a: *mut PyObject, b: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyByteArray_FromStringAndSize")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyByteArray_FromStringAndSize")] pub fn PyByteArray_FromStringAndSize(string: *const c_char, len: Py_ssize_t) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyByteArray_Size")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyByteArray_Size")] pub fn PyByteArray_Size(bytearray: *mut PyObject) -> Py_ssize_t; - #[cfg_attr(PyPy, link_name = "PyPyByteArray_AsString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyByteArray_AsString")] pub fn PyByteArray_AsString(bytearray: *mut PyObject) -> *mut c_char; - #[cfg_attr(PyPy, link_name = "PyPyByteArray_Resize")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyByteArray_Resize")] pub fn PyByteArray_Resize(bytearray: *mut PyObject, len: Py_ssize_t) -> c_int; } diff --git a/pyo3-ffi/src/bytesobject.rs b/pyo3-ffi/src/bytesobject.rs index c99e0dabefa..7e2b2ecb2cd 100644 --- a/pyo3-ffi/src/bytesobject.rs +++ b/pyo3-ffi/src/bytesobject.rs @@ -4,7 +4,7 @@ use core::ffi::{c_char, c_int}; #[cfg(not(RustPython))] extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyBytes_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBytes_Type")] pub static mut PyBytes_Type: PyTypeObject; pub static mut PyBytesIter_Type: PyTypeObject; } @@ -27,25 +27,26 @@ extern_libpython! { #[cfg(RustPython)] pub fn PyBytes_CheckExact(op: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyBytes_FromStringAndSize")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBytes_FromStringAndSize")] pub fn PyBytes_FromStringAndSize(arg1: *const c_char, arg2: Py_ssize_t) -> *mut PyObject; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBytes_FromString")] pub fn PyBytes_FromString(arg1: *const c_char) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyBytes_FromObject")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBytes_FromObject")] pub fn PyBytes_FromObject(arg1: *mut PyObject) -> *mut PyObject; // skipped PyBytes_FromFormatV //#[cfg_attr(PyPy, link_name = "PyPyBytes_FromFormatV")] //pub fn PyBytes_FromFormatV(arg1: *const c_char, arg2: va_list) // -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyBytes_FromFormat")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBytes_FromFormat")] pub fn PyBytes_FromFormat(arg1: *const c_char, ...) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyBytes_Size")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBytes_Size")] pub fn PyBytes_Size(arg1: *mut PyObject) -> Py_ssize_t; - #[cfg_attr(PyPy, link_name = "PyPyBytes_AsString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBytes_AsString")] pub fn PyBytes_AsString(arg1: *mut PyObject) -> *mut c_char; pub fn PyBytes_Repr(arg1: *mut PyObject, arg2: c_int) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyBytes_Concat")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBytes_Concat")] pub fn PyBytes_Concat(arg1: *mut *mut PyObject, arg2: *mut PyObject); - #[cfg_attr(PyPy, link_name = "PyPyBytes_ConcatAndDel")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBytes_ConcatAndDel")] pub fn PyBytes_ConcatAndDel(arg1: *mut *mut PyObject, arg2: *mut PyObject); pub fn PyBytes_DecodeEscape( arg1: *const c_char, @@ -54,7 +55,7 @@ extern_libpython! { arg4: Py_ssize_t, arg5: *const c_char, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyBytes_AsStringAndSize")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBytes_AsStringAndSize")] pub fn PyBytes_AsStringAndSize( obj: *mut PyObject, s: *mut *mut c_char, diff --git a/pyo3-ffi/src/ceval.rs b/pyo3-ffi/src/ceval.rs index fdc25da8a0d..d558020f03c 100644 --- a/pyo3-ffi/src/ceval.rs +++ b/pyo3-ffi/src/ceval.rs @@ -3,7 +3,7 @@ use crate::pytypedefs::PyThreadState; use core::ffi::{c_char, c_int, c_void}; extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyEval_EvalCode")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyEval_EvalCode")] pub fn PyEval_EvalCode( arg1: *mut PyObject, arg2: *mut PyObject, @@ -26,7 +26,7 @@ extern_libpython! { #[cfg(not(Py_3_13))] #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] - #[cfg_attr(PyPy, link_name = "PyPyEval_CallObjectWithKeywords")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyEval_CallObjectWithKeywords")] pub fn PyEval_CallObjectWithKeywords( func: *mut PyObject, obj: *mut PyObject, @@ -45,24 +45,24 @@ pub unsafe fn PyEval_CallObject(func: *mut PyObject, arg: *mut PyObject) -> *mut extern_libpython! { #[cfg(not(Py_3_13))] #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] - #[cfg_attr(PyPy, link_name = "PyPyEval_CallFunction")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyEval_CallFunction")] pub fn PyEval_CallFunction(obj: *mut PyObject, format: *const c_char, ...) -> *mut PyObject; #[cfg(not(Py_3_13))] #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] - #[cfg_attr(PyPy, link_name = "PyPyEval_CallMethod")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyEval_CallMethod")] pub fn PyEval_CallMethod( obj: *mut PyObject, methodname: *const c_char, format: *const c_char, ... ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyEval_GetBuiltins")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyEval_GetBuiltins")] pub fn PyEval_GetBuiltins() -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyEval_GetGlobals")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyEval_GetGlobals")] pub fn PyEval_GetGlobals() -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyEval_GetLocals")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyEval_GetLocals")] pub fn PyEval_GetLocals() -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyEval_GetFrame")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyEval_GetFrame")] pub fn PyEval_GetFrame() -> *mut crate::PyFrameObject; #[cfg(Py_3_13)] @@ -75,41 +75,41 @@ extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyEval_GetFrameLocals")] pub fn PyEval_GetFrameLocals() -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPy_AddPendingCall")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_AddPendingCall")] pub fn Py_AddPendingCall( func: Option c_int>, arg: *mut c_void, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPy_MakePendingCalls")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_MakePendingCalls")] pub fn Py_MakePendingCalls() -> c_int; - #[cfg_attr(PyPy, link_name = "PyPy_SetRecursionLimit")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_SetRecursionLimit")] pub fn Py_SetRecursionLimit(arg1: c_int); - #[cfg_attr(PyPy, link_name = "PyPy_GetRecursionLimit")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_GetRecursionLimit")] pub fn Py_GetRecursionLimit() -> c_int; #[cfg(Py_3_9)] - #[cfg_attr(PyPy, link_name = "PyPy_EnterRecursiveCall")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_EnterRecursiveCall")] pub fn Py_EnterRecursiveCall(arg1: *const c_char) -> c_int; #[cfg(Py_3_9)] - #[cfg_attr(PyPy, link_name = "PyPy_LeaveRecursiveCall")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_LeaveRecursiveCall")] pub fn Py_LeaveRecursiveCall(); - #[cfg_attr(PyPy, link_name = "PyPyEval_GetFuncName")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyEval_GetFuncName")] pub fn PyEval_GetFuncName(arg1: *mut PyObject) -> *const c_char; - #[cfg_attr(PyPy, link_name = "PyPyEval_GetFuncDesc")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyEval_GetFuncDesc")] pub fn PyEval_GetFuncDesc(arg1: *mut PyObject) -> *const c_char; - #[cfg_attr(PyPy, link_name = "PyPyEval_EvalFrame")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyEval_EvalFrame")] pub fn PyEval_EvalFrame(arg1: *mut crate::PyFrameObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyEval_EvalFrameEx")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyEval_EvalFrameEx")] pub fn PyEval_EvalFrameEx(f: *mut crate::PyFrameObject, exc: c_int) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyEval_SaveThread")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyEval_SaveThread")] pub fn PyEval_SaveThread() -> *mut PyThreadState; #[cfg(not(Py_3_13))] - #[cfg_attr(PyPy, link_name = "PyPyEval_ThreadsInitialized")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyEval_ThreadsInitialized")] #[cfg_attr( Py_3_9, deprecated( @@ -117,7 +117,7 @@ extern_libpython! { ) )] pub fn PyEval_ThreadsInitialized() -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyEval_InitThreads")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyEval_InitThreads")] #[cfg_attr( Py_3_9, deprecated( @@ -131,9 +131,9 @@ extern_libpython! { #[cfg(not(Py_3_13))] #[deprecated(note = "Deprecated in Python 3.2")] pub fn PyEval_ReleaseLock(); - #[cfg_attr(PyPy, link_name = "PyPyEval_AcquireThread")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyEval_AcquireThread")] pub fn PyEval_AcquireThread(tstate: *mut PyThreadState); - #[cfg_attr(PyPy, link_name = "PyPyEval_ReleaseThread")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyEval_ReleaseThread")] pub fn PyEval_ReleaseThread(tstate: *mut PyThreadState); } @@ -147,14 +147,14 @@ extern_libpython! { mod raw { use crate::pytypedefs::PyThreadState; extern_libpython! { "C-unwind" { - #[cfg_attr(PyPy, link_name = "PyPyEval_RestoreThread")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyEval_RestoreThread")] pub fn PyEval_RestoreThread(tstate: *mut PyThreadState); }} } #[cfg(any(Py_3_14, target_arch = "wasm32"))] extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyEval_RestoreThread")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyEval_RestoreThread")] pub fn PyEval_RestoreThread(tstate: *mut PyThreadState); } diff --git a/pyo3-ffi/src/codecs.rs b/pyo3-ffi/src/codecs.rs index 4d53f4e354f..fb146a43ddf 100644 --- a/pyo3-ffi/src/codecs.rs +++ b/pyo3-ffi/src/codecs.rs @@ -9,11 +9,13 @@ extern_libpython! { // skipped non-limited _PyCodec_Lookup from Include/codecs.h // skipped non-limited _PyCodec_Forget from Include/codecs.h pub fn PyCodec_KnownEncoding(encoding: *const c_char) -> c_int; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCodec_Encode")] pub fn PyCodec_Encode( object: *mut PyObject, encoding: *const c_char, errors: *const c_char, ) -> *mut PyObject; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCodec_Decode")] pub fn PyCodec_Decode( object: *mut PyObject, encoding: *const c_char, @@ -24,14 +26,16 @@ extern_libpython! { // skipped non-limited _PyCodec_DecodeText from Include/codecs.h // skipped non-limited _PyCodecInfo_GetIncrementalDecoder from Include/codecs.h // skipped non-limited _PyCodecInfo_GetIncrementalEncoder from Include/codecs.h + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCodec_Encoder")] pub fn PyCodec_Encoder(encoding: *const c_char) -> *mut PyObject; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCodec_Decoder")] pub fn PyCodec_Decoder(encoding: *const c_char) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyCodec_IncrementalEncoder")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCodec_IncrementalEncoder")] pub fn PyCodec_IncrementalEncoder( encoding: *const c_char, errors: *const c_char, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyCodec_IncrementalDecoder")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCodec_IncrementalDecoder")] pub fn PyCodec_IncrementalDecoder( encoding: *const c_char, errors: *const c_char, diff --git a/pyo3-ffi/src/complexobject.rs b/pyo3-ffi/src/complexobject.rs index 7e4b08ac075..f04dfbe7c67 100644 --- a/pyo3-ffi/src/complexobject.rs +++ b/pyo3-ffi/src/complexobject.rs @@ -3,7 +3,7 @@ use core::ffi::{c_double, c_int}; #[cfg(not(RustPython))] extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyComplex_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyComplex_Type")] pub static mut PyComplex_Type: PyTypeObject; } @@ -26,11 +26,11 @@ extern_libpython! { pub fn PyComplex_CheckExact(op: *mut PyObject) -> c_int; // skipped non-limited PyComplex_FromCComplex - #[cfg_attr(PyPy, link_name = "PyPyComplex_FromDoubles")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyComplex_FromDoubles")] pub fn PyComplex_FromDoubles(real: c_double, imag: c_double) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyComplex_RealAsDouble")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyComplex_RealAsDouble")] pub fn PyComplex_RealAsDouble(op: *mut PyObject) -> c_double; - #[cfg_attr(PyPy, link_name = "PyPyComplex_ImagAsDouble")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyComplex_ImagAsDouble")] pub fn PyComplex_ImagAsDouble(op: *mut PyObject) -> c_double; } diff --git a/pyo3-ffi/src/context.rs b/pyo3-ffi/src/context.rs index 5defd44dbfa..e8338a1b4a7 100644 --- a/pyo3-ffi/src/context.rs +++ b/pyo3-ffi/src/context.rs @@ -48,13 +48,17 @@ extern_libpython! { pub fn PyContext_Enter(ctx: *mut PyObject) -> c_int; pub fn PyContext_Exit(ctx: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyContextVar_New")] pub fn PyContextVar_New(name: *const c_char, def: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyContextVar_Get")] pub fn PyContextVar_Get( var: *mut PyObject, default_value: *mut PyObject, value: *mut *mut PyObject, ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyContextVar_Set")] pub fn PyContextVar_Set(var: *mut PyObject, value: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyContextVar_Reset")] pub fn PyContextVar_Reset(var: *mut PyObject, token: *mut PyObject) -> c_int; // skipped non-limited _PyContext_NewHamtForTests } diff --git a/pyo3-ffi/src/cpython/abstract_.rs b/pyo3-ffi/src/cpython/abstract_.rs index f8931d5e004..9ab15dfe8a7 100644 --- a/pyo3-ffi/src/cpython/abstract_.rs +++ b/pyo3-ffi/src/cpython/abstract_.rs @@ -194,27 +194,27 @@ pub unsafe fn PyObject_CheckBuffer(o: *mut PyObject) -> c_int { #[cfg(not(Py_3_11))] // moved to src/buffer.rs from 3.11 extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyObject_GetBuffer")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_GetBuffer")] pub fn PyObject_GetBuffer(obj: *mut PyObject, view: *mut Py_buffer, flags: c_int) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyBuffer_GetPointer")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBuffer_GetPointer")] pub fn PyBuffer_GetPointer( view: *mut Py_buffer, indices: *mut Py_ssize_t, ) -> *mut core::ffi::c_void; - #[cfg_attr(PyPy, link_name = "PyPyBuffer_SizeFromFormat")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBuffer_SizeFromFormat")] #[cfg(not(Py_3_9))] // return value changed from c_int to Py_ssize_t in 3.9 pub fn PyBuffer_SizeFromFormat(format: *const c_char) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyBuffer_SizeFromFormat")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBuffer_SizeFromFormat")] #[cfg(Py_3_9)] pub fn PyBuffer_SizeFromFormat(format: *const c_char) -> Py_ssize_t; - #[cfg_attr(PyPy, link_name = "PyPyBuffer_ToContiguous")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBuffer_ToContiguous")] pub fn PyBuffer_ToContiguous( buf: *mut core::ffi::c_void, view: *mut Py_buffer, len: Py_ssize_t, order: c_char, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyBuffer_FromContiguous")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBuffer_FromContiguous")] pub fn PyBuffer_FromContiguous( view: *mut Py_buffer, buf: *mut core::ffi::c_void, @@ -222,7 +222,7 @@ extern_libpython! { order: c_char, ) -> c_int; pub fn PyObject_CopyData(dest: *mut PyObject, src: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyBuffer_IsContiguous")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBuffer_IsContiguous")] pub fn PyBuffer_IsContiguous(view: *const Py_buffer, fort: c_char) -> c_int; pub fn PyBuffer_FillContiguousStrides( ndims: c_int, @@ -231,7 +231,7 @@ extern_libpython! { itemsize: c_int, fort: c_char, ); - #[cfg_attr(PyPy, link_name = "PyPyBuffer_FillInfo")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBuffer_FillInfo")] pub fn PyBuffer_FillInfo( view: *mut Py_buffer, o: *mut PyObject, @@ -240,7 +240,7 @@ extern_libpython! { readonly: c_int, flags: c_int, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyBuffer_Release")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBuffer_Release")] pub fn PyBuffer_Release(view: *mut Py_buffer); } diff --git a/pyo3-ffi/src/cpython/cellobject.rs b/pyo3-ffi/src/cpython/cellobject.rs index 628bfa399af..3b9e2da484f 100644 --- a/pyo3-ffi/src/cpython/cellobject.rs +++ b/pyo3-ffi/src/cpython/cellobject.rs @@ -12,6 +12,7 @@ extern_libpython! { pub fn PyCell_New(o: *mut PyObject) -> *mut PyObject; pub fn PyCell_Get(o: *mut PyObject) -> *mut PyObject; pub fn PyCell_Set(o: *mut PyObject, val: *mut PyObject) -> c_int; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCell_Type")] pub static mut PyCell_Type: PyTypeObject; } diff --git a/pyo3-ffi/src/cpython/ceval.rs b/pyo3-ffi/src/cpython/ceval.rs index ea12ccfc338..54f96149cf4 100644 --- a/pyo3-ffi/src/cpython/ceval.rs +++ b/pyo3-ffi/src/cpython/ceval.rs @@ -14,8 +14,8 @@ extern_libpython! { // skipped private _PyEval_EvalFrameDefault - // was moved to the unstable API tier on Py_3_12, use link_name for older versions - #[cfg_attr(Py_3_12, link_name = "_PyEval_RequestCodeExtraIndex")] + // Was moved to the unstable API tier on Py_3_12; older versions export the private name. + #[cfg_attr(not(Py_3_12), link_name = "_PyEval_RequestCodeExtraIndex")] pub fn PyUnstable_Eval_RequestCodeExtraIndex(func: freefunc) -> Py_ssize_t; } diff --git a/pyo3-ffi/src/cpython/code.rs b/pyo3-ffi/src/cpython/code.rs index 10c25cad9cd..1206656829e 100644 --- a/pyo3-ffi/src/cpython/code.rs +++ b/pyo3-ffi/src/cpython/code.rs @@ -130,6 +130,7 @@ extern_libpython! { firstlineno: c_int, ) -> *mut PyCodeObject; #[cfg(not(GraalPy))] + #[cfg_attr(PyPy, link_name = "PyPyCode_Addr2Line")] pub fn PyCode_Addr2Line(arg1: *mut PyCodeObject, arg2: c_int) -> c_int; // skipped PyCodeAddressRange "for internal use only" // skipped _PyCode_CheckLineNumber diff --git a/pyo3-ffi/src/cpython/dictobject.rs b/pyo3-ffi/src/cpython/dictobject.rs index df98a94c870..b1327ab6e68 100644 --- a/pyo3-ffi/src/cpython/dictobject.rs +++ b/pyo3-ffi/src/cpython/dictobject.rs @@ -7,7 +7,7 @@ use crate::PyObject; #[cfg(all(not(PyPy), Py_3_13))] use core::ffi::c_char; -#[cfg(all(not(PyPy), Py_3_12))] +#[cfg(Py_3_12)] use core::ffi::c_int; #[cfg(not(PyPy))] @@ -56,6 +56,7 @@ extern_libpython! { extern_libpython! { #[cfg(not(GraalPy))] + #[cfg_attr(PyPy, link_name = "PyPyDict_SetDefault")] pub fn PyDict_SetDefault( mp: *mut PyObject, key: *mut PyObject, diff --git a/pyo3-ffi/src/cpython/funcobject.rs b/pyo3-ffi/src/cpython/funcobject.rs index b1e4c052a15..d2a30e399f5 100644 --- a/pyo3-ffi/src/cpython/funcobject.rs +++ b/pyo3-ffi/src/cpython/funcobject.rs @@ -59,7 +59,7 @@ pub struct PyFunctionObject { } extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyFunction_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyFunction_Type")] pub static mut PyFunction_Type: crate::PyTypeObject; } @@ -75,8 +75,11 @@ extern_libpython! { globals: *mut PyObject, qualname: *mut PyObject, ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyFunction_GetCode")] pub fn PyFunction_GetCode(op: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyFunction_GetGlobals")] pub fn PyFunction_GetGlobals(op: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyFunction_GetModule")] pub fn PyFunction_GetModule(op: *mut PyObject) -> *mut PyObject; pub fn PyFunction_GetDefaults(op: *mut PyObject) -> *mut PyObject; pub fn PyFunction_SetDefaults(op: *mut PyObject, defaults: *mut PyObject) -> c_int; diff --git a/pyo3-ffi/src/cpython/longintrepr.rs b/pyo3-ffi/src/cpython/longintrepr.rs deleted file mode 100644 index 427f067178c..00000000000 --- a/pyo3-ffi/src/cpython/longintrepr.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::{PyObject, Py_ssize_t}; -use core::ffi::{c_int, c_void}; - -use crate::Py_uintptr_t; - -// skipped PyLong_BASE -// skipped PyLong_MASK -// skipped _PyLong_New -// skipped _PyLong_Copy -// skipped _PyLong_FromDigits -// skipped _PyLong_SIGN_MASK -// skipped _PyLong_NON_SIZE_BITS -// skipped PyUnstable_Long_IsCompact -// skipped PyUnstable_Long_CompactValue - -#[derive(Copy, Clone)] -#[repr(C)] -pub struct PyLongLayout { - pub bits_per_digit: u8, - pub digit_size: u8, - pub digits_order: i8, - pub digit_endianness: i8, -} - -extern_libpython! { - pub fn PyLong_GetNativeLayout() -> *const PyLongLayout; -} - -#[repr(C)] -pub struct PyLongExport { - pub value: i64, - pub negative: u8, - pub ndigits: Py_ssize_t, - pub digits: *const c_void, - _reserved: Py_uintptr_t, -} - -extern_libpython! { - pub fn PyLong_Export(obj: *mut PyObject, export_long: *mut PyLongExport) -> c_int; - pub fn PyLong_FreeExport(export_long: *mut PyLongExport); -} - -opaque_struct!(pub PyLongWriter); - -extern_libpython! { - pub fn PyLongWriter_Create( - negative: c_int, - ndigits: Py_ssize_t, - digits: *mut *mut c_void, - ) -> *mut PyLongWriter; - - pub fn PyLongWriter_Finish(writer: *mut PyLongWriter) -> *mut PyObject; - - pub fn PyLongWriter_Discard(writer: *mut PyLongWriter); -} diff --git a/pyo3-ffi/src/cpython/marshal.rs b/pyo3-ffi/src/cpython/marshal.rs index bd09e37baf2..bcdbcaa5386 100644 --- a/pyo3-ffi/src/cpython/marshal.rs +++ b/pyo3-ffi/src/cpython/marshal.rs @@ -9,10 +9,13 @@ pub const Py_MARSHAL_VERSION: c_int = 6; pub const Py_MARSHAL_VERSION: c_int = 5; extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyMarshal_WriteObjectToString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMarshal_WriteObjectToString")] pub fn PyMarshal_WriteObjectToString(object: *mut PyObject, version: c_int) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyMarshal_ReadObjectFromString")] + #[cfg_attr( + all(PyPy, not(Py_3_12)), + link_name = "PyPyMarshal_ReadObjectFromString" + )] pub fn PyMarshal_ReadObjectFromString(data: *const c_char, len: Py_ssize_t) -> *mut PyObject; pub fn PyMarshal_WriteLongToFile(value: c_long, file: *mut FILE, version: c_int); diff --git a/pyo3-ffi/src/cpython/mod.rs b/pyo3-ffi/src/cpython/mod.rs index 8fe53588384..84a6b2344f8 100644 --- a/pyo3-ffi/src/cpython/mod.rs +++ b/pyo3-ffi/src/cpython/mod.rs @@ -24,8 +24,6 @@ pub(crate) mod initconfig; pub(crate) mod listobject; #[cfg(Py_3_13)] pub(crate) mod lock; -#[cfg(Py_3_14)] -pub(crate) mod longintrepr; pub(crate) mod longobject; pub(crate) mod marshal; #[cfg(all(Py_3_9, not(PyPy)))] @@ -75,8 +73,6 @@ pub use self::initconfig::*; pub use self::listobject::*; #[cfg(Py_3_13)] pub use self::lock::*; -#[cfg(Py_3_14)] -pub use self::longintrepr::*; pub use self::longobject::*; pub use self::marshal::*; #[cfg(all(Py_3_9, not(PyPy)))] diff --git a/pyo3-ffi/src/cpython/object.rs b/pyo3-ffi/src/cpython/object.rs index 5cba19215fb..9d9688f08d9 100644 --- a/pyo3-ffi/src/cpython/object.rs +++ b/pyo3-ffi/src/cpython/object.rs @@ -343,8 +343,6 @@ extern_libpython! { // skipped private _PyObject_GetDictPtr pub fn PyObject_CallFinalizer(arg1: *mut PyObject); - #[cfg_attr(PyPy, link_name = "PyPyObject_CallFinalizerFromDealloc")] - pub fn PyObject_CallFinalizerFromDealloc(arg1: *mut PyObject) -> c_int; // skipped private _PyObject_GenericGetAttrWithDict // skipped private _PyObject_GenericSetAttrWithDict @@ -370,6 +368,7 @@ extern_libpython! { // skipped Py_TRASHCAN_END // skipped PyObject_GetItemData +// skipped PyObject_GetItemData_DuringGC // skipped PyObject_VisitManagedDict // skipped _PyObject_SetManagedDict diff --git a/pyo3-ffi/src/cpython/pydebug.rs b/pyo3-ffi/src/cpython/pydebug.rs index 389c4ea9ef3..8d978690fc5 100644 --- a/pyo3-ffi/src/cpython/pydebug.rs +++ b/pyo3-ffi/src/cpython/pydebug.rs @@ -9,6 +9,7 @@ extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPy_VerboseFlag")] pub static mut Py_VerboseFlag: c_int; #[deprecated(note = "Python 3.12")] + #[cfg_attr(PyPy, link_name = "PyPy_QuietFlag")] pub static mut Py_QuietFlag: c_int; #[deprecated(note = "Python 3.12")] #[cfg_attr(PyPy, link_name = "PyPy_InteractiveFlag")] @@ -26,9 +27,6 @@ extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPy_BytesWarningFlag")] pub static mut Py_BytesWarningFlag: c_int; #[deprecated(note = "Python 3.12")] - #[cfg_attr(PyPy, link_name = "PyPy_UseClassExceptionsFlag")] - pub static mut Py_UseClassExceptionsFlag: c_int; - #[deprecated(note = "Python 3.12")] #[cfg_attr(PyPy, link_name = "PyPy_FrozenFlag")] pub static mut Py_FrozenFlag: c_int; #[deprecated(note = "Python 3.12")] @@ -41,25 +39,28 @@ extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPy_NoUserSiteDirectory")] pub static mut Py_NoUserSiteDirectory: c_int; #[deprecated(note = "Python 3.12")] + #[cfg_attr(PyPy, link_name = "PyPy_UnbufferedStdioFlag")] pub static mut Py_UnbufferedStdioFlag: c_int; #[cfg_attr(PyPy, link_name = "PyPy_HashRandomizationFlag")] pub static mut Py_HashRandomizationFlag: c_int; #[deprecated(note = "Python 3.12")] + #[cfg_attr(PyPy, link_name = "PyPy_IsolatedFlag")] pub static mut Py_IsolatedFlag: c_int; #[cfg(windows)] #[deprecated(note = "Python 3.12")] pub static mut Py_LegacyWindowsFSEncodingFlag: c_int; #[cfg(windows)] #[deprecated(note = "Python 3.12")] + #[cfg_attr(PyPy, link_name = "PyPy_LegacyWindowsStdioFlag")] pub static mut Py_LegacyWindowsStdioFlag: c_int; } extern_libpython! { - #[cfg(Py_3_11)] + #[cfg(all(Py_3_11, not(PyPy)))] pub fn Py_GETENV(name: *const c_char) -> *mut c_char; } -#[cfg(not(Py_3_11))] +#[cfg(any(PyPy, not(Py_3_11)))] #[inline(always)] pub unsafe fn Py_GETENV(name: *const c_char) -> *mut c_char { #[allow(deprecated)] diff --git a/pyo3-ffi/src/cpython/pyframe.rs b/pyo3-ffi/src/cpython/pyframe.rs index 7b75f6e6ddd..4b05ac30eaa 100644 --- a/pyo3-ffi/src/cpython/pyframe.rs +++ b/pyo3-ffi/src/cpython/pyframe.rs @@ -20,6 +20,7 @@ pub const PyUnstable_EXECUTABLE_KIND_METHOD_DESCRIPTOR: c_int = 4; pub const PyUnstable_EXECUTABLE_KINDS: c_int = 5; extern_libpython! { + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyFrame_Type")] pub static mut PyFrame_Type: PyTypeObject; #[cfg(Py_3_13)] @@ -45,32 +46,42 @@ extern_libpython! { pub fn PyFrame_GetBack(frame: *mut PyFrameObject) -> *mut PyFrameObject; #[cfg(Py_3_11)] + #[cfg_attr(PyPy, link_name = "PyPyFrame_GetLocals")] pub fn PyFrame_GetLocals(frame: *mut PyFrameObject) -> *mut PyObject; #[cfg(Py_3_11)] + #[cfg_attr(PyPy, link_name = "PyPyFrame_GetGlobals")] pub fn PyFrame_GetGlobals(frame: *mut PyFrameObject) -> *mut PyObject; #[cfg(Py_3_11)] + #[cfg_attr(PyPy, link_name = "PyPyFrame_GetBuiltins")] pub fn PyFrame_GetBuiltins(frame: *mut PyFrameObject) -> *mut PyObject; #[cfg(Py_3_11)] + #[cfg_attr(PyPy, link_name = "PyPyFrame_GetGenerator")] pub fn PyFrame_GetGenerator(frame: *mut PyFrameObject) -> *mut PyObject; #[cfg(Py_3_11)] + #[cfg_attr(PyPy, link_name = "PyPyFrame_GetLasti")] pub fn PyFrame_GetLasti(frame: *mut PyFrameObject) -> c_int; #[cfg(Py_3_12)] + #[cfg_attr(PyPy, link_name = "PyPyFrame_GetVar")] pub fn PyFrame_GetVar(frame: *mut PyFrameObject, name: *mut PyObject) -> *mut PyObject; #[cfg(Py_3_12)] + #[cfg_attr(PyPy, link_name = "PyPyFrame_GetVarString")] pub fn PyFrame_GetVarString(frame: *mut PyFrameObject, name: *mut c_char) -> *mut PyObject; #[cfg(Py_3_12)] + #[cfg(not(PyPy))] pub fn PyUnstable_InterpreterFrame_GetCode(frame: *mut _PyInterpreterFrame) -> *mut PyObject; #[cfg(Py_3_12)] + #[cfg(not(PyPy))] pub fn PyUnstable_InterpreterFrame_GetLasti(frame: *mut _PyInterpreterFrame) -> c_int; #[cfg(Py_3_12)] + #[cfg(not(PyPy))] pub fn PyUnstable_InterpreterFrame_GetLine(frame: *mut _PyInterpreterFrame) -> c_int; } diff --git a/pyo3-ffi/src/cpython/pystate.rs b/pyo3-ffi/src/cpython/pystate.rs index 52e15224cc2..3e8549d558f 100644 --- a/pyo3-ffi/src/cpython/pystate.rs +++ b/pyo3-ffi/src/cpython/pystate.rs @@ -1,3 +1,5 @@ +#[cfg(all(Py_3_11, not(PyPy)))] +use crate::cpython::pyframe::_PyInterpreterFrame; use crate::PyThreadState; use crate::{PyFrameObject, PyInterpreterState, PyObject}; use core::ffi::c_int; @@ -12,6 +14,20 @@ pub type Py_tracefunc = unsafe extern "C" fn( arg: *mut PyObject, ) -> c_int; +#[cfg(all(not(Py_3_11), not(PyPy)))] +pub type _PyFrameEvalFunction = unsafe extern "C" fn( + tstate: *mut PyThreadState, + frame: *mut PyFrameObject, + throwflag: c_int, +) -> *mut PyObject; + +#[cfg(all(Py_3_11, not(PyPy)))] +pub type _PyFrameEvalFunction = unsafe extern "C" fn( + tstate: *mut PyThreadState, + frame: *mut _PyInterpreterFrame, + throwflag: c_int, +) -> *mut PyObject; + pub const PyTrace_CALL: c_int = 0; pub const PyTrace_EXCEPTION: c_int = 1; pub const PyTrace_LINE: c_int = 2; @@ -50,11 +66,14 @@ extern_libpython! { pub fn PyThreadState_GetUnchecked() -> *mut PyThreadState; #[cfg(not(Py_3_13))] + #[cfg_attr(PyPy, link_name = "_PyPyThreadState_UncheckedGet")] pub(crate) fn _PyThreadState_UncheckedGet() -> *mut PyThreadState; #[cfg(Py_3_11)] + #[cfg_attr(PyPy, link_name = "PyPyThreadState_EnterTracing")] pub fn PyThreadState_EnterTracing(state: *mut PyThreadState); #[cfg(Py_3_11)] + #[cfg_attr(PyPy, link_name = "PyPyThreadState_LeaveTracing")] pub fn PyThreadState_LeaveTracing(state: *mut PyThreadState); #[cfg_attr(PyPy, link_name = "PyPyGILState_Check")] @@ -76,10 +95,20 @@ extern_libpython! { #[cfg(not(PyPy))] pub fn PyThreadState_Next(tstate: *mut PyThreadState) -> *mut PyThreadState; - #[cfg_attr(PyPy, link_name = "PyPyThreadState_DeleteCurrent")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyThreadState_DeleteCurrent")] pub fn PyThreadState_DeleteCurrent(); -} -// skipped private _PyFrameEvalFunction -// skipped private _PyInterpreterState_GetEvalFrameFunc -// skipped private _PyInterpreterState_SetEvalFrameFunc + #[cfg(all(Py_3_9, not(Py_3_11), not(PyPy)))] + pub fn _PyInterpreterState_GetEvalFrameFunc( + interp: *mut PyInterpreterState, + ) -> Option<_PyFrameEvalFunction>; + #[cfg(all(Py_3_11, not(PyPy)))] + pub fn _PyInterpreterState_GetEvalFrameFunc( + interp: *mut PyInterpreterState, + ) -> _PyFrameEvalFunction; + #[cfg(all(Py_3_9, not(PyPy)))] + pub fn _PyInterpreterState_SetEvalFrameFunc( + interp: *mut PyInterpreterState, + eval_frame: Option<_PyFrameEvalFunction>, + ); +} diff --git a/pyo3-ffi/src/cpython/pythonrun.rs b/pyo3-ffi/src/cpython/pythonrun.rs index e2df4ea298e..02ee3adfd65 100644 --- a/pyo3-ffi/src/cpython/pythonrun.rs +++ b/pyo3-ffi/src/cpython/pythonrun.rs @@ -1,5 +1,5 @@ use crate::object::*; -#[cfg(not(any(PyPy, GraalPy, Py_LIMITED_API, Py_3_10)))] +#[cfg(not(any(PyPy, GraalPy, Py_3_10)))] use crate::pyarena::PyArena; use crate::PyCompilerFlags; #[cfg(not(any(PyPy, GraalPy, Py_3_10)))] @@ -97,6 +97,10 @@ extern_libpython! { flags: *mut PyCompilerFlags, ) -> *mut PyObject; + // skipped Py_CompileString - there is a symbol defined for this since Python 3.13 + // but the symbol is overridden by a macro definition to call Py_CompileStringExFlags + // inline (see below) + #[cfg(not(any(PyPy, GraalPy)))] pub fn Py_CompileStringExFlags( str: *const c_char, @@ -105,7 +109,6 @@ extern_libpython! { flags: *mut PyCompilerFlags, optimize: c_int, ) -> *mut PyObject; - #[cfg(not(Py_LIMITED_API))] pub fn Py_CompileStringObject( str: *const c_char, filename: *mut PyObject, @@ -150,7 +153,7 @@ extern_libpython! { arg2: *const c_char, arg3: *mut PyCompilerFlags, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyRun_SimpleString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyRun_SimpleString")] pub fn PyRun_SimpleString(s: *const c_char) -> c_int; #[cfg(not(any(PyPy, GraalPy)))] pub fn PyRun_SimpleFile(f: *mut FILE, p: *const c_char) -> c_int; diff --git a/pyo3-ffi/src/descrobject.rs b/pyo3-ffi/src/descrobject.rs index cd65e7b6a92..17ad25fabce 100644 --- a/pyo3-ffi/src/descrobject.rs +++ b/pyo3-ffi/src/descrobject.rs @@ -37,33 +37,34 @@ impl Default for PyGetSetDef { #[cfg(not(RustPython))] extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyClassMethodDescr_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyClassMethodDescr_Type")] pub static mut PyClassMethodDescr_Type: PyTypeObject; - #[cfg_attr(PyPy, link_name = "PyPyGetSetDescr_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyGetSetDescr_Type")] pub static mut PyGetSetDescr_Type: PyTypeObject; - #[cfg_attr(PyPy, link_name = "PyPyMemberDescr_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMemberDescr_Type")] pub static mut PyMemberDescr_Type: PyTypeObject; - #[cfg_attr(PyPy, link_name = "PyPyMethodDescr_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMethodDescr_Type")] pub static mut PyMethodDescr_Type: PyTypeObject; - #[cfg_attr(PyPy, link_name = "PyPyWrapperDescr_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyWrapperDescr_Type")] pub static mut PyWrapperDescr_Type: PyTypeObject; - #[cfg_attr(PyPy, link_name = "PyPyDictProxy_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDictProxy_Type")] pub static mut PyDictProxy_Type: PyTypeObject; - #[cfg_attr(PyPy, link_name = "PyPyProperty_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyProperty_Type")] pub static mut PyProperty_Type: PyTypeObject; } extern_libpython! { + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDescr_NewMethod")] pub fn PyDescr_NewMethod(arg1: *mut PyTypeObject, arg2: *mut PyMethodDef) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyDescr_NewClassMethod")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDescr_NewClassMethod")] pub fn PyDescr_NewClassMethod(arg1: *mut PyTypeObject, arg2: *mut PyMethodDef) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyDescr_NewMember")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDescr_NewMember")] pub fn PyDescr_NewMember(arg1: *mut PyTypeObject, arg2: *mut PyMemberDef) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyDescr_NewGetSet")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDescr_NewGetSet")] pub fn PyDescr_NewGetSet(arg1: *mut PyTypeObject, arg2: *mut PyGetSetDef) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyDictProxy_New")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDictProxy_New")] pub fn PyDictProxy_New(arg1: *mut PyObject) -> *mut PyObject; pub fn PyWrapper_New(arg1: *mut PyObject, arg2: *mut PyObject) -> *mut PyObject; } @@ -128,6 +129,8 @@ pub const _Py_WRITE_RESTRICTED: c_int = 4; // Deprecated, no-op. Do not reuse th pub const Py_RELATIVE_OFFSET: c_int = 8; extern_libpython! { + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMember_GetOne")] pub fn PyMember_GetOne(addr: *const c_char, l: *mut PyMemberDef) -> *mut PyObject; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMember_SetOne")] pub fn PyMember_SetOne(addr: *mut c_char, l: *mut PyMemberDef, value: *mut PyObject) -> c_int; } diff --git a/pyo3-ffi/src/dictobject.rs b/pyo3-ffi/src/dictobject.rs index fec8f459e75..d85afa40fdf 100644 --- a/pyo3-ffi/src/dictobject.rs +++ b/pyo3-ffi/src/dictobject.rs @@ -4,7 +4,7 @@ use core::ffi::{c_char, c_int}; #[cfg(not(RustPython))] extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyDict_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDict_Type")] pub static mut PyDict_Type: PyTypeObject; } @@ -25,52 +25,51 @@ extern_libpython! { pub fn PyDict_Check(op: *mut PyObject) -> c_int; #[cfg(RustPython)] pub fn PyDict_CheckExact(op: *mut PyObject) -> c_int; - - #[cfg_attr(PyPy, link_name = "PyPyDict_New")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDict_New")] pub fn PyDict_New() -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyDict_GetItem")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDict_GetItem")] pub fn PyDict_GetItem(mp: *mut PyObject, key: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyDict_GetItemWithError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDict_GetItemWithError")] pub fn PyDict_GetItemWithError(mp: *mut PyObject, key: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyDict_SetItem")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDict_SetItem")] pub fn PyDict_SetItem(mp: *mut PyObject, key: *mut PyObject, item: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyDict_DelItem")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDict_DelItem")] pub fn PyDict_DelItem(mp: *mut PyObject, key: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyDict_Clear")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDict_Clear")] pub fn PyDict_Clear(mp: *mut PyObject); - #[cfg_attr(PyPy, link_name = "PyPyDict_Next")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDict_Next")] pub fn PyDict_Next( mp: *mut PyObject, pos: *mut Py_ssize_t, key: *mut *mut PyObject, value: *mut *mut PyObject, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyDict_Keys")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDict_Keys")] pub fn PyDict_Keys(mp: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyDict_Values")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDict_Values")] pub fn PyDict_Values(mp: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyDict_Items")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDict_Items")] pub fn PyDict_Items(mp: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyDict_Size")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDict_Size")] pub fn PyDict_Size(mp: *mut PyObject) -> Py_ssize_t; - #[cfg_attr(PyPy, link_name = "PyPyDict_Copy")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDict_Copy")] pub fn PyDict_Copy(mp: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyDict_Contains")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDict_Contains")] pub fn PyDict_Contains(mp: *mut PyObject, key: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyDict_Update")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDict_Update")] pub fn PyDict_Update(mp: *mut PyObject, other: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyDict_Merge")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDict_Merge")] pub fn PyDict_Merge(mp: *mut PyObject, other: *mut PyObject, _override: c_int) -> c_int; pub fn PyDict_MergeFromSeq2(d: *mut PyObject, seq2: *mut PyObject, _override: c_int) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyDict_GetItemString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDict_GetItemString")] pub fn PyDict_GetItemString(dp: *mut PyObject, key: *const c_char) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyDict_SetItemString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDict_SetItemString")] pub fn PyDict_SetItemString( dp: *mut PyObject, key: *const c_char, item: *mut PyObject, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyDict_DelItemString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDict_DelItemString")] pub fn PyDict_DelItemString(dp: *mut PyObject, key: *const c_char) -> c_int; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyDict_GetItemRef")] @@ -98,7 +97,9 @@ extern_libpython! { #[cfg(not(RustPython))] extern_libpython! { + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDictKeys_Type")] pub static mut PyDictKeys_Type: PyTypeObject; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyDictValues_Type")] pub static mut PyDictValues_Type: PyTypeObject; pub static mut PyDictItems_Type: PyTypeObject; } diff --git a/pyo3-ffi/src/enumobject.rs b/pyo3-ffi/src/enumobject.rs index cd2ba09c14d..80d6686e55e 100644 --- a/pyo3-ffi/src/enumobject.rs +++ b/pyo3-ffi/src/enumobject.rs @@ -2,5 +2,6 @@ use crate::object::PyTypeObject; extern_libpython! { pub static mut PyEnum_Type: PyTypeObject; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyReversed_Type")] pub static mut PyReversed_Type: PyTypeObject; } diff --git a/pyo3-ffi/src/fileobject.rs b/pyo3-ffi/src/fileobject.rs index 537e998bafa..3c8873f92f0 100644 --- a/pyo3-ffi/src/fileobject.rs +++ b/pyo3-ffi/src/fileobject.rs @@ -4,6 +4,7 @@ use core::ffi::{c_char, c_int}; pub const PY_STDIOTEXTMODE: &str = "b"; extern_libpython! { + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyFile_FromFd")] pub fn PyFile_FromFd( arg1: c_int, arg2: *const c_char, @@ -14,13 +15,13 @@ extern_libpython! { arg7: *const c_char, arg8: c_int, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyFile_GetLine")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyFile_GetLine")] pub fn PyFile_GetLine(arg1: *mut PyObject, arg2: c_int) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyFile_WriteObject")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyFile_WriteObject")] pub fn PyFile_WriteObject(arg1: *mut PyObject, arg2: *mut PyObject, arg3: c_int) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyFile_WriteString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyFile_WriteString")] pub fn PyFile_WriteString(arg1: *const c_char, arg2: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyFile_AsFileDescriptor")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_AsFileDescriptor")] pub fn PyObject_AsFileDescriptor(arg1: *mut PyObject) -> c_int; } diff --git a/pyo3-ffi/src/floatobject.rs b/pyo3-ffi/src/floatobject.rs index 5597d6d2922..e245344894e 100644 --- a/pyo3-ffi/src/floatobject.rs +++ b/pyo3-ffi/src/floatobject.rs @@ -7,7 +7,7 @@ opaque_struct!(pub PyFloatObject); extern_libpython! { #[cfg(not(RustPython))] - #[cfg_attr(PyPy, link_name = "PyPyFloat_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyFloat_Type")] pub static mut PyFloat_Type: PyTypeObject; #[cfg(RustPython)] @@ -35,11 +35,11 @@ extern_libpython! { pub fn PyFloat_GetMax() -> c_double; pub fn PyFloat_GetMin() -> c_double; pub fn PyFloat_GetInfo() -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyFloat_FromString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyFloat_FromString")] pub fn PyFloat_FromString(arg1: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyFloat_FromDouble")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyFloat_FromDouble")] pub fn PyFloat_FromDouble(arg1: c_double) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyFloat_AsDouble")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyFloat_AsDouble")] pub fn PyFloat_AsDouble(arg1: *mut PyObject) -> c_double; } diff --git a/pyo3-ffi/src/genericaliasobject.rs b/pyo3-ffi/src/genericaliasobject.rs index ccd9678278f..f628ce1b743 100644 --- a/pyo3-ffi/src/genericaliasobject.rs +++ b/pyo3-ffi/src/genericaliasobject.rs @@ -5,9 +5,10 @@ use crate::PyTypeObject; extern_libpython! { #[cfg(Py_3_9)] - #[cfg_attr(PyPy, link_name = "PyPy_GenericAlias")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_GenericAlias")] pub fn Py_GenericAlias(origin: *mut PyObject, args: *mut PyObject) -> *mut PyObject; #[cfg(all(Py_3_9, not(RustPython)))] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_GenericAliasType")] pub static mut Py_GenericAliasType: PyTypeObject; } diff --git a/pyo3-ffi/src/impl_/macros.rs b/pyo3-ffi/src/impl_/macros.rs index 1ef2ce99091..88226fce03c 100644 --- a/pyo3-ffi/src/impl_/macros.rs +++ b/pyo3-ffi/src/impl_/macros.rs @@ -15,7 +15,7 @@ macro_rules! extern_libpython_cpython_private_fn { ($(#[$attrs:meta])* $vis:vis $name:ident($($args:tt)*) $(-> $ret:ty)?) => { #[cfg_attr( - all(windows, target_arch = "x86", not(any(PyPy, GraalPy))), + all(windows, pyo3_use_raw_dylib, target_arch = "x86"), link_name = concat!("_", stringify!($name)) )] $(#[$attrs])* @@ -86,6 +86,18 @@ macro_rules! extern_libpython_maybe_private_fn { ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; + ( + [_PyInterpreterState_GetEvalFrameFunc] + $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? + ) => { + extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } + }; + ( + [_PyInterpreterState_SetEvalFrameFunc] + $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? + ) => { + extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } + }; ( [_PyObject_GC_New] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? diff --git a/pyo3-ffi/src/import.rs b/pyo3-ffi/src/import.rs index 974083c3c25..ea22a1036e8 100644 --- a/pyo3-ffi/src/import.rs +++ b/pyo3-ffi/src/import.rs @@ -4,9 +4,9 @@ use core::ffi::{c_char, c_int, c_long}; extern_libpython! { pub fn PyImport_GetMagicNumber() -> c_long; pub fn PyImport_GetMagicTag() -> *const c_char; - #[cfg_attr(PyPy, link_name = "PyPyImport_ExecCodeModule")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyImport_ExecCodeModule")] pub fn PyImport_ExecCodeModule(name: *const c_char, co: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyImport_ExecCodeModuleEx")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyImport_ExecCodeModuleEx")] pub fn PyImport_ExecCodeModuleEx( name: *const c_char, co: *mut PyObject, @@ -24,23 +24,23 @@ extern_libpython! { pathname: *mut PyObject, cpathname: *mut PyObject, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyImport_GetModuleDict")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyImport_GetModuleDict")] pub fn PyImport_GetModuleDict() -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyImport_GetModule")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyImport_GetModule")] pub fn PyImport_GetModule(name: *mut PyObject) -> *mut PyObject; pub fn PyImport_AddModuleObject(name: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyImport_AddModule")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyImport_AddModule")] pub fn PyImport_AddModule(name: *const c_char) -> *mut PyObject; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyImport_AddModuleRef")] pub fn PyImport_AddModuleRef(name: *const c_char) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyImport_ImportModule")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyImport_ImportModule")] pub fn PyImport_ImportModule(name: *const c_char) -> *mut PyObject; #[cfg(not(Py_3_15))] #[deprecated(note = "Python 3.13")] - #[cfg_attr(PyPy, link_name = "PyPyImport_ImportModuleNoBlock")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyImport_ImportModuleNoBlock")] pub fn PyImport_ImportModuleNoBlock(name: *const c_char) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyImport_ImportModuleLevel")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyImport_ImportModuleLevel")] pub fn PyImport_ImportModuleLevel( name: *const c_char, globals: *mut PyObject, @@ -48,7 +48,10 @@ extern_libpython! { fromlist: *mut PyObject, level: c_int, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyImport_ImportModuleLevelObject")] + #[cfg_attr( + all(PyPy, not(Py_3_12)), + link_name = "PyPyImport_ImportModuleLevelObject" + )] pub fn PyImport_ImportModuleLevelObject( name: *mut PyObject, globals: *mut PyObject, @@ -70,9 +73,9 @@ pub unsafe fn PyImport_ImportModuleEx( extern_libpython! { pub fn PyImport_GetImporter(path: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyImport_Import")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyImport_Import")] pub fn PyImport_Import(name: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyImport_ReloadModule")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyImport_ReloadModule")] pub fn PyImport_ReloadModule(m: *mut PyObject) -> *mut PyObject; #[cfg(not(Py_3_9))] #[deprecated(note = "Removed in Python 3.9 as it was \"For internal use only\".")] diff --git a/pyo3-ffi/src/intrcheck.rs b/pyo3-ffi/src/intrcheck.rs index 32702d171a8..fbc01fb9cd5 100644 --- a/pyo3-ffi/src/intrcheck.rs +++ b/pyo3-ffi/src/intrcheck.rs @@ -1,7 +1,7 @@ use core::ffi::c_int; extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyOS_InterruptOccurred")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyOS_InterruptOccurred")] pub fn PyOS_InterruptOccurred() -> c_int; #[cfg(not(Py_3_10))] #[deprecated(note = "Not documented in Python API; see Python 3.10 release notes")] @@ -11,7 +11,7 @@ extern_libpython! { pub fn PyOS_AfterFork_Parent(); pub fn PyOS_AfterFork_Child(); #[deprecated(note = "use PyOS_AfterFork_Child instead")] - #[cfg_attr(PyPy, link_name = "PyPyOS_AfterFork")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyOS_AfterFork")] pub fn PyOS_AfterFork(); // skipped non-limited _PyOS_IsMainThread diff --git a/pyo3-ffi/src/iterobject.rs b/pyo3-ffi/src/iterobject.rs index 4236a6be48f..82c715db2a7 100644 --- a/pyo3-ffi/src/iterobject.rs +++ b/pyo3-ffi/src/iterobject.rs @@ -17,7 +17,7 @@ extern_libpython! { #[cfg(RustPython)] pub fn PySeqIter_Check(op: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPySeqIter_New")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySeqIter_New")] pub fn PySeqIter_New(arg1: *mut PyObject) -> *mut PyObject; } @@ -31,6 +31,6 @@ extern_libpython! { #[cfg(RustPython)] pub fn PyCallIter_Check(op: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyCallIter_New")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCallIter_New")] pub fn PyCallIter_New(arg1: *mut PyObject, arg2: *mut PyObject) -> *mut PyObject; } diff --git a/pyo3-ffi/src/listobject.rs b/pyo3-ffi/src/listobject.rs index ed5f1cd5f7c..a43991dafe6 100644 --- a/pyo3-ffi/src/listobject.rs +++ b/pyo3-ffi/src/listobject.rs @@ -4,7 +4,7 @@ use core::ffi::c_int; #[cfg(not(RustPython))] extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyList_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyList_Type")] pub static mut PyList_Type: PyTypeObject; pub static mut PyListIter_Type: PyTypeObject; pub static mut PyListRevIter_Type: PyTypeObject; @@ -28,28 +28,28 @@ extern_libpython! { #[cfg(RustPython)] pub fn PyList_CheckExact(op: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyList_New")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyList_New")] pub fn PyList_New(size: Py_ssize_t) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyList_Size")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyList_Size")] pub fn PyList_Size(arg1: *mut PyObject) -> Py_ssize_t; - #[cfg_attr(PyPy, link_name = "PyPyList_GetItem")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyList_GetItem")] pub fn PyList_GetItem(arg1: *mut PyObject, arg2: Py_ssize_t) -> *mut PyObject; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyList_GetItemRef")] pub fn PyList_GetItemRef(arg1: *mut PyObject, arg2: Py_ssize_t) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyList_SetItem")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyList_SetItem")] pub fn PyList_SetItem(arg1: *mut PyObject, arg2: Py_ssize_t, arg3: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyList_Insert")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyList_Insert")] pub fn PyList_Insert(arg1: *mut PyObject, arg2: Py_ssize_t, arg3: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyList_Append")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyList_Append")] pub fn PyList_Append(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyList_GetSlice")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyList_GetSlice")] pub fn PyList_GetSlice( arg1: *mut PyObject, arg2: Py_ssize_t, arg3: Py_ssize_t, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyList_SetSlice")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyList_SetSlice")] pub fn PyList_SetSlice( arg1: *mut PyObject, arg2: Py_ssize_t, @@ -60,11 +60,11 @@ extern_libpython! { pub fn PyList_Extend(list: *mut PyObject, iterable: *mut PyObject) -> c_int; #[cfg(Py_3_13)] pub fn PyList_Clear(list: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyList_Sort")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyList_Sort")] pub fn PyList_Sort(arg1: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyList_Reverse")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyList_Reverse")] pub fn PyList_Reverse(arg1: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyList_AsTuple")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyList_AsTuple")] pub fn PyList_AsTuple(arg1: *mut PyObject) -> *mut PyObject; // CPython macros exported as functions on PyPy or GraalPy diff --git a/pyo3-ffi/src/longobject.rs b/pyo3-ffi/src/longobject.rs index 7c777fecc14..c9bfd9650e5 100644 --- a/pyo3-ffi/src/longobject.rs +++ b/pyo3-ffi/src/longobject.rs @@ -1,5 +1,7 @@ use crate::object::*; use crate::pyport::Py_ssize_t; +#[cfg(any(all(Py_3_14, not(Py_LIMITED_API)), Py_3_15))] +use crate::Py_uintptr_t; use core::ffi::{c_char, c_double, c_int, c_long, c_longlong, c_ulong, c_ulonglong, c_void}; use libc::size_t; @@ -23,27 +25,27 @@ extern_libpython! { #[cfg(RustPython)] pub fn PyLong_CheckExact(op: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyLong_FromLong")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_FromLong")] pub fn PyLong_FromLong(arg1: c_long) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyLong_FromUnsignedLong")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_FromUnsignedLong")] pub fn PyLong_FromUnsignedLong(arg1: c_ulong) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyLong_FromSize_t")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_FromSize_t")] pub fn PyLong_FromSize_t(arg1: size_t) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyLong_FromSsize_t")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_FromSsize_t")] pub fn PyLong_FromSsize_t(arg1: Py_ssize_t) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyLong_FromDouble")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_FromDouble")] pub fn PyLong_FromDouble(arg1: c_double) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyLong_AsLong")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_AsLong")] pub fn PyLong_AsLong(arg1: *mut PyObject) -> c_long; - #[cfg_attr(PyPy, link_name = "PyPyLong_AsLongAndOverflow")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_AsLongAndOverflow")] pub fn PyLong_AsLongAndOverflow(arg1: *mut PyObject, arg2: *mut c_int) -> c_long; - #[cfg_attr(PyPy, link_name = "PyPyLong_AsSsize_t")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_AsSsize_t")] pub fn PyLong_AsSsize_t(arg1: *mut PyObject) -> Py_ssize_t; - #[cfg_attr(PyPy, link_name = "PyPyLong_AsSize_t")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_AsSize_t")] pub fn PyLong_AsSize_t(arg1: *mut PyObject) -> size_t; - #[cfg_attr(PyPy, link_name = "PyPyLong_AsUnsignedLong")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_AsUnsignedLong")] pub fn PyLong_AsUnsignedLong(arg1: *mut PyObject) -> c_ulong; - #[cfg_attr(PyPy, link_name = "PyPyLong_AsUnsignedLongMask")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_AsUnsignedLongMask")] pub fn PyLong_AsUnsignedLongMask(arg1: *mut PyObject) -> c_ulong; // skipped non-limited PyLong_AsInt @@ -113,25 +115,25 @@ extern_libpython! { // skipped _Py_PARSE_INTPTR // skipped _Py_PARSE_UINTPTR - #[cfg_attr(PyPy, link_name = "PyPyLong_AsDouble")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_AsDouble")] pub fn PyLong_AsDouble(arg1: *mut PyObject) -> c_double; - #[cfg_attr(PyPy, link_name = "PyPyLong_FromVoidPtr")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_FromVoidPtr")] pub fn PyLong_FromVoidPtr(arg1: *mut c_void) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyLong_AsVoidPtr")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_AsVoidPtr")] pub fn PyLong_AsVoidPtr(arg1: *mut PyObject) -> *mut c_void; - #[cfg_attr(PyPy, link_name = "PyPyLong_FromLongLong")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_FromLongLong")] pub fn PyLong_FromLongLong(arg1: c_longlong) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyLong_FromUnsignedLongLong")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_FromUnsignedLongLong")] pub fn PyLong_FromUnsignedLongLong(arg1: c_ulonglong) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyLong_AsLongLong")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_AsLongLong")] pub fn PyLong_AsLongLong(arg1: *mut PyObject) -> c_longlong; - #[cfg_attr(PyPy, link_name = "PyPyLong_AsUnsignedLongLong")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_AsUnsignedLongLong")] pub fn PyLong_AsUnsignedLongLong(arg1: *mut PyObject) -> c_ulonglong; - #[cfg_attr(PyPy, link_name = "PyPyLong_AsUnsignedLongLongMask")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_AsUnsignedLongLongMask")] pub fn PyLong_AsUnsignedLongLongMask(arg1: *mut PyObject) -> c_ulonglong; - #[cfg_attr(PyPy, link_name = "PyPyLong_AsLongLongAndOverflow")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_AsLongLongAndOverflow")] pub fn PyLong_AsLongLongAndOverflow(arg1: *mut PyObject, arg2: *mut c_int) -> c_longlong; - #[cfg_attr(PyPy, link_name = "PyPyLong_FromString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_FromString")] pub fn PyLong_FromString( arg1: *const c_char, arg2: *mut *mut c_char, @@ -143,3 +145,50 @@ extern_libpython! { pub fn PyOS_strtoul(arg1: *const c_char, arg2: *mut *mut c_char, arg3: c_int) -> c_ulong; pub fn PyOS_strtol(arg1: *const c_char, arg2: *mut *mut c_char, arg3: c_int) -> c_long; } + +#[cfg(any(all(Py_3_14, not(Py_LIMITED_API)), Py_3_15))] +#[derive(Copy, Clone)] +#[repr(C)] +pub struct PyLongLayout { + pub bits_per_digit: u8, + pub digit_size: u8, + pub digits_order: i8, + pub digit_endianness: i8, +} + +#[cfg(any(all(Py_3_14, not(Py_LIMITED_API)), Py_3_15))] +extern_libpython! { + pub fn PyLong_GetNativeLayout() -> *const PyLongLayout; +} + +#[cfg(any(all(Py_3_14, not(Py_LIMITED_API)), Py_3_15))] +#[repr(C)] +pub struct PyLongExport { + pub value: i64, + pub negative: u8, + pub ndigits: Py_ssize_t, + pub digits: *const c_void, + _reserved: Py_uintptr_t, +} + +#[cfg(any(all(Py_3_14, not(Py_LIMITED_API)), Py_3_15))] +extern_libpython! { + pub fn PyLong_Export(obj: *mut PyObject, export_long: *mut PyLongExport) -> c_int; + pub fn PyLong_FreeExport(export_long: *mut PyLongExport); +} + +#[cfg(any(all(Py_3_14, not(Py_LIMITED_API)), Py_3_15))] +opaque_struct!(pub PyLongWriter); + +#[cfg(any(all(Py_3_14, not(Py_LIMITED_API)), Py_3_15))] +extern_libpython! { + pub fn PyLongWriter_Create( + negative: c_int, + ndigits: Py_ssize_t, + digits: *mut *mut c_void, + ) -> *mut PyLongWriter; + + pub fn PyLongWriter_Finish(writer: *mut PyLongWriter) -> *mut PyObject; + + pub fn PyLongWriter_Discard(writer: *mut PyLongWriter); +} diff --git a/pyo3-ffi/src/memoryobject.rs b/pyo3-ffi/src/memoryobject.rs index 7ebc2a211d7..35b4ac605f7 100644 --- a/pyo3-ffi/src/memoryobject.rs +++ b/pyo3-ffi/src/memoryobject.rs @@ -6,7 +6,7 @@ use core::ffi::{c_char, c_int}; extern_libpython! { #[cfg(not(RustPython))] - #[cfg_attr(PyPy, link_name = "PyPyMemoryView_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMemoryView_Type")] pub static mut PyMemoryView_Type: PyTypeObject; #[cfg(RustPython)] @@ -23,18 +23,18 @@ pub unsafe fn PyMemoryView_Check(op: *mut PyObject) -> c_int { // skipped non-limited PyMemoryView_GET_BASE extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyMemoryView_FromObject")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMemoryView_FromObject")] pub fn PyMemoryView_FromObject(base: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyMemoryView_FromMemory")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMemoryView_FromMemory")] pub fn PyMemoryView_FromMemory( mem: *mut c_char, size: Py_ssize_t, flags: c_int, ) -> *mut PyObject; #[cfg(any(Py_3_11, not(Py_LIMITED_API)))] - #[cfg_attr(PyPy, link_name = "PyPyMemoryView_FromBuffer")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMemoryView_FromBuffer")] pub fn PyMemoryView_FromBuffer(view: *const crate::Py_buffer) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyMemoryView_GetContiguous")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMemoryView_GetContiguous")] pub fn PyMemoryView_GetContiguous( base: *mut PyObject, buffertype: c_int, diff --git a/pyo3-ffi/src/methodobject.rs b/pyo3-ffi/src/methodobject.rs index b4ee2c5f36a..3b2a880afdb 100644 --- a/pyo3-ffi/src/methodobject.rs +++ b/pyo3-ffi/src/methodobject.rs @@ -20,7 +20,7 @@ pub struct PyCFunctionObject { extern_libpython! { #[cfg(not(RustPython))] - #[cfg_attr(PyPy, link_name = "PyPyCFunction_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCFunction_Type")] pub static mut PyCFunction_Type: PyTypeObject; #[cfg(RustPython)] @@ -89,12 +89,13 @@ pub type PyCMethod = unsafe extern "C" fn( ) -> *mut PyObject; extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyCFunction_GetFunction")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCFunction_GetFunction")] pub fn PyCFunction_GetFunction(f: *mut PyObject) -> Option; pub fn PyCFunction_GetSelf(f: *mut PyObject) -> *mut PyObject; pub fn PyCFunction_GetFlags(f: *mut PyObject) -> c_int; #[cfg(not(Py_3_13))] #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCFunction_Call")] pub fn PyCFunction_Call( f: *mut PyObject, args: *mut PyObject, @@ -248,7 +249,7 @@ pub unsafe fn PyCFunction_NewEx( #[cfg(Py_3_9)] extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyCMethod_New")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCMethod_New")] pub fn PyCMethod_New( ml: *mut PyMethodDef, slf: *mut PyObject, diff --git a/pyo3-ffi/src/modsupport.rs b/pyo3-ffi/src/modsupport.rs index 9971556cffc..f09876fb81f 100644 --- a/pyo3-ffi/src/modsupport.rs +++ b/pyo3-ffi/src/modsupport.rs @@ -5,11 +5,11 @@ use crate::pyport::Py_ssize_t; use core::ffi::{c_char, c_int, c_long}; extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyArg_Parse")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyArg_Parse")] pub fn PyArg_Parse(arg1: *mut PyObject, arg2: *const c_char, ...) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyArg_ParseTuple")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyArg_ParseTuple")] pub fn PyArg_ParseTuple(arg1: *mut PyObject, arg2: *const c_char, ...) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyArg_ParseTupleAndKeywords")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyArg_ParseTupleAndKeywords")] pub fn PyArg_ParseTupleAndKeywords( arg1: *mut PyObject, arg2: *mut PyObject, @@ -23,7 +23,7 @@ extern_libpython! { // skipped PyArg_VaParseTupleAndKeywords pub fn PyArg_ValidateKeywordArguments(arg1: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyArg_UnpackTuple")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyArg_UnpackTuple")] pub fn PyArg_UnpackTuple( arg1: *mut PyObject, arg2: *const c_char, @@ -32,39 +32,38 @@ extern_libpython! { ... ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPy_BuildValue")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_BuildValue")] pub fn Py_BuildValue(arg1: *const c_char, ...) -> *mut PyObject; // skipped Py_VaBuildValue #[cfg(Py_3_13)] pub fn PyModule_Add(module: *mut PyObject, name: *const c_char, value: *mut PyObject) -> c_int; #[cfg(Py_3_10)] - #[cfg_attr(PyPy, link_name = "PyPyModule_AddObjectRef")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyModule_AddObjectRef")] pub fn PyModule_AddObjectRef( module: *mut PyObject, name: *const c_char, value: *mut PyObject, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyModule_AddObject")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyModule_AddObject")] pub fn PyModule_AddObject( module: *mut PyObject, name: *const c_char, value: *mut PyObject, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyModule_AddIntConstant")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyModule_AddIntConstant")] pub fn PyModule_AddIntConstant( module: *mut PyObject, name: *const c_char, value: c_long, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyModule_AddStringConstant")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyModule_AddStringConstant")] pub fn PyModule_AddStringConstant( module: *mut PyObject, name: *const c_char, value: *const c_char, ) -> c_int; #[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))] - #[cfg_attr(PyPy, link_name = "PyPyModule_AddType")] pub fn PyModule_AddType( module: *mut PyObject, type_: *mut crate::object::PyTypeObject, @@ -72,8 +71,9 @@ extern_libpython! { // skipped PyModule_AddIntMacro // skipped PyModule_AddStringMacro pub fn PyModule_SetDocString(arg1: *mut PyObject, arg2: *const c_char) -> c_int; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyModule_AddFunctions")] pub fn PyModule_AddFunctions(arg1: *mut PyObject, arg2: *mut PyMethodDef) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyModule_ExecDef")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyModule_ExecDef")] pub fn PyModule_ExecDef(module: *mut PyObject, def: *mut PyModuleDef) -> c_int; } @@ -84,7 +84,7 @@ pub const PYTHON_ABI_VERSION: i32 = 3; extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyModule_Create2")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyModule_Create2")] pub fn PyModule_Create2(module: *mut PyModuleDef, apiver: c_int) -> *mut PyObject; } @@ -102,7 +102,7 @@ pub unsafe fn PyModule_Create(module: *mut PyModuleDef) -> *mut PyObject { extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyModule_FromDefAndSpec2")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyModule_FromDefAndSpec2")] pub fn PyModule_FromDefAndSpec2( def: *mut PyModuleDef, spec: *mut PyObject, diff --git a/pyo3-ffi/src/moduleobject.rs b/pyo3-ffi/src/moduleobject.rs index f4ff551474a..5029a82d534 100644 --- a/pyo3-ffi/src/moduleobject.rs +++ b/pyo3-ffi/src/moduleobject.rs @@ -11,7 +11,7 @@ use core::ffi::{c_char, c_int, c_void}; #[cfg(not(RustPython))] extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyModule_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyModule_Type")] pub static mut PyModule_Type: PyTypeObject; } @@ -33,15 +33,15 @@ extern_libpython! { #[cfg(RustPython)] pub fn PyModule_CheckExact(op: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyModule_NewObject")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyModule_NewObject")] pub fn PyModule_NewObject(name: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyModule_New")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyModule_New")] pub fn PyModule_New(name: *const c_char) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyModule_GetDict")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyModule_GetDict")] pub fn PyModule_GetDict(arg1: *mut PyObject) -> *mut PyObject; #[cfg(not(PyPy))] pub fn PyModule_GetNameObject(arg1: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyModule_GetName")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyModule_GetName")] pub fn PyModule_GetName(arg1: *mut PyObject) -> *const c_char; #[cfg(not(all(windows, PyPy)))] #[deprecated(note = "Python 3.2")] @@ -51,11 +51,11 @@ extern_libpython! { // skipped non-limited _PyModule_Clear // skipped non-limited _PyModule_ClearDict // skipped non-limited _PyModuleSpec_IsInitializing - #[cfg_attr(PyPy, link_name = "PyPyModule_GetDef")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyModule_GetDef")] pub fn PyModule_GetDef(arg1: *mut PyObject) -> *mut PyModuleDef; - #[cfg_attr(PyPy, link_name = "PyPyModule_GetState")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyModule_GetState")] pub fn PyModule_GetState(arg1: *mut PyObject) -> *mut c_void; - #[cfg_attr(PyPy, link_name = "PyPyModuleDef_Init")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyModuleDef_Init")] pub fn PyModuleDef_Init(arg1: *mut PyModuleDef) -> *mut PyObject; #[cfg(not(RustPython))] @@ -125,12 +125,19 @@ extern_libpython! { pub fn PyUnstable_Module_SetGIL(module: *mut PyObject, gil: *mut c_void) -> c_int; } -#[cfg(Py_3_15)] extern_libpython! { + #[cfg(Py_3_15)] pub fn PyModule_FromSlotsAndSpec(slots: *const PySlot, spec: *mut PyObject) -> *mut PyObject; + #[cfg(Py_3_15)] pub fn PyModule_Exec(_mod: *mut PyObject) -> c_int; + #[cfg(Py_3_15)] pub fn PyModule_GetStateSize(_mod: *mut PyObject, result: *mut Py_ssize_t) -> c_int; + #[cfg(Py_3_15)] pub fn PyModule_GetToken(module: *mut PyObject, result: *mut *mut c_void) -> c_int; + #[cfg(Py_3_15)] + pub fn PyModule_GetState_DuringGC(module: *mut PyObject) -> *mut c_void; + #[cfg(Py_3_15)] + pub fn PyModule_GetToken_DuringGC(module: *mut PyObject, result: *mut *mut c_void) -> c_int; } #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))] diff --git a/pyo3-ffi/src/object.rs b/pyo3-ffi/src/object.rs index a9bc476b4b7..6ed951d8668 100644 --- a/pyo3-ffi/src/object.rs +++ b/pyo3-ffi/src/object.rs @@ -61,7 +61,7 @@ struct Aligner(c_char); #[repr(C)] #[derive(Copy, Clone)] -#[cfg(all(Py_3_12, not(Py_GIL_DISABLED)))] +#[cfg(all(all(Py_3_12, not(PyPy)), not(Py_GIL_DISABLED)))] /// This union is anonymous in CPython, so the name was given by PyO3 because /// Rust union need a name. pub union PyObjectObRefcnt { @@ -76,7 +76,7 @@ pub union PyObjectObRefcnt { _aligner: Aligner, } -#[cfg(all(Py_3_12, not(Py_GIL_DISABLED)))] +#[cfg(all(all(Py_3_12, not(PyPy)), not(Py_GIL_DISABLED)))] impl core::fmt::Debug for PyObjectObRefcnt { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { // SAFETY: always valid to print `ob_refcnt` as a number @@ -84,7 +84,7 @@ impl core::fmt::Debug for PyObjectObRefcnt { } } -#[cfg(all(not(Py_3_12), not(Py_GIL_DISABLED)))] +#[cfg(all(not(all(Py_3_12, not(PyPy))), not(Py_GIL_DISABLED)))] pub type PyObjectObRefcnt = Py_ssize_t; const _PyObject_MIN_ALIGNMENT: usize = 4; @@ -117,7 +117,7 @@ pub struct PyObject { pub ob_ref_shared: AtomicIsize, // shared reference count #[cfg(not(Py_GIL_DISABLED))] pub ob_refcnt: PyObjectObRefcnt, - #[cfg(PyPy)] + #[cfg(all(PyPy, not(Py_3_12)))] pub ob_pypy_link: Py_ssize_t, pub ob_type: *mut PyTypeObject, } @@ -147,11 +147,11 @@ pub const PyObject_HEAD_INIT: PyObject = PyObject { ob_ref_local: AtomicU32::new(refcount::_Py_IMMORTAL_REFCNT_LOCAL), #[cfg(Py_GIL_DISABLED)] ob_ref_shared: AtomicIsize::new(0), - #[cfg(all(not(Py_GIL_DISABLED), Py_3_12))] + #[cfg(all(not(Py_GIL_DISABLED), not(PyPy), Py_3_12))] ob_refcnt: PyObjectObRefcnt { ob_refcnt: 1 }, - #[cfg(not(Py_3_12))] + #[cfg(any(not(Py_3_12), PyPy))] ob_refcnt: 1, - #[cfg(PyPy)] + #[cfg(all(PyPy, not(Py_3_12)))] ob_pypy_link: 0, ob_type: core::ptr::null_mut(), }; @@ -187,7 +187,7 @@ pub unsafe fn Py_Is(x: *mut PyObject, y: *mut PyObject) -> c_int { #[cfg(any(GraalPy, PyPy, RustPython))] #[cfg_attr(docsrs, doc(cfg(all())))] extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPy_Is")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_Is")] pub fn Py_Is(x: *mut PyObject, y: *mut PyObject) -> c_int; } @@ -225,9 +225,9 @@ extern_libpython! { #[cfg(not(RustPython))] extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyLong_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyLong_Type")] pub static mut PyLong_Type: PyTypeObject; - #[cfg_attr(PyPy, link_name = "PyPyBool_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBool_Type")] pub static mut PyBool_Type: PyTypeObject; } @@ -339,17 +339,17 @@ pub struct PyType_Spec { } extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyType_FromSpec")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyType_FromSpec")] pub fn PyType_FromSpec(arg1: *mut PyType_Spec) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyType_FromSpecWithBases")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyType_FromSpecWithBases")] pub fn PyType_FromSpecWithBases(arg1: *mut PyType_Spec, arg2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyType_GetSlot")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyType_GetSlot")] pub fn PyType_GetSlot(arg1: *mut PyTypeObject, arg2: c_int) -> *mut c_void; #[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))] - #[cfg_attr(PyPy, link_name = "PyPyType_FromModuleAndSpec")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyType_FromModuleAndSpec")] pub fn PyType_FromModuleAndSpec( module: *mut PyObject, spec: *mut PyType_Spec, @@ -357,19 +357,19 @@ extern_libpython! { ) -> *mut PyObject; #[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))] - #[cfg_attr(PyPy, link_name = "PyPyType_GetModule")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyType_GetModule")] pub fn PyType_GetModule(arg1: *mut PyTypeObject) -> *mut PyObject; #[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))] - #[cfg_attr(PyPy, link_name = "PyPyType_GetModuleState")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyType_GetModuleState")] pub fn PyType_GetModuleState(arg1: *mut PyTypeObject) -> *mut c_void; #[cfg(Py_3_11)] - #[cfg_attr(PyPy, link_name = "PyPyType_GetName")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyType_GetName")] pub fn PyType_GetName(arg1: *mut PyTypeObject) -> *mut PyObject; #[cfg(Py_3_11)] - #[cfg_attr(PyPy, link_name = "PyPyType_GetQualName")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyType_GetQualName")] pub fn PyType_GetQualName(arg1: *mut PyTypeObject) -> *mut PyObject; #[cfg(Py_3_13)] @@ -381,7 +381,7 @@ extern_libpython! { pub fn PyType_GetModuleName(arg1: *mut PyTypeObject) -> *mut PyObject; #[cfg(Py_3_12)] - #[cfg_attr(PyPy, link_name = "PyPyType_FromMetaclass")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyType_FromMetaclass")] pub fn PyType_FromMetaclass( metaclass: *mut PyTypeObject, module: *mut PyObject, @@ -390,11 +390,10 @@ extern_libpython! { ) -> *mut PyObject; #[cfg(Py_3_12)] - #[cfg_attr(PyPy, link_name = "PyPyObject_GetTypeData")] pub fn PyObject_GetTypeData(obj: *mut PyObject, cls: *mut PyTypeObject) -> *mut c_void; #[cfg(Py_3_12)] - #[cfg_attr(PyPy, link_name = "PyPyType_GetTypeDataSize")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyType_GetTypeDataSize")] pub fn PyType_GetTypeDataSize(cls: *mut PyTypeObject) -> Py_ssize_t; #[cfg(Py_3_14)] @@ -409,7 +408,7 @@ extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyType_FromSlot")] pub fn PyType_FromSlots(slots: *mut PySlot) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyType_IsSubtype")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyType_IsSubtype")] pub fn PyType_IsSubtype(a: *mut PyTypeObject, b: *mut PyTypeObject) -> c_int; } @@ -421,11 +420,11 @@ pub unsafe fn PyObject_TypeCheck(ob: *mut PyObject, tp: *mut PyTypeObject) -> c_ extern_libpython! { /// built-in 'type' #[cfg(not(RustPython))] - #[cfg_attr(PyPy, link_name = "PyPyType_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyType_Type")] pub static mut PyType_Type: PyTypeObject; /// built-in 'object' #[cfg(not(RustPython))] - #[cfg_attr(PyPy, link_name = "PyPyBaseObject_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBaseObject_Type")] pub static mut PyBaseObject_Type: PyTypeObject; /// built-in 'super' #[cfg(not(RustPython))] @@ -433,40 +432,40 @@ extern_libpython! { pub fn PyType_GetFlags(arg1: *mut PyTypeObject) -> c_ulong; - #[cfg_attr(PyPy, link_name = "PyPyType_Ready")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyType_Ready")] pub fn PyType_Ready(t: *mut PyTypeObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyType_GenericAlloc")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyType_GenericAlloc")] pub fn PyType_GenericAlloc(t: *mut PyTypeObject, nitems: Py_ssize_t) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyType_GenericNew")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyType_GenericNew")] pub fn PyType_GenericNew( t: *mut PyTypeObject, args: *mut PyObject, kwds: *mut PyObject, ) -> *mut PyObject; pub fn PyType_ClearCache() -> c_uint; - #[cfg_attr(PyPy, link_name = "PyPyType_Modified")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyType_Modified")] pub fn PyType_Modified(t: *mut PyTypeObject); - #[cfg_attr(PyPy, link_name = "PyPyObject_Repr")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_Repr")] pub fn PyObject_Repr(o: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_Str")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_Str")] pub fn PyObject_Str(o: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_ASCII")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_ASCII")] pub fn PyObject_ASCII(arg1: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_Bytes")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_Bytes")] pub fn PyObject_Bytes(arg1: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_RichCompare")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_RichCompare")] pub fn PyObject_RichCompare( arg1: *mut PyObject, arg2: *mut PyObject, arg3: c_int, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_RichCompareBool")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_RichCompareBool")] pub fn PyObject_RichCompareBool(arg1: *mut PyObject, arg2: *mut PyObject, arg3: c_int) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyObject_GetAttrString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_GetAttrString")] pub fn PyObject_GetAttrString(arg1: *mut PyObject, arg2: *const c_char) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_SetAttrString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_SetAttrString")] pub fn PyObject_SetAttrString( arg1: *mut PyObject, arg2: *const c_char, @@ -475,9 +474,9 @@ extern_libpython! { #[cfg(any(Py_3_13, all(PyPy, not(Py_3_11))))] // CPython defined in 3.12 as an inline function in abstract.h #[cfg_attr(PyPy, link_name = "PyPyObject_DelAttrString")] pub fn PyObject_DelAttrString(arg1: *mut PyObject, arg2: *const c_char) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyObject_HasAttrString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_HasAttrString")] pub fn PyObject_HasAttrString(arg1: *mut PyObject, arg2: *const c_char) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyObject_GetAttr")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_GetAttr")] pub fn PyObject_GetAttr(arg1: *mut PyObject, arg2: *mut PyObject) -> *mut PyObject; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyObject_GetOptionalAttr")] @@ -493,13 +492,13 @@ extern_libpython! { arg2: *const c_char, arg3: *mut *mut PyObject, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyObject_SetAttr")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_SetAttr")] pub fn PyObject_SetAttr(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject) -> c_int; #[cfg(any(Py_3_13, all(PyPy, not(Py_3_11))))] // CPython defined in 3.12 as an inline function in abstract.h #[cfg_attr(PyPy, link_name = "PyPyObject_DelAttr")] pub fn PyObject_DelAttr(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyObject_HasAttr")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_HasAttr")] pub fn PyObject_HasAttr(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyObject_HasAttrWithError")] @@ -507,41 +506,43 @@ extern_libpython! { #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyObject_HasAttrStringWithError")] pub fn PyObject_HasAttrStringWithError(arg1: *mut PyObject, arg2: *const c_char) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyObject_SelfIter")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_SelfIter")] pub fn PyObject_SelfIter(arg1: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_GenericGetAttr")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_GenericGetAttr")] pub fn PyObject_GenericGetAttr(arg1: *mut PyObject, arg2: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_GenericSetAttr")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_GenericSetAttr")] pub fn PyObject_GenericSetAttr( arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject, ) -> c_int; #[cfg(not(all(Py_LIMITED_API, not(Py_3_10))))] - #[cfg_attr(PyPy, link_name = "PyPyObject_GenericGetDict")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_GenericGetDict")] pub fn PyObject_GenericGetDict(arg1: *mut PyObject, arg2: *mut c_void) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_GenericSetDict")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_GenericSetDict")] pub fn PyObject_GenericSetDict( arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut c_void, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyObject_Hash")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_Hash")] pub fn PyObject_Hash(arg1: *mut PyObject) -> Py_hash_t; - #[cfg_attr(PyPy, link_name = "PyPyObject_HashNotImplemented")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_HashNotImplemented")] pub fn PyObject_HashNotImplemented(arg1: *mut PyObject) -> Py_hash_t; - #[cfg_attr(PyPy, link_name = "PyPyObject_IsTrue")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_IsTrue")] pub fn PyObject_IsTrue(arg1: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyObject_Not")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_Not")] pub fn PyObject_Not(arg1: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyCallable_Check")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCallable_Check")] pub fn PyCallable_Check(arg1: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyObject_ClearWeakRefs")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_ClearWeakRefs")] pub fn PyObject_ClearWeakRefs(arg1: *mut PyObject); - #[cfg_attr(PyPy, link_name = "PyPyObject_Dir")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_Dir")] pub fn PyObject_Dir(arg1: *mut PyObject) -> *mut PyObject; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_ReprEnter")] pub fn Py_ReprEnter(arg1: *mut PyObject) -> c_int; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_ReprLeave")] pub fn Py_ReprLeave(arg1: *mut PyObject); } @@ -650,7 +651,7 @@ extern_libpython! { pub fn Py_GetConstantBorrowed(constant_id: c_uint) -> *mut PyObject; #[cfg(all(not(GraalPy), not(all(Py_3_13, Py_LIMITED_API))))] - #[cfg_attr(PyPy, link_name = "_PyPy_NoneStruct")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "_PyPy_NoneStruct")] static mut _Py_NoneStruct: PyObject; #[cfg(GraalPy)] @@ -678,7 +679,7 @@ pub unsafe fn Py_IsNone(x: *mut PyObject) -> c_int { extern_libpython! { #[cfg(all(not(GraalPy), not(all(Py_3_13, Py_LIMITED_API))))] - #[cfg_attr(PyPy, link_name = "_PyPy_NotImplementedStruct")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "_PyPy_NotImplementedStruct")] static mut _Py_NotImplementedStruct: PyObject; #[cfg(GraalPy)] @@ -768,7 +769,33 @@ extern_libpython! { #[cfg(Py_3_14)] pub fn PyType_Freeze(tp: *mut crate::PyTypeObject) -> c_int; + #[cfg(any(Py_3_15, not(Py_LIMITED_API)))] + #[cfg_attr(PyPy, link_name = "PyPyObject_CallFinalizerFromDealloc")] + pub fn PyObject_CallFinalizerFromDealloc(arg1: *mut crate::PyObject) -> c_int; + #[cfg(Py_3_15)] pub fn PyType_GetModuleByToken(_type: *mut PyTypeObject, token: *const c_void) -> *mut PyObject; + + #[cfg(Py_3_15)] + pub fn PyObject_GetTypeData_DuringGC(o: *mut PyObject, cls: *mut PyTypeObject) -> *mut c_void; + + #[cfg(Py_3_15)] + pub fn PyType_GetModuleState_DuringGC(type_: *mut PyTypeObject) -> *mut c_void; + + #[cfg(Py_3_15)] + pub fn PyType_GetBaseByToken_DuringGC( + type_: *mut PyTypeObject, + tp_token: *mut c_void, + result: *mut *mut PyTypeObject, + ) -> c_int; + + #[cfg(Py_3_15)] + pub fn PyType_GetModule_DuringGC(type_: *mut PyTypeObject) -> *mut PyObject; + + #[cfg(Py_3_15)] + pub fn PyType_GetModuleByToken_DuringGC( + type_: *mut PyTypeObject, + mod_token: *const c_void, + ) -> *mut PyObject; } diff --git a/pyo3-ffi/src/objimpl.rs b/pyo3-ffi/src/objimpl.rs index bad80a1eae2..b32ad28e77c 100644 --- a/pyo3-ffi/src/objimpl.rs +++ b/pyo3-ffi/src/objimpl.rs @@ -5,13 +5,13 @@ use crate::object::*; use crate::pyport::Py_ssize_t; extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyObject_Malloc")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_Malloc")] pub fn PyObject_Malloc(size: size_t) -> *mut c_void; - #[cfg_attr(PyPy, link_name = "PyPyObject_Calloc")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_Calloc")] pub fn PyObject_Calloc(nelem: size_t, elsize: size_t) -> *mut c_void; - #[cfg_attr(PyPy, link_name = "PyPyObject_Realloc")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_Realloc")] pub fn PyObject_Realloc(ptr: *mut c_void, new_size: size_t) -> *mut c_void; - #[cfg_attr(PyPy, link_name = "PyPyObject_Free")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_Free")] pub fn PyObject_Free(ptr: *mut c_void); // skipped PyObject_MALLOC @@ -20,9 +20,9 @@ extern_libpython! { // skipped PyObject_Del // skipped PyObject_DEL - #[cfg_attr(PyPy, link_name = "PyPyObject_Init")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_Init")] pub fn PyObject_Init(arg1: *mut PyObject, arg2: *mut PyTypeObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyObject_InitVar")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_InitVar")] pub fn PyObject_InitVar( arg1: *mut PyVarObject, arg2: *mut PyTypeObject, @@ -32,9 +32,9 @@ extern_libpython! { // skipped PyObject_INIT // skipped PyObject_INIT_VAR - #[cfg_attr(PyPy, link_name = "_PyPyObject_New")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "_PyPyObject_New")] fn _PyObject_New(typeobj: *mut PyTypeObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "_PyPyObject_NewVar")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "_PyPyObject_NewVar")] fn _PyObject_NewVar(typeobj: *mut PyTypeObject, n: Py_ssize_t) -> *mut PyVarObject; } @@ -52,20 +52,27 @@ pub unsafe fn PyObject_NewVar(typeobj: *mut PyTypeObject, n: Py_ssize_t) -> * // skipped PyObject_NEW_VAR +#[cfg(not(all(PyPy, not(Py_3_12))))] +type PyGCCollectReturn = Py_ssize_t; + +// PyPy before 3.12 seems to use `int` for return type +#[cfg(all(PyPy, not(Py_3_12)))] +type PyGCCollectReturn = c_int; + extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyGC_Collect")] - pub fn PyGC_Collect() -> Py_ssize_t; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyGC_Collect")] + pub fn PyGC_Collect() -> PyGCCollectReturn; #[cfg(Py_3_10)] - #[cfg_attr(PyPy, link_name = "PyPyGC_Enable")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyGC_Enable")] pub fn PyGC_Enable() -> c_int; #[cfg(Py_3_10)] - #[cfg_attr(PyPy, link_name = "PyPyGC_Disable")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyGC_Disable")] pub fn PyGC_Disable() -> c_int; #[cfg(Py_3_10)] - #[cfg_attr(PyPy, link_name = "PyPyGC_IsEnabled")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyGC_IsEnabled")] pub fn PyGC_IsEnabled() -> c_int; } @@ -84,9 +91,9 @@ pub unsafe fn PyObject_GC_Resize(op: *mut PyObject, n: Py_ssize_t) -> *mut T } extern_libpython! { - #[cfg_attr(PyPy, link_name = "_PyPyObject_GC_New")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "_PyPyObject_GC_New")] fn _PyObject_GC_New(typeobj: *mut PyTypeObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "_PyPyObject_GC_NewVar")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "_PyPyObject_GC_NewVar")] fn _PyObject_GC_NewVar(typeobj: *mut PyTypeObject, n: Py_ssize_t) -> *mut PyVarObject; #[cfg(not(PyPy))] @@ -95,7 +102,7 @@ extern_libpython! { #[cfg(not(PyPy))] pub fn PyObject_GC_UnTrack(arg1: *mut c_void); - #[cfg_attr(PyPy, link_name = "PyPyObject_GC_Del")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_GC_Del")] pub fn PyObject_GC_Del(arg1: *mut c_void); } @@ -111,10 +118,10 @@ pub unsafe fn PyObject_GC_NewVar(typeobj: *mut PyTypeObject, n: Py_ssize_t) - extern_libpython! { #[cfg(any(all(Py_3_9, not(PyPy)), Py_3_10))] // added in 3.9, or 3.10 on PyPy - #[cfg_attr(PyPy, link_name = "PyPyObject_GC_IsTracked")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_GC_IsTracked")] pub fn PyObject_GC_IsTracked(arg1: *mut PyObject) -> c_int; #[cfg(any(all(Py_3_9, not(PyPy)), Py_3_10))] // added in 3.9, or 3.10 on PyPy - #[cfg_attr(PyPy, link_name = "PyPyObject_GC_IsFinalized")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_GC_IsFinalized")] pub fn PyObject_GC_IsFinalized(arg1: *mut PyObject) -> c_int; } diff --git a/pyo3-ffi/src/osmodule.rs b/pyo3-ffi/src/osmodule.rs index 84ce095ac2f..1e7c53dcc80 100644 --- a/pyo3-ffi/src/osmodule.rs +++ b/pyo3-ffi/src/osmodule.rs @@ -1,6 +1,6 @@ use crate::object::PyObject; extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyOS_FSPath")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyOS_FSPath")] pub fn PyOS_FSPath(path: *mut PyObject) -> *mut PyObject; } diff --git a/pyo3-ffi/src/pybuffer.rs b/pyo3-ffi/src/pybuffer.rs index 83975c744e1..bed166cb5f2 100644 --- a/pyo3-ffi/src/pybuffer.rs +++ b/pyo3-ffi/src/pybuffer.rs @@ -59,20 +59,20 @@ extern_libpython! { #[cfg(not(PyPy))] pub fn PyObject_CheckBuffer(obj: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyObject_GetBuffer")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyObject_GetBuffer")] pub fn PyObject_GetBuffer(obj: *mut PyObject, view: *mut Py_buffer, flags: c_int) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyBuffer_GetPointer")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBuffer_GetPointer")] pub fn PyBuffer_GetPointer(view: *const Py_buffer, indices: *const Py_ssize_t) -> *mut c_void; - #[cfg_attr(PyPy, link_name = "PyPyBuffer_SizeFromFormat")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBuffer_SizeFromFormat")] pub fn PyBuffer_SizeFromFormat(format: *const c_char) -> Py_ssize_t; - #[cfg_attr(PyPy, link_name = "PyPyBuffer_ToContiguous")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBuffer_ToContiguous")] pub fn PyBuffer_ToContiguous( buf: *mut c_void, view: *const Py_buffer, len: Py_ssize_t, order: c_char, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyBuffer_FromContiguous")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBuffer_FromContiguous")] pub fn PyBuffer_FromContiguous( view: *const Py_buffer, buf: *const c_void, @@ -80,7 +80,7 @@ extern_libpython! { order: c_char, ) -> c_int; pub fn PyObject_CopyData(dest: *mut PyObject, src: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyBuffer_IsContiguous")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBuffer_IsContiguous")] pub fn PyBuffer_IsContiguous(view: *const Py_buffer, fort: c_char) -> c_int; pub fn PyBuffer_FillContiguousStrides( ndims: c_int, @@ -89,7 +89,7 @@ extern_libpython! { itemsize: c_int, fort: c_char, ); - #[cfg_attr(PyPy, link_name = "PyPyBuffer_FillInfo")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBuffer_FillInfo")] pub fn PyBuffer_FillInfo( view: *mut Py_buffer, o: *mut PyObject, @@ -98,7 +98,7 @@ extern_libpython! { readonly: c_int, flags: c_int, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyBuffer_Release")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyBuffer_Release")] pub fn PyBuffer_Release(view: *mut Py_buffer); } diff --git a/pyo3-ffi/src/pycapsule.rs b/pyo3-ffi/src/pycapsule.rs index a5a3df91057..b7767a568bc 100644 --- a/pyo3-ffi/src/pycapsule.rs +++ b/pyo3-ffi/src/pycapsule.rs @@ -3,7 +3,7 @@ use core::ffi::{c_char, c_int, c_void}; #[cfg(not(RustPython))] extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyCapsule_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCapsule_Type")] pub static mut PyCapsule_Type: PyTypeObject; } @@ -19,33 +19,33 @@ extern_libpython! { #[cfg(RustPython)] pub fn PyCapsule_CheckExact(ob: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyCapsule_New")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCapsule_New")] pub fn PyCapsule_New( pointer: *mut c_void, name: *const c_char, destructor: Option, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyCapsule_GetPointer")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCapsule_GetPointer")] pub fn PyCapsule_GetPointer(capsule: *mut PyObject, name: *const c_char) -> *mut c_void; - #[cfg_attr(PyPy, link_name = "PyPyCapsule_GetDestructor")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCapsule_GetDestructor")] pub fn PyCapsule_GetDestructor(capsule: *mut PyObject) -> Option; - #[cfg_attr(PyPy, link_name = "PyPyCapsule_GetName")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCapsule_GetName")] pub fn PyCapsule_GetName(capsule: *mut PyObject) -> *const c_char; - #[cfg_attr(PyPy, link_name = "PyPyCapsule_GetContext")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCapsule_GetContext")] pub fn PyCapsule_GetContext(capsule: *mut PyObject) -> *mut c_void; - #[cfg_attr(PyPy, link_name = "PyPyCapsule_IsValid")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCapsule_IsValid")] pub fn PyCapsule_IsValid(capsule: *mut PyObject, name: *const c_char) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyCapsule_SetPointer")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCapsule_SetPointer")] pub fn PyCapsule_SetPointer(capsule: *mut PyObject, pointer: *mut c_void) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyCapsule_SetDestructor")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCapsule_SetDestructor")] pub fn PyCapsule_SetDestructor( capsule: *mut PyObject, destructor: Option, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyCapsule_SetName")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCapsule_SetName")] pub fn PyCapsule_SetName(capsule: *mut PyObject, name: *const c_char) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyCapsule_SetContext")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCapsule_SetContext")] pub fn PyCapsule_SetContext(capsule: *mut PyObject, context: *mut c_void) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyCapsule_Import")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyCapsule_Import")] pub fn PyCapsule_Import(name: *const c_char, no_block: c_int) -> *mut c_void; } diff --git a/pyo3-ffi/src/pyerrors.rs b/pyo3-ffi/src/pyerrors.rs index 29892bdf561..11f13a09a84 100644 --- a/pyo3-ffi/src/pyerrors.rs +++ b/pyo3-ffi/src/pyerrors.rs @@ -3,39 +3,39 @@ use crate::pyport::Py_ssize_t; use core::ffi::{c_char, c_int}; extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyErr_SetNone")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_SetNone")] pub fn PyErr_SetNone(arg1: *mut PyObject); - #[cfg_attr(PyPy, link_name = "PyPyErr_SetObject")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_SetObject")] pub fn PyErr_SetObject(arg1: *mut PyObject, arg2: *mut PyObject); - #[cfg_attr(PyPy, link_name = "PyPyErr_SetString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_SetString")] pub fn PyErr_SetString(exception: *mut PyObject, string: *const c_char); - #[cfg_attr(PyPy, link_name = "PyPyErr_Occurred")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_Occurred")] pub fn PyErr_Occurred() -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyErr_Clear")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_Clear")] pub fn PyErr_Clear(); #[cfg_attr(Py_3_12, deprecated(note = "Use PyErr_GetRaisedException() instead."))] - #[cfg_attr(PyPy, link_name = "PyPyErr_Fetch")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_Fetch")] pub fn PyErr_Fetch( arg1: *mut *mut PyObject, arg2: *mut *mut PyObject, arg3: *mut *mut PyObject, ); #[cfg_attr(Py_3_12, deprecated(note = "Use PyErr_SetRaisedException() instead."))] - #[cfg_attr(PyPy, link_name = "PyPyErr_Restore")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_Restore")] pub fn PyErr_Restore(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject); - #[cfg_attr(PyPy, link_name = "PyPyErr_GetExcInfo")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_GetExcInfo")] pub fn PyErr_GetExcInfo( arg1: *mut *mut PyObject, arg2: *mut *mut PyObject, arg3: *mut *mut PyObject, ); - #[cfg_attr(PyPy, link_name = "PyPyErr_SetExcInfo")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_SetExcInfo")] pub fn PyErr_SetExcInfo(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject); - #[cfg_attr(PyPy, link_name = "PyPy_FatalError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_FatalError")] pub fn Py_FatalError(message: *const c_char) -> !; - #[cfg_attr(PyPy, link_name = "PyPyErr_GivenExceptionMatches")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_GivenExceptionMatches")] pub fn PyErr_GivenExceptionMatches(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyErr_ExceptionMatches")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_ExceptionMatches")] pub fn PyErr_ExceptionMatches(arg1: *mut PyObject) -> c_int; #[cfg_attr( Py_3_12, @@ -43,33 +43,35 @@ extern_libpython! { note = "Use PyErr_GetRaisedException() instead, to avoid any possible de-normalization." ) )] - #[cfg_attr(PyPy, link_name = "PyPyErr_NormalizeException")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_NormalizeException")] pub fn PyErr_NormalizeException( arg1: *mut *mut PyObject, arg2: *mut *mut PyObject, arg3: *mut *mut PyObject, ); #[cfg(Py_3_12)] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_GetRaisedException")] pub fn PyErr_GetRaisedException() -> *mut PyObject; #[cfg(Py_3_12)] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_SetRaisedException")] pub fn PyErr_SetRaisedException(exc: *mut PyObject); #[cfg(Py_3_11)] - #[cfg_attr(PyPy, link_name = "PyPyErr_GetHandledException")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_GetHandledException")] pub fn PyErr_GetHandledException() -> *mut PyObject; #[cfg(Py_3_11)] - #[cfg_attr(PyPy, link_name = "PyPyErr_SetHandledException")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_SetHandledException")] pub fn PyErr_SetHandledException(exc: *mut PyObject); - #[cfg_attr(PyPy, link_name = "PyPyException_SetTraceback")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyException_SetTraceback")] pub fn PyException_SetTraceback(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyException_GetTraceback")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyException_GetTraceback")] pub fn PyException_GetTraceback(arg1: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyException_GetCause")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyException_GetCause")] pub fn PyException_GetCause(arg1: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyException_SetCause")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyException_SetCause")] pub fn PyException_SetCause(arg1: *mut PyObject, arg2: *mut PyObject); - #[cfg_attr(PyPy, link_name = "PyPyException_GetContext")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyException_GetContext")] pub fn PyException_GetContext(arg1: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyException_SetContext")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyException_SetContext")] pub fn PyException_SetContext(arg1: *mut PyObject, arg2: *mut PyObject); #[cfg(RustPython)] @@ -125,179 +127,194 @@ pub unsafe fn PyUnicodeDecodeError_Create( } extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyExc_BaseException")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_BaseException")] pub static mut PyExc_BaseException: *mut PyObject; #[cfg(Py_3_11)] - #[cfg_attr(PyPy, link_name = "PyPyExc_BaseExceptionGroup")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_BaseExceptionGroup")] pub static mut PyExc_BaseExceptionGroup: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_Exception")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_Exception")] pub static mut PyExc_Exception: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_StopAsyncIteration")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_StopAsyncIteration")] pub static mut PyExc_StopAsyncIteration: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_StopIteration")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_StopIteration")] pub static mut PyExc_StopIteration: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_GeneratorExit")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_GeneratorExit")] pub static mut PyExc_GeneratorExit: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_ArithmeticError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_ArithmeticError")] pub static mut PyExc_ArithmeticError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_LookupError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_LookupError")] pub static mut PyExc_LookupError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_AssertionError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_AssertionError")] pub static mut PyExc_AssertionError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_AttributeError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_AttributeError")] pub static mut PyExc_AttributeError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_BufferError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_BufferError")] pub static mut PyExc_BufferError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_EOFError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_EOFError")] pub static mut PyExc_EOFError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_FloatingPointError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_FloatingPointError")] pub static mut PyExc_FloatingPointError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_OSError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_OSError")] pub static mut PyExc_OSError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_ImportError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_ImportError")] pub static mut PyExc_ImportError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_ModuleNotFoundError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_ModuleNotFoundError")] pub static mut PyExc_ModuleNotFoundError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_IndexError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_IndexError")] pub static mut PyExc_IndexError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_KeyError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_KeyError")] pub static mut PyExc_KeyError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_KeyboardInterrupt")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_KeyboardInterrupt")] pub static mut PyExc_KeyboardInterrupt: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_MemoryError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_MemoryError")] pub static mut PyExc_MemoryError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_NameError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_NameError")] pub static mut PyExc_NameError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_OverflowError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_OverflowError")] pub static mut PyExc_OverflowError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_RuntimeError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_RuntimeError")] pub static mut PyExc_RuntimeError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_RecursionError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_RecursionError")] pub static mut PyExc_RecursionError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_NotImplementedError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_NotImplementedError")] pub static mut PyExc_NotImplementedError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_SyntaxError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_SyntaxError")] pub static mut PyExc_SyntaxError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_IndentationError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_IndentationError")] pub static mut PyExc_IndentationError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_TabError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_TabError")] pub static mut PyExc_TabError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_ReferenceError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_ReferenceError")] pub static mut PyExc_ReferenceError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_SystemError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_SystemError")] pub static mut PyExc_SystemError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_SystemExit")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_SystemExit")] pub static mut PyExc_SystemExit: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_TypeError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_TypeError")] pub static mut PyExc_TypeError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_UnboundLocalError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_UnboundLocalError")] pub static mut PyExc_UnboundLocalError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_UnicodeError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_UnicodeError")] pub static mut PyExc_UnicodeError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_UnicodeEncodeError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_UnicodeEncodeError")] pub static mut PyExc_UnicodeEncodeError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_UnicodeDecodeError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_UnicodeDecodeError")] pub static mut PyExc_UnicodeDecodeError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_UnicodeTranslateError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_UnicodeTranslateError")] pub static mut PyExc_UnicodeTranslateError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_ValueError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_ValueError")] pub static mut PyExc_ValueError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_ZeroDivisionError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_ZeroDivisionError")] pub static mut PyExc_ZeroDivisionError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_BlockingIOError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_BlockingIOError")] pub static mut PyExc_BlockingIOError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_BrokenPipeError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_BrokenPipeError")] pub static mut PyExc_BrokenPipeError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_ChildProcessError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_ChildProcessError")] pub static mut PyExc_ChildProcessError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_ConnectionError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_ConnectionError")] pub static mut PyExc_ConnectionError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_ConnectionAbortedError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_ConnectionAbortedError")] pub static mut PyExc_ConnectionAbortedError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_ConnectionRefusedError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_ConnectionRefusedError")] pub static mut PyExc_ConnectionRefusedError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_ConnectionResetError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_ConnectionResetError")] pub static mut PyExc_ConnectionResetError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_FileExistsError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_FileExistsError")] pub static mut PyExc_FileExistsError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_FileNotFoundError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_FileNotFoundError")] pub static mut PyExc_FileNotFoundError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_InterruptedError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_InterruptedError")] pub static mut PyExc_InterruptedError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_IsADirectoryError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_IsADirectoryError")] pub static mut PyExc_IsADirectoryError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_NotADirectoryError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_NotADirectoryError")] pub static mut PyExc_NotADirectoryError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_PermissionError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_PermissionError")] pub static mut PyExc_PermissionError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_ProcessLookupError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_ProcessLookupError")] pub static mut PyExc_ProcessLookupError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_TimeoutError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_TimeoutError")] pub static mut PyExc_TimeoutError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_OSError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_OSError")] + #[cfg_attr(all(PyPy, Py_3_12), link_name = "PyExc_OSError")] pub static mut PyExc_EnvironmentError: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_OSError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_OSError")] + #[cfg_attr(all(PyPy, Py_3_12), link_name = "PyExc_OSError")] pub static mut PyExc_IOError: *mut PyObject; #[cfg(windows)] - #[cfg_attr(PyPy, link_name = "PyPyExc_OSError")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_OSError")] + #[cfg_attr(all(PyPy, Py_3_12), link_name = "PyExc_OSError")] pub static mut PyExc_WindowsError: *mut PyObject; - pub static mut PyExc_RecursionErrorInst: *mut PyObject; - /* Predefined warning categories */ - #[cfg_attr(PyPy, link_name = "PyPyExc_Warning")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_Warning")] pub static mut PyExc_Warning: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_UserWarning")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_UserWarning")] pub static mut PyExc_UserWarning: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_DeprecationWarning")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_DeprecationWarning")] pub static mut PyExc_DeprecationWarning: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_PendingDeprecationWarning")] + #[cfg_attr( + all(PyPy, not(Py_3_12)), + link_name = "PyPyExc_PendingDeprecationWarning" + )] pub static mut PyExc_PendingDeprecationWarning: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_SyntaxWarning")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_SyntaxWarning")] pub static mut PyExc_SyntaxWarning: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_RuntimeWarning")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_RuntimeWarning")] pub static mut PyExc_RuntimeWarning: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_FutureWarning")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_FutureWarning")] pub static mut PyExc_FutureWarning: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_ImportWarning")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_ImportWarning")] pub static mut PyExc_ImportWarning: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_UnicodeWarning")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_UnicodeWarning")] pub static mut PyExc_UnicodeWarning: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_BytesWarning")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_BytesWarning")] pub static mut PyExc_BytesWarning: *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyExc_ResourceWarning")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_ResourceWarning")] pub static mut PyExc_ResourceWarning: *mut PyObject; #[cfg(Py_3_10)] - #[cfg_attr(PyPy, link_name = "PyPyExc_EncodingWarning")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyExc_EncodingWarning")] pub static mut PyExc_EncodingWarning: *mut PyObject; } extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyErr_BadArgument")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_BadArgument")] pub fn PyErr_BadArgument() -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyErr_NoMemory")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_NoMemory")] pub fn PyErr_NoMemory() -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyErr_SetFromErrno")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_SetFromErrno")] pub fn PyErr_SetFromErrno(arg1: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyErr_SetFromErrnoWithFilenameObject")] + #[cfg_attr( + all(PyPy, not(Py_3_12)), + link_name = "PyPyErr_SetFromErrnoWithFilenameObject" + )] pub fn PyErr_SetFromErrnoWithFilenameObject( arg1: *mut PyObject, arg2: *mut PyObject, ) -> *mut PyObject; + #[cfg_attr( + all(PyPy, not(Py_3_12)), + link_name = "PyPyErr_SetFromErrnoWithFilenameObjects" + )] pub fn PyErr_SetFromErrnoWithFilenameObjects( arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject, ) -> *mut PyObject; + #[cfg_attr( + all(PyPy, not(Py_3_12)), + link_name = "PyPyErr_SetFromErrnoWithFilename" + )] pub fn PyErr_SetFromErrnoWithFilename( exc: *mut PyObject, filename: *const c_char, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyErr_Format")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_Format")] pub fn PyErr_Format(exception: *mut PyObject, format: *const c_char, ...) -> *mut PyObject; pub fn PyErr_SetImportErrorSubclass( arg1: *mut PyObject, @@ -311,7 +328,7 @@ extern_libpython! { arg3: *mut PyObject, ) -> *mut PyObject; #[cfg(PyPy)] - #[cfg_attr(PyPy, link_name = "PyPyErr_BadInternalCall")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_BadInternalCall")] pub fn PyErr_BadInternalCall(); #[cfg(not(PyPy))] @@ -334,33 +351,33 @@ pub unsafe fn PyErr_BadInternalCall() { } extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyErr_NewException")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_NewException")] pub fn PyErr_NewException( name: *const c_char, base: *mut PyObject, dict: *mut PyObject, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyErr_NewExceptionWithDoc")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_NewExceptionWithDoc")] pub fn PyErr_NewExceptionWithDoc( name: *const c_char, doc: *const c_char, base: *mut PyObject, dict: *mut PyObject, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyErr_WriteUnraisable")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_WriteUnraisable")] pub fn PyErr_WriteUnraisable(arg1: *mut PyObject); - #[cfg_attr(PyPy, link_name = "PyPyErr_CheckSignals")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_CheckSignals")] pub fn PyErr_CheckSignals() -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyErr_SetInterrupt")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_SetInterrupt")] pub fn PyErr_SetInterrupt(); #[cfg(Py_3_10)] - #[cfg_attr(PyPy, link_name = "PyPyErr_SetInterruptEx")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_SetInterruptEx")] pub fn PyErr_SetInterruptEx(signum: c_int) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyErr_SyntaxLocation")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_SyntaxLocation")] pub fn PyErr_SyntaxLocation(filename: *const c_char, lineno: c_int); - #[cfg_attr(PyPy, link_name = "PyPyErr_SyntaxLocationEx")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_SyntaxLocationEx")] pub fn PyErr_SyntaxLocationEx(filename: *const c_char, lineno: c_int, col_offset: c_int); - #[cfg_attr(PyPy, link_name = "PyPyErr_ProgramText")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_ProgramText")] pub fn PyErr_ProgramText(filename: *const c_char, lineno: c_int) -> *mut PyObject; #[cfg(not(PyPy))] pub fn PyUnicodeDecodeError_Create( diff --git a/pyo3-ffi/src/pyframe.rs b/pyo3-ffi/src/pyframe.rs index fca98ac5cbe..f09aa717686 100644 --- a/pyo3-ffi/src/pyframe.rs +++ b/pyo3-ffi/src/pyframe.rs @@ -5,6 +5,7 @@ use crate::PyFrameObject; use core::ffi::c_int; extern_libpython! { + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyFrame_GetLineNumber")] pub fn PyFrame_GetLineNumber(frame: *mut PyFrameObject) -> c_int; #[cfg(not(GraalPy))] diff --git a/pyo3-ffi/src/pylifecycle.rs b/pyo3-ffi/src/pylifecycle.rs index 758f5228f61..84daae097cb 100644 --- a/pyo3-ffi/src/pylifecycle.rs +++ b/pyo3-ffi/src/pylifecycle.rs @@ -9,13 +9,13 @@ extern_libpython! { pub fn Py_Finalize(); pub fn Py_FinalizeEx() -> c_int; - #[cfg_attr(PyPy, link_name = "PyPy_IsInitialized")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_IsInitialized")] pub fn Py_IsInitialized() -> c_int; pub fn Py_NewInterpreter() -> *mut PyThreadState; pub fn Py_EndInterpreter(arg1: *mut PyThreadState); - #[cfg_attr(PyPy, link_name = "PyPy_AtExit")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_AtExit")] pub fn Py_AtExit(func: Option) -> c_int; pub fn Py_Exit(arg1: c_int) -> !; @@ -29,7 +29,7 @@ extern_libpython! { )] pub fn Py_SetProgramName(arg1: *const wchar_t); #[cfg(not(Py_3_15))] - #[cfg_attr(PyPy, link_name = "PyPy_GetProgramName")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_GetProgramName")] #[cfg_attr( Py_3_13, deprecated(note = "Deprecated since Python 3.13. Use `sys.executable` instead.") @@ -82,7 +82,7 @@ extern_libpython! { // skipped _Py_CheckPython3 - #[cfg_attr(PyPy, link_name = "PyPy_GetVersion")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_GetVersion")] pub fn Py_GetVersion() -> *const c_char; pub fn Py_GetPlatform() -> *const c_char; pub fn Py_GetCopyright() -> *const c_char; @@ -93,10 +93,13 @@ extern_libpython! { type PyOS_sighandler_t = unsafe extern "C" fn(arg1: c_int); extern_libpython! { + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyOS_getsig")] pub fn PyOS_getsig(arg1: c_int) -> PyOS_sighandler_t; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyOS_setsig")] pub fn PyOS_setsig(arg1: c_int, arg2: PyOS_sighandler_t) -> PyOS_sighandler_t; #[cfg(Py_3_11)] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_Version")] pub static Py_Version: core::ffi::c_ulong; #[cfg(Py_3_13)] diff --git a/pyo3-ffi/src/pymem.rs b/pyo3-ffi/src/pymem.rs index 45e57ef1db6..7ddf864f43c 100644 --- a/pyo3-ffi/src/pymem.rs +++ b/pyo3-ffi/src/pymem.rs @@ -2,12 +2,12 @@ use core::ffi::c_void; use libc::size_t; extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyMem_Malloc")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMem_Malloc")] pub fn PyMem_Malloc(size: size_t) -> *mut c_void; - #[cfg_attr(PyPy, link_name = "PyPyMem_Calloc")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMem_Calloc")] pub fn PyMem_Calloc(nelem: size_t, elsize: size_t) -> *mut c_void; - #[cfg_attr(PyPy, link_name = "PyPyMem_Realloc")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMem_Realloc")] pub fn PyMem_Realloc(ptr: *mut c_void, new_size: size_t) -> *mut c_void; - #[cfg_attr(PyPy, link_name = "PyPyMem_Free")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyMem_Free")] pub fn PyMem_Free(ptr: *mut c_void); } diff --git a/pyo3-ffi/src/pystate.rs b/pyo3-ffi/src/pystate.rs index 776c302aa61..e5468f83bba 100644 --- a/pyo3-ffi/src/pystate.rs +++ b/pyo3-ffi/src/pystate.rs @@ -29,22 +29,22 @@ extern_libpython! { #[cfg(not(PyPy))] pub fn PyInterpreterState_GetID(arg1: *mut PyInterpreterState) -> i64; - #[cfg_attr(PyPy, link_name = "PyPyState_AddModule")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyState_AddModule")] pub fn PyState_AddModule(arg1: *mut PyObject, arg2: *mut PyModuleDef) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyState_RemoveModule")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyState_RemoveModule")] pub fn PyState_RemoveModule(arg1: *mut PyModuleDef) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyState_FindModule")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyState_FindModule")] pub fn PyState_FindModule(arg1: *mut PyModuleDef) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyThreadState_New")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyThreadState_New")] pub fn PyThreadState_New(arg1: *mut PyInterpreterState) -> *mut PyThreadState; - #[cfg_attr(PyPy, link_name = "PyPyThreadState_Clear")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyThreadState_Clear")] pub fn PyThreadState_Clear(arg1: *mut PyThreadState); - #[cfg_attr(PyPy, link_name = "PyPyThreadState_Delete")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyThreadState_Delete")] pub fn PyThreadState_Delete(arg1: *mut PyThreadState); - #[cfg_attr(PyPy, link_name = "PyPyThreadState_Get")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyThreadState_Get")] pub fn PyThreadState_Get() -> *mut PyThreadState; } @@ -54,9 +54,9 @@ pub unsafe fn PyThreadState_GET() -> *mut PyThreadState { } extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyThreadState_Swap")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyThreadState_Swap")] pub fn PyThreadState_Swap(arg1: *mut PyThreadState) -> *mut PyThreadState; - #[cfg_attr(PyPy, link_name = "PyPyThreadState_GetDict")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyThreadState_GetDict")] pub fn PyThreadState_GetDict() -> *mut PyObject; #[cfg(not(PyPy))] pub fn PyThreadState_SetAsyncExc(arg1: c_long, arg2: *mut PyObject) -> c_int; @@ -90,13 +90,13 @@ pub enum PyGILState_STATE { mod raw { #[cfg(not(any(Py_3_14, target_arch = "wasm32")))] extern_libpython! { "C-unwind" { - #[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyGILState_Ensure")] pub fn PyGILState_Ensure() -> super::PyGILState_STATE; }} #[cfg(any(Py_3_14, target_arch = "wasm32"))] extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyGILState_Ensure")] pub fn PyGILState_Ensure() -> super::PyGILState_STATE; } } @@ -131,7 +131,7 @@ pub unsafe extern "C" fn PyGILState_Ensure() -> PyGILState_STATE { pub use self::raw::PyGILState_Ensure; extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyGILState_Release")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyGILState_Release")] pub fn PyGILState_Release(arg1: PyGILState_STATE); #[cfg(not(PyPy))] pub fn PyGILState_GetThisThreadState() -> *mut PyThreadState; diff --git a/pyo3-ffi/src/pystrtod.rs b/pyo3-ffi/src/pystrtod.rs index 43ac27b26cd..9f851adadf2 100644 --- a/pyo3-ffi/src/pystrtod.rs +++ b/pyo3-ffi/src/pystrtod.rs @@ -2,13 +2,13 @@ use crate::object::PyObject; use core::ffi::{c_char, c_double, c_int}; extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyOS_string_to_double")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyOS_string_to_double")] pub fn PyOS_string_to_double( str: *const c_char, endptr: *mut *mut c_char, overflow_exception: *mut PyObject, ) -> c_double; - #[cfg_attr(PyPy, link_name = "PyPyOS_double_to_string")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyOS_double_to_string")] pub fn PyOS_double_to_string( val: c_double, format_code: c_char, diff --git a/pyo3-ffi/src/pythonrun.rs b/pyo3-ffi/src/pythonrun.rs index 91c12a1931c..47e9fa7ce26 100644 --- a/pyo3-ffi/src/pythonrun.rs +++ b/pyo3-ffi/src/pythonrun.rs @@ -9,14 +9,15 @@ extern_libpython! { #[cfg(any(all(Py_LIMITED_API, not(PyPy)), GraalPy))] pub fn Py_CompileString(string: *const c_char, p: *const c_char, s: c_int) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyErr_Print")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_Print")] pub fn PyErr_Print(); - #[cfg_attr(PyPy, link_name = "PyPyErr_PrintEx")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_PrintEx")] pub fn PyErr_PrintEx(arg1: c_int); - #[cfg_attr(PyPy, link_name = "PyPyErr_Display")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_Display")] pub fn PyErr_Display(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject); #[cfg(Py_3_12)] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_DisplayException")] pub fn PyErr_DisplayException(exc: *mut PyObject); } diff --git a/pyo3-ffi/src/rangeobject.rs b/pyo3-ffi/src/rangeobject.rs index ffecd07eb32..dd0fc3e8800 100644 --- a/pyo3-ffi/src/rangeobject.rs +++ b/pyo3-ffi/src/rangeobject.rs @@ -3,7 +3,7 @@ use core::ffi::c_int; extern_libpython! { #[cfg(not(RustPython))] - #[cfg_attr(PyPy, link_name = "PyPyRange_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyRange_Type")] pub static mut PyRange_Type: PyTypeObject; #[cfg(not(RustPython))] pub static mut PyRangeIter_Type: PyTypeObject; diff --git a/pyo3-ffi/src/refcount.rs b/pyo3-ffi/src/refcount.rs index 4da10969b4a..e349aaa2f89 100644 --- a/pyo3-ffi/src/refcount.rs +++ b/pyo3-ffi/src/refcount.rs @@ -2,7 +2,10 @@ use crate::pyport::Py_ssize_t; use crate::PyObject; #[cfg(all(not(Py_LIMITED_API), py_sys_config = "Py_REF_DEBUG"))] use core::ffi::c_char; -#[cfg(any(Py_3_12, all(py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API))))] +#[cfg(any( + all(Py_3_12, not(PyPy)), + all(py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)) +))] use core::ffi::c_int; #[cfg(all(Py_3_14, any(not(Py_GIL_DISABLED), target_pointer_width = "32")))] use core::ffi::c_long; @@ -102,7 +105,15 @@ pub unsafe fn Py_REFCNT(ob: *mut PyObject) -> Py_ssize_t { #[cfg(all(not(Py_GIL_DISABLED), not(all(Py_LIMITED_API, Py_3_14)), Py_3_12))] { - (*ob).ob_refcnt.ob_refcnt + #[cfg(not(PyPy))] + { + (*ob).ob_refcnt.ob_refcnt + } + + #[cfg(PyPy)] + { + (*ob).ob_refcnt + } } #[cfg(all(not(Py_GIL_DISABLED), not(Py_3_12), not(GraalPy)))] @@ -118,6 +129,7 @@ pub unsafe fn Py_REFCNT(ob: *mut PyObject) -> Py_ssize_t { #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))] #[cfg(Py_3_12)] +#[cfg(not(PyPy))] #[inline(always)] unsafe fn _Py_IsImmortal(op: *mut PyObject) -> c_int { #[cfg(all(target_pointer_width = "64", not(Py_GIL_DISABLED)))] @@ -159,10 +171,10 @@ extern_libpython! { #[cfg_attr(PyPy, link_name = "_PyPy_Dealloc")] fn _Py_Dealloc(arg1: *mut PyObject); - #[cfg_attr(PyPy, link_name = "PyPy_IncRef")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_IncRef")] #[cfg_attr(GraalPy, link_name = "_Py_IncRef")] pub fn Py_IncRef(o: *mut PyObject); - #[cfg_attr(PyPy, link_name = "PyPy_DecRef")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPy_DecRef")] #[cfg_attr(GraalPy, link_name = "_Py_DecRef")] pub fn Py_DecRef(o: *mut PyObject); @@ -183,7 +195,8 @@ pub unsafe fn Py_INCREF(op: *mut PyObject) { Py_GIL_DISABLED, Py_LIMITED_API, py_sys_config = "Py_REF_DEBUG", - GraalPy + GraalPy, + PyPy ))] { // _Py_IncRef was added to the ABI in 3.10; skips null checks @@ -203,7 +216,8 @@ pub unsafe fn Py_INCREF(op: *mut PyObject) { Py_GIL_DISABLED, Py_LIMITED_API, py_sys_config = "Py_REF_DEBUG", - GraalPy + GraalPy, + PyPy )))] { #[cfg(all(Py_3_14, target_pointer_width = "64"))] @@ -260,7 +274,8 @@ pub unsafe fn Py_DECREF(op: *mut PyObject) { Py_GIL_DISABLED, Py_LIMITED_API, all(py_sys_config = "Py_REF_DEBUG", not(Py_3_12)), - GraalPy + GraalPy, + PyPy ))] { // _Py_DecRef was added to the ABI in 3.10; skips null checks @@ -279,7 +294,8 @@ pub unsafe fn Py_DECREF(op: *mut PyObject) { Py_GIL_DISABLED, Py_LIMITED_API, all(py_sys_config = "Py_REF_DEBUG", not(Py_3_12)), - GraalPy + GraalPy, + PyPy )))] { #[cfg(Py_3_12)] diff --git a/pyo3-ffi/src/setobject.rs b/pyo3-ffi/src/setobject.rs index 505b50d6bed..e168feb025b 100644 --- a/pyo3-ffi/src/setobject.rs +++ b/pyo3-ffi/src/setobject.rs @@ -4,41 +4,41 @@ use core::ffi::c_int; #[cfg(not(RustPython))] extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPySet_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySet_Type")] pub static mut PySet_Type: PyTypeObject; - #[cfg_attr(PyPy, link_name = "PyPyFrozenSet_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyFrozenSet_Type")] pub static mut PyFrozenSet_Type: PyTypeObject; pub static mut PySetIter_Type: PyTypeObject; } extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPySet_New")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySet_New")] pub fn PySet_New(arg1: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyFrozenSet_New")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyFrozenSet_New")] pub fn PyFrozenSet_New(arg1: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPySet_Add")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySet_Add")] pub fn PySet_Add(set: *mut PyObject, key: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPySet_Clear")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySet_Clear")] pub fn PySet_Clear(set: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPySet_Contains")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySet_Contains")] pub fn PySet_Contains(anyset: *mut PyObject, key: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPySet_Discard")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySet_Discard")] pub fn PySet_Discard(set: *mut PyObject, key: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPySet_Pop")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySet_Pop")] pub fn PySet_Pop(set: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPySet_Size")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySet_Size")] pub fn PySet_Size(anyset: *mut PyObject) -> Py_ssize_t; - #[cfg(any(PyPy, RustPython))] + #[cfg(any(all(PyPy, not(Py_3_12)), RustPython))] #[cfg_attr(PyPy, link_name = "PyPyFrozenSet_CheckExact")] pub fn PyFrozenSet_CheckExact(ob: *mut PyObject) -> c_int; - #[cfg(any(PyPy, RustPython))] + #[cfg(any(all(PyPy, not(Py_3_12)), RustPython))] #[cfg_attr(PyPy, link_name = "PyPyFrozenSet_Check")] pub fn PyFrozenSet_Check(ob: *mut PyObject) -> c_int; - #[cfg(any(PyPy, RustPython))] + #[cfg(any(all(PyPy, not(Py_3_12)), RustPython))] #[cfg_attr(PyPy, link_name = "PyPyAnySet_CheckExact")] pub fn PyAnySet_CheckExact(ob: *mut PyObject) -> c_int; @@ -48,26 +48,26 @@ extern_libpython! { #[cfg(RustPython)] pub fn PySet_CheckExact(op: *mut PyObject) -> c_int; - #[cfg(any(PyPy, RustPython))] + #[cfg(any(all(PyPy, not(Py_3_12)), RustPython))] #[cfg_attr(PyPy, link_name = "PyPySet_Check")] pub fn PySet_Check(ob: *mut PyObject) -> c_int; } #[inline] -#[cfg(not(any(PyPy, GraalPy, RustPython)))] +#[cfg(not(any(all(PyPy, not(Py_3_12)), GraalPy, RustPython)))] pub unsafe fn PyFrozenSet_CheckExact(ob: *mut PyObject) -> c_int { (Py_TYPE(ob) == &raw mut PyFrozenSet_Type) as c_int } #[inline] -#[cfg(not(any(PyPy, RustPython)))] +#[cfg(not(any(all(PyPy, not(Py_3_12)), RustPython)))] pub unsafe fn PyFrozenSet_Check(ob: *mut PyObject) -> c_int { (Py_TYPE(ob) == &raw mut PyFrozenSet_Type || PyType_IsSubtype(Py_TYPE(ob), &raw mut PyFrozenSet_Type) != 0) as c_int } #[inline] -#[cfg(not(any(PyPy, RustPython)))] +#[cfg(not(any(all(PyPy, not(Py_3_12)), RustPython)))] pub unsafe fn PyAnySet_CheckExact(ob: *mut PyObject) -> c_int { (Py_TYPE(ob) == &raw mut PySet_Type || Py_TYPE(ob) == &raw mut PyFrozenSet_Type) as c_int } @@ -87,7 +87,7 @@ pub unsafe fn PySet_CheckExact(op: *mut PyObject) -> c_int { } #[inline] -#[cfg(not(any(PyPy, RustPython)))] +#[cfg(not(any(all(PyPy, not(Py_3_12)), RustPython)))] pub unsafe fn PySet_Check(ob: *mut PyObject) -> c_int { (Py_TYPE(ob) == &raw mut PySet_Type || PyType_IsSubtype(Py_TYPE(ob), &raw mut PySet_Type) != 0) as c_int diff --git a/pyo3-ffi/src/sliceobject.rs b/pyo3-ffi/src/sliceobject.rs index 175a65f0622..2a30e82e054 100644 --- a/pyo3-ffi/src/sliceobject.rs +++ b/pyo3-ffi/src/sliceobject.rs @@ -4,7 +4,7 @@ use core::ffi::c_int; extern_libpython! { #[cfg(all(not(GraalPy), not(all(Py_3_13, Py_LIMITED_API))))] - #[cfg_attr(PyPy, link_name = "_PyPy_EllipsisObject")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "_PyPy_EllipsisObject")] static mut _Py_EllipsisObject: PyObject; #[cfg(GraalPy)] @@ -37,7 +37,7 @@ pub struct PySliceObject { #[cfg(not(RustPython))] extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPySlice_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySlice_Type")] pub static mut PySlice_Type: PyTypeObject; pub static mut PyEllipsis_Type: PyTypeObject; } @@ -52,7 +52,7 @@ extern_libpython! { #[cfg(RustPython)] pub fn PySlice_Check(op: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPySlice_New")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySlice_New")] pub fn PySlice_New( start: *mut PyObject, stop: *mut PyObject, @@ -62,7 +62,7 @@ extern_libpython! { // skipped non-limited _PySlice_FromIndices // skipped non-limited _PySlice_GetLongIndices - #[cfg_attr(PyPy, link_name = "PyPySlice_GetIndices")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySlice_GetIndices")] pub fn PySlice_GetIndices( r: *mut PyObject, length: Py_ssize_t, @@ -91,7 +91,7 @@ pub unsafe fn PySlice_GetIndicesEx( } extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPySlice_Unpack")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySlice_Unpack")] pub fn PySlice_Unpack( slice: *mut PyObject, start: *mut Py_ssize_t, @@ -99,7 +99,7 @@ extern_libpython! { step: *mut Py_ssize_t, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPySlice_AdjustIndices")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySlice_AdjustIndices")] pub fn PySlice_AdjustIndices( length: Py_ssize_t, start: *mut Py_ssize_t, diff --git a/pyo3-ffi/src/structseq.rs b/pyo3-ffi/src/structseq.rs index 7a1a70015a8..b4979102e38 100644 --- a/pyo3-ffi/src/structseq.rs +++ b/pyo3-ffi/src/structseq.rs @@ -21,6 +21,7 @@ pub struct PyStructSequence_Desc { extern_libpython! { #[cfg(any(Py_3_11, all(Py_3_9, not(Py_LIMITED_API))))] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyStructSequence_UnnamedField")] pub static PyStructSequence_UnnamedField: *const c_char; } @@ -38,7 +39,7 @@ extern_libpython! { #[cfg(not(PyPy))] pub fn PyStructSequence_NewType(desc: *mut PyStructSequence_Desc) -> *mut PyTypeObject; - #[cfg_attr(PyPy, link_name = "PyPyStructSequence_New")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyStructSequence_New")] pub fn PyStructSequence_New(_type: *mut PyTypeObject) -> *mut PyObject; } diff --git a/pyo3-ffi/src/sysmodule.rs b/pyo3-ffi/src/sysmodule.rs index d5c2bebdf8c..aaa61d66fb0 100644 --- a/pyo3-ffi/src/sysmodule.rs +++ b/pyo3-ffi/src/sysmodule.rs @@ -3,9 +3,9 @@ use core::ffi::{c_char, c_int}; use libc::wchar_t; extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPySys_GetObject")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySys_GetObject")] pub fn PySys_GetObject(arg1: *const c_char) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPySys_SetObject")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySys_SetObject")] pub fn PySys_SetObject(arg1: *const c_char, arg2: *mut PyObject) -> c_int; #[cfg_attr( @@ -24,9 +24,9 @@ extern_libpython! { pub fn PySys_SetArgvEx(arg1: c_int, arg2: *mut *mut wchar_t, arg3: c_int); pub fn PySys_SetPath(arg1: *const wchar_t); - #[cfg_attr(PyPy, link_name = "PyPySys_WriteStdout")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySys_WriteStdout")] pub fn PySys_WriteStdout(format: *const c_char, ...); - #[cfg_attr(PyPy, link_name = "PyPySys_WriteStderr")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPySys_WriteStderr")] pub fn PySys_WriteStderr(format: *const c_char, ...); pub fn PySys_FormatStdout(format: *const c_char, ...); pub fn PySys_FormatStderr(format: *const c_char, ...); diff --git a/pyo3-ffi/src/traceback.rs b/pyo3-ffi/src/traceback.rs index 9cfb11dcb9d..d5c034268bb 100644 --- a/pyo3-ffi/src/traceback.rs +++ b/pyo3-ffi/src/traceback.rs @@ -2,22 +2,22 @@ use crate::object::*; use core::ffi::c_int; extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyTraceBack_Here")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyTraceBack_Here")] pub fn PyTraceBack_Here(arg1: *mut crate::PyFrameObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyTraceBack_Print")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyTraceBack_Print")] pub fn PyTraceBack_Print(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; #[cfg(not(RustPython))] - #[cfg_attr(PyPy, link_name = "PyPyTraceBack_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyTraceBack_Type")] pub static mut PyTraceBack_Type: PyTypeObject; - #[cfg(any(PyPy, RustPython))] + #[cfg(any(all(PyPy, not(Py_3_12)), RustPython))] #[cfg_attr(PyPy, link_name = "PyPyTraceBack_Check")] pub fn PyTraceBack_Check(op: *mut PyObject) -> c_int; } #[inline] -#[cfg(not(any(PyPy, RustPython)))] +#[cfg(not(any(all(PyPy, not(Py_3_12)), RustPython)))] pub unsafe fn PyTraceBack_Check(op: *mut PyObject) -> c_int { Py_IS_TYPE(op, &raw mut PyTraceBack_Type) } diff --git a/pyo3-ffi/src/tupleobject.rs b/pyo3-ffi/src/tupleobject.rs index e415eabfe66..bc71c798558 100644 --- a/pyo3-ffi/src/tupleobject.rs +++ b/pyo3-ffi/src/tupleobject.rs @@ -4,7 +4,7 @@ use core::ffi::c_int; #[cfg(not(RustPython))] extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyTuple_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyTuple_Type")] pub static mut PyTuple_Type: PyTypeObject; pub static mut PyTupleIter_Type: PyTypeObject; } @@ -27,21 +27,21 @@ extern_libpython! { #[cfg(RustPython)] pub fn PyTuple_CheckExact(op: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyTuple_New")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyTuple_New")] pub fn PyTuple_New(size: Py_ssize_t) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyTuple_Size")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyTuple_Size")] pub fn PyTuple_Size(arg1: *mut PyObject) -> Py_ssize_t; - #[cfg_attr(PyPy, link_name = "PyPyTuple_GetItem")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyTuple_GetItem")] pub fn PyTuple_GetItem(arg1: *mut PyObject, arg2: Py_ssize_t) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyTuple_SetItem")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyTuple_SetItem")] pub fn PyTuple_SetItem(arg1: *mut PyObject, arg2: Py_ssize_t, arg3: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyTuple_GetSlice")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyTuple_GetSlice")] pub fn PyTuple_GetSlice( arg1: *mut PyObject, arg2: Py_ssize_t, arg3: Py_ssize_t, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyTuple_Pack")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyTuple_Pack")] pub fn PyTuple_Pack(arg1: Py_ssize_t, ...) -> *mut PyObject; #[cfg(any(all(Py_3_15, not(Py_LIMITED_API)), RustPython))] pub fn PyTuple_FromArray(array: *const *mut PyObject, size: Py_ssize_t) -> *mut PyObject; diff --git a/pyo3-ffi/src/unicodeobject.rs b/pyo3-ffi/src/unicodeobject.rs index 07dab310a15..40390058742 100644 --- a/pyo3-ffi/src/unicodeobject.rs +++ b/pyo3-ffi/src/unicodeobject.rs @@ -9,7 +9,7 @@ pub type Py_UCS1 = u8; extern_libpython! { #[cfg(not(RustPython))] - #[cfg_attr(PyPy, link_name = "PyPyUnicode_Type")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_Type")] pub static mut PyUnicode_Type: PyTypeObject; #[cfg(not(RustPython))] pub static mut PyUnicodeIter_Type: PyTypeObject; @@ -39,75 +39,81 @@ pub const Py_UNICODE_REPLACEMENT_CHARACTER: Py_UCS4 = 0xFFFD; extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromStringAndSize")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_FromStringAndSize")] pub fn PyUnicode_FromStringAndSize(u: *const c_char, size: Py_ssize_t) -> *mut PyObject; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_FromString")] pub fn PyUnicode_FromString(u: *const c_char) -> *mut PyObject; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_Substring")] pub fn PyUnicode_Substring( str: *mut PyObject, start: Py_ssize_t, end: Py_ssize_t, ) -> *mut PyObject; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_AsUCS4")] pub fn PyUnicode_AsUCS4( unicode: *mut PyObject, buffer: *mut Py_UCS4, buflen: Py_ssize_t, copy_null: c_int, ) -> *mut Py_UCS4; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_AsUCS4Copy")] pub fn PyUnicode_AsUCS4Copy(unicode: *mut PyObject) -> *mut Py_UCS4; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_GetLength")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_GetLength")] pub fn PyUnicode_GetLength(unicode: *mut PyObject) -> Py_ssize_t; #[cfg(not(Py_3_12))] #[deprecated(note = "Removed in Python 3.12")] #[cfg_attr(PyPy, link_name = "PyPyUnicode_GetSize")] pub fn PyUnicode_GetSize(unicode: *mut PyObject) -> Py_ssize_t; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_ReadChar")] pub fn PyUnicode_ReadChar(unicode: *mut PyObject, index: Py_ssize_t) -> Py_UCS4; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_WriteChar")] pub fn PyUnicode_WriteChar( unicode: *mut PyObject, index: Py_ssize_t, character: Py_UCS4, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_Resize")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_Resize")] pub fn PyUnicode_Resize(unicode: *mut *mut PyObject, length: Py_ssize_t) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromEncodedObject")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_FromEncodedObject")] pub fn PyUnicode_FromEncodedObject( obj: *mut PyObject, encoding: *const c_char, errors: *const c_char, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromObject")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_FromObject")] pub fn PyUnicode_FromObject(obj: *mut PyObject) -> *mut PyObject; // #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromFormatV")] // pub fn PyUnicode_FromFormatV(format: *const c_char, vargs: va_list) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromFormat")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_FromFormat")] pub fn PyUnicode_FromFormat(format: *const c_char, ...) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_InternInPlace")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_InternInPlace")] pub fn PyUnicode_InternInPlace(arg1: *mut *mut PyObject); #[cfg(not(Py_3_12))] #[cfg_attr(Py_3_10, deprecated(note = "Python 3.10"))] pub fn PyUnicode_InternImmortal(arg1: *mut *mut PyObject); - #[cfg_attr(PyPy, link_name = "PyPyUnicode_InternFromString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_InternFromString")] pub fn PyUnicode_InternFromString(u: *const c_char) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromWideChar")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_FromWideChar")] pub fn PyUnicode_FromWideChar(w: *const wchar_t, size: Py_ssize_t) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsWideChar")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_AsWideChar")] pub fn PyUnicode_AsWideChar( unicode: *mut PyObject, w: *mut wchar_t, size: Py_ssize_t, ) -> Py_ssize_t; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsWideCharString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_AsWideCharString")] pub fn PyUnicode_AsWideCharString( unicode: *mut PyObject, size: *mut Py_ssize_t, ) -> *mut wchar_t; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromOrdinal")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_FromOrdinal")] pub fn PyUnicode_FromOrdinal(ordinal: c_int) -> *mut PyObject; #[cfg(not(Py_3_9))] pub fn PyUnicode_ClearFreeList() -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_GetDefaultEncoding")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_GetDefaultEncoding")] pub fn PyUnicode_GetDefaultEncoding() -> *const c_char; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_Decode")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_Decode")] pub fn PyUnicode_Decode( s: *const c_char, size: Py_ssize_t, @@ -130,13 +136,13 @@ extern_libpython! { ) -> *mut PyObject; #[cfg(not(Py_3_15))] #[deprecated(note = "use PyCodec_Encode() instead")] - #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsEncodedObject")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_AsEncodedObject")] pub fn PyUnicode_AsEncodedObject( unicode: *mut PyObject, encoding: *const c_char, errors: *const c_char, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsEncodedString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_AsEncodedString")] pub fn PyUnicode_AsEncodedString( unicode: *mut PyObject, encoding: *const c_char, @@ -161,7 +167,7 @@ extern_libpython! { errors: *const c_char, consumed: *mut Py_ssize_t, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeUTF8")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_DecodeUTF8")] pub fn PyUnicode_DecodeUTF8( string: *const c_char, length: Py_ssize_t, @@ -173,12 +179,12 @@ extern_libpython! { errors: *const c_char, consumed: *mut Py_ssize_t, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF8String")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_AsUTF8String")] pub fn PyUnicode_AsUTF8String(unicode: *mut PyObject) -> *mut PyObject; #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] - #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF8AndSize")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_AsUTF8AndSize")] pub fn PyUnicode_AsUTF8AndSize(unicode: *mut PyObject, size: *mut Py_ssize_t) -> *const c_char; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeUTF32")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_DecodeUTF32")] pub fn PyUnicode_DecodeUTF32( string: *const c_char, length: Py_ssize_t, @@ -192,9 +198,9 @@ extern_libpython! { byteorder: *mut c_int, consumed: *mut Py_ssize_t, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF32String")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_AsUTF32String")] pub fn PyUnicode_AsUTF32String(unicode: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeUTF16")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_DecodeUTF16")] pub fn PyUnicode_DecodeUTF16( string: *const c_char, length: Py_ssize_t, @@ -208,36 +214,47 @@ extern_libpython! { byteorder: *mut c_int, consumed: *mut Py_ssize_t, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF16String")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_AsUTF16String")] pub fn PyUnicode_AsUTF16String(unicode: *mut PyObject) -> *mut PyObject; pub fn PyUnicode_DecodeUnicodeEscape( string: *const c_char, length: Py_ssize_t, errors: *const c_char, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUnicodeEscapeString")] + #[cfg_attr( + all(PyPy, not(Py_3_12)), + link_name = "PyPyUnicode_AsUnicodeEscapeString" + )] pub fn PyUnicode_AsUnicodeEscapeString(unicode: *mut PyObject) -> *mut PyObject; + #[cfg_attr( + all(PyPy, not(Py_3_12)), + link_name = "PyPyUnicode_DecodeRawUnicodeEscape" + )] pub fn PyUnicode_DecodeRawUnicodeEscape( string: *const c_char, length: Py_ssize_t, errors: *const c_char, ) -> *mut PyObject; + #[cfg_attr( + all(PyPy, not(Py_3_12)), + link_name = "PyPyUnicode_AsRawUnicodeEscapeString" + )] pub fn PyUnicode_AsRawUnicodeEscapeString(unicode: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeLatin1")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_DecodeLatin1")] pub fn PyUnicode_DecodeLatin1( string: *const c_char, length: Py_ssize_t, errors: *const c_char, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsLatin1String")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_AsLatin1String")] pub fn PyUnicode_AsLatin1String(unicode: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeASCII")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_DecodeASCII")] pub fn PyUnicode_DecodeASCII( string: *const c_char, length: Py_ssize_t, errors: *const c_char, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsASCIIString")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_AsASCIIString")] pub fn PyUnicode_AsASCIIString(unicode: *mut PyObject) -> *mut PyObject; pub fn PyUnicode_DecodeCharmap( string: *const c_char, @@ -254,34 +271,42 @@ extern_libpython! { // skipped PyUnicode_DecodeCodePageStateful // skipped PyUnicode_AsMBCSString // skipped PyUnicode_EncodeCodePage + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_DecodeLocaleAndSize")] pub fn PyUnicode_DecodeLocaleAndSize( str: *const c_char, len: Py_ssize_t, errors: *const c_char, ) -> *mut PyObject; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_DecodeLocale")] pub fn PyUnicode_DecodeLocale(str: *const c_char, errors: *const c_char) -> *mut PyObject; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_EncodeLocale")] pub fn PyUnicode_EncodeLocale(unicode: *mut PyObject, errors: *const c_char) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_FSConverter")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_FSConverter")] pub fn PyUnicode_FSConverter(arg1: *mut PyObject, arg2: *mut c_void) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_FSDecoder")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_FSDecoder")] pub fn PyUnicode_FSDecoder(arg1: *mut PyObject, arg2: *mut c_void) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeFSDefault")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_DecodeFSDefault")] pub fn PyUnicode_DecodeFSDefault(s: *const c_char) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeFSDefaultAndSize")] + #[cfg_attr( + all(PyPy, not(Py_3_12)), + link_name = "PyPyUnicode_DecodeFSDefaultAndSize" + )] pub fn PyUnicode_DecodeFSDefaultAndSize(s: *const c_char, size: Py_ssize_t) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_EncodeFSDefault")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_EncodeFSDefault")] pub fn PyUnicode_EncodeFSDefault(unicode: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_Concat")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_Concat")] pub fn PyUnicode_Concat(left: *mut PyObject, right: *mut PyObject) -> *mut PyObject; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_Append")] pub fn PyUnicode_Append(pleft: *mut *mut PyObject, right: *mut PyObject); + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_AppendAndDel")] pub fn PyUnicode_AppendAndDel(pleft: *mut *mut PyObject, right: *mut PyObject); - #[cfg_attr(PyPy, link_name = "PyPyUnicode_Split")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_Split")] pub fn PyUnicode_Split( s: *mut PyObject, sep: *mut PyObject, maxsplit: Py_ssize_t, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_Splitlines")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_Splitlines")] pub fn PyUnicode_Splitlines(s: *mut PyObject, keepends: c_int) -> *mut PyObject; pub fn PyUnicode_Partition(s: *mut PyObject, sep: *mut PyObject) -> *mut PyObject; pub fn PyUnicode_RPartition(s: *mut PyObject, sep: *mut PyObject) -> *mut PyObject; @@ -295,18 +320,18 @@ extern_libpython! { table: *mut PyObject, errors: *const c_char, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_Join")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_Join")] pub fn PyUnicode_Join(separator: *mut PyObject, seq: *mut PyObject) -> *mut PyObject; } -#[cfg(PyPy)] +#[cfg(all(PyPy, not(Py_3_12)))] type TailmatchResult = c_int; -#[cfg(not(PyPy))] +#[cfg(not(all(PyPy, not(Py_3_12))))] type TailmatchResult = Py_ssize_t; extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyUnicode_Tailmatch")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_Tailmatch")] pub fn PyUnicode_Tailmatch( str: *mut PyObject, substr: *mut PyObject, @@ -314,7 +339,7 @@ extern_libpython! { end: Py_ssize_t, direction: c_int, ) -> TailmatchResult; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_Find")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_Find")] pub fn PyUnicode_Find( str: *mut PyObject, substr: *mut PyObject, @@ -322,6 +347,7 @@ extern_libpython! { end: Py_ssize_t, direction: c_int, ) -> Py_ssize_t; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_FindChar")] pub fn PyUnicode_FindChar( str: *mut PyObject, ch: Py_UCS4, @@ -329,23 +355,26 @@ extern_libpython! { end: Py_ssize_t, direction: c_int, ) -> Py_ssize_t; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_Count")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_Count")] pub fn PyUnicode_Count( str: *mut PyObject, substr: *mut PyObject, start: Py_ssize_t, end: Py_ssize_t, ) -> Py_ssize_t; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_Replace")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_Replace")] pub fn PyUnicode_Replace( str: *mut PyObject, substr: *mut PyObject, replstr: *mut PyObject, maxcount: Py_ssize_t, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_Compare")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_Compare")] pub fn PyUnicode_Compare(left: *mut PyObject, right: *mut PyObject) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_CompareWithASCIIString")] + #[cfg_attr( + all(PyPy, not(Py_3_12)), + link_name = "PyPyUnicode_CompareWithASCIIString" + )] pub fn PyUnicode_CompareWithASCIIString(left: *mut PyObject, right: *const c_char) -> c_int; #[cfg(Py_3_13)] pub fn PyUnicode_EqualToUTF8(unicode: *mut PyObject, string: *const c_char) -> c_int; @@ -356,13 +385,16 @@ extern_libpython! { size: Py_ssize_t, ) -> c_int; // skipped PyUnicode_Equal + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_RichCompare")] pub fn PyUnicode_RichCompare( left: *mut PyObject, right: *mut PyObject, op: c_int, ) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyUnicode_Format")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_Format")] pub fn PyUnicode_Format(format: *mut PyObject, args: *mut PyObject) -> *mut PyObject; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_Contains")] pub fn PyUnicode_Contains(container: *mut PyObject, element: *mut PyObject) -> c_int; + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyUnicode_IsIdentifier")] pub fn PyUnicode_IsIdentifier(s: *mut PyObject) -> c_int; } diff --git a/pyo3-ffi/src/warnings.rs b/pyo3-ffi/src/warnings.rs index 4277c4daf84..47577d3e59a 100644 --- a/pyo3-ffi/src/warnings.rs +++ b/pyo3-ffi/src/warnings.rs @@ -3,13 +3,13 @@ use crate::pyport::Py_ssize_t; use core::ffi::{c_char, c_int}; extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyErr_WarnEx")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_WarnEx")] pub fn PyErr_WarnEx( category: *mut PyObject, message: *const c_char, stack_level: Py_ssize_t, ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyErr_WarnFormat")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_WarnFormat")] pub fn PyErr_WarnFormat( category: *mut PyObject, stack_level: Py_ssize_t, @@ -22,7 +22,7 @@ extern_libpython! { format: *const c_char, ... ) -> c_int; - #[cfg_attr(PyPy, link_name = "PyPyErr_WarnExplicit")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_WarnExplicit")] pub fn PyErr_WarnExplicit( category: *mut PyObject, message: *const c_char, diff --git a/pyo3-ffi/src/weakrefobject.rs b/pyo3-ffi/src/weakrefobject.rs index 84b94125ca7..a68b2d17cd8 100644 --- a/pyo3-ffi/src/weakrefobject.rs +++ b/pyo3-ffi/src/weakrefobject.rs @@ -17,34 +17,33 @@ extern_libpython! { #[cfg(not(RustPython))] static mut _PyWeakref_CallableProxyType: PyTypeObject; - #[cfg(any(PyPy, RustPython))] + #[cfg(any(all(PyPy, not(Py_3_12)), RustPython))] #[cfg_attr(PyPy, link_name = "PyPyWeakref_CheckRef")] pub fn PyWeakref_CheckRef(op: *mut PyObject) -> c_int; - #[cfg(any(PyPy, RustPython))] + #[cfg(any(all(PyPy, not(Py_3_12)), RustPython))] #[cfg_attr(PyPy, link_name = "PyPyWeakref_CheckRefExact")] pub fn PyWeakref_CheckRefExact(op: *mut PyObject) -> c_int; - #[cfg(any(PyPy, RustPython))] + #[cfg(any(all(PyPy, not(Py_3_12)), RustPython))] #[cfg_attr(PyPy, link_name = "PyPyWeakref_CheckProxy")] pub fn PyWeakref_CheckProxy(op: *mut PyObject) -> c_int; } #[inline] -#[cfg(not(any(PyPy, RustPython)))] -#[cfg(not(RustPython))] +#[cfg(not(any(all(PyPy, not(Py_3_12)), RustPython)))] pub unsafe fn PyWeakref_CheckRef(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, &raw mut _PyWeakref_RefType) } #[inline] -#[cfg(not(any(PyPy, RustPython)))] +#[cfg(not(any(all(PyPy, not(Py_3_12)), RustPython)))] pub unsafe fn PyWeakref_CheckRefExact(op: *mut PyObject) -> c_int { Py_IS_TYPE(op, &raw mut _PyWeakref_RefType) } #[inline] -#[cfg(not(any(PyPy, RustPython)))] +#[cfg(not(any(all(PyPy, not(Py_3_12)), RustPython)))] pub unsafe fn PyWeakref_CheckProxy(op: *mut PyObject) -> c_int { (Py_IS_TYPE(op, &raw mut _PyWeakref_ProxyType) > 0 || Py_IS_TYPE(op, &raw mut _PyWeakref_CallableProxyType) > 0) as c_int @@ -56,12 +55,12 @@ pub unsafe fn PyWeakref_Check(op: *mut PyObject) -> c_int { } extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyWeakref_NewRef")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyWeakref_NewRef")] pub fn PyWeakref_NewRef(ob: *mut PyObject, callback: *mut PyObject) -> *mut PyObject; - #[cfg_attr(PyPy, link_name = "PyPyWeakref_NewProxy")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyWeakref_NewProxy")] pub fn PyWeakref_NewProxy(ob: *mut PyObject, callback: *mut PyObject) -> *mut PyObject; #[cfg(not(Py_3_15))] - #[cfg_attr(PyPy, link_name = "PyPyWeakref_GetObject")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyWeakref_GetObject")] #[cfg_attr( Py_3_13, deprecated(note = "deprecated since Python 3.13. Use `PyWeakref_GetRef` instead.") diff --git a/pyo3-introspection/src/stubs.rs b/pyo3-introspection/src/stubs.rs index f89c250eef8..365b9bb3cf1 100644 --- a/pyo3-introspection/src/stubs.rs +++ b/pyo3-introspection/src/stubs.rs @@ -2,11 +2,12 @@ use crate::model::{ Argument, Arguments, Attribute, Class, Constant, Expr, Function, Module, Operator, VariableLengthArgument, }; +use std::ascii; use std::borrow::Cow; -use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::fmt::Write; use std::iter::once; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::str::FromStr; /// Generates the [type stubs](https://typing.readthedocs.io/en/latest/source/stubs.html) of a given module. @@ -15,33 +16,35 @@ use std::str::FromStr; /// in files with a relevant name. pub fn module_stub_files(module: &Module) -> HashMap { let mut output_files = HashMap::new(); - add_module_stub_files(module, &[], &mut output_files); + add_module_stub_files(module, Path::new(""), &[], &mut output_files); output_files } fn add_module_stub_files( module: &Module, - module_path: &[&str], + directory: &Path, + parents: &[&str], output_files: &mut HashMap, ) { - let mut file_path = PathBuf::new(); - for e in module_path { - file_path = file_path.join(e); - } output_files.insert( - file_path.join("__init__.pyi"), - module_stubs(module, module_path), + directory.join("__init__.pyi"), + module_stubs(module, parents), ); - let mut module_path = module_path.to_vec(); - module_path.push(&module.name); + let mut parents = parents.to_vec(); + parents.push(&module.name); for submodule in &module.modules { if submodule.modules.is_empty() { output_files.insert( - file_path.join(format!("{}.pyi", submodule.name)), - module_stubs(submodule, &module_path), + directory.join(format!("{}.pyi", submodule.name)), + module_stubs(submodule, &parents), ); } else { - add_module_stub_files(submodule, &module_path, output_files); + add_module_stub_files( + submodule, + &directory.join(&submodule.name), + &parents, + output_files, + ); } } } @@ -94,7 +97,9 @@ fn module_stubs(module: &Module, parents: &[&str]) -> String { let mut final_elements = Vec::new(); if let Some(docstring) = &module.docstring { - final_elements.push(format!("\"\"\"\n{docstring}\n\"\"\"")); + let mut buffer = String::new(); + push_docstring(&mut buffer, "", docstring); + final_elements.push(buffer); } final_elements.extend(imports.imports); final_elements.extend(elements); @@ -259,14 +264,28 @@ fn push_indented(buffer: &mut String, indent: &str, text: &str) { /// Appends a `"""`-quoted docstring indented by `indent`, starting on a fresh line. fn push_docstring(buffer: &mut String, indent: &str, docstring: &str) { - buffer.push('\n'); + if !buffer.is_empty() { + buffer.push('\n'); + } buffer.push_str(indent); buffer.push_str("\"\"\""); for line in docstring.lines() { buffer.push('\n'); if !line.is_empty() { buffer.push_str(indent); - buffer.push_str(line); + let mut quotes = 0; + for c in line.chars() { + quotes = if c == '"' { quotes + 1 } else { 0 }; + if quotes == 3 { + buffer.push('\\'); + quotes = 0; + } + if c.is_ascii_control() || c == '\\' { + buffer.extend(ascii::escape_default(c as u8).map(char::from)); + } else { + buffer.push(c); + } + } } } buffer.push('\n'); @@ -274,6 +293,21 @@ fn push_docstring(buffer: &mut String, indent: &str, docstring: &str) { buffer.push_str("\"\"\""); } +/// Collects the operands of a `|` chain in source order, skipping repeats. +fn flatten_union<'a>(expr: &'a Expr, operands: &mut Vec<&'a Expr>, seen: &mut HashSet<&'a Expr>) { + if let Expr::BinOp { + left, + op: Operator::BitOr, + right, + } = expr + { + flatten_union(left, operands, seen); + flatten_union(right, operands, seen); + } else if seen.insert(expr) { + operands.push(expr); + } +} + fn attribute_stubs(attribute: &Attribute, imports: &Imports) -> String { let mut buffer = attribute.name.clone(); if let Some(annotation) = &attribute.annotation { @@ -482,13 +516,20 @@ impl Imports { buffer.push_str(attr); } } - Expr::BinOp { left, op, right } => { - self.serialize_expr(left, buffer); - buffer.push(' '); - buffer.push(match op { - Operator::BitOr => '|', - }); - self.serialize_expr(right, buffer); + Expr::BinOp { + op: Operator::BitOr, + .. + } => { + // Union deduplication needs to happen here because the macro + // generation only sees unresolved associated constants. + let mut operands = Vec::new(); + flatten_union(expr, &mut operands, &mut HashSet::new()); + for (index, operand) in operands.into_iter().enumerate() { + if index > 0 { + buffer.push_str(" | "); + } + self.serialize_expr(operand, buffer); + } } Expr::Tuple { elts } => { buffer.push('('); @@ -507,8 +548,12 @@ impl Imports { self.serialize_expr(value, buffer); buffer.push('['); if let Expr::Tuple { elts } = &**slice { - // We don't display the tuple parentheses - self.serialize_elts(elts, buffer); + if elts.is_empty() { + // Empty tuples need parentheses to avoid invalid syntax like `tuple[]` + buffer.push_str("()"); + } else { + self.serialize_elts(elts, buffer); + } } else { self.serialize_expr(slice, buffer); } @@ -972,7 +1017,7 @@ mod tests { /// is an empty line. Padding it out to the body indentation is trailing whitespace, which /// `W293` flags and which nobody can fix by hand in a generated file. #[test] - fn docstring_blank_lines_are_not_padded_with_indentation() { + fn docstrings_are_escaped_and_blank_lines_are_not_padded() { let module = Module { name: "bar".into(), modules: Vec::new(), @@ -991,22 +1036,30 @@ mod tests { }, returns: None, is_async: false, - docstring: Some("Summary.\n\nDetail.".into()), + docstring: Some("Summary.\n\nC:\\Users\\someone\\".into()), }], attributes: Vec::new(), decorators: Vec::new(), inner_classes: Vec::new(), - docstring: Some("Class summary.\n\nClass detail.".into()), + docstring: Some( + concat!( + "Class summary.\n\n", + r#"Quotes: "a" "" """ """" """"" """""" """""""."#, + "\n", + r#"Edges: \"""\ """"#, + ) + .into(), + ), }], functions: Vec::new(), attributes: vec![Attribute { name: "CONST".into(), value: None, annotation: None, - docstring: Some("Const summary.\n\nConst detail.".into()), + docstring: Some("Const summary.\n\nControls: \x0007\t\r. Unicode: café 🦀.".into()), }], incomplete: false, - docstring: None, + docstring: Some("\"\"\" C:\\Users\\someone".into()), }; let stubs = module_stubs(&module, &["foo"]); @@ -1016,9 +1069,116 @@ mod tests { .any(|line| !line.is_empty() && line.trim().is_empty()), "generated stubs contain a blank line padded with whitespace:\n{stubs:?}" ); - // The indentation of the non-empty lines is unaffected. - assert!(stubs.contains("\n Class summary.\n\n Class detail.\n")); - assert!(stubs.contains("\n Summary.\n\n Detail.\n")); - assert!(stubs.contains("\nConst summary.\n\nConst detail.\n")); + // Escaping preserves the indentation and paragraph breaks in every scope. + assert!(stubs.starts_with("\"\"\"\n\"\"\\\" C:\\\\Users\\\\someone\n\"\"\"\n")); + assert!(stubs.contains(concat!( + "\n Class summary.\n\n", + r#" Quotes: "a" "" ""\" ""\"" ""\""" ""\"""\" ""\"""\""."#, + "\n", + r#" Edges: \\""\"\\ ""\""#, + "\n", + ))); + assert!(stubs.contains("\n Summary.\n\n C:\\\\Users\\\\someone\\\\\n")); + assert!(stubs.contains("\nConst summary.\n\nControls: \\x0007\\t\\r. Unicode: café 🦀.\n")); + } + + #[test] + fn union_members_are_deduplicated_and_spaced() { + let str_ = || Expr::Name { id: "str".into() }; + let path_like = || Expr::Subscript { + value: Box::new(Expr::Attribute { + value: Box::new(Expr::Name { id: "os".into() }), + attr: "PathLike".into(), + }), + slice: Box::new(str_()), + }; + let union = |left: Expr, right: Expr| Expr::BinOp { + left: Box::new(left), + op: Operator::BitOr, + right: Box::new(right), + }; + let imports = Imports { + imports: Vec::new(), + renaming: BTreeMap::from([ + (("builtins".into(), "str".into()), "str".into()), + (("os".into(), "PathLike".into()), "PathLike".into()), + ]), + }; + let serialize = |expr| { + let mut buffer = String::new(); + imports.serialize_expr(&expr, &mut buffer); + buffer + }; + + // `str | os.PathLike[str] | str`, nested to the right + assert_eq!( + serialize(union(str_(), union(path_like(), str_()))), + "str | PathLike[str]" + ); + // and the same chain nested to the left + assert_eq!( + serialize(union(union(str_(), path_like()), str_())), + "str | PathLike[str]" + ); + } + + #[test] + fn nested_packages_are_written_into_their_own_directory() { + let attribute = |name: &str| Attribute { + name: name.into(), + value: None, + annotation: Some(Expr::Attribute { + value: Box::new(Expr::Name { id: "top".into() }), + attr: "Top".into(), + }), + docstring: None, + }; + let module = |name: &str, modules: Vec, attributes: Vec| Module { + name: name.into(), + modules, + classes: Vec::new(), + functions: Vec::new(), + attributes, + incomplete: false, + docstring: None, + }; + let mut top = module( + "top", + vec![ + module( + "child", + vec![module("grandchild", Vec::new(), vec![attribute("deep")])], + vec![attribute("mid")], + ), + module("sibling", Vec::new(), Vec::new()), + ], + Vec::new(), + ); + top.classes.push(Class { + name: "Top".into(), + bases: Vec::new(), + methods: Vec::new(), + attributes: Vec::new(), + decorators: Vec::new(), + inner_classes: Vec::new(), + docstring: None, + }); + + let files = module_stub_files(&top); + let mut paths = files.keys().cloned().collect::>(); + paths.sort(); + assert_eq!( + paths, + [ + PathBuf::from("__init__.pyi"), + PathBuf::from("child/__init__.pyi"), + PathBuf::from("child/grandchild.pyi"), + PathBuf::from("sibling.pyi"), + ] + ); + // The parents passed to `module_stubs` must stay the module names, not the directories. + assert!(files[Path::new("child/__init__.pyi")].contains("from .. import Top")); + assert!(files[Path::new("child/grandchild.pyi")].contains("from .. import Top")); + assert!(files[Path::new("sibling.pyi")].is_empty()); } } diff --git a/pyo3-macros-backend/src/py_expr.rs b/pyo3-macros-backend/src/py_expr.rs index 893addcc8b2..584faca2d5c 100644 --- a/pyo3-macros-backend/src/py_expr.rs +++ b/pyo3-macros-backend/src/py_expr.rs @@ -1,6 +1,6 @@ //! Define a data structure for Python type hints, mixing static data from macros and call to Pyo3 constants. -use crate::utils::PyO3CratePath; +use crate::utils::{PyO3CratePath, StaticIdent}; use proc_macro2::TokenStream; use quote::quote; use std::borrow::Cow; @@ -22,6 +22,10 @@ pub enum PyExpr { ArgumentType(Type), /// The Python type matching the given Rust type given as a function returned value ReturnType(Type), + /// The Python type `__next__` yields, without the `Option` meaning `StopIteration` + IterNextReturnType(Type), + /// The Python type `__anext__` yields, without the `Option` meaning `StopAsyncIteration` + AsyncIterNextReturnType(Type), /// The Python type matching the given Rust type Type(Type), /// A name @@ -116,6 +120,20 @@ impl PyExpr { Self::ReturnType(clean_type(t, self_type)) } + /// The type hint of the Rust type used as the output type of `__next__` + /// + /// If self_type is set, self_type will replace Self in the given type + pub fn from_iter_next_return_type(t: Type, self_type: Option<&Type>) -> Self { + Self::IterNextReturnType(clean_type(t, self_type)) + } + + /// The type hint of the Rust type used as the output type of `__anext__` + /// + /// If self_type is set, self_type will replace Self in the given type + pub fn from_async_iter_next_return_type(t: Type, self_type: Option<&Type>) -> Self { + Self::AsyncIterNextReturnType(clean_type(t, self_type)) + } + /// The type hint of the Rust type `PyTypeCheck` trait. /// /// If self_type is set, self_type will replace Self in the given type @@ -228,6 +246,18 @@ impl PyExpr { TYPE }} } + Self::IterNextReturnType(t) => iter_next_output_type( + pyo3_crate_path, + t, + ITER_NEXT_OUTPUT, + ITER_NEXT_TYPE_FALLBACK, + ), + Self::AsyncIterNextReturnType(t) => iter_next_output_type( + pyo3_crate_path, + t, + ASYNC_ITER_NEXT_OUTPUT, + ASYNC_ITER_NEXT_TYPE_FALLBACK, + ), Self::Type(t) => { quote! { <#t as #pyo3_crate_path::type_object::PyTypeCheck>::TYPE_HINT } } @@ -287,6 +317,30 @@ impl PyExpr { } } +const ITER_NEXT_OUTPUT: StaticIdent = StaticIdent::new("IterNextOutput"); +const ITER_NEXT_TYPE_FALLBACK: StaticIdent = StaticIdent::new("IterNextTypeFallback"); +const ASYNC_ITER_NEXT_OUTPUT: StaticIdent = StaticIdent::new("AsyncIterNextOutput"); +const ASYNC_ITER_NEXT_TYPE_FALLBACK: StaticIdent = StaticIdent::new("AsyncIterNextTypeFallback"); + +/// The type hint of what `__next__` / `__anext__` yields, read off the same wrapper the slot uses +/// to convert the returned value so that the stub and the runtime agree on which return types say +/// "iteration is over" with `None`. +fn iter_next_output_type( + pyo3_crate_path: &PyO3CratePath, + t: &Type, + wrapper: StaticIdent, + fallback: StaticIdent, +) -> TokenStream { + quote! {{ + #[allow( + unused_imports, + reason = "the fallback trait is unused when the inherent const applies" + )] + use #pyo3_crate_path::impl_::pymethods::#fallback as _; + #pyo3_crate_path::impl_::pymethods::#wrapper::<#t>::OUTPUT_TYPE + }} +} + fn clean_type(mut t: Type, self_type: Option<&Type>) -> Type { if let Some(self_type) = self_type { replace_self(&mut t, self_type); diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index a9c55ca13c0..2e5d6288571 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -2995,7 +2995,7 @@ impl<'a> PyClassImplsBuilder<'a> { #input_type fn extract(obj: #pyo3_path::Borrowed<'a, 'py, #pyo3_path::PyAny>) -> ::std::result::Result>::Error> { - ::std::result::Result::Ok(::std::clone::Clone::clone(&*obj.extract::<#pyo3_path::PyClassGuard<'_, #cls>>()?)) + #pyo3_path::impl_::pyclass::extract_pyclass_with_clone(obj) } } } diff --git a/pyo3-macros-backend/src/pyfunction/signature.rs b/pyo3-macros-backend/src/pyfunction/signature.rs index f8873eebffd..4243f60f658 100644 --- a/pyo3-macros-backend/src/pyfunction/signature.rs +++ b/pyo3-macros-backend/src/pyfunction/signature.rs @@ -10,6 +10,7 @@ use quote::ToTokens; use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, + parse_quote, punctuated::Punctuated, spanned::Spanned, Expr, Token, @@ -585,6 +586,26 @@ impl<'a> FunctionSignature<'a> { } } + /// Gives the last `count` positional parameters a `None` default, matching a CPython slot + /// wrapper which substitutes `None` for the trailing arguments the caller may omit. + pub fn default_trailing_parameters_to_none(&mut self, count: usize) { + let mut defaulted = 0; + for arg in self.arguments.iter_mut().rev() { + if defaulted == count { + break; + } + if let FnArg::Regular(arg) = arg { + arg.default_value = Some(Box::new(parse_quote!(None))); + defaulted += 1; + } + } + for _ in 0..defaulted { + self.python_signature + .default_positional_parameters + .push(parse_quote!(None)); + } + } + pub fn text_signature(&self, self_argument: Option<&str>) -> String { let mut output = String::new(); output.push('('); diff --git a/pyo3-macros-backend/src/pyimpl.rs b/pyo3-macros-backend/src/pyimpl.rs index 471d1bb18e7..123e092167c 100644 --- a/pyo3-macros-backend/src/pyimpl.rs +++ b/pyo3-macros-backend/src/pyimpl.rs @@ -496,6 +496,14 @@ pub fn method_introspection_code( PyExpr::from_return_type(parse_quote!(#pyo3_path::PyClassGuard), Some(parent)) } else { match spec.output.clone() { + // `__next__` and `__anext__` may say "iteration is over" with `None`, in which case + // that `Option` is not part of the Python-visible return type. + ReturnType::Type(_, t) if name.as_str() == "__next__" => { + PyExpr::from_iter_next_return_type(*t, Some(parent)) + } + ReturnType::Type(_, t) if name.as_str() == "__anext__" => { + PyExpr::from_async_iter_next_return_type(*t, Some(parent)) + } ReturnType::Type(_, t) => PyExpr::from_return_type(*t, Some(parent)), ReturnType::Default => PyExpr::none(), } diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index 885e8f73640..5587994f6a5 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -211,6 +211,14 @@ impl PyMethodProtoKind { | PyMethodProtoKind::Clear => false, } } + + fn optional_trailing_args(&self) -> usize { + match self { + PyMethodProtoKind::Slot(slot) => slot.optional_trailing_args(), + PyMethodProtoKind::SlotFragment(fragment) => fragment.optional_trailing_args(), + PyMethodProtoKind::Call | PyMethodProtoKind::Traverse | PyMethodProtoKind::Clear => 0, + } + } } impl<'a> PyMethod<'a> { @@ -234,6 +242,8 @@ impl<'a> PyMethod<'a> { spec.signature .python_signature .make_all_parameters_positional_only(); + spec.signature + .default_trailing_parameters_to_none(proto.optional_trailing_args()); } } @@ -1094,20 +1104,19 @@ pub const __HASH__: SlotDef = )); pub const __RICHCMP__: SlotDef = SlotDef::new("Py_tp_richcompare", "richcmpfunc") .extract_error_mode(ExtractErrorMode::NotImplemented); -const __GET__: SlotDef = SlotDef::new("Py_tp_descr_get", "descrgetfunc"); +const __GET__: SlotDef = SlotDef::new("Py_tp_descr_get", "descrgetfunc") + // `__get__($self, instance, owner=None, /)` + .with_optional_trailing_args(1); const __ITER__: SlotDef = SlotDef::new("Py_tp_iter", "getiterfunc"); -const __NEXT__: SlotDef = SlotDef::new("Py_tp_iternext", "iternextfunc") - .return_specialized_conversion( - TokenGenerator(|_| quote! { IterBaseKind, IterOptionKind, IterResultOptionKind }), - TokenGenerator(|_| quote! { iter_tag }), - ); +const __NEXT__: SlotDef = SlotDef::new("Py_tp_iternext", "iternextfunc").return_iter_conversion( + StaticIdent::new("IterNextOutput"), + StaticIdent::new("IterNextConvertFallback"), +); const __AWAIT__: SlotDef = SlotDef::new("Py_am_await", "unaryfunc"); const __AITER__: SlotDef = SlotDef::new("Py_am_aiter", "unaryfunc"); -const __ANEXT__: SlotDef = SlotDef::new("Py_am_anext", "unaryfunc").return_specialized_conversion( - TokenGenerator( - |_| quote! { AsyncIterBaseKind, AsyncIterOptionKind, AsyncIterResultOptionKind }, - ), - TokenGenerator(|_| quote! { async_iter_tag }), +const __ANEXT__: SlotDef = SlotDef::new("Py_am_anext", "unaryfunc").return_iter_conversion( + StaticIdent::new("AsyncIterNextOutput"), + StaticIdent::new("AsyncIterNextConvertFallback"), ); pub const __LEN__: SlotDef = SlotDef::new("Py_mp_length", "lenfunc"); const __CONTAINS__: SlotDef = SlotDef::new("Py_sq_contains", "objobjproc"); @@ -1233,6 +1242,7 @@ impl Ty { let ty = arg.ty(); extract_error_mode.handle_error( quote! { + #[allow(unreachable_code, reason = "error type might be !")] ::std::convert::TryInto::<#ty>::try_into(#ident).map_err(|e| #pyo3_path::exceptions::PyValueError::new_err(e.to_string())) }, ctx @@ -1299,7 +1309,10 @@ fn extract_object( enum ReturnMode { ReturnSelf, Conversion(TokenGenerator), - SpecializedConversion(TokenGenerator, TokenGenerator), + /// `__next__` / `__anext__`: the return value goes through the wrapper named first, whose + /// inherent `convert` handles the return types saying "iteration is over" with `None`, and + /// whose fallback trait, named second, handles all the others. + IterConversion(StaticIdent, StaticIdent), } impl ReturnMode { @@ -1313,13 +1326,15 @@ impl ReturnMode { #pyo3_path::impl_::callback::convert(py, _result) } } - ReturnMode::SpecializedConversion(traits, tag) => { - let traits = TokenGeneratorCtx(*traits, ctx); - let tag = TokenGeneratorCtx(*tag, ctx); + ReturnMode::IterConversion(wrapper, fallback) => { quote! { let _result = #call; - use #pyo3_path::impl_::pymethods::{#traits}; - (&_result).#tag().convert(py, _result) + #[allow( + unused_imports, + reason = "the fallback trait is unused when the inherent `convert` applies" + )] + use #pyo3_path::impl_::pymethods::#fallback as _; + #pyo3_path::impl_::pymethods::#wrapper(_result).convert(py) } } ReturnMode::ReturnSelf => quote! { @@ -1340,6 +1355,7 @@ pub struct SlotDef { extract_error_mode: ExtractErrorMode, return_mode: Option, require_unsafe: bool, + optional_trailing_args: usize, } enum SlotCallingConvention { @@ -1364,6 +1380,17 @@ impl SlotDef { ) } + /// How many trailing arguments CPython's slot wrapper lets the caller omit, each of which + /// reaches the slot as `None`. + pub const fn optional_trailing_args(&self) -> usize { + self.optional_trailing_args + } + + const fn with_optional_trailing_args(mut self, count: usize) -> Self { + self.optional_trailing_args = count; + self + } + const fn new(slot: &'static str, func_ty: &'static str) -> Self { // The FFI function pointer type determines the arguments and return type let (calling_convention, ret_ty) = match func_ty.as_bytes() { @@ -1419,6 +1446,7 @@ impl SlotDef { extract_error_mode: ExtractErrorMode::Raise, return_mode: None, require_unsafe: false, + optional_trailing_args: 0, } } @@ -1434,12 +1462,8 @@ impl SlotDef { self } - const fn return_specialized_conversion( - mut self, - traits: TokenGenerator, - tag: TokenGenerator, - ) -> Self { - self.return_mode = Some(ReturnMode::SpecializedConversion(traits, tag)); + const fn return_iter_conversion(mut self, wrapper: StaticIdent, fallback: StaticIdent) -> Self { + self.return_mode = Some(ReturnMode::IterConversion(wrapper, fallback)); self } @@ -1474,6 +1498,8 @@ impl SlotDef { ret_ty, return_mode, require_unsafe, + // introspection only, not part of codegen + optional_trailing_args: _, } = self; if *require_unsafe { ensure_spanned!( @@ -1692,6 +1718,7 @@ struct SlotFragmentDef { /// Those fragments must use `Checked` so that a type mismatch returns /// `NotImplemented` instead of causing undefined behaviour. self_conversion: SelfConversionPolicy, + optional_trailing_args: usize, } impl SlotFragmentDef { @@ -1702,6 +1729,7 @@ impl SlotFragmentDef { extract_error_mode: ExtractErrorMode::Raise, ret_ty: Ty::Void, self_conversion: SelfConversionPolicy::checked(), + optional_trailing_args: 0, } } @@ -1721,6 +1749,7 @@ impl SlotFragmentDef { extract_error_mode: ExtractErrorMode::NotImplemented, ret_ty: Ty::Object, self_conversion: SelfConversionPolicy::checked(), + optional_trailing_args: 0, } } @@ -1739,6 +1768,16 @@ impl SlotFragmentDef { self } + /// See [`SlotDef::optional_trailing_args`]. + const fn optional_trailing_args(&self) -> usize { + self.optional_trailing_args + } + + const fn with_optional_trailing_args(mut self, count: usize) -> Self { + self.optional_trailing_args = count; + self + } + fn generate_pyproto_fragment( &self, cls: &syn::Type, @@ -1752,6 +1791,8 @@ impl SlotFragmentDef { extract_error_mode, ret_ty, self_conversion, + // introspection only, not part of codegen + optional_trailing_args: _, } = self; let fragment_trait = format_ident!("PyClass{}SlotFragment", fragment); let method = syn::Ident::new(fragment, Span::call_site()); @@ -1866,10 +1907,14 @@ const __ROR__: SlotFragmentDef = SlotFragmentDef::binary_operator("__ror__"); const __POW__: SlotFragmentDef = SlotFragmentDef::new("__pow__", &[Ty::Object, Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) - .ret_ty(Ty::Object); + .ret_ty(Ty::Object) + // `__pow__($self, value, mod=None, /)` + .with_optional_trailing_args(1); const __RPOW__: SlotFragmentDef = SlotFragmentDef::new("__rpow__", &[Ty::Object, Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) - .ret_ty(Ty::Object); + .ret_ty(Ty::Object) + // `__rpow__($self, value, mod=None, /)` + .with_optional_trailing_args(1); const __LT__: SlotFragmentDef = SlotFragmentDef::new("__lt__", &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) @@ -1969,3 +2014,18 @@ fn doc_to_optional_cstr(doc: Option<&PythonDoc>, ctx: &Ctx) -> Result=1.16") - # https://github.com/zopefoundation/zope.interface/issues/316 - # - is a dependency of gevent - try_install_binary("zope.interface", "<7") try_install_binary("gevent", ">=22.10.2") + # hypothesis itself depends on PyO3 so newer Python versions may fail + # to build + try_install_binary("hypothesis", ">=6.156.1") ignored_paths = [] if sys.version_info < (3, 10): # Match syntax is only available in Python >= 3.10 diff --git a/pytests/pyproject.toml b/pytests/pyproject.toml index f36f6d94376..66f8349d65a 100644 --- a/pytests/pyproject.toml +++ b/pytests/pyproject.toml @@ -20,12 +20,12 @@ classifiers = [ [project.optional-dependencies] dev = [ - "hypothesis>=3.55", # mypy doesn't build on GraalPy when installed via uv "mypy~=1.0; platform_python_implementation != 'GraalVM'", "pyrefly~=0.57.0", "pytest-asyncio>=0.21,<2", "pytest-benchmark>=3.4", "pytest>=7", - "typing_extensions>=4.0.0" + # 4.2 for `assert_type` + "typing_extensions>=4.2.0" ] diff --git a/pytests/src/awaitable.rs b/pytests/src/awaitable.rs index e13a569c3c6..e3ea38bd730 100644 --- a/pytests/src/awaitable.rs +++ b/pytests/src/awaitable.rs @@ -21,7 +21,7 @@ pub mod awaitable { #[pymethods] impl IterAwaitable { #[new] - fn new(result: Py) -> Self { + pub(crate) fn new(result: Py) -> Self { IterAwaitable { result: Some(Ok(result)), } diff --git a/pytests/src/buf_and_str.rs b/pytests/src/buf_and_str.rs index caed774c975..3b621dce2e5 100644 --- a/pytests/src/buf_and_str.rs +++ b/pytests/src/buf_and_str.rs @@ -40,9 +40,8 @@ pub mod buf_and_str { } #[staticmethod] - pub fn from_buffer(buf: &Bound<'_, PyAny>) -> PyResult { - let buf = PyBuffer::::get(buf)?; - Ok(buf.item_count()) + pub fn from_buffer(buf: PyBuffer) -> usize { + buf.item_count() } } diff --git a/pytests/src/lib.rs b/pytests/src/lib.rs index 5771f79f1ab..75229dace64 100644 --- a/pytests/src/lib.rs +++ b/pytests/src/lib.rs @@ -52,13 +52,18 @@ mod pyo3_pytests { fn init(m: &Bound<'_, PyModule>) -> PyResult<()> { let sys = PyModule::import(m.py(), "sys")?; let sys_modules = sys.getattr("modules")?.cast_into::()?; + #[cfg(feature = "experimental-inspect")] + sys_modules.set_item("pyo3_pytests.annotations", m.getattr("annotations")?)?; sys_modules.set_item("pyo3_pytests.awaitable", m.getattr("awaitable")?)?; + #[cfg(any(not(Py_LIMITED_API), Py_3_11))] sys_modules.set_item("pyo3_pytests.buf_and_str", m.getattr("buf_and_str")?)?; sys_modules.set_item("pyo3_pytests.comparisons", m.getattr("comparisons")?)?; + sys_modules.set_item("pyo3_pytests.consts", m.getattr("consts")?)?; #[cfg(not(Py_LIMITED_API))] sys_modules.set_item("pyo3_pytests.datetime", m.getattr("datetime")?)?; sys_modules.set_item("pyo3_pytests.dict_iter", m.getattr("dict_iter")?)?; sys_modules.set_item("pyo3_pytests.enums", m.getattr("enums")?)?; + sys_modules.set_item("pyo3_pytests.exception", m.getattr("exception")?)?; sys_modules.set_item("pyo3_pytests.misc", m.getattr("misc")?)?; sys_modules.set_item("pyo3_pytests.objstore", m.getattr("objstore")?)?; sys_modules.set_item("pyo3_pytests.othermod", m.getattr("othermod")?)?; diff --git a/pytests/src/misc.rs b/pytests/src/misc.rs index 68b4bd08da1..1fa19ab65d2 100644 --- a/pytests/src/misc.rs +++ b/pytests/src/misc.rs @@ -1,3 +1,5 @@ +use std::cell::Cell; + use pyo3::{ prelude::*, types::{PyDict, PyString}, @@ -31,32 +33,35 @@ fn hammer_attaching_in_thread() -> LockHolder { LockHolder { sender } } -/// Wrapper to mark Receiver as Sync. -struct SyncReceiver(std::sync::mpsc::Receiver); - -impl std::ops::Deref for SyncReceiver { - type Target = std::sync::mpsc::Receiver; +#[pyclass] +struct MustDropWhileAttached; - fn deref(&self) -> &Self::Target { - &self.0 +impl Drop for MustDropWhileAttached { + fn drop(&mut self) { + // SAFETY: always callable; fatal error (abort) if the thread is not attached. + unsafe { pyo3::ffi::PyThreadState_Get() }; } } -// SAFETY: only used to allow the receiver to be used after detaching -unsafe impl Sync for SyncReceiver {} +thread_local! { + // Dropped when the thread exits, which on older CPython happens inside + // PyEval_RestoreThread when reattaching during finalization. + static DROPPED_ON_THREAD_EXIT: Cell>> = const { Cell::new(None) }; +} #[pyfunction] -fn detach_during_finalization() -> LockHolder { +fn detach_during_finalization(py: Python<'_>) -> LockHolder { let (sender, receiver) = std::sync::mpsc::channel(); - let receiver = SyncReceiver(receiver); + let (ready_sender, ready_receiver) = std::sync::mpsc::channel(); std::thread::spawn(move || { Python::attach(|py| { - py.detach(|| { - receiver.recv().ok(); - // Interpreter is finalizing while we try to reattach after returning - }); + DROPPED_ON_THREAD_EXIT.set(Some(Py::new(py, MustDropWhileAttached).unwrap())); + ready_sender.send(()).unwrap(); + py.detach(move || receiver.recv().ok()); + // Interpreter is finalizing while we try to reattach after returning }); }); + py.detach(move || ready_receiver.recv()).unwrap(); LockHolder { sender } } diff --git a/pytests/src/path.rs b/pytests/src/path.rs index 11e64a628e7..b8024e0ac95 100644 --- a/pytests/src/path.rs +++ b/pytests/src/path.rs @@ -14,4 +14,20 @@ pub mod path { fn take_pathbuf(path: PathBuf) -> PathBuf { path } + + /// The two variants overlap: `String` accepts `str` and `PathBuf` accepts + /// `str | os.PathLike[str]`, so the union the derive builds repeats `str`. + #[derive(FromPyObject)] + enum NameOrPath { + Name(String), + Path(PathBuf), + } + + #[pyfunction] + fn take_name_or_path(value: NameOrPath) -> PathBuf { + match value { + NameOrPath::Name(name) => PathBuf::from(name), + NameOrPath::Path(path) => path, + } + } } diff --git a/pytests/src/pyclasses.rs b/pytests/src/pyclasses.rs index a1caa584d46..7a07d550c00 100644 --- a/pytests/src/pyclasses.rs +++ b/pytests/src/pyclasses.rs @@ -7,6 +7,8 @@ use pyo3::types::{PyComplex, PyType}; #[cfg(not(any(Py_LIMITED_API, GraalPy)))] use pyo3::types::{PyDict, PyTuple}; +use crate::awaitable::awaitable::IterAwaitable; + #[pyclass(from_py_object)] #[derive(Clone, Default)] pub struct EmptyClass {} @@ -50,6 +52,92 @@ impl PyClassIter { } } +/// This is for demonstrating how to stop iteration by returning `None` from __next__ +#[pyclass] +#[derive(Default)] +struct PyClassOptionIter { + count: usize, +} + +#[pymethods] +impl PyClassOptionIter { + #[new] + pub fn new() -> Self { + Default::default() + } + + fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf + } + + fn __next__(&mut self) -> Option { + if self.count < 5 { + self.count += 1; + Some(self.count) + } else { + None + } + } +} + +/// This is for demonstrating how to stop iteration by returning `None` from a fallible __next__ +#[pyclass] +#[derive(Default)] +struct PyClassResultOptionIter { + count: usize, +} + +#[pymethods] +impl PyClassResultOptionIter { + #[new] + pub fn new() -> Self { + Default::default() + } + + fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf + } + + #[expect(clippy::unnecessary_wraps, reason = "covering the fallible signature")] + fn __next__(&mut self) -> PyResult> { + if self.count < 5 { + self.count += 1; + Ok(Some(self.count)) + } else { + Ok(None) + } + } +} + +/// This is for demonstrating how to stop iteration by returning `None` from __anext__ +#[pyclass] +#[derive(Default)] +struct PyClassOptionAsyncIter { + count: usize, +} + +#[pymethods] +impl PyClassOptionAsyncIter { + #[new] + pub fn new() -> Self { + Default::default() + } + + fn __aiter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf + } + + fn __anext__(&mut self, py: Python<'_>) -> PyResult> { + if self.count >= 5 { + return Ok(None); + } + self.count += 1; + // `__anext__` hands back an awaitable, which `async for` awaits for the next value. + let value = self.count.into_pyobject(py)?.into_any().unbind(); + Ok(Some(IterAwaitable::new(value))) + } +} + #[pyclass] #[derive(Default)] struct PyClassThreadIter { @@ -341,6 +429,7 @@ pub mod pyclasses { #[pymodule_export] use super::{ map_a_class, AssertingBaseClass, ClassWithDecorators, ClassWithoutConstructor, EmptyClass, - Number, PlainObject, PyClassIter, PyClassThreadIter, + Number, PlainObject, PyClassIter, PyClassOptionAsyncIter, PyClassOptionIter, + PyClassResultOptionIter, PyClassThreadIter, }; } diff --git a/pytests/src/pyfunctions.rs b/pytests/src/pyfunctions.rs index e0c8d882514..9ce91aa08f7 100644 --- a/pytests/src/pyfunctions.rs +++ b/pytests/src/pyfunctions.rs @@ -4,6 +4,11 @@ use pyo3::types::{PyDict, PyTuple}; #[pyfunction(signature = ())] fn none() {} +#[pyfunction] +fn nested_empty_tuples() -> ((), ((),)) { + ((), ((),)) +} + // Exposed under a different name than the Rust one, which the generated stubs have to use. #[pyfunction(name = "renamed")] fn rust_name_of_renamed() -> usize { @@ -147,8 +152,9 @@ pub mod pyfunctions { use super::with_async; #[pymodule_export] use super::{ - args_kwargs, many_keyword_arguments, none, positional_only, rust_name_of_renamed, simple, - simple_args, simple_args_kwargs, simple_kwargs, with_typed_args, + args_kwargs, many_keyword_arguments, nested_empty_tuples, none, positional_only, + rust_name_of_renamed, simple, simple_args, simple_args_kwargs, simple_kwargs, + with_typed_args, }; // Likewise for a `cfg`-ed out last member. diff --git a/pytests/stubs/buf_and_str.pyi b/pytests/stubs/buf_and_str.pyi index a6b64db3862..85bf7314e7f 100644 --- a/pytests/stubs/buf_and_str.pyi +++ b/pytests/stubs/buf_and_str.pyi @@ -2,8 +2,8 @@ Objects related to PyBuffer and PyStr """ -from collections.abc import Sequence -from typing import Any, final +from collections.abc import Buffer, Sequence +from typing import final @final class BytesExtractor: @@ -12,7 +12,7 @@ class BytesExtractor: """ def __new__(cls, /) -> BytesExtractor: ... @staticmethod - def from_buffer(buf: Any) -> int: ... + def from_buffer(buf: Buffer) -> int: ... @staticmethod def from_bytes(bytes: bytes) -> int: ... @staticmethod diff --git a/pytests/stubs/path.pyi b/pytests/stubs/path.pyi index 03bbb36a2e7..c73a08c842f 100644 --- a/pytests/stubs/path.pyi +++ b/pytests/stubs/path.pyi @@ -2,4 +2,5 @@ from os import PathLike from pathlib import Path def make_path() -> Path: ... +def take_name_or_path(value: str | PathLike[str]) -> Path: ... def take_pathbuf(path: str | PathLike[str]) -> Path: ... diff --git a/pytests/stubs/pyclasses.pyi b/pytests/stubs/pyclasses.pyi index 64692e0dc9c..077d6659c18 100644 --- a/pytests/stubs/pyclasses.pyi +++ b/pytests/stubs/pyclasses.pyi @@ -1,3 +1,4 @@ +from .awaitable import IterAwaitable from _typeshed import Incomplete from typing import Final, final @@ -79,7 +80,7 @@ class Number: def __new__(cls, /, value: int) -> Number: ... def __or__(self, other: object, /) -> Number: ... def __pos__(self, /) -> Number: ... - def __pow__(self, other: object, modulo: object, /) -> Number: ... + def __pow__(self, other: object, modulo: object = None, /) -> Number: ... def __repr__(self, /) -> str: ... def __rshift__(self, other: object, /) -> Number: ... def __str__(self, /) -> str: ... @@ -121,6 +122,33 @@ class PyClassIter: """ def __next__(self, /) -> int: ... +@final +class PyClassOptionAsyncIter: + """ + This is for demonstrating how to stop iteration by returning `None` from __anext__ + """ + def __aiter__(self, /) -> PyClassOptionAsyncIter: ... + def __anext__(self, /) -> IterAwaitable: ... + def __new__(cls, /) -> PyClassOptionAsyncIter: ... + +@final +class PyClassOptionIter: + """ + This is for demonstrating how to stop iteration by returning `None` from __next__ + """ + def __iter__(self, /) -> PyClassOptionIter: ... + def __new__(cls, /) -> PyClassOptionIter: ... + def __next__(self, /) -> int: ... + +@final +class PyClassResultOptionIter: + """ + This is for demonstrating how to stop iteration by returning `None` from a fallible __next__ + """ + def __iter__(self, /) -> PyClassResultOptionIter: ... + def __new__(cls, /) -> PyClassResultOptionIter: ... + def __next__(self, /) -> int: ... + @final class PyClassThreadIter: def __new__(cls, /) -> PyClassThreadIter: ... diff --git a/pytests/stubs/pyfunctions.pyi b/pytests/stubs/pyfunctions.pyi index 3428d60375a..fa2b0718b4f 100644 --- a/pytests/stubs/pyfunctions.pyi +++ b/pytests/stubs/pyfunctions.pyi @@ -20,6 +20,7 @@ def many_keyword_arguments( owl: Any | None = None, penguin: Any | None = None, ) -> None: ... +def nested_empty_tuples() -> tuple[tuple[()], tuple[tuple[()]]]: ... def none() -> None: ... def positional_only(a: Any, /, b: Any) -> tuple[Any, Any]: ... def renamed() -> int: ... diff --git a/pytests/tests/test_datetime.py b/pytests/tests/test_datetime.py index e0d77f87b03..a67d8555c4b 100644 --- a/pytests/tests/test_datetime.py +++ b/pytests/tests/test_datetime.py @@ -1,13 +1,8 @@ import datetime as pdt -import platform import re -import struct -import sys import pyo3_pytests.datetime as rdt import pytest -from hypothesis import example, given -from hypothesis import strategies as st # Constants @@ -41,75 +36,17 @@ def tzname(self, dt): MAX_MICROSECONDS = int(pdt.timedelta.max.total_seconds() * 1e6) MIN_MICROSECONDS = int(pdt.timedelta.min.total_seconds() * 1e6) -# The reason we don't use platform.architecture() here is that it's not -# reliable on macOS. See https://stackoverflow.com/a/1405971/823869. Similarly, -# sys.maxsize is not reliable on Windows. See -# https://stackoverflow.com/questions/1405913/how-do-i-determine-if-my-python-shell-is-executing-in-32bit-or-64bit-mode-on-os/1405971#comment6209952_1405971 -# and https://stackoverflow.com/a/3411134/823869. -_pointer_size = struct.calcsize("P") -if _pointer_size == 8: - IS_32_BIT = False -elif _pointer_size == 4: - IS_32_BIT = True -else: - raise RuntimeError("unexpected pointer size: " + repr(_pointer_size)) -IS_WINDOWS = sys.platform == "win32" - -if IS_WINDOWS: - MIN_DATETIME = pdt.datetime(1970, 1, 1, 0, 0, 0) - if IS_32_BIT: - MAX_DATETIME = pdt.datetime(2038, 1, 18, 23, 59, 59) - else: - MAX_DATETIME = pdt.datetime(3000, 12, 31, 23, 59, 59) -else: - if IS_32_BIT: - # TS ±2147483648 (2**31) - MIN_DATETIME = pdt.datetime(1901, 12, 13, 20, 45, 52) - MAX_DATETIME = pdt.datetime(2038, 1, 19, 3, 14, 8) - else: - MIN_DATETIME = pdt.datetime(1, 1, 2, 0, 0) - MAX_DATETIME = pdt.datetime(9999, 12, 31, 18, 59, 59) - -PYPY = platform.python_implementation() == "PyPy" - # Tests def test_date(): assert rdt.make_date(2017, 9, 1) == pdt.date(2017, 9, 1) -@given(d=st.dates()) -def test_date_accessors(d): - act = rdt.get_date_tuple(d) - exp = (d.year, d.month, d.day) - - assert act == exp - - def test_invalid_date_fails(): with pytest.raises(ValueError): rdt.make_date(2017, 2, 30) -@given(d=st.dates(MIN_DATETIME.date(), MAX_DATETIME.date())) -def test_date_from_timestamp(d): - try: - ts = pdt.datetime.timestamp(d) - except Exception: - # out of range for timestamp - return - - try: - expected = pdt.date.fromtimestamp(ts) - except Exception as pdt_fail: - # date from timestamp failed; expect the same from Rust binding - with pytest.raises(type(pdt_fail)) as exc_info: - rdt.date_from_timestamp(ts) - assert str(exc_info.value) == str(pdt_fail) - else: - assert rdt.date_from_timestamp(ts) == expected - - @pytest.mark.parametrize( "args, kwargs", [ @@ -127,26 +64,6 @@ def test_time(args, kwargs): assert rdt.get_time_tzinfo(act) == exp.tzinfo -@given(t=st.times()) -def test_time_hypothesis(t): - act = rdt.get_time_tuple(t) - exp = (t.hour, t.minute, t.second, t.microsecond) - - assert act == exp - - -@given(t=st.times()) -def test_time_tuple_fold(t): - t_nofold = t.replace(fold=0) - t_fold = t.replace(fold=1) - - for t in (t_nofold, t_fold): - act = rdt.get_time_tuple_fold(t) - exp = (t.hour, t.minute, t.second, t.microsecond, t.fold) - - assert act == exp - - @pytest.mark.parametrize("fold", [False, True]) def test_time_with_fold(fold): t = rdt.time_with_fold(0, 0, 0, 0, None, fold) @@ -206,26 +123,6 @@ def test_datetime(args, kwargs): assert rdt.get_datetime_tzinfo(act) == exp.tzinfo -@given(dt=st.datetimes()) -def test_datetime_tuple(dt): - act = rdt.get_datetime_tuple(dt) - exp = dt.timetuple()[0:6] + (dt.microsecond,) - - assert act == exp - - -@given(dt=st.datetimes()) -def test_datetime_tuple_fold(dt): - dt_fold = dt.replace(fold=1) - dt_nofold = dt.replace(fold=0) - - for dt in (dt_fold, dt_nofold): - act = rdt.get_datetime_tuple_fold(dt) - exp = dt.timetuple()[0:6] + (dt.microsecond, dt.fold) - - assert act == exp - - def test_invalid_datetime_fails(): with pytest.raises(ValueError): rdt.make_datetime(2011, 1, 42, 0, 0, 0, 0) @@ -236,26 +133,6 @@ def test_datetime_typeerror(): rdt.make_datetime("2011", 1, 1, 0, 0, 0, 0) # type: ignore[bad-argument-type] -@given(dt=st.datetimes(MIN_DATETIME, MAX_DATETIME)) -@example(dt=pdt.datetime(1971, 1, 2, 0, 0)) -def test_datetime_from_timestamp(dt): - try: - ts = pdt.datetime.timestamp(dt) - except Exception: - # out of range for timestamp - return - - try: - expected = pdt.datetime.fromtimestamp(ts) - except Exception as pdt_fail: - # datetime from timestamp failed; expect the same from Rust binding - with pytest.raises(type(pdt_fail)) as exc_info: - rdt.datetime_from_timestamp(ts) - assert str(exc_info.value) == str(pdt_fail) - else: - assert rdt.datetime_from_timestamp(ts) == expected - - def test_datetime_from_timestamp_tzinfo(): d1 = rdt.datetime_from_timestamp(0, tz=UTC) d2 = rdt.datetime_from_timestamp(0, tz=UTC) @@ -285,14 +162,6 @@ def test_delta(args): assert act == exp -@given(td=st.timedeltas()) -def test_delta_accessors(td): - act = rdt.get_delta_tuple(td) - exp = (td.days, td.seconds, td.microseconds) - - assert act == exp - - @pytest.mark.parametrize( "args,err_type", [ diff --git a/pytests/tests/test_datetime_hypothesis.py b/pytests/tests/test_datetime_hypothesis.py new file mode 100644 index 00000000000..606f2441df4 --- /dev/null +++ b/pytests/tests/test_datetime_hypothesis.py @@ -0,0 +1,133 @@ +import datetime as pdt +import struct +import sys + +import pyo3_pytests.datetime as rdt +import pytest + +hypothesis = pytest.importorskip("hypothesis") +st = pytest.importorskip("hypothesis.strategies") + +# The reason we don't use platform.architecture() here is that it's not +# reliable on macOS. See https://stackoverflow.com/a/1405971/823869. Similarly, +# sys.maxsize is not reliable on Windows. See +# https://stackoverflow.com/questions/1405913/how-do-i-determine-if-my-python-shell-is-executing-in-32bit-or-64bit-mode-on-os/1405971#comment6209952_1405971 +# and https://stackoverflow.com/a/3411134/823869. +_pointer_size = struct.calcsize("P") +if _pointer_size == 8: + IS_32_BIT = False +elif _pointer_size == 4: + IS_32_BIT = True +else: + raise RuntimeError("unexpected pointer size: " + repr(_pointer_size)) +IS_WINDOWS = sys.platform == "win32" + +if IS_WINDOWS: + MIN_DATETIME = pdt.datetime(1970, 1, 1, 0, 0, 0) # noqa: DTZ001 + if IS_32_BIT: + MAX_DATETIME = pdt.datetime(2038, 1, 18, 23, 59, 59) # noqa: DTZ001 + else: + MAX_DATETIME = pdt.datetime(3000, 12, 31, 23, 59, 59) # noqa: DTZ001 +else: + if IS_32_BIT: + # TS ±2147483648 (2**31) + MIN_DATETIME = pdt.datetime(1901, 12, 13, 20, 45, 52) # noqa: DTZ001 + MAX_DATETIME = pdt.datetime(2038, 1, 19, 3, 14, 8) # noqa: DTZ001 + else: + MIN_DATETIME = pdt.datetime(1, 1, 2, 0, 0) # noqa: DTZ001 + MAX_DATETIME = pdt.datetime(9999, 12, 31, 18, 59, 59) # noqa: DTZ001 + + +@hypothesis.given(d=st.dates()) +def test_date_accessors(d): + act = rdt.get_date_tuple(d) + exp = (d.year, d.month, d.day) + + assert act == exp + + +@hypothesis.given(dt=st.datetimes(MIN_DATETIME, MAX_DATETIME)) +def test_date_from_timestamp(dt): + try: + ts = pdt.datetime.timestamp(dt) + except OverflowError: + # out of range for timestamp + return + + try: + expected = pdt.date.fromtimestamp(ts) # noqa: DTZ012 + except OverflowError as pdt_fail: + # date from timestamp failed; expect the same from Rust binding + with pytest.raises(type(pdt_fail)) as exc_info: + rdt.date_from_timestamp(ts) + assert str(exc_info.value) == str(pdt_fail) + else: + assert rdt.date_from_timestamp(ts) == expected + + +@hypothesis.given(t=st.times()) +def test_time_hypothesis(t): + act = rdt.get_time_tuple(t) + exp = (t.hour, t.minute, t.second, t.microsecond) + + assert act == exp + + +@hypothesis.given(t=st.times()) +def test_time_tuple_fold(t): + t_nofold = t.replace(fold=0) + t_fold = t.replace(fold=1) + + for t in (t_nofold, t_fold): # noqa: PLR1704 + act = rdt.get_time_tuple_fold(t) + exp = (t.hour, t.minute, t.second, t.microsecond, t.fold) + + assert act == exp + + +@hypothesis.given(dt=st.datetimes()) +def test_datetime_tuple(dt): + act = rdt.get_datetime_tuple(dt) + exp = dt.timetuple()[0:6] + (dt.microsecond,) + + assert act == exp + + +@hypothesis.given(dt=st.datetimes()) +def test_datetime_tuple_fold(dt): + dt_fold = dt.replace(fold=1) + dt_nofold = dt.replace(fold=0) + + for dt in (dt_fold, dt_nofold): # noqa: PLR1704 + act = rdt.get_datetime_tuple_fold(dt) + exp = dt.timetuple()[0:6] + (dt.microsecond, dt.fold) + + assert act == exp + + +@hypothesis.given(dt=st.datetimes(MIN_DATETIME, MAX_DATETIME)) +@hypothesis.example(dt=pdt.datetime(1971, 1, 2, 0, 0)) # noqa: DTZ001 +def test_datetime_from_timestamp(dt): + try: + ts = pdt.datetime.timestamp(dt) + except OverflowError: + # out of range for timestamp + return + + try: + expected = pdt.datetime.fromtimestamp(ts) # noqa: DTZ006 + except OverflowError as pdt_fail: + # datetime from timestamp failed; expect the same from Rust binding + with pytest.raises(type(pdt_fail)) as exc_info: + rdt.datetime_from_timestamp(ts) + assert str(exc_info.value) == str(pdt_fail) + else: + assert rdt.datetime_from_timestamp(ts) == expected + + +@hypothesis.given(td=st.timedeltas()) +def test_delta_accessors(td): + act = rdt.get_delta_tuple(td) + exp = (td.days, td.seconds, td.microseconds) + + assert act == exp diff --git a/pytests/tests/test_othermod.py b/pytests/tests/test_othermod.py index f2dd9ad8fd2..4de4f946e8a 100644 --- a/pytests/tests/test_othermod.py +++ b/pytests/tests/test_othermod.py @@ -1,23 +1,5 @@ -from hypothesis import given, assume -from hypothesis import strategies as st - from pyo3_pytests import othermod -INTEGER31_ST = st.integers(min_value=(-(2**30)), max_value=(2**30 - 1)) -USIZE_ST = st.integers(min_value=othermod.USIZE_MIN, max_value=othermod.USIZE_MAX) - - -# If the full 32 bits are used here, then you can get failures that look like this: -# hypothesis.errors.FailedHealthCheck: It looks like your strategy is filtering out a lot of data. -# Health check found 50 filtered examples but only 7 good ones. -# -# Limit the range to 31 bits to avoid this problem. -@given(x=INTEGER31_ST) -def test_double(x): - expected = x * 2 - assume(-(2**31) <= expected <= (2**31 - 1)) - assert othermod.double(x) == expected - def test_modclass(): # Test that the repr of the class itself doesn't crash anything @@ -34,10 +16,3 @@ def test_modclass_instance(): assert isinstance(mi, othermod.ModClass) assert isinstance(mi, object) - - -@given(x=USIZE_ST) -def test_modclas_noop(x): - mi = othermod.ModClass() - - assert mi.noop(x) == x diff --git a/pytests/tests/test_othermod_hypothesis.py b/pytests/tests/test_othermod_hypothesis.py new file mode 100644 index 00000000000..594f61e5a4e --- /dev/null +++ b/pytests/tests/test_othermod_hypothesis.py @@ -0,0 +1,27 @@ +import pytest +from pyo3_pytests import othermod + +hypothesis = pytest.importorskip("hypothesis") +st = pytest.importorskip("hypothesis.strategies") + +INTEGER31_ST = st.integers(min_value=(-(2**30)), max_value=(2**30 - 1)) +USIZE_ST = st.integers(min_value=othermod.USIZE_MIN, max_value=othermod.USIZE_MAX) + + +# If the full 32 bits are used here, then you can get failures that look like this: +# hypothesis.errors.FailedHealthCheck: It looks like your strategy is filtering out a lot of data. +# Health check found 50 filtered examples but only 7 good ones. +# +# Limit the range to 31 bits to avoid this problem. +@hypothesis.given(x=INTEGER31_ST) +def test_double(x): + expected = x * 2 + hypothesis.assume(-(2**31) <= expected <= (2**31 - 1)) + assert othermod.double(x) == expected + + +@hypothesis.given(x=USIZE_ST) +def test_modclas_noop(x): + mi = othermod.ModClass() + + assert mi.noop(x) == x diff --git a/pytests/tests/test_pyclasses.py b/pytests/tests/test_pyclasses.py index bfbf87819a6..baad7b1c66b 100644 --- a/pytests/tests/test_pyclasses.py +++ b/pytests/tests/test_pyclasses.py @@ -1,9 +1,13 @@ +import asyncio import platform import sys +from collections.abc import Iterator from typing import Type import pytest from pyo3_pytests import pyclasses +from pyo3_pytests.awaitable import IterAwaitable +from typing_extensions import assert_type def test_empty_class_init(benchmark): @@ -55,6 +59,42 @@ def test_iter(): assert excinfo.value.value == "Ended" +@pytest.mark.parametrize( + "cls", [pyclasses.PyClassOptionIter, pyclasses.PyClassResultOptionIter] +) +def test_option_iter(cls): + assert list(cls()) == [1, 2, 3, 4, 5] + + i = cls() + for _ in range(5): + next(i) + with pytest.raises(StopIteration): + next(i) + + +@pytest.mark.skipif( + sys.implementation.name == "graalpy" and sys.implementation.version < (25, 1), + reason="`async for` on GraalPy < 25.1 lets a synchronously raised StopAsyncIteration escape", +) +def test_option_async_iter(): + async def collect(): + return [value async for value in pyclasses.PyClassOptionAsyncIter()] + + assert asyncio.run(collect()) == [1, 2, 3, 4, 5] + + +def test_option_iter_type_hints() -> None: + # `None` stops the iteration rather than being yielded, so these classes are `Iterator[int]` + # and not `Iterator[int | None]` + plain: Iterator[int] = pyclasses.PyClassOptionIter() + fallible: Iterator[int] = pyclasses.PyClassResultOptionIter() + assert_type(next(plain), int) + assert_type(next(fallible), int) + + # `__anext__` likewise hands back the awaitable itself, not `IterAwaitable | None` + assert_type(pyclasses.PyClassOptionAsyncIter().__anext__(), IterAwaitable) + + @pytest.mark.skipif( platform.machine() in ["wasm32", "wasm64"], reason="not supporting threads in CI for WASM yet", diff --git a/src/buffer.rs b/src/buffer.rs index 915375facf7..eaaec04000e 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -222,7 +222,11 @@ impl FromPyObject<'_, '_> for PyBuffer { type Error = PyErr; #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: PyStaticExpr = type_hint_identifier!("collections.abc", "Buffer"); + const INPUT_TYPE: PyStaticExpr = if cfg!(Py_3_12) { + type_hint_identifier!("collections.abc", "Buffer") + } else { + type_hint_identifier!("typing_extensions", "Buffer") + }; fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result, Self::Error> { Self::get(&obj) @@ -795,6 +799,24 @@ mod tests { use crate::types::PyBytes; use crate::Python; + #[cfg(feature = "experimental-inspect")] + #[test] + fn collections_abc_is_only_chosen_when_it_has_buffer() { + Python::attach(|py| { + let hint = as FromPyObject<'_, '_>>::INPUT_TYPE.to_string(); + if hint == "collections.abc.Buffer" { + let collections_abc_has_it = py + .import("collections.abc") + .unwrap() + .hasattr("Buffer") + .unwrap(); + assert!(collections_abc_has_it); + } else { + assert_eq!(hint, "typing_extensions.Buffer"); + } + }); + } + #[test] fn test_debug() { Python::attach(|py| { diff --git a/src/conversions/chrono_tz.rs b/src/conversions/chrono_tz.rs index 153e48817a0..3b8220dd514 100644 --- a/src/conversions/chrono_tz.rs +++ b/src/conversions/chrono_tz.rs @@ -98,16 +98,18 @@ impl FromPyObject<'_, '_> for Tz { #[cfg(all(test, not(windows)))] // Troubles loading timezones on Windows mod tests { - use super::*; use crate::prelude::PyAnyMethods; + #[cfg(feature = "chrono")] use crate::types::IntoPyDict; use crate::types::PyTzInfo; - use crate::Bound; - use crate::Python; - use chrono::offset::LocalResult; - use chrono::NaiveDate; - use chrono::{DateTime, Utc}; + use crate::{Bound, IntoPyObject, Python}; + #[cfg(feature = "chrono")] + use alloc::string::ToString; + #[cfg(feature = "chrono")] + use chrono::{offset::LocalResult, DateTime, NaiveDate, Utc}; use chrono_tz::Tz; + #[cfg(feature = "chrono")] + use core::str::FromStr; #[test] fn test_frompyobject() { @@ -125,6 +127,7 @@ mod tests { } #[test] + #[cfg(feature = "chrono")] fn test_ambiguous_datetime_to_pyobject() { let dates = [ DateTime::::from_str("2020-10-24 23:00:00 UTC").unwrap(), @@ -173,6 +176,7 @@ mod tests { } #[test] + #[cfg(feature = "chrono")] fn test_nonexistent_datetime_from_pyobject() { // Pacific_Apia skipped the 30th of December 2011 entirely @@ -204,7 +208,6 @@ mod tests { } #[test] - #[cfg(not(Py_GIL_DISABLED))] // https://github.com/python/cpython/issues/116738#issuecomment-2404360445 fn test_into_pyobject() { Python::attach(|py| { let assert_eq = |l: Bound<'_, PyTzInfo>, r: Bound<'_, PyTzInfo>| { diff --git a/src/conversions/hashbrown.rs b/src/conversions/hashbrown.rs index c841523e874..50f94224672 100644 --- a/src/conversions/hashbrown.rs +++ b/src/conversions/hashbrown.rs @@ -150,16 +150,20 @@ where fn extract(ob: Borrowed<'_, 'py, PyAny>) -> PyResult { match ob.cast::() { - Ok(set) => set - .iter() - .map(|any| any.extract().map_err(Into::into)) - .collect(), + Ok(set) => { + let mut result = Self::with_capacity_and_hasher(set.len(), S::default()); + for item in set.iter() { + result.insert(item.extract().map_err(Into::into)?); + } + Ok(result) + } Err(err) => { if let Ok(frozen_set) = ob.cast::() { - frozen_set - .iter() - .map(|any| any.extract().map_err(Into::into)) - .collect() + let mut result = Self::with_capacity_and_hasher(frozen_set.len(), S::default()); + for item in frozen_set.iter() { + result.insert(item.extract().map_err(Into::into)?); + } + Ok(result) } else { Err(PyErr::from(err)) } diff --git a/src/conversions/serde.rs b/src/conversions/serde.rs index bb3f4f1c58f..4aae89a95a0 100644 --- a/src/conversions/serde.rs +++ b/src/conversions/serde.rs @@ -44,3 +44,84 @@ where Python::attach(|py| Py::new(py, deserialized).map_err(|e| de::Error::custom(e.to_string()))) } } + +#[cfg(all(test, feature = "macros"))] +mod tests { + use crate::prelude::*; + + use serde::{Deserialize, Serialize}; + + #[pyclass(crate = "crate")] + #[derive(Debug, Serialize, Deserialize)] + struct Group { + name: alloc::string::String, + } + + #[pyclass(crate = "crate")] + #[derive(Debug, Serialize, Deserialize)] + struct User { + username: alloc::string::String, + group: Option>, + friends: alloc::vec::Vec>, + } + + #[test] + fn test_serialize() { + let friend1 = User { + username: "friend 1".into(), + group: None, + friends: vec![], + }; + let friend2 = User { + username: "friend 2".into(), + group: None, + friends: vec![], + }; + + let user = Python::attach(|py| { + let py_friend1 = Py::new(py, friend1).expect("failed to create friend 1"); + let py_friend2 = Py::new(py, friend2).expect("failed to create friend 2"); + + let friends = vec![py_friend1, py_friend2]; + let py_group = Py::new( + py, + Group { + name: "group name".into(), + }, + ) + .unwrap(); + + User { + username: "danya".into(), + group: Some(py_group), + friends, + } + }); + + let serialized = serde_json::to_string(&user).expect("failed to serialize"); + assert_eq!( + serialized, + r#"{"username":"danya","group":{"name":"group name"},"friends":[{"username":"friend 1","group":null,"friends":[]},{"username":"friend 2","group":null,"friends":[]}]}"# + ); + } + + #[test] + fn test_deserialize() { + let serialized = r#"{"username": "danya", "friends": + [{"username": "friend", "group": {"name": "danya's friends"}, "friends": []}]}"#; + let user: User = serde_json::from_str(serialized).expect("failed to deserialize"); + + assert_eq!(user.username, "danya"); + assert!(user.group.is_none()); + assert_eq!(user.friends.len(), 1usize); + let friend = user.friends.first().unwrap(); + + Python::attach(|py| { + assert_eq!(friend.borrow(py).username, "friend"); + assert_eq!( + friend.borrow(py).group.as_ref().unwrap().borrow(py).name, + "danya's friends" + ) + }); + } +} diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 50e72cca5f9..76f5a293e22 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -174,6 +174,7 @@ macro_rules! int_fits_c_long { fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result { let val: c_long = extract_int!(obj, -1, ffi::PyLong_AsLong)?; + #[allow(unreachable_code, reason = "error type might be !")] <$rust_type>::try_from(val) .map_err(|e| exceptions::PyOverflowError::new_err(e.to_string())) } @@ -356,10 +357,10 @@ int_convert_u64_or_i64!( true ); -#[cfg(all(Py_3_14, not(Py_LIMITED_API)))] +#[cfg(any(all(Py_3_14, not(Py_LIMITED_API)), Py_3_15))] pub(crate) const PYLONG_BITS_IN_DIGIT: usize = 30; -#[cfg(all(Py_3_14, not(Py_LIMITED_API)))] +#[cfg(any(all(Py_3_14, not(Py_LIMITED_API)), Py_3_15))] pub(crate) fn is_30bit_layout() -> bool { static DIGITS: std::sync::OnceLock = std::sync::OnceLock::new(); @@ -380,10 +381,10 @@ pub(crate) fn is_30bit_layout() -> bool { }) } -#[cfg(all(Py_3_14, not(Py_LIMITED_API)))] +#[cfg(any(all(Py_3_14, not(Py_LIMITED_API)), Py_3_15))] struct ExportGuard(ffi::PyLongExport); -#[cfg(all(Py_3_14, not(Py_LIMITED_API)))] +#[cfg(any(all(Py_3_14, not(Py_LIMITED_API)), Py_3_15))] impl Drop for ExportGuard { fn drop(&mut self) { unsafe { ffi::PyLong_FreeExport(&mut self.0) }; @@ -391,7 +392,7 @@ impl Drop for ExportGuard { } // Builds an int from an iterator of 30-bit digits -#[cfg(all(Py_3_14, not(Py_LIMITED_API)))] +#[cfg(any(all(Py_3_14, not(Py_LIMITED_API)), Py_3_15))] #[inline] pub(crate) fn pylong_from_digits<'py, I: ExactSizeIterator>( py: Python<'py>, @@ -416,7 +417,7 @@ pub(crate) fn pylong_from_digits<'py, I: ExactSizeIterator>( } // Visits 30-bit digits LSB-first and deals with freeing the export -#[cfg(all(Py_3_14, not(Py_LIMITED_API)))] +#[cfg(any(all(Py_3_14, not(Py_LIMITED_API)), Py_3_15))] #[inline] pub(crate) fn pylong_visit_digits( obj: Borrowed<'_, '_, PyAny>, @@ -444,7 +445,7 @@ pub(crate) fn pylong_visit_digits( } } -#[cfg(not(Py_LIMITED_API))] +#[cfg(any(not(Py_LIMITED_API), Py_3_15))] mod fast_128bit_int_conversion { use super::*; @@ -627,7 +628,7 @@ pub(crate) fn int_from_le_bytes<'py, const IS_SIGNED: bool>( } } -#[cfg(all(Py_3_13, not(Py_LIMITED_API)))] +#[cfg(any(all(Py_3_13, not(Py_LIMITED_API)), Py_3_15))] pub(crate) fn int_from_ne_bytes<'py, const IS_SIGNED: bool>( py: Python<'py>, bytes: &[u8], @@ -650,7 +651,7 @@ pub(crate) fn nb_index<'py>(obj: &Bound<'py, PyAny>) -> PyResult) -> Result { match ob.cast::() { - Ok(set) => set - .iter() - .map(|any| any.extract().map_err(Into::into)) - .collect(), + Ok(set) => { + let mut result = Self::with_capacity_and_hasher(set.len(), S::default()); + for item in set.iter() { + result.insert(item.extract().map_err(Into::into)?); + } + Ok(result) + } Err(err) => { if let Ok(frozen_set) = ob.cast::() { - frozen_set - .iter() - .map(|any| any.extract().map_err(Into::into)) - .collect() + let mut result = Self::with_capacity_and_hasher(frozen_set.len(), S::default()); + for item in frozen_set.iter() { + result.insert(item.extract().map_err(Into::into)?); + } + Ok(result) } else { Err(PyErr::from(err)) } @@ -120,16 +125,20 @@ where fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { match ob.cast::() { - Ok(set) => set - .iter() - .map(|any| any.extract().map_err(Into::into)) - .collect(), + Ok(set) => { + let mut values = Vec::with_capacity(set.len()); + for item in set.iter() { + values.push(item.extract().map_err(Into::into)?); + } + Ok(values.into_iter().collect()) + } Err(err) => { if let Ok(frozen_set) = ob.cast::() { - frozen_set - .iter() - .map(|any| any.extract().map_err(Into::into)) - .collect() + let mut values = Vec::with_capacity(frozen_set.len()); + for item in frozen_set.iter() { + values.push(item.extract().map_err(Into::into)?); + } + Ok(values.into_iter().collect()) } else { Err(PyErr::from(err)) } diff --git a/src/conversions/std/string.rs b/src/conversions/std/string.rs index 25ea3824e85..7f217e31389 100644 --- a/src/conversions/std/string.rs +++ b/src/conversions/std/string.rs @@ -212,6 +212,22 @@ mod tests { }) } + #[test] + fn test_extract_str_surrogate() { + use crate::exceptions::PyUnicodeEncodeError; + + Python::attach(|py| { + let value = py.eval(cr"'\ud800'", None, None).unwrap(); + let err = value.extract::().unwrap_err(); + + assert!(err.is_instance_of::(py)); + assert_eq!( + err.value(py).to_string(), + "'utf-8' codec can't encode character '\\ud800' in position 0: surrogates not allowed" + ); + }); + } + #[test] fn test_extract_char() { Python::attach(|py| { diff --git a/src/exceptions.rs b/src/exceptions.rs index 2cf91845789..cd5f5e6e355 100644 --- a/src/exceptions.rs +++ b/src/exceptions.rs @@ -270,7 +270,8 @@ macro_rules! create_exception_type_hint( ); macro_rules! impl_native_exception ( - ($name:ident, $exc_name:ident, $python_name:expr, $doc:expr, $layout:path $(, #checkfunction=$checkfunction:path)?) => ( + ($name:ident, $exc_name:ident, $python_name:literal, $doc:expr, $layout:path $(, #checkfunction=$checkfunction:path)?) => ( + #[doc = concat!("Represents Python's [`", $python_name, "`](https://docs.python.org/3/library/exceptions.html#", $python_name, ") exception.")] #[doc = $doc] #[repr(transparent)] #[allow(clippy::upper_case_acronyms, reason = "Python exception names")] @@ -284,24 +285,17 @@ macro_rules! impl_native_exception ( }, "builtins", $python_name $(, #checkfunction=$checkfunction)?); $crate::pyobject_subclassable_native_type!($name, $layout); ); - ($name:ident, $exc_name:ident, $python_name:expr, $doc:expr) => ( + ($name:ident, $exc_name:ident, $python_name:literal, $doc:expr) => ( impl_native_exception!($name, $exc_name, $python_name, $doc, $crate::ffi::PyBaseExceptionObject); ) ); +/// Create doc examples for the native exceptions macro_rules! native_doc( - ($name: literal, $alt: literal) => ( - concat!( -"Represents Python's [`", $name, "`](https://docs.python.org/3/library/exceptions.html#", $name, ") exception. - -", $alt - ) - ); + (skip_example) => (""); ($name: literal) => ( concat!( " -Represents Python's [`", $name, "`](https://docs.python.org/3/library/exceptions.html#", $name, ") exception. - # Example: Raising ", $name, " from Rust This exception can be sent to Python code by converting it into a @@ -338,10 +332,9 @@ except ", $name, " as e: ``` use pyo3::prelude::*; use pyo3::exceptions::Py", $name, "; -use pyo3::ffi::c_str; Python::attach(|py| { - let result: PyResult<()> = py.run(c_str!(\"raise ", $name, "\"), None, None); + let result: PyResult<()> = py.run(c\"raise ", $name, "\", None, None); let error_type = match result { Ok(_) => \"Not an error\", @@ -585,26 +578,26 @@ impl_native_exception!( PyUnicodeDecodeError, PyExc_UnicodeDecodeError, "UnicodeDecodeError", - native_doc!("UnicodeDecodeError", "") + native_doc!(skip_example) ); impl_native_exception!( PyUnicodeEncodeError, PyExc_UnicodeEncodeError, "UnicodeEncodeError", - native_doc!("UnicodeEncodeError", "") + native_doc!(skip_example) ); impl_native_exception!( PyUnicodeTranslateError, PyExc_UnicodeTranslateError, "UnicodeTranslateError", - native_doc!("UnicodeTranslateError", "") + native_doc!(skip_example) ); #[cfg(Py_3_11)] impl_native_exception!( PyBaseExceptionGroup, PyExc_BaseExceptionGroup, "BaseExceptionGroup", - native_doc!("BaseExceptionGroup", "") + native_doc!(skip_example) ); impl_native_exception!( PyValueError, diff --git a/src/impl_/pyclass.rs b/src/impl_/pyclass.rs index 83584cd9724..ba5188459cb 100644 --- a/src/impl_/pyclass.rs +++ b/src/impl_/pyclass.rs @@ -12,9 +12,10 @@ use crate::{ }, internal::pyclass_init::PyObjectInit, pycell::{impl_::PyClassObjectLayout, PyBorrowError}, + pyclass::PyClassGuardError, types::{any::PyAnyMethods, PyBool}, - Borrowed, IntoPyObject, IntoPyObjectExt, Py, PyAny, PyClass, PyClassGuard, PyErr, PyResult, - PyTypeCheck, PyTypeInfo, Python, + Borrowed, FromPyObject, IntoPyObject, IntoPyObjectExt, Py, PyAny, PyClass, PyClassGuard, PyErr, + PyResult, PyTypeCheck, PyTypeInfo, Python, }; use core::{ ffi::CStr, @@ -46,6 +47,16 @@ pub const fn weaklist_offset() -> PyObjectOffset { ::Layout::WEAKLIST_OFFSET } +/// Extracts a `T: PyClass + Clone` from a Python object by cloning it out of +/// the [`PyClassGuard`]. +#[inline] +pub fn extract_pyclass_with_clone<'a, 'py, T: PyClass + Clone>( + obj: Borrowed<'a, 'py, PyAny>, +) -> Result> { + let guard = as FromPyObject<'a, 'py>>::extract(obj)?; + Ok(T::clone(&guard)) +} + mod sealed { pub trait Sealed {} diff --git a/src/impl_/pymethods.rs b/src/impl_/pymethods.rs index 917f4863b2a..838c33bfcf7 100644 --- a/src/impl_/pymethods.rs +++ b/src/impl_/pymethods.rs @@ -3,9 +3,13 @@ use crate::exceptions::PyStopAsyncIteration; use crate::impl_::callback::IntoPyCallbackOutput; +#[cfg(feature = "experimental-inspect")] +use crate::impl_::introspection::PyReturnType; use crate::impl_::panic::PanicTrap; use crate::impl_::pycell::PyClassObjectBaseLayout; use crate::impl_::pyclass::PyClassDict as _; +#[cfg(feature = "experimental-inspect")] +use crate::inspect::PyStaticExpr; use crate::internal::get_slot::{get_slot, TP_BASE, TP_CLEAR, TP_TRAVERSE}; use crate::internal::pyclass_init::PyClassInit; use crate::internal::state::ForbidAttaching; @@ -610,167 +614,104 @@ unsafe fn call_super_clear( 0 } -// Autoref-based specialization for handling `__next__` returning `Option` - -pub struct IterBaseTag; - -impl IterBaseTag { - #[inline] - pub fn convert<'py, Value, Target>(self, py: Python<'py>, value: Value) -> PyResult - where - Value: IntoPyCallbackOutput<'py, Target>, - { - value.convert(py) - } -} - -pub trait IterBaseKind { - #[inline] - fn iter_tag(&self) -> IterBaseTag { - IterBaseTag - } -} - -impl IterBaseKind for &Value {} - -pub struct IterOptionTag; - -impl IterOptionTag { - #[inline] - pub fn convert<'py, Value>( - self, - py: Python<'py>, - value: Option, - ) -> PyResult<*mut ffi::PyObject> - where - Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>, - { - match value { - Some(value) => value.convert(py), - None => Ok(null_mut()), +// `__next__` and `__anext__` may say "iteration is over" by returning `None`, written either as +// `Option` or as `Result, E>`. The slot conversion and the `experimental-inspect` +// type hint both read that off the same wrapper: the inherent items below match those two shapes +// and win over the blanket fallback impls, which cover every other return type. The sync and the +// async wrapper come from one macro so they cannot drift apart either. +macro_rules! iter_next_output { + ($wrapper:ident, $convert_fallback:ident, $type_fallback:ident, exhausted: $exhausted:expr) => { + pub struct $wrapper(pub T); + + // The conversion bound sits on the method rather than on the impl, so that a return type + // which cannot be converted at all is reported as the missing `IntoPyCallbackOutput` + // rather than as this trait not being implemented. + pub trait $convert_fallback { + type Value; + + fn convert<'py, Target>(self, py: Python<'py>) -> PyResult + where + Self::Value: IntoPyCallbackOutput<'py, Target>; } - } -} -pub trait IterOptionKind { - #[inline] - fn iter_tag(&self) -> IterOptionTag { - IterOptionTag - } -} - -impl IterOptionKind for Option {} + impl $convert_fallback for $wrapper { + type Value = Value; -pub struct IterResultOptionTag; - -impl IterResultOptionTag { - #[inline] - pub fn convert<'py, Value, Error>( - self, - py: Python<'py>, - value: Result, Error>, - ) -> PyResult<*mut ffi::PyObject> - where - Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>, - Error: Into, - { - match value { - Ok(Some(value)) => value.convert(py), - Ok(None) => Ok(null_mut()), - Err(err) => Err(err.into()), + #[inline] + fn convert<'py, Target>(self, py: Python<'py>) -> PyResult + where + Value: IntoPyCallbackOutput<'py, Target>, + { + self.0.convert(py) + } } - } -} -pub trait IterResultOptionKind { - #[inline] - fn iter_tag(&self) -> IterResultOptionTag { - IterResultOptionTag - } -} - -impl IterResultOptionKind for Result, Error> {} - -// Autoref-based specialization for handling `__anext__` returning `Option` - -pub struct AsyncIterBaseTag; - -impl AsyncIterBaseTag { - #[inline] - pub fn convert<'py, Value, Target>(self, py: Python<'py>, value: Value) -> PyResult - where - Value: IntoPyCallbackOutput<'py, Target>, - { - value.convert(py) - } -} - -pub trait AsyncIterBaseKind { - #[inline] - fn async_iter_tag(&self) -> AsyncIterBaseTag { - AsyncIterBaseTag - } -} - -impl AsyncIterBaseKind for &Value {} - -pub struct AsyncIterOptionTag; + #[cfg(feature = "experimental-inspect")] + pub trait $type_fallback { + const OUTPUT_TYPE: PyStaticExpr; + } -impl AsyncIterOptionTag { - #[inline] - pub fn convert<'py, Value>( - self, - py: Python<'py>, - value: Option, - ) -> PyResult<*mut ffi::PyObject> - where - Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>, - { - match value { - Some(value) => value.convert(py), - None => Err(PyStopAsyncIteration::new_err(())), + #[cfg(feature = "experimental-inspect")] + impl $type_fallback for $wrapper { + const OUTPUT_TYPE: PyStaticExpr = ::OUTPUT_TYPE; } - } -} -pub trait AsyncIterOptionKind { - #[inline] - fn async_iter_tag(&self) -> AsyncIterOptionTag { - AsyncIterOptionTag - } -} + impl $wrapper> { + #[inline] + pub fn convert<'py>(self, py: Python<'py>) -> PyResult<*mut ffi::PyObject> + where + Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>, + { + match self.0 { + Some(value) => value.convert(py), + None => $exhausted, + } + } + } -impl AsyncIterOptionKind for Option {} + #[cfg(feature = "experimental-inspect")] + impl $wrapper> { + pub const OUTPUT_TYPE: PyStaticExpr = ::OUTPUT_TYPE; + } -pub struct AsyncIterResultOptionTag; + impl $wrapper, Error>> { + #[inline] + pub fn convert<'py>(self, py: Python<'py>) -> PyResult<*mut ffi::PyObject> + where + Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>, + Error: Into, + { + match self.0 { + Ok(Some(value)) => value.convert(py), + Ok(None) => $exhausted, + Err(err) => Err(err.into()), + } + } + } -impl AsyncIterResultOptionTag { - #[inline] - pub fn convert<'py, Value, Error>( - self, - py: Python<'py>, - value: Result, Error>, - ) -> PyResult<*mut ffi::PyObject> - where - Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>, - Error: Into, - { - match value { - Ok(Some(value)) => value.convert(py), - Ok(None) => Err(PyStopAsyncIteration::new_err(())), - Err(err) => Err(err.into()), + #[cfg(feature = "experimental-inspect")] + impl $wrapper, Error>> { + pub const OUTPUT_TYPE: PyStaticExpr = ::OUTPUT_TYPE; } - } + }; } -pub trait AsyncIterResultOptionKind { - #[inline] - fn async_iter_tag(&self) -> AsyncIterResultOptionTag { - AsyncIterResultOptionTag - } -} +iter_next_output!( + IterNextOutput, + IterNextConvertFallback, + IterNextTypeFallback, + exhausted: Ok(null_mut()) +); -impl AsyncIterResultOptionKind for Result, Error> {} +// Unlike `tp_iternext`, `am_anext` has no "returned null, no error set" convention: doing that +// makes CPython raise `SystemError: error return without exception set`, so exhaustion has to be +// signalled by raising `StopAsyncIteration` directly. +iter_next_output!( + AsyncIterNextOutput, + AsyncIterNextConvertFallback, + AsyncIterNextTypeFallback, + exhausted: Err(PyStopAsyncIteration::new_err(())) +); /// Re-exported so that `#[new]` generated code can resolve the type tag for `tp_new_impl` pub use crate::internal::pyclass_init::tp_new_resolver; @@ -796,6 +737,33 @@ where #[cfg(test)] mod tests { + #[test] + #[cfg(feature = "experimental-inspect")] + fn iter_next_output_type() { + use super::{AsyncIterNextOutput, AsyncIterNextTypeFallback as _}; + use super::{IterNextOutput, IterNextTypeFallback as _}; + use crate::PyResult; + + // `None` ends the iteration instead of being yielded, so it is not part of the type + for hint in [ + IterNextOutput::>::OUTPUT_TYPE, + IterNextOutput::>>::OUTPUT_TYPE, + AsyncIterNextOutput::>::OUTPUT_TYPE, + AsyncIterNextOutput::>>::OUTPUT_TYPE, + // and a return type without that encoding is left as it is + IterNextOutput::>::OUTPUT_TYPE, + AsyncIterNextOutput::::OUTPUT_TYPE, + ] { + assert_eq!(hint.to_string(), "builtins.int"); + } + + // only the outermost `Option` is the one meaning "iteration is over" + assert_eq!( + IterNextOutput::>>::OUTPUT_TYPE.to_string(), + "builtins.list[builtins.int | None]" + ); + } + #[test] #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] fn test_fastcall_function_with_keywords() { diff --git a/src/impl_/pymodule.rs b/src/impl_/pymodule.rs index 64f767becfc..77816775af4 100644 --- a/src/impl_/pymodule.rs +++ b/src/impl_/pymodule.rs @@ -33,16 +33,17 @@ use portable_atomic::AtomicI64; #[cfg(not(any(PyPy, GraalPy)))] use crate::exceptions::PyImportError; +use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(any(not(all(Py_LIMITED_API, Py_GIL_DISABLED)), Py_3_15))] use crate::internal_tricks::array_ptr_as_mut; use crate::prelude::PyTypeMethods; +use crate::{err::error_on_minusone, py_result_ext::PyResultExt}; use crate::{ ffi, impl_::pyfunction::PyFunctionDef, types::{PyModule, PyModuleMethods}, Bound, PyClass, PyResult, PyTypeInfo, }; -use crate::{ffi_ptr_ext::FfiPtrExt, PyErr}; use crate::{ sync::PyOnceLock, types::{any::PyAnyMethods, dict::PyDictMethods, PyDict}, @@ -54,11 +55,8 @@ pub struct ModuleDef { // wrapped in UnsafeCell so that Rust compiler treats this as interior mutability #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))] ffi_def: UnsafeCell, - #[cfg(Py_3_15)] name: &'static CStr, #[cfg(Py_3_15)] - doc: &'static CStr, - #[cfg(Py_3_15)] slots: &'static PyModuleSlots, /// Interpreter ID where module was initialized (not applicable on PyPy). #[cfg(all( @@ -83,44 +81,34 @@ impl ModuleDef { ) -> Self { // This is only used in PyO3 for append_to_inittab on Python 3.15 and newer. // There could also be other tools that need the legacy init hook. - #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))] - #[allow(clippy::declare_interior_mutable_const)] - const INIT: ffi::PyModuleDef = ffi::PyModuleDef { - m_base: ffi::PyModuleDef_HEAD_INIT, - m_name: core::ptr::null(), - m_doc: core::ptr::null(), - m_size: 0, - m_methods: core::ptr::null_mut(), - m_slots: core::ptr::null_mut(), - m_traverse: None, - m_clear: None, - m_free: None, - }; - #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))] let ffi_def = UnsafeCell::new(ffi::PyModuleDef { + m_base: ffi::PyModuleDef_HEAD_INIT, m_name: name.as_ptr(), m_doc: doc.as_ptr(), + m_size: 0, + m_methods: core::ptr::null_mut(), m_slots: array_ptr_as_mut({ cfg_select! { Py_3_15 => secondary_slots.0.get(), _ => slots.0.get(), } }), - ..INIT + m_traverse: None, + m_clear: None, + m_free: None, }); #[cfg(any(not(Py_3_15), all(Py_LIMITED_API, Py_GIL_DISABLED)))] let _ = secondary_slots; + #[cfg(all(Py_LIMITED_API, Py_GIL_DISABLED))] + let _ = doc; ModuleDef { #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))] ffi_def, - #[cfg(Py_3_15)] name, #[cfg(Py_3_15)] - doc, - #[cfg(Py_3_15)] slots, // -1 is never expected to be a valid interpreter ID #[cfg(all( @@ -187,66 +175,33 @@ impl ModuleDef { static SIMPLE_NAMESPACE: PyOnceLock> = PyOnceLock::new(); let simple_ns = SIMPLE_NAMESPACE.import(py, "types", "SimpleNamespace")?; - #[cfg(not(Py_3_15))] - { - let ffi_def = self.ffi_def.get(); - - let m_name = unsafe { CStr::from_ptr((*ffi_def).m_name) }; - let name = m_name - .to_str() - .map_err(|e| { - crate::exceptions::PyUnicodeDecodeError::new_err_from_utf8( - py, - m_name.to_bytes(), - e, - ) - })? - .to_string(); - let kwargs = PyDict::new(py); - kwargs.set_item("name", name)?; - let spec = simple_ns.call((), Some(&kwargs))?; - - self.module - .get_or_try_init(py, || { - let def = self.ffi_def.get(); - let module = unsafe { - ffi::PyModule_FromDefAndSpec(def, spec.as_ptr()).assume_owned_or_err(py)? + let kwargs = PyDict::new(py); + kwargs.set_item("name", self.name)?; + let spec = simple_ns.call((), Some(&kwargs))?; + + self.module + .get_or_try_init(py, || { + // SAFETY: slots / def are static and fully initialized, spec is a valid object, + // and these functions are known to create a valid module object on success + let module: Bound<'_, PyModule> = unsafe { + cfg_select! { + Py_3_15 => ffi::PyModule_FromSlotsAndSpec(self.get_slots(), spec.as_ptr()), + not(Py_3_15) => ffi::PyModule_FromDefAndSpec(self.ffi_def.get(), spec.as_ptr()), + }.assume_owned_or_err(py) + .cast_into_unchecked() + }?; + + // SAFETY: module is a known valid module object + error_on_minusone(py, unsafe { + cfg_select! { + Py_3_15 => ffi::PyModule_Exec(module.as_ptr()), + not(Py_3_15) => ffi::PyModule_ExecDef(module.as_ptr(), self.ffi_def.get()), } - .cast_into()?; - if unsafe { ffi::PyModule_ExecDef(module.as_ptr(), def) } != 0 { - return Err(PyErr::fetch(py)); - } - Ok(module.unbind()) - }) - .map(|py_module| py_module.clone_ref(py)) - } + })?; - #[cfg(Py_3_15)] - { - let name = self.name; - let doc = self.doc; - let kwargs = PyDict::new(py); - kwargs.set_item("name", name)?; - let spec = simple_ns.call((), Some(&kwargs))?; - - self.module - .get_or_try_init(py, || { - let slots = self.get_slots(); - let module = unsafe { - ffi::PyModule_FromSlotsAndSpec(slots, spec.as_ptr()) - .assume_owned_or_err(py)? - } - .cast_into()?; - if unsafe { ffi::PyModule_SetDocString(module.as_ptr(), doc.as_ptr()) } != 0 { - return Err(PyErr::fetch(py)); - } - if unsafe { ffi::PyModule_Exec(module.as_ptr()) } != 0 { - return Err(PyErr::fetch(py)); - } - Ok(module.unbind()) - }) - .map(|py_module| py_module.clone_ref(py)) - } + Ok(module.unbind()) + }) + .map(|py_module| py_module.clone_ref(py)) } #[cfg(Py_3_15)] @@ -682,11 +637,8 @@ mod tests { assert_eq!(secondary_slots[0].value, SLOTS.0.get().cast()); assert!(secondary_slots[1] == ffi::PyModuleDef_Slot::default()); } - #[cfg(Py_3_15)] - { - assert_eq!(module_def.name, NAME); - assert_eq!(module_def.doc, DOC); - } + + assert_eq!(module_def.name, NAME); } #[test] diff --git a/src/inspect.rs b/src/inspect.rs index 33f27e4e018..71550d0ec31 100644 --- a/src/inspect.rs +++ b/src/inspect.rs @@ -267,7 +267,23 @@ impl fmt::Display for PyStaticExpr { } Ok(()) } - PyStaticConstant::Str(value) => write!(f, "{value:?}"), + PyStaticConstant::Str(value) => { + // Not `{value:?}`: Rust escapes as `\u{1b}`, which Python cannot parse. + f.write_char('"')?; + for c in value.chars() { + match c { + '"' => f.write_str("\\\"")?, + '\n' => f.write_str("\\n")?, + '\r' => f.write_str("\\r")?, + '\t' => f.write_str("\\t")?, + '\\' => f.write_str("\\\\")?, + '\0' => f.write_str("\\0")?, + c @ '\x00'..'\x20' => write!(f, "\\x{:02x}", u32::from(c))?, + c => f.write_char(c)?, + } + } + f.write_char('"') + } PyStaticConstant::Ellipsis => f.write_str("..."), }, Self::Name { id, .. } => f.write_str(id), @@ -302,8 +318,12 @@ impl fmt::Display for PyStaticExpr { value.fmt(f)?; f.write_char('[')?; if let PyStaticExpr::Tuple { elts } = slice { - // We don't display the tuple parentheses - fmt_elements(elts, f)?; + if elts.is_empty() { + // Empty tuples need parentheses to avoid invalid syntax like `tuple[]` + f.write_str("()")?; + } else { + fmt_elements(elts, f)?; + } } else { slice.fmt(f)?; } @@ -471,6 +491,38 @@ mod tests { ) } + #[test] + fn test_control_characters_in_str_constants() { + // Rust's `{:?}` renders these as `\u{1b}`, which is not valid Python. + for (value, expected) in [ + ("\u{1b}", r#""\x1b""#), + ("\u{7}", r#""\x07""#), + ("\u{b}\u{c}", r#""\x0b\x0c""#), + ("a\nb\r\u{1b}", r#""a\nb\r\x1b""#), + ] { + let expr = PyStaticExpr::Constant { + value: PyStaticConstant::Str(value), + }; + assert_eq!(expr.to_string(), expected); + } + } + + #[test] + fn test_empty_tuple_type_hints() { + use crate::IntoPyObject; + + for (expr, expected) in [ + (<()>::OUTPUT_TYPE, "builtins.tuple[()]"), + (<((),)>::OUTPUT_TYPE, "builtins.tuple[builtins.tuple[()]]"), + ( + <(i32, ())>::OUTPUT_TYPE, + "builtins.tuple[builtins.int, builtins.tuple[()]]", + ), + ] { + assert_eq!(expr.to_string(), expected); + } + } + #[test] fn test_serialize_for_introspection() { fn check_serialization(expr: PyStaticExpr, expected: &str) { diff --git a/src/internal/state.rs b/src/internal/state.rs index 9776c2b52ac..3b38b6dced2 100644 --- a/src/internal/state.rs +++ b/src/internal/state.rs @@ -259,15 +259,15 @@ impl SuspendAttach { impl Drop for SuspendAttach { fn drop(&mut self) { + // SAFETY: tstate come from call to PyEval_SaveThread and it was not re-attached yet + unsafe { ffi::PyEval_RestoreThread(self.tstate) }; ATTACH_COUNT.with(|c| c.set(self.count)); - unsafe { - ffi::PyEval_RestoreThread(self.tstate); - - // Update counts of `Py` that were dropped while not attached. - #[cfg(not(pyo3_disable_reference_pool))] - if let Some(pool) = POOL.get() { - pool.drop_deferred_references(Python::assume_attached()); - } + // Update counts of `Py` that were dropped while not attached. + #[cfg(not(pyo3_disable_reference_pool))] + { + // SAFETY: just re-attached + let py = unsafe { Python::assume_attached() }; + get_pool().drop_deferred_references(py); } } } diff --git a/src/internal_tricks.rs b/src/internal_tricks.rs index 04d6a8f56d7..8865258778a 100644 --- a/src/internal_tricks.rs +++ b/src/internal_tricks.rs @@ -52,8 +52,7 @@ pub(crate) fn traverse_eq(f: Option, g: ffi::traverseproc) -> // TODO: use Box::into_non_null when stabilized pub(crate) fn box_into_non_null(b: Box) -> NonNull { - // SAFETY: `Box::into_raw` guarantees an non-null pointer - unsafe { NonNull::new_unchecked(Box::into_raw(b)) } + NonNull::from(Box::leak(b)) } /// Replacement for the unstable `<*mut [T; N]>::as_mut_ptr` method, which avoids diff --git a/src/pybacked.rs b/src/pybacked.rs index 5b03bdcfcc9..1839d54a8a9 100644 --- a/src/pybacked.rs +++ b/src/pybacked.rs @@ -5,6 +5,7 @@ #[cfg(feature = "experimental-inspect")] use crate::inspect::PyStaticExpr; +use crate::sync::critical_section::with_critical_section; #[cfg(feature = "experimental-inspect")] use crate::type_hint_union; use crate::{ @@ -271,7 +272,14 @@ impl From> for PyBackedBytes { impl From> for PyBackedBytes { fn from(py_bytearray: Bound<'_, PyByteArray>) -> Self { - let s = Arc::<[u8]>::from(py_bytearray.to_vec()); + let s = with_critical_section(&py_bytearray, || { + // SAFETY: + // * `py_bytearray` is a `Bound` object, which guarantees that the Python GIL is held. + // * For free-threaded Python, a critical section is used in lieu of the GIL. + // * We don't interact with the interpreter + // * We don't mutate the underlying slice + Arc::<[u8]>::from(unsafe { py_bytearray.as_bytes() }) + }); let data = NonNull::from(s.as_ref()); Self { storage: PyBackedBytesStorage::Rust(s), diff --git a/src/types/bytes.rs b/src/types/bytes.rs index 2e01ddad3ce..c62b4cf5af5 100644 --- a/src/types/bytes.rs +++ b/src/types/bytes.rs @@ -209,6 +209,15 @@ impl<'a> Borrowed<'a, '_, PyBytes> { /// Gets the Python string as a byte slice. #[allow(clippy::wrong_self_convention)] pub(crate) fn as_bytes(self) -> &'a [u8] { + #[cfg(not(Py_LIMITED_API))] + unsafe { + let buffer = ffi::PyBytes_AS_STRING(self.as_ptr()).cast::(); + let length = ffi::Py_SIZE(self.as_ptr()) as usize; + debug_assert!(!buffer.is_null()); + core::slice::from_raw_parts(buffer, length) + } + + #[cfg(Py_LIMITED_API)] unsafe { let buffer = ffi::PyBytes_AsString(self.as_ptr()) as *const u8; let length = ffi::PyBytes_Size(self.as_ptr()) as usize; @@ -470,6 +479,17 @@ mod tests { }) } + #[test] + fn test_py_as_bytes() { + let pyobj: Py = Python::attach(|py| PyBytes::new(py, b"abc").unbind()); + + let data = Python::attach(|py| pyobj.as_bytes(py)); + + assert_eq!(data, b"abc"); + + Python::attach(move |_py| drop(pyobj)); + } + #[test] fn test_with_writer() { Python::attach(|py| { diff --git a/src/types/datetime.rs b/src/types/datetime.rs index 4854ddae6a4..954f205dcc1 100644 --- a/src/types/datetime.rs +++ b/src/types/datetime.rs @@ -890,8 +890,17 @@ fn opt_to_pyobj(opt: Option<&Bound<'_, PyTzInfo>>) -> *mut ffi::PyObject { #[cfg(test)] mod tests { use super::*; + + #[cfg(not(Py_LIMITED_API))] + use crate::ffi::PyDateTime_IMPORT; #[cfg(feature = "macros")] use crate::py_run; + use crate::types::{IntoPyDict, PyDate, PyDateTime, PyTime, PyTzInfo}; + + use alloc::ffi::CString; + use core::iter; + + use assert_approx_eq::assert_approx_eq; #[test] #[cfg(feature = "macros")] @@ -998,4 +1007,219 @@ mod tests { PyTzInfo::fixed_offset(py, PyDelta::new(py, 1, 0, 0, true).unwrap()).unwrap_err(); }) } + + fn _get_subclasses<'py>( + py: Python<'py>, + py_type: &str, + args: &str, + ) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>, Bound<'py, PyAny>)> { + // Import the class from Python and create some subclasses + let datetime = py.import("datetime")?; + + let locals = [(py_type, datetime.getattr(py_type)?)] + .into_py_dict(py) + .unwrap(); + + let make_subclass_py = CString::new(format!("class Subklass({py_type}):\n pass"))?; + + let make_sub_subclass_py = c"class SubSubklass(Subklass):\n pass"; + + py.run(&make_subclass_py, None, Some(&locals))?; + py.run(make_sub_subclass_py, None, Some(&locals))?; + + // Construct an instance of the base class + let obj = py.eval( + &CString::new(format!("{py_type}({args})"))?, + None, + Some(&locals), + )?; + + // Construct an instance of the subclass + let sub_obj = py.eval( + &CString::new(format!("Subklass({args})"))?, + None, + Some(&locals), + )?; + + // Construct an instance of the sub-subclass + let sub_sub_obj = py.eval( + &CString::new(format!("SubSubklass({args})"))?, + None, + Some(&locals), + )?; + + Ok((obj, sub_obj, sub_sub_obj)) + } + + #[cfg(not(Py_LIMITED_API))] + macro_rules! assert_check_exact { + ($check_func:ident, $check_func_exact:ident, $obj: expr) => { + unsafe { + use crate::ffi::*; + assert_ne!($check_func(($obj).as_ptr()), 0); + assert_ne!($check_func_exact(($obj).as_ptr()), 0); + } + }; + } + + #[cfg(not(Py_LIMITED_API))] + macro_rules! assert_check_only { + ($check_func:ident, $check_func_exact:ident, $obj: expr) => { + unsafe { + use crate::ffi::*; + assert_ne!($check_func(($obj).as_ptr()), 0); + assert_eq!($check_func_exact(($obj).as_ptr()), 0); + } + }; + } + + #[test] + #[cfg(not(Py_LIMITED_API))] + fn test_date_check() { + Python::attach(|py| { + let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "date", "2018, 1, 1").unwrap(); + unsafe { PyDateTime_IMPORT() } + assert_check_exact!(PyDate_Check, PyDate_CheckExact, obj); + assert_check_only!(PyDate_Check, PyDate_CheckExact, sub_obj); + assert_check_only!(PyDate_Check, PyDate_CheckExact, sub_sub_obj); + assert!(obj.is_instance_of::()); + assert!(!obj.is_instance_of::()); + assert!(!obj.is_instance_of::()); + }); + } + + #[test] + #[cfg(not(Py_LIMITED_API))] + fn test_time_check() { + Python::attach(|py| { + let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "time", "12, 30, 15").unwrap(); + unsafe { PyDateTime_IMPORT() } + + assert_check_exact!(PyTime_Check, PyTime_CheckExact, obj); + assert_check_only!(PyTime_Check, PyTime_CheckExact, sub_obj); + assert_check_only!(PyTime_Check, PyTime_CheckExact, sub_sub_obj); + assert!(!obj.is_instance_of::()); + assert!(obj.is_instance_of::()); + assert!(!obj.is_instance_of::()); + }); + } + + #[test] + #[cfg(not(Py_LIMITED_API))] + fn test_datetime_check() { + Python::attach(|py| { + let (obj, sub_obj, sub_sub_obj) = + _get_subclasses(py, "datetime", "2018, 1, 1, 13, 30, 15") + .map_err(|e| e.display(py)) + .unwrap(); + unsafe { PyDateTime_IMPORT() } + + assert_check_only!(PyDate_Check, PyDate_CheckExact, obj); + assert_check_exact!(PyDateTime_Check, PyDateTime_CheckExact, obj); + assert_check_only!(PyDateTime_Check, PyDateTime_CheckExact, sub_obj); + assert_check_only!(PyDateTime_Check, PyDateTime_CheckExact, sub_sub_obj); + assert!(obj.is_instance_of::()); + assert!(!obj.is_instance_of::()); + assert!(obj.is_instance_of::()); + }); + } + + #[test] + #[cfg(not(Py_LIMITED_API))] + fn test_delta_check() { + Python::attach(|py| { + let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "timedelta", "1, -3").unwrap(); + unsafe { PyDateTime_IMPORT() } + + assert_check_exact!(PyDelta_Check, PyDelta_CheckExact, obj); + assert_check_only!(PyDelta_Check, PyDelta_CheckExact, sub_obj); + assert_check_only!(PyDelta_Check, PyDelta_CheckExact, sub_sub_obj); + }); + } + + #[test] + fn test_datetime_utc() { + Python::attach(|py| { + let utc = PyTzInfo::utc(py).unwrap(); + + let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(&utc)).unwrap(); + + let locals = [("dt", dt)].into_py_dict(py).unwrap(); + + let offset: f32 = py + .eval(c"dt.utcoffset().total_seconds()", None, Some(&locals)) + .unwrap() + .extract() + .unwrap(); + assert_approx_eq!(offset, 0f32); + }); + } + + static INVALID_DATES: &[(i32, u8, u8)] = &[ + (-1, 1, 1), + (0, 1, 1), + (10000, 1, 1), + (2 << 30, 1, 1), + (2018, 0, 1), + (2018, 13, 1), + (2018, 1, 0), + (2017, 2, 29), + (2018, 1, 32), + ]; + + static INVALID_TIMES: &[(u8, u8, u8, u32)] = + &[(25, 0, 0, 0), (255, 0, 0, 0), (0, 60, 0, 0), (0, 0, 61, 0)]; + + #[test] + fn test_pydate_out_of_bounds() { + Python::attach(|py| { + for val in INVALID_DATES { + let (year, month, day) = val; + let dt = PyDate::new(py, *year, *month, *day); + dt.unwrap_err(); + } + }); + } + + #[test] + fn test_pytime_out_of_bounds() { + Python::attach(|py| { + for val in INVALID_TIMES { + let (hour, minute, second, microsecond) = val; + let dt = PyTime::new(py, *hour, *minute, *second, *microsecond, None); + dt.unwrap_err(); + } + }); + } + + #[test] + fn test_pydatetime_out_of_bounds() { + Python::attach(|py| { + let valid_time = (0, 0, 0, 0); + let valid_date = (2018, 1, 1); + + let invalid_dates = INVALID_DATES.iter().zip(iter::repeat(&valid_time)); + let invalid_times = iter::repeat(&valid_date).zip(INVALID_TIMES.iter()); + + let vals = invalid_dates.chain(invalid_times); + + for val in vals { + let (date, time) = val; + let (year, month, day) = date; + let (hour, minute, second, microsecond) = time; + let dt = PyDateTime::new( + py, + *year, + *month, + *day, + *hour, + *minute, + *second, + *microsecond, + None, + ); + dt.unwrap_err(); + } + }); + } } diff --git a/src/types/frozenset.rs b/src/types/frozenset.rs index 6fa9a838f6e..f3afc323487 100644 --- a/src/types/frozenset.rs +++ b/src/types/frozenset.rs @@ -159,7 +159,15 @@ pub trait PyFrozenSetMethods<'py>: crate::sealed::Sealed { impl<'py> PyFrozenSetMethods<'py> for Bound<'py, PyFrozenSet> { #[inline] fn len(&self) -> usize { - unsafe { ffi::PySet_Size(self.as_ptr()) as usize } + let size = cfg_select! { + // SAFETY: self is a valid frozenset object. + not(any(Py_LIMITED_API, PyPy, GraalPy)) => unsafe { + ffi::PySet_GET_SIZE(self.as_ptr()) + }, + // SAFETY: self is a valid frozenset object. + _ => unsafe { ffi::PySet_Size(self.as_ptr()) }, + }; + size as usize } fn contains(&self, key: K) -> PyResult diff --git a/src/types/set.rs b/src/types/set.rs index 0106f852cbb..94f479ffff4 100644 --- a/src/types/set.rs +++ b/src/types/set.rs @@ -148,7 +148,15 @@ impl<'py> PySetMethods<'py> for Bound<'py, PySet> { #[inline] fn len(&self) -> usize { - unsafe { ffi::PySet_Size(self.as_ptr()) as usize } + let size = cfg_select! { + // SAFETY: self is a valid set object. + not(any(Py_LIMITED_API, PyPy, GraalPy)) => unsafe { + ffi::PySet_GET_SIZE(self.as_ptr()) + }, + // SAFETY: self is a valid set object. + _ => unsafe { ffi::PySet_Size(self.as_ptr()) }, + }; + size as usize } fn contains(&self, key: K) -> PyResult diff --git a/tests/test_anyhow.rs b/tests/test_anyhow.rs deleted file mode 100644 index 96ce5370a8d..00000000000 --- a/tests/test_anyhow.rs +++ /dev/null @@ -1,45 +0,0 @@ -#![cfg(feature = "anyhow")] - -use pyo3::wrap_pyfunction; - -#[test] -fn test_anyhow_py_function_ok_result() { - use pyo3::{py_run, pyfunction, Python}; - - #[pyfunction] - #[expect(clippy::unnecessary_wraps)] - fn produce_ok_result() -> anyhow::Result { - Ok(String::from("OK buddy")) - } - - Python::attach(|py| { - let func = wrap_pyfunction!(produce_ok_result)(py).unwrap(); - - py_run!( - py, - func, - r#" - func() - "# - ); - }); -} - -#[test] -fn test_anyhow_py_function_err_result() { - use pyo3::prelude::PyDictMethods; - use pyo3::{pyfunction, types::PyDict, Python}; - - #[pyfunction] - fn produce_err_result() -> anyhow::Result { - anyhow::bail!("error time") - } - - Python::attach(|py| { - let func = wrap_pyfunction!(produce_err_result)(py).unwrap(); - let locals = PyDict::new(py); - locals.set_item("func", func).unwrap(); - - py.run(c"func()", None, Some(&locals)).unwrap_err(); - }); -} diff --git a/tests/test_bytes.rs b/tests/test_bytes.rs deleted file mode 100644 index 0caaf2a37ff..00000000000 --- a/tests/test_bytes.rs +++ /dev/null @@ -1,52 +0,0 @@ -#![cfg(feature = "macros")] - -use pyo3::prelude::*; -use pyo3::types::PyBytes; - -mod test_utils; - -#[pyfunction] -fn bytes_pybytes_conversion(bytes: &[u8]) -> &[u8] { - bytes -} - -#[test] -fn test_pybytes_bytes_conversion() { - Python::attach(|py| { - let f = wrap_pyfunction!(bytes_pybytes_conversion)(py).unwrap(); - py_assert!(py, f, "f(b'Hello World') == b'Hello World'"); - }); -} - -#[pyfunction] -fn bytes_vec_conversion(py: Python<'_>, bytes: Vec) -> Bound<'_, PyBytes> { - PyBytes::new(py, bytes.as_slice()) -} - -#[test] -fn test_pybytes_vec_conversion() { - Python::attach(|py| { - let f = wrap_pyfunction!(bytes_vec_conversion)(py).unwrap(); - py_assert!(py, f, "f(b'Hello World') == b'Hello World'"); - }); -} - -#[test] -fn test_bytearray_vec_conversion() { - Python::attach(|py| { - let f = wrap_pyfunction!(bytes_vec_conversion)(py).unwrap(); - py_assert!(py, f, "f(bytearray(b'Hello World')) == b'Hello World'"); - }); -} - -#[test] -fn test_py_as_bytes() { - let pyobj: pyo3::Py = - Python::attach(|py| pyo3::types::PyBytes::new(py, b"abc").unbind()); - - let data = Python::attach(|py| pyobj.as_bytes(py)); - - assert_eq!(data, b"abc"); - - Python::attach(move |_py| drop(pyobj)); -} diff --git a/tests/test_datetime.rs b/tests/test_datetime.rs deleted file mode 100644 index 6168a37522b..00000000000 --- a/tests/test_datetime.rs +++ /dev/null @@ -1,226 +0,0 @@ -// TODO https://github.com/PyO3/pyo3/issues/5487 -#![allow(clippy::undocumented_unsafe_blocks)] -#![cfg(not(Py_LIMITED_API))] - -use pyo3::prelude::*; -use pyo3::types::{IntoPyDict, PyDate, PyDateTime, PyTime, PyTzInfo}; -use pyo3_ffi::PyDateTime_IMPORT; -use std::ffi::CString; - -fn _get_subclasses<'py>( - py: Python<'py>, - py_type: &str, - args: &str, -) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>, Bound<'py, PyAny>)> { - // Import the class from Python and create some subclasses - let datetime = py.import("datetime")?; - - let locals = [(py_type, datetime.getattr(py_type)?)] - .into_py_dict(py) - .unwrap(); - - let make_subclass_py = CString::new(format!("class Subklass({py_type}):\n pass"))?; - - let make_sub_subclass_py = c"class SubSubklass(Subklass):\n pass"; - - py.run(&make_subclass_py, None, Some(&locals))?; - py.run(make_sub_subclass_py, None, Some(&locals))?; - - // Construct an instance of the base class - let obj = py.eval( - &CString::new(format!("{py_type}({args})"))?, - None, - Some(&locals), - )?; - - // Construct an instance of the subclass - let sub_obj = py.eval( - &CString::new(format!("Subklass({args})"))?, - None, - Some(&locals), - )?; - - // Construct an instance of the sub-subclass - let sub_sub_obj = py.eval( - &CString::new(format!("SubSubklass({args})"))?, - None, - Some(&locals), - )?; - - Ok((obj, sub_obj, sub_sub_obj)) -} - -macro_rules! assert_check_exact { - ($check_func:ident, $check_func_exact:ident, $obj: expr) => { - unsafe { - use pyo3::ffi::*; - assert_ne!($check_func(($obj).as_ptr()), 0); - assert_ne!($check_func_exact(($obj).as_ptr()), 0); - } - }; -} - -macro_rules! assert_check_only { - ($check_func:ident, $check_func_exact:ident, $obj: expr) => { - unsafe { - use pyo3::ffi::*; - assert_ne!($check_func(($obj).as_ptr()), 0); - assert_eq!($check_func_exact(($obj).as_ptr()), 0); - } - }; -} - -#[test] -fn test_date_check() { - Python::attach(|py| { - let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "date", "2018, 1, 1").unwrap(); - unsafe { PyDateTime_IMPORT() } - assert_check_exact!(PyDate_Check, PyDate_CheckExact, obj); - assert_check_only!(PyDate_Check, PyDate_CheckExact, sub_obj); - assert_check_only!(PyDate_Check, PyDate_CheckExact, sub_sub_obj); - assert!(obj.is_instance_of::()); - assert!(!obj.is_instance_of::()); - assert!(!obj.is_instance_of::()); - }); -} - -#[test] -fn test_time_check() { - Python::attach(|py| { - let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "time", "12, 30, 15").unwrap(); - unsafe { PyDateTime_IMPORT() } - - assert_check_exact!(PyTime_Check, PyTime_CheckExact, obj); - assert_check_only!(PyTime_Check, PyTime_CheckExact, sub_obj); - assert_check_only!(PyTime_Check, PyTime_CheckExact, sub_sub_obj); - assert!(!obj.is_instance_of::()); - assert!(obj.is_instance_of::()); - assert!(!obj.is_instance_of::()); - }); -} - -#[test] -fn test_datetime_check() { - Python::attach(|py| { - let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "datetime", "2018, 1, 1, 13, 30, 15") - .map_err(|e| e.display(py)) - .unwrap(); - unsafe { PyDateTime_IMPORT() } - - assert_check_only!(PyDate_Check, PyDate_CheckExact, obj); - assert_check_exact!(PyDateTime_Check, PyDateTime_CheckExact, obj); - assert_check_only!(PyDateTime_Check, PyDateTime_CheckExact, sub_obj); - assert_check_only!(PyDateTime_Check, PyDateTime_CheckExact, sub_sub_obj); - assert!(obj.is_instance_of::()); - assert!(!obj.is_instance_of::()); - assert!(obj.is_instance_of::()); - }); -} - -#[test] -fn test_delta_check() { - Python::attach(|py| { - let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "timedelta", "1, -3").unwrap(); - unsafe { PyDateTime_IMPORT() } - - assert_check_exact!(PyDelta_Check, PyDelta_CheckExact, obj); - assert_check_only!(PyDelta_Check, PyDelta_CheckExact, sub_obj); - assert_check_only!(PyDelta_Check, PyDelta_CheckExact, sub_sub_obj); - }); -} - -#[test] -fn test_datetime_utc() { - use assert_approx_eq::assert_approx_eq; - use pyo3::types::PyDateTime; - - Python::attach(|py| { - let utc = PyTzInfo::utc(py).unwrap(); - - let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(&utc)).unwrap(); - - let locals = [("dt", dt)].into_py_dict(py).unwrap(); - - let offset: f32 = py - .eval(c"dt.utcoffset().total_seconds()", None, Some(&locals)) - .unwrap() - .extract() - .unwrap(); - assert_approx_eq!(offset, 0f32); - }); -} - -static INVALID_DATES: &[(i32, u8, u8)] = &[ - (-1, 1, 1), - (0, 1, 1), - (10000, 1, 1), - (2 << 30, 1, 1), - (2018, 0, 1), - (2018, 13, 1), - (2018, 1, 0), - (2017, 2, 29), - (2018, 1, 32), -]; - -static INVALID_TIMES: &[(u8, u8, u8, u32)] = - &[(25, 0, 0, 0), (255, 0, 0, 0), (0, 60, 0, 0), (0, 0, 61, 0)]; - -#[test] -fn test_pydate_out_of_bounds() { - use pyo3::types::PyDate; - - Python::attach(|py| { - for val in INVALID_DATES { - let (year, month, day) = val; - let dt = PyDate::new(py, *year, *month, *day); - dt.unwrap_err(); - } - }); -} - -#[test] -fn test_pytime_out_of_bounds() { - use pyo3::types::PyTime; - - Python::attach(|py| { - for val in INVALID_TIMES { - let (hour, minute, second, microsecond) = val; - let dt = PyTime::new(py, *hour, *minute, *second, *microsecond, None); - dt.unwrap_err(); - } - }); -} - -#[test] -fn test_pydatetime_out_of_bounds() { - use pyo3::types::PyDateTime; - use std::iter; - - Python::attach(|py| { - let valid_time = (0, 0, 0, 0); - let valid_date = (2018, 1, 1); - - let invalid_dates = INVALID_DATES.iter().zip(iter::repeat(&valid_time)); - let invalid_times = iter::repeat(&valid_date).zip(INVALID_TIMES.iter()); - - let vals = invalid_dates.chain(invalid_times); - - for val in vals { - let (date, time) = val; - let (year, month, day) = date; - let (hour, minute, second, microsecond) = time; - let dt = PyDateTime::new( - py, - *year, - *month, - *day, - *hour, - *minute, - *second, - *microsecond, - None, - ); - dt.unwrap_err(); - } - }); -} diff --git a/tests/test_enum.rs b/tests/test_enum.rs index b503c9762d9..104d852717f 100644 --- a/tests/test_enum.rs +++ b/tests/test_enum.rs @@ -6,8 +6,10 @@ use pyo3::types::PyString; mod test_utils; +// `Copy` + `from_py_object` is a regression test for `clippy::clone_on_copy` +// firing in the generated `FromPyObject` implementation (#6308) #[pyclass(eq, eq_int, from_py_object)] -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum MyEnum { Variant, OtherVariant, diff --git a/tests/test_serde.rs b/tests/test_serde.rs deleted file mode 100644 index 1c8954abe68..00000000000 --- a/tests/test_serde.rs +++ /dev/null @@ -1,79 +0,0 @@ -#![cfg(feature = "serde")] - -use pyo3::prelude::*; - -use serde::{Deserialize, Serialize}; - -#[pyclass] -#[derive(Debug, Serialize, Deserialize)] -struct Group { - name: String, -} - -#[pyclass] -#[derive(Debug, Serialize, Deserialize)] -struct User { - username: String, - group: Option>, - friends: Vec>, -} - -#[test] -fn test_serialize() { - let friend1 = User { - username: "friend 1".into(), - group: None, - friends: vec![], - }; - let friend2 = User { - username: "friend 2".into(), - group: None, - friends: vec![], - }; - - let user = Python::attach(|py| { - let py_friend1 = Py::new(py, friend1).expect("failed to create friend 1"); - let py_friend2 = Py::new(py, friend2).expect("failed to create friend 2"); - - let friends = vec![py_friend1, py_friend2]; - let py_group = Py::new( - py, - Group { - name: "group name".into(), - }, - ) - .unwrap(); - - User { - username: "danya".into(), - group: Some(py_group), - friends, - } - }); - - let serialized = serde_json::to_string(&user).expect("failed to serialize"); - assert_eq!( - serialized, - r#"{"username":"danya","group":{"name":"group name"},"friends":[{"username":"friend 1","group":null,"friends":[]},{"username":"friend 2","group":null,"friends":[]}]}"# - ); -} - -#[test] -fn test_deserialize() { - let serialized = r#"{"username": "danya", "friends": - [{"username": "friend", "group": {"name": "danya's friends"}, "friends": []}]}"#; - let user: User = serde_json::from_str(serialized).expect("failed to deserialize"); - - assert_eq!(user.username, "danya"); - assert!(user.group.is_none()); - assert_eq!(user.friends.len(), 1usize); - let friend = user.friends.first().unwrap(); - - Python::attach(|py| { - assert_eq!(friend.borrow(py).username, "friend"); - assert_eq!( - friend.borrow(py).group.as_ref().unwrap().borrow(py).name, - "danya's friends" - ) - }); -} diff --git a/tests/test_string.rs b/tests/test_string.rs deleted file mode 100644 index 1648e067760..00000000000 --- a/tests/test_string.rs +++ /dev/null @@ -1,22 +0,0 @@ -#![cfg(feature = "macros")] - -use pyo3::prelude::*; - -mod test_utils; - -#[pyfunction] -fn take_str(_s: &str) {} - -#[test] -fn test_unicode_encode_error() { - Python::attach(|py| { - let take_str = wrap_pyfunction!(take_str)(py).unwrap(); - py_expect_exception!( - py, - take_str, - "take_str('\\ud800')", - PyUnicodeEncodeError, - "'utf-8' codec can't encode character '\\ud800' in position 0: surrogates not allowed" - ); - }); -} diff --git a/tests/ui/base/Cargo.toml b/tests/ui/base/Cargo.toml index 0077c74f9cd..13edb88961b 100644 --- a/tests/ui/base/Cargo.toml +++ b/tests/ui/base/Cargo.toml @@ -2,6 +2,7 @@ name = "pyo3_ui_tests" version = "0.1.0" edition = "2021" +publish = false [dependencies] pyo3 = { version = "0.29.2", default-features = false, path = "../../../" } diff --git a/tests/ui/invalid_pyclass_args.default.stderr b/tests/ui/invalid_pyclass_args.default.stderr index 38db1f8f55e..fec93e82dc5 100644 --- a/tests/ui/invalid_pyclass_args.default.stderr +++ b/tests/ui/invalid_pyclass_args.default.stderr @@ -440,7 +440,7 @@ error[E0277]: `Box` cannot be used as a Pyt HashOptRequiresHash NewFromFieldsWithManualNew and $N others - = note: required for `Box` to implement `pyo3::FromPyObject<'_, '_>` + = note: required for `Box` to implement `FromPyObject<'_, '_>` = note: required for `Box` to implement `pyo3::impl_::extract_argument::PyFunctionArgument<'_, '_, '_, true>` note: required by a bound in `pyo3::impl_::extract_argument::extract_argument` --> src/impl_/extract_argument.rs @@ -469,7 +469,7 @@ error[E0277]: `Box` cannot be used as a Pyt HashOptRequiresHash NewFromFieldsWithManualNew and $N others - = note: required for `Box` to implement `pyo3::FromPyObject<'_, '_>` + = note: required for `Box` to implement `FromPyObject<'_, '_>` = note: required for `Box` to implement `pyo3::impl_::extract_argument::PyFunctionArgument<'_, '_, '_, true>` note: required by a bound in `pyo3::impl_::extract_argument::extract_argument` --> src/impl_/extract_argument.rs @@ -507,7 +507,7 @@ help: the following other types implement trait `pyo3::impl_::extract_argument:: | | for &'holder mut T | |______________________^ `&'holder mut T` implements `pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'holder, '_, false>` = note: required for `Box` to implement `Clone` - = note: required for `Box` to implement `pyo3::FromPyObject<'_, '_>` + = note: required for `Box` to implement `FromPyObject<'_, '_>` = note: required for `Box` to implement `pyo3::impl_::extract_argument::PyFunctionArgument<'_, '_, '_, true>` note: required by a bound in `pyo3::impl_::extract_argument::extract_argument` --> src/impl_/extract_argument.rs diff --git a/tests/ui/invalid_pyclass_args.inspect.stderr b/tests/ui/invalid_pyclass_args.inspect.stderr index db32279ab8a..f6aa4245656 100644 --- a/tests/ui/invalid_pyclass_args.inspect.stderr +++ b/tests/ui/invalid_pyclass_args.inspect.stderr @@ -470,7 +470,7 @@ error[E0277]: `Box` cannot be used as a Pyt HashOptRequiresHash NewFromFieldsWithManualNew and $N others - = note: required for `Box` to implement `pyo3::FromPyObject<'_, '_>` + = note: required for `Box` to implement `FromPyObject<'_, '_>` = note: required for `Box` to implement `pyo3::impl_::extract_argument::PyFunctionArgument<'_, '_, '_, true>` note: required by a bound in `pyo3::impl_::extract_argument::extract_argument` --> src/impl_/extract_argument.rs @@ -499,7 +499,7 @@ error[E0277]: `Box` cannot be used as a Pyt HashOptRequiresHash NewFromFieldsWithManualNew and $N others - = note: required for `Box` to implement `pyo3::FromPyObject<'_, '_>` + = note: required for `Box` to implement `FromPyObject<'_, '_>` = note: required for `Box` to implement `pyo3::impl_::extract_argument::PyFunctionArgument<'_, '_, '_, true>` note: required by a bound in `pyo3::impl_::extract_argument::extract_argument` --> src/impl_/extract_argument.rs @@ -537,7 +537,7 @@ help: the following other types implement trait `pyo3::impl_::extract_argument:: | | for &'holder mut T | |______________________^ `&'holder mut T` implements `pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'holder, '_, false>` = note: required for `Box` to implement `Clone` - = note: required for `Box` to implement `pyo3::FromPyObject<'_, '_>` + = note: required for `Box` to implement `FromPyObject<'_, '_>` = note: required for `Box` to implement `pyo3::impl_::extract_argument::PyFunctionArgument<'_, '_, '_, true>` note: required by a bound in `pyo3::impl_::extract_argument::extract_argument` --> src/impl_/extract_argument.rs diff --git a/wasm/common.mk b/wasm/common.mk index 8c0ce9848ce..aa7b106b0f6 100644 --- a/wasm/common.mk +++ b/wasm/common.mk @@ -6,20 +6,18 @@ CURDIR=$(abspath .) BUILDROOT ?= $(CURDIR)/builddir PYTHON ?= python3 PYMAJORMINORMICRO ?= $(shell $(PYTHON) --version 2>&1 | awk '{print $$2}') +PYPRERELEASE ?= # Set version variables. version_tuple := $(subst ., ,$(PYMAJORMINORMICRO:v%=%)) PYMAJOR=$(word 1,$(version_tuple)) PYMINOR=$(word 2,$(version_tuple)) PYMICRO=$(word 3,$(version_tuple)) -PYVERSION=$(PYMAJORMINORMICRO) +PYVERSION=$(PYMAJORMINORMICRO)$(PYPRERELEASE) PYMAJORMINOR=$(PYMAJOR).$(PYMINOR) -ifneq ($(PYMAJORMINOR),3.14) -$(error PYMAJORMINOR must be 3.14, got '$(PYMAJORMINOR)') -endif - -PYTHONURL=https://www.python.org/ftp/python/$(PYMAJORMINORMICRO)/Python-$(PYVERSION).tgz +PYTHONRELEASE=$(shell echo $(PYVERSION) | sed -E 's/(a|b|rc)[0-9]+$$//') +PYTHONURL=https://www.python.org/ftp/python/$(PYTHONRELEASE)/Python-$(PYVERSION).tgz PYTHONTARBALL=$(BUILDROOT)/downloads/Python-$(PYVERSION).tgz PYTHONBUILD=$(BUILDROOT)/build/Python-$(PYVERSION) @@ -40,5 +38,11 @@ $(PYTHONBUILD)/.exists: $(PYTHONTARBALL) ) touch $@ +.PHONY: prepare clean + +# downloads the Python source and extracts ready for config +# parsing and build +prepare: $(PYTHONBUILD)/.exists + clean: rm -rf $(BUILDROOT) diff --git a/wasm/emscripten/Makefile b/wasm/emscripten/Makefile index e0c36435234..3f668f24128 100644 --- a/wasm/emscripten/Makefile +++ b/wasm/emscripten/Makefile @@ -1,6 +1,10 @@ include ../common.mk -NODE_VERSION=24.18.0 +ifneq ($(PYMAJORMINOR),3.15) +$(error PYMAJORMINOR must be 3.15, got '$(PYMAJORMINOR)') +endif + +NODE_VERSION=24.20.0 PLATFORM=wasm32_emscripten SYSCONFIGDATA_NAME=_sysconfigdata__$(PLATFORM) diff --git a/wasm/wasi/Makefile b/wasm/wasi/Makefile index 4d5bb241d5d..c25d1d5de79 100644 --- a/wasm/wasi/Makefile +++ b/wasm/wasi/Makefile @@ -1,9 +1,13 @@ include ../common.mk -WASI_SDK_VERSION=24 +ifneq ($(PYMAJORMINOR),3.15) +$(error PYMAJORMINOR must be 3.15, got '$(PYMAJORMINOR)') +endif + +WASI_SDK_VERSION=$(shell $(PYTHON) -c 'import tomllib; print(tomllib.load(open("$(PYTHONBUILD)/Platforms/WASI/config.toml", "rb"))["targets"]["wasi-sdk"])') WASMTIME_VERSION=46.0.1 -CONFIG_SITE=$(PYTHONBUILD)/Tools/wasm/wasi/config.site-wasm32-wasi +CONFIG_SITE=$(PYTHONBUILD)/Platforms/WASI/config.site-wasm32-wasi CROSSBUILD=$(PYTHONBUILD)/cross-build/wasm32-wasip1 LIBDIR=$(CROSSBUILD)/build/lib.wasi-wasm32-$(PYMAJORMINOR) @@ -36,7 +40,7 @@ endif all: $(LIBDIR)/libpython$(PYMAJORMINOR).a -$(WASI_SDK_DIR)/.exists: $(BUILDROOT)/.exists +$(WASI_SDK_DIR)/.exists: $(PYTHONBUILD)/.exists [ -d $(WASI_SDK_DIR) ] || mkdir -p $(WASI_SDK_DIR) curl -s -S --location $(WASI_SDK_URL) | \ tar --strip-components 1 --directory $(WASI_SDK_DIR) --extract --gunzip @@ -58,10 +62,9 @@ $(LIBDIR)/libpython$(PYMAJORMINOR).a: $(PYTHONBUILD)/.patched $(WASI_SDK_DEP) $( cd $(PYTHONBUILD) && \ WASI_SDK_PATH=$(WASI_SDK_DIR) \ PATH=$(WASMTIME_PATH)$(PATH) \ - $(PYTHON) Tools/wasm/wasi build -- --config-cache + $(PYTHON) Platforms/WASI build -- --config-cache # Collect the static libraries the test build links against cp $(CROSSBUILD)/libpython$(PYMAJORMINOR).a \ - $(CROSSBUILD)/Modules/_hacl/libHacl_HMAC.a \ - $(CROSSBUILD)/Modules/_decimal/libmpdec/libmpdec.a \ + $(CROSSBUILD)/Modules/_hacl/*.a \ $(CROSSBUILD)/Modules/expat/libexpat.a \ $(LIBDIR)