From a6ca723596b3d876b5c534bedd8620c672538f94 Mon Sep 17 00:00:00 2001 From: chiri Date: Sun, 21 Jun 2026 23:34:56 +0300 Subject: [PATCH 01/50] more cases for bigint benchmark (#6148) --- pyo3-benches/benches/bench_bigint.rs | 74 +++++++++++++++++++--------- 1 file changed, 50 insertions(+), 24 deletions(-) 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); From ec959bd1fef07e28d6835a620e577ac91dcce169 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers <7943856+bschoenmaeckers@users.noreply.github.com> Date: Thu, 25 Jun 2026 18:19:48 +0200 Subject: [PATCH 02/50] Enable `PyLong(Writer|Export)` api on abi3 from 3.15+ (#6160) --- newsfragments/6160.added.md | 1 + pyo3-ffi/src/cpython/longintrepr.rs | 55 ----------------------------- pyo3-ffi/src/cpython/mod.rs | 4 --- pyo3-ffi/src/longobject.rs | 49 +++++++++++++++++++++++++ src/conversions/std/num.rs | 18 +++++----- 5 files changed, 59 insertions(+), 68 deletions(-) create mode 100644 newsfragments/6160.added.md delete mode 100644 pyo3-ffi/src/cpython/longintrepr.rs 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/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/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/longobject.rs b/pyo3-ffi/src/longobject.rs index 7c777fecc14..5c72771a7f2 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; @@ -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/src/conversions/std/num.rs b/src/conversions/std/num.rs index 50e72cca5f9..18c9dc62f16 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -356,10 +356,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 +380,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 +391,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 +416,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 +444,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 +627,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 +650,7 @@ pub(crate) fn nb_index<'py>(obj: &Bound<'py, PyAny>) -> PyResult Date: Mon, 13 Jul 2026 10:29:22 -0700 Subject: [PATCH 03/50] fix typo in performance.md (#6201) --- guide/src/performance.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/src/performance.md b/guide/src/performance.md index 8b5d91fd7ed..8588070959e 100644 --- a/guide/src/performance.md +++ b/guide/src/performance.md @@ -105,7 +105,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 From b11724a7cb948f4c95dfde2d71be6ceb1a06bb39 Mon Sep 17 00:00:00 2001 From: Bruno Kolenbrander <59372212+mejrs@users.noreply.github.com> Date: Sun, 19 Jul 2026 13:37:43 +0200 Subject: [PATCH 04/50] update `native_doc` and use c style literals (#6210) --- src/exceptions.rs | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) 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, From 0e69581b2100cbfb1c707e06aef6631a3f8ce676 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 09:15:58 +0200 Subject: [PATCH 05/50] build(deps): bump actions/setup-node from 6 to 7 (#6218) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6 to 7. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3e1d250dd1..c608ddca37a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -509,7 +509,7 @@ jobs: with: targets: wasm32-unknown-emscripten components: rust-src - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version: 24 - uses: actions/cache/restore@v6 From 751f1e23b214db582e17f1e16d5dd7d6ae6aaf26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 09:16:56 +0200 Subject: [PATCH 06/50] build(deps): bump actions/setup-python from 6 to 7 (#6217) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6 to 7. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/benches.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/changelog.yml | 2 +- .github/workflows/ci-cache-warmup.yml | 2 +- .github/workflows/ci.yml | 24 ++++++++++++------------ .github/workflows/coverage-pr-base.yml | 2 +- .github/workflows/netlify-build.yml | 2 +- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/benches.yml b/.github/workflows/benches.yml index 6dfb55b08a9..faab8f39d73 100644 --- a/.github/workflows/benches.yml +++ b/.github/workflows/benches.yml @@ -25,7 +25,7 @@ jobs: # 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 }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e5482361ea..793b3aecf5b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,7 +44,7 @@ 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 }} diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 2938aa128b1..5fc28dde746 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - 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..3749df579ab 100644 --- a/.github/workflows/ci-cache-warmup.yml +++ b/.github/workflows/ci-cache-warmup.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: "3.14" - uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c608ddca37a..5ce66536e3c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: "3.14" - uses: astral-sh/setup-uv@v7 @@ -55,7 +55,7 @@ jobs: 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/setup-python@v7 with: python-version: "3.14" - name: resolve MSRV @@ -68,7 +68,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: "3.14" - name: Fetch merge base @@ -90,7 +90,7 @@ jobs: 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 @@ -440,7 +440,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: "3.14" - uses: Swatinem/rust-cache@v2 @@ -461,7 +461,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: "3.14" - uses: Swatinem/rust-cache@v2 @@ -483,7 +483,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: "3.14" - uses: Swatinem/rust-cache@v2 @@ -650,7 +650,7 @@ jobs: - rust: ${{ needs.resolve.outputs.MSRV }} steps: - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: "3.15-dev" - uses: Swatinem/rust-cache@v2 @@ -701,7 +701,7 @@ jobs: - 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 @@ -810,7 +810,7 @@ jobs: - 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 }} @@ -829,7 +829,7 @@ jobs: steps: - uses: actions/checkout@v7.0.0 - 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 @@ -848,7 +848,7 @@ jobs: - 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 diff --git a/.github/workflows/coverage-pr-base.yml b/.github/workflows/coverage-pr-base.yml index 6d45a0312a1..43394913102 100644 --- a/.github/workflows/coverage-pr-base.yml +++ b/.github/workflows/coverage-pr-base.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - 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..678eab58b4e 100644 --- a/.github/workflows/netlify-build.yml +++ b/.github/workflows/netlify-build.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7.0.0 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: "3.14" - uses: astral-sh/setup-uv@v7 From 77c5ea331a12937a0903455202f06f252101ae6c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 13:08:26 +0200 Subject: [PATCH 07/50] build(deps): bump actions/checkout from 7.0.0 to 7.0.1 (#6216) Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0 to 7.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v7...v7.0.1) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/benches.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/changelog.yml | 2 +- .github/workflows/ci-cache-warmup.yml | 2 +- .github/workflows/ci.yml | 40 +++++++++++++------------- .github/workflows/coverage-pr-base.yml | 2 +- .github/workflows/netlify-build.yml | 2 +- .github/workflows/python-wheel.yml | 8 +++--- .github/workflows/release.yml | 2 +- 9 files changed, 31 insertions(+), 31 deletions(-) diff --git a/.github/workflows/benches.yml b/.github/workflows/benches.yml index faab8f39d73..2bf9bd3bcc9 100644 --- a/.github/workflows/benches.yml +++ b/.github/workflows/benches.yml @@ -18,7 +18,7 @@ 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') }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 793b3aecf5b..76617aea247 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 }} diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 5fc28dde746..ad46e947379 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -9,7 +9,7 @@ jobs: name: Check changelog entry runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: actions/setup-python@v7 with: python-version: '3.14' diff --git a/.github/workflows/ci-cache-warmup.yml b/.github/workflows/ci-cache-warmup.yml index 3749df579ab..31f44324a25 100644 --- a/.github/workflows/ci-cache-warmup.yml +++ b/.github/workflows/ci-cache-warmup.yml @@ -9,7 +9,7 @@ jobs: cross-compilation-windows: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: actions/setup-python@v7 with: python-version: "3.14" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ce66536e3c..d92f1cdcaea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: fmt: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: actions/setup-python@v7 with: python-version: "3.14" @@ -54,7 +54,7 @@ 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/checkout@v7.0.1 - uses: actions/setup-python@v7 with: python-version: "3.14" @@ -67,7 +67,7 @@ jobs: needs: [fmt] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: actions/setup-python@v7 with: python-version: "3.14" @@ -85,7 +85,7 @@ 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 }} @@ -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,7 +439,7 @@ jobs: needs: [fmt] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: actions/setup-python@v7 with: python-version: "3.14" @@ -460,7 +460,7 @@ jobs: needs: [fmt] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: actions/setup-python@v7 with: python-version: "3.14" @@ -482,7 +482,7 @@ jobs: needs: [fmt] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: actions/setup-python@v7 with: python-version: "3.14" @@ -500,7 +500,7 @@ jobs: needs: [fmt] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: astral-sh/setup-uv@v7 with: save-cache: ${{ needs.resolve.outputs.save-cache }} @@ -539,7 +539,7 @@ jobs: needs: [fmt] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: astral-sh/setup-uv@v7 with: save-cache: ${{ needs.resolve.outputs.save-cache }} @@ -583,7 +583,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') }} @@ -601,7 +601,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 +619,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,7 +649,7 @@ jobs: include: - rust: ${{ needs.resolve.outputs.MSRV }} steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: actions/setup-python@v7 with: python-version: "3.15-dev" @@ -695,7 +695,7 @@ 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 @@ -749,7 +749,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,7 +806,7 @@ 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 }} @@ -827,7 +827,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 - uses: dtolnay/rust-toolchain@stable - uses: actions/setup-python@v7 with: @@ -844,7 +844,7 @@ 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 diff --git a/.github/workflows/coverage-pr-base.yml b/.github/workflows/coverage-pr-base.yml index 43394913102..8b531d89360 100644 --- a/.github/workflows/coverage-pr-base.yml +++ b/.github/workflows/coverage-pr-base.yml @@ -12,7 +12,7 @@ jobs: coverage-pr-base: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: actions/setup-python@v7 with: python-version: '3.14' diff --git a/.github/workflows/netlify-build.yml b/.github/workflows/netlify-build.yml index 678eab58b4e..42fbf3023b5 100644 --- a/.github/workflows/netlify-build.yml +++ b/.github/workflows/netlify-build.yml @@ -19,7 +19,7 @@ jobs: guide-build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.0 + - uses: actions/checkout@v7.0.1 - uses: actions/setup-python@v7 with: python-version: "3.14" 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..5f779864bda 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 }} From ff1450b8669dc1a2673fc4863030c333facf6815 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 23 Jul 2026 13:23:39 -0400 Subject: [PATCH 08/50] Use PySet_GET_SIZE for sets and frozensets (#6226) --- src/types/frozenset.rs | 10 +++++++++- src/types/set.rs | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) 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 From 9122729b203e587546da09f5b1e1029720fa46ab Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 24 Jul 2026 04:32:13 -0700 Subject: [PATCH 09/50] Preallocate Rust sets when extracting Python sets (#6225) * Preallocate sets when extracting Python set values * Document Python set extraction allocation improvement * Remove ineffective set capacity assertions * Preallocate BTreeSet extraction buffers * Fix Clippy Vec import for set extraction --- newsfragments/6225.changed.md | 1 + src/conversions/hashbrown.rs | 20 ++++++++++------- src/conversions/std/set.rs | 41 +++++++++++++++++++++-------------- 3 files changed, 38 insertions(+), 24 deletions(-) create mode 100644 newsfragments/6225.changed.md 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/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/std/set.rs b/src/conversions/std/set.rs index ce8f4ecf108..d028fc47dd1 100644 --- a/src/conversions/std/set.rs +++ b/src/conversions/std/set.rs @@ -1,3 +1,4 @@ +use alloc::vec::Vec; use core::{cmp, hash}; use std::collections; @@ -58,16 +59,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 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)) } From 0f0e436d8e68f2c413e0fd191cd45387bf8703e1 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Fri, 24 Jul 2026 18:12:57 +0100 Subject: [PATCH 10/50] update `PySet_GET_SIZE` and similar for free-threaded Python (#6230) * update `PySet_GET_SIZE` and similar for free-threaded Python * split `cpython` portion of `setobject.h` off --- newsfragments/6230.fixed.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/6230.fixed.md 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. From 50bb22cdbc1901740d51d35b11a79a02a785341a Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Sat, 1 Aug 2026 00:27:04 +0100 Subject: [PATCH 11/50] internal: simplify module creation conditional branches (#6275) * internal: simplify module creation conditional branches * further cleanup * fixup test --- src/impl_/pymodule.rs | 122 +++++++++++++----------------------------- 1 file changed, 37 insertions(+), 85 deletions(-) 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] From 8b3addf3d5bccf5d6e3982c9851f8622c8a5951d Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Thu, 6 Aug 2026 07:22:22 +0100 Subject: [PATCH 12/50] ci: fix ffi-check for backport of `Py_CompileStringFlags` symbol (#6302) * ci: fix ffi-check for backport of `Py_CompileStringFlags` symbol * always use latest Python versions --- .github/workflows/build.yml | 3 +-- pyo3-ffi-check/macro/src/lib.rs | 2 +- pyo3-ffi/src/cpython/pythonrun.rs | 7 +++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 76617aea247..7329e246834 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,8 +48,7 @@ jobs: 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/pyo3-ffi-check/macro/src/lib.rs b/pyo3-ffi-check/macro/src/lib.rs index 2c448640362..7cf78e5642d 100644 --- a/pyo3-ffi-check/macro/src/lib.rs +++ b/pyo3-ffi-check/macro/src/lib.rs @@ -428,7 +428,7 @@ const MACRO_EXCLUSIONS: &[(&str, &str)] = &[ ("PyVectorcall_NARGS", "not(Py_3_12)"), ("Py_CLEAR", ""), ("Py_CompileString", "not(Py_3_10)"), - ("Py_CompileStringFlags", "all(not(PyPy), not(Py_3_15))"), + ("Py_CompileStringFlags", "all(not(PyPy), not(Py_3_13))"), ("Py_DECREF", ""), ("Py_Ellipsis", ""), ("Py_False", ""), diff --git a/pyo3-ffi/src/cpython/pythonrun.rs b/pyo3-ffi/src/cpython/pythonrun.rs index e2df4ea298e..d20ff8a984b 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, From 229cc7290f7d1ed1d321e9a6181d95ff67f3c047 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 8 Aug 2026 15:24:55 -0400 Subject: [PATCH 13/50] fix merge queue breakages (#6310) * ci: pin the `careful` job to nightly-2026-08-06 From nightly-2026-08-07 cargo no longer finds the sysroot `cargo careful` builds for itself when running test binaries. Proc-macro crates link `libstd` dynamically, so the `pyo3-macros` test binary can't start: error while loading shared libraries: libstd-.so: cannot open shared object file: No such file or directory That aborts the job before it gets to anything else. Pin to the last nightly where it works; revert to `@nightly` once https://github.com/RalfJung/cargo-careful/issues/55 is fixed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01N96dd4VafzejRKxEuk1pU4 * ci: fix `wasm32-wasip1` job against CPython 3.14.7 Two problems, both surfaced by CPython 3.14 moving to a new patch release: * `test-wasm` points the embedded interpreter at `PYTHONPATH=/lib`, which has never existed in the CPython checkout. It worked because `getpath` found the build tree itself, via the `Modules/Setup.local` landmark next to the "executable". CPython 3.14.7 removed that fallback (gh-151544), so the test binaries now die on startup with "Fatal Python error: Failed to import encodings module". Pass the stdlib and the WASI build directory explicitly. * The `.nox/wasi` cache is keyed on `UV_PYTHON` ("3.14"), not on the CPython version nox builds and then looks for, so a new patch release restores the previous one's build and skips the build step. `cargo test` then fails immediately with "failed to search the lib dir at PYO3_CROSS_LIB_DIR=...". Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01N96dd4VafzejRKxEuk1pU4 * Update .github/workflows/ci.yml Co-authored-by: David Hewitt * Update noxfile.py Co-authored-by: David Hewitt * Update .github/workflows/ci.yml --------- Co-authored-by: Claude Co-authored-by: David Hewitt --- .github/workflows/ci.yml | 16 +++++++++++++--- noxfile.py | 5 ++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d92f1cdcaea..0a5f47b3928 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -467,8 +467,11 @@ jobs: - 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 @@ -557,12 +560,19 @@ jobs: # 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 + # Key the cache on the CPython version nox will actually build so a new + # patch release busts cache properly + - name: Resolve CPython version for the WASI build + id: wasi-python + run: | + version=$(uv run --no-project --python "$UV_PYTHON" python -c 'import sys; print(".".join(map(str, sys.version_info[:3])))') + echo "version=$version" >> "$GITHUB_OUTPUT" - 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 }} + key: wasi-${{ hashFiles('wasm/wasi/*', 'wasm/common.mk') }}-${{ hashFiles('noxfile.py') }}-${{ steps.wasi-python.outputs.version }} - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} @@ -576,7 +586,7 @@ jobs: with: path: | .nox/wasi - key: wasi-${{ hashFiles('wasm/wasi/*', 'wasm/common.mk') }}-${{ hashFiles('noxfile.py') }}-${{ env.UV_PYTHON }} + key: wasi-${{ hashFiles('wasm/wasi/*', 'wasm/common.mk') }}-${{ hashFiles('noxfile.py') }}-${{ steps.wasi-python.outputs.version }} test-debug: if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} diff --git a/noxfile.py b/noxfile.py index dff06ff6fdf..2a018799f9f 100644 --- a/noxfile.py +++ b/noxfile.py @@ -552,8 +552,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( [ From 8c09a895b58d79f18811445871f1ed3023516416 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 8 Aug 2026 15:28:59 -0400 Subject: [PATCH 14/50] fix `clippy::clone_on_copy` firing on `#[pyclass(from_py_object)]` + `Copy` classes (#6309) * fix `clippy::clone_on_copy` firing on `#[pyclass(from_py_object)]` + `Copy` classes Move the clone out of the generated `FromPyObject` impl into a generic helper where the type is only known to be `Clone`, so the lint cannot trigger. Fixes #6308. Co-Authored-By: Claude Fable 5 * bless UI test snapshot for trimmed `FromPyObject` diagnostic paths The generated `extract` body no longer references `FromPyObject` items inline, which changes how rustc renders the trait path in unrelated diagnostics in the same crate. Co-Authored-By: Claude Fable 5 * bless `default` revision UI test snapshot as well Same trimmed-path diagnostic drift as the `inspect` revision; this snapshot only runs without `experimental-inspect` enabled. Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 --- newsfragments/6309.fixed.md | 1 + pyo3-macros-backend/src/pyclass.rs | 2 +- src/impl_/pyclass.rs | 15 +++++++++++++-- tests/test_enum.rs | 4 +++- tests/ui/invalid_pyclass_args.default.stderr | 6 +++--- tests/ui/invalid_pyclass_args.inspect.stderr | 6 +++--- 6 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 newsfragments/6309.fixed.md 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/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/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/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/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 From 36130ad2e711660400a55b506c634daf777084d8 Mon Sep 17 00:00:00 2001 From: Recoordinate Date: Sun, 9 Aug 2026 07:46:35 +0700 Subject: [PATCH 15/50] Fix documentation typo (#6312) --- guide/src/free-threading.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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. From 4d2e0170e964723f1546166f168d912487189790 Mon Sep 17 00:00:00 2001 From: Jonas Dedden Date: Sun, 9 Aug 2026 19:43:17 +0200 Subject: [PATCH 16/50] fix(inspect): deduplicate union members and space the `|` on both sides (#6273) Review --- newsfragments/6273.fixed.md | 1 + pyo3-introspection/src/stubs.rs | 78 +++++++++++++++++++++++++++++---- pytests/src/path.rs | 16 +++++++ pytests/stubs/path.pyi | 1 + 4 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 newsfragments/6273.fixed.md 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/pyo3-introspection/src/stubs.rs b/pyo3-introspection/src/stubs.rs index f89c250eef8..7c7dd113397 100644 --- a/pyo3-introspection/src/stubs.rs +++ b/pyo3-introspection/src/stubs.rs @@ -3,7 +3,7 @@ use crate::model::{ VariableLengthArgument, }; 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; @@ -274,6 +274,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 +497,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('('); @@ -1021,4 +1043,44 @@ mod tests { assert!(stubs.contains("\n Summary.\n\n Detail.\n")); assert!(stubs.contains("\nConst summary.\n\nConst detail.\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]" + ); + } } 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/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: ... From 450bc5d180609e622d0b72b097a9c4b646d76753 Mon Sep 17 00:00:00 2001 From: Francisco Gouveia Date: Sun, 9 Aug 2026 20:19:20 +0100 Subject: [PATCH 17/50] chore: remove redundant clone (#6304) Co-authored-by: David Hewitt --- pyo3-build-config/src/impl_.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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. From 2bcd593c4db9649264f6286532aa019412af1068 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Aug 2026 21:48:40 +0000 Subject: [PATCH 18/50] build(deps): bump CodSpeedHQ/action from 5.0.1 to 5.0.3 (#6316) Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 5.0.1 to 5.0.3. - [Release notes](https://github.com/codspeedhq/action/releases) - [Changelog](https://github.com/CodSpeedHQ/action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codspeedhq/action/compare/v5.0.1...v5.0.3) --- updated-dependencies: - dependency-name: CodSpeedHQ/action dependency-version: 5.0.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/benches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benches.yml b/.github/workflows/benches.yml index 2bf9bd3bcc9..e1d0de8e6d8 100644 --- a/.github/workflows/benches.yml +++ b/.github/workflows/benches.yml @@ -47,7 +47,7 @@ jobs: tool: cargo-codspeed - name: Run the benchmarks - uses: CodSpeedHQ/action@v5.0.1 + uses: CodSpeedHQ/action@v5.0.3 with: run: uvx nox -s codspeed token: ${{ secrets.CODSPEED_TOKEN }} From dd64c90897f24326cd591e67e92755c0576453dd Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Sun, 16 Aug 2026 12:02:06 +0000 Subject: [PATCH 19/50] refactor: move some tests inside the lib (#6311) * refactor: move some tests inside the lib * fix abi3 gates * correct std imports * fixup imports * fixup chrono-tz tests * format --- Cargo.toml | 16 --- src/conversions/chrono_tz.rs | 17 +-- src/conversions/serde.rs | 81 ++++++++++++ src/conversions/std/string.rs | 16 +++ src/types/bytes.rs | 11 ++ src/types/datetime.rs | 224 +++++++++++++++++++++++++++++++++ tests/test_anyhow.rs | 45 ------- tests/test_bytes.rs | 52 -------- tests/test_datetime.rs | 226 ---------------------------------- tests/test_serde.rs | 79 ------------ tests/test_string.rs | 22 ---- 11 files changed, 342 insertions(+), 447 deletions(-) delete mode 100644 tests/test_anyhow.rs delete mode 100644 tests/test_bytes.rs delete mode 100644 tests/test_datetime.rs delete mode 100644 tests/test_serde.rs delete mode 100644 tests/test_string.rs 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/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/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/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/types/bytes.rs b/src/types/bytes.rs index 2e01ddad3ce..c4b30460997 100644 --- a/src/types/bytes.rs +++ b/src/types/bytes.rs @@ -470,6 +470,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/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_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" - ); - }); -} From fd8d48795d7dbfc29f9fc8bd8e1205d7869f6c5c Mon Sep 17 00:00:00 2001 From: Jason Mak Date: Thu, 20 Aug 2026 18:31:48 +0000 Subject: [PATCH 20/50] docs: add String interning section to the performance guide (#6336) Addresses one item from gh-3310's checklist ("String intern!"). The guide already covers extract-vs-cast, Bound::py, calling conventions, Python::detach, and the reference pool, but never mentioned intern!, even though it's one of the simplest, most broadly applicable optimizations (avoiding a repeated PyString allocation at call sites that reuse the same string, e.g. dict keys/attribute names). The example follows the same before/after style as the rest of the guide and is adapted from the intern! macro's own doc comment in src/sync.rs, which is already an existing, passing doctest - not new, unverified example code. Not addressing gh-3310's other checklist items in this PR: - The Vec/Cow<[u8]> item was confirmed already resolved by the IntoPyObject rework in the issue's own comment thread (by @Icxolu), so documenting it as a live gotcha would now be inaccurate. - The remaining items (conversion overhead in general, #[pyo3(get)] deep-cloning, dictionary dispatch) need more source investigation than fits one PR; leaving those for follow-up. --- guide/src/performance.md | 41 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/guide/src/performance.md b/guide/src/performance.md index 8588070959e..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. From fcc7fa99cb6da0fcef8170dfd91393031cabc5f0 Mon Sep 17 00:00:00 2001 From: person93 Date: Fri, 21 Aug 2026 19:13:11 +0000 Subject: [PATCH 21/50] blocklist suspicious symbols in pyo3-ffi-check bindgen (#6337) --- pyo3-ffi-check/definitions/build.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pyo3-ffi-check/definitions/build.rs b/pyo3-ffi-check/definitions/build.rs index 992da8a360c..02e765d77ed 100644 --- a/pyo3-ffi-check/definitions/build.rs +++ b/pyo3-ffi-check/definitions/build.rs @@ -61,7 +61,13 @@ 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)) + .blocklist_item("memcpy") + .blocklist_item("memmove") + .blocklist_item("memset") + .blocklist_item("memcmp") + .blocklist_item("strlen") + .blocklist_item("bcmp"); if matches!( config.implementation(), From 21b4d6a218a78ee172d31601f1e76d867ce71a4b Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Sun, 23 Aug 2026 05:53:20 +0000 Subject: [PATCH 22/50] ci: bump wasi builds to use 3.15, read WASI SDK from cpython config (#6340) * ci: bump wasi builds to use 3.15, read WASI SDK from cpython config * fixup emscripten build --- .github/actions/build-emscripten/action.yml | 51 ++++++++++++++ .github/actions/build-wasi/action.yml | 59 +++++++++++++++++ .github/workflows/ci-cache-warmup.yml | 16 +++++ .github/workflows/ci.yml | 73 ++------------------- noxfile.py | 35 ++++++++-- wasm/common.mk | 16 +++-- wasm/emscripten/Makefile | 4 ++ wasm/wasi/Makefile | 15 +++-- 8 files changed, 184 insertions(+), 85 deletions(-) create mode 100644 .github/actions/build-emscripten/action.yml create mode 100644 .github/actions/build-wasi/action.yml 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/ci-cache-warmup.yml b/.github/workflows/ci-cache-warmup.yml index 31f44324a25..5e386b7a960 100644 --- a/.github/workflows/ci-cache-warmup.yml +++ b/.github/workflows/ci-cache-warmup.yml @@ -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 0a5f47b3928..5039f01e8f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -500,93 +500,28 @@ 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.1 - - uses: astral-sh/setup-uv@v7 + - 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@v7 - 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.1 - - uses: astral-sh/setup-uv@v7 + - 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 - # Key the cache on the CPython version nox will actually build so a new - # patch release busts cache properly - - name: Resolve CPython version for the WASI build - id: wasi-python - run: | - version=$(uv run --no-project --python "$UV_PYTHON" python -c 'import sys; print(".".join(map(str, sys.version_info[:3])))') - echo "version=$version" >> "$GITHUB_OUTPUT" - - uses: actions/cache/restore@v6 - id: cache - with: - path: | - .nox/wasi - key: wasi-${{ hashFiles('wasm/wasi/*', 'wasm/common.mk') }}-${{ hashFiles('noxfile.py') }}-${{ steps.wasi-python.outputs.version }} - - 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') }}-${{ steps.wasi-python.outputs.version }} test-debug: if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} diff --git a/noxfile.py b/noxfile.py index 2a018799f9f..459420e9cf2 100644 --- a/noxfile.py +++ b/noxfile.py @@ -486,6 +486,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/156243 + "-C link-arg=-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$stringToNewUTF8", ] ) session.env["RUSTDOCFLAGS"] = session.env["RUSTFLAGS"] @@ -524,14 +526,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 +540,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() @@ -564,7 +586,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", ] 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..4264cfc36a1 100644 --- a/wasm/emscripten/Makefile +++ b/wasm/emscripten/Makefile @@ -1,5 +1,9 @@ include ../common.mk +ifneq ($(PYMAJORMINOR),3.15) +$(error PYMAJORMINOR must be 3.15, got '$(PYMAJORMINOR)') +endif + NODE_VERSION=24.18.0 PLATFORM=wasm32_emscripten 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) From e0da5e478bd12931900ce672b0c4a4c24955d731 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Sun, 23 Aug 2026 07:54:51 +0000 Subject: [PATCH 23/50] ci: fail merge if wasm build fails (#6341) --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5039f01e8f3..e15fa31ed8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -812,6 +812,7 @@ jobs: - careful - docsrs - emscripten + - wasm32-wasip1 - test-debug - test-version-limits - check-feature-powerset From 1060d1b573635b9cde25d9a49d4941df19c071e6 Mon Sep 17 00:00:00 2001 From: Jonas Dedden Date: Sun, 23 Aug 2026 07:57:22 +0000 Subject: [PATCH 24/50] `experimental-inspect`: Iterator `__next__` not optional (#6274) * `experimental-inspect`: cover the `Option`-returning `__next__` / `__anext__` shapes Route A: one wrapper carrying both the conversion and the type hint Review pass on the `IterNextOutput` wrapper pytests: assert the generated `__next__` / `__anext__` hints * Address review - Use `StaticIdent` for the hard-coded wrapper / fallback names instead of `&str` + `format_ident!` (and `TokenGenerator` on the slot side). - Drop the `asyncness` guard around the `__next__` / `__anext__` return type: `async fn` does not compile for *any* slot method today (the slot body hands the future straight to `IntoPyCallbackOutput`), so the guard was unreachable. - Note in `impl_/pymethods.rs` that `am_anext` has no null-without-error convention, so `StopAsyncIteration` has to be raised explicitly. * pytests: skip the async-iter test on GraalPy < 25.1 `PyClassOptionAsyncIter.__anext__` signals exhaustion the way the `am_anext` slot has to: it raises `StopAsyncIteration` synchronously, since the slot has no "returned null, no error set" convention. GraalPy < 25.1 lets such a synchronous raise escape `async for` instead of ending the loop. That is not specific to native classes -- a plain Python class with a non-`async def __anext__` reproduces it -- and it is already fixed in GraalPy 25.1, so gate the test on the version rather than on the implementation alone. --- guide/src/class/protocols.md | 2 +- newsfragments/6274.fixed.md | 1 + pyo3-macros-backend/src/py_expr.rs | 56 +++++- pyo3-macros-backend/src/pyimpl.rs | 8 + pyo3-macros-backend/src/pymethod.rs | 42 +++-- pytests/pyproject.toml | 3 +- pytests/src/awaitable.rs | 2 +- pytests/src/pyclasses.rs | 91 +++++++++- pytests/stubs/pyclasses.pyi | 28 +++ pytests/tests/test_pyclasses.py | 40 +++++ src/impl_/pymethods.rs | 260 ++++++++++++---------------- 11 files changed, 360 insertions(+), 173 deletions(-) create mode 100644 newsfragments/6274.fixed.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/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/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/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..10fc7a77bdd 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -1096,18 +1096,15 @@ 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 __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"); @@ -1299,7 +1296,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 +1313,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! { @@ -1434,12 +1436,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 } diff --git a/pytests/pyproject.toml b/pytests/pyproject.toml index f36f6d94376..9c43b3e24d0 100644 --- a/pytests/pyproject.toml +++ b/pytests/pyproject.toml @@ -27,5 +27,6 @@ dev = [ "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/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/stubs/pyclasses.pyi b/pytests/stubs/pyclasses.pyi index 64692e0dc9c..e385b2f886b 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 @@ -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/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/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() { From e1d20412b03619a586ea78302b659997125193ad Mon Sep 17 00:00:00 2001 From: person93 Date: Thu, 27 Aug 2026 05:35:06 +0000 Subject: [PATCH 25/50] internal: suppress warning caused by stabilization of never type (#6354) * suppress warning * suppress warning in proc-macro generated code --- pyo3-macros-backend/src/pymethod.rs | 1 + src/conversions/std/num.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index 10fc7a77bdd..6690f7b5ce4 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -1230,6 +1230,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 diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 18c9dc62f16..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())) } From 49fe213aecfa1bd514c93899c506e03d8cec50a0 Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 27 Aug 2026 17:05:11 +0000 Subject: [PATCH 26/50] ci: Use Cargo workspace publishing (#6355) * Use Cargo workspace publishing Simplify the publishing setup by using https://doc.rust-lang.org/cargo/CHANGELOG.html#cargo-190-2025-09-18 * Remove `cargo publish` for noxfile It's only runnable in CI anyway. --- .github/workflows/release.yml | 2 +- noxfile.py | 14 -------------- tests/ui/base/Cargo.toml | 1 + 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5f779864bda..40fac6907d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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/noxfile.py b/noxfile.py index 459420e9cf2..1e2db49e0a9 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 @@ -1964,10 +1954,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/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 = "../../../" } From b4c5f1218f068062554a5b7356018cecaf958529 Mon Sep 17 00:00:00 2001 From: Peter Faiman Date: Fri, 28 Aug 2026 08:20:07 +0000 Subject: [PATCH 27/50] Fix double copy creating `PyBackedBytes` from `PyByteArray` (#6357) `Arc::<[u8]>::from(py_bytearray.to_vec())` creates a `Vec` copy of the `PyByteArray` contents, then immediately copies that `Vec` into an `Arc<[u8]>`. It should copy the `PyByteArray` contents directly into the `Arc<[u8]>`. This PR inlines `PyByteArrayMethods::to_vec` into `PyBackedBytes::from` without the unnecessary copy. See: . --- src/pybacked.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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), From 20ef214f39c8142676c4c24a1613e4e95882a4ee Mon Sep 17 00:00:00 2001 From: Jonas Dedden Date: Fri, 28 Aug 2026 13:46:25 +0000 Subject: [PATCH 28/50] `experimental-inspect`: write nested package stubs into their own directory (#6365) * experimental-inspect: write nested package stubs into their own directory * Correct newsfragement number --- newsfragments/6365.fixed.md | 1 + pyo3-introspection/src/stubs.rs | 90 ++++++++++++++++++++++++++++----- 2 files changed, 77 insertions(+), 14 deletions(-) create mode 100644 newsfragments/6365.fixed.md 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/pyo3-introspection/src/stubs.rs b/pyo3-introspection/src/stubs.rs index 7c7dd113397..0713ab9dd36 100644 --- a/pyo3-introspection/src/stubs.rs +++ b/pyo3-introspection/src/stubs.rs @@ -6,7 +6,7 @@ use std::borrow::Cow; 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 +15,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, + ); } } } @@ -1083,4 +1085,64 @@ mod tests { "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()); + } } From 4e9497a1c7597854ac1b6526b4c4cdb157506355 Mon Sep 17 00:00:00 2001 From: Jonas Dedden Date: Fri, 28 Aug 2026 21:27:47 +0000 Subject: [PATCH 29/50] internal: register all pytests submodules in sys.modules (#6361) --- pytests/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) 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")?)?; From 5288c06fa9aeb3f594fa497aecb3adfc6c0b44ae Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Mon, 31 Aug 2026 17:22:30 +0000 Subject: [PATCH 30/50] internal: preliminary fixes for PyPy 3.12 (#6374) * preliminary fixes for PyPy 3.12 * fixup --- pyo3-ffi/src/cpython/dictobject.rs | 2 +- pyo3-ffi/src/cpython/pyframe.rs | 3 +++ pyo3-ffi/src/object.rs | 1 - pyo3-ffi/src/pyerrors.rs | 2 ++ pyo3-ffi/src/pythonrun.rs | 1 + 5 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pyo3-ffi/src/cpython/dictobject.rs b/pyo3-ffi/src/cpython/dictobject.rs index df98a94c870..b7f91ab56df 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))] diff --git a/pyo3-ffi/src/cpython/pyframe.rs b/pyo3-ffi/src/cpython/pyframe.rs index 7b75f6e6ddd..121cdc4eca6 100644 --- a/pyo3-ffi/src/cpython/pyframe.rs +++ b/pyo3-ffi/src/cpython/pyframe.rs @@ -66,11 +66,14 @@ extern_libpython! { 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/object.rs b/pyo3-ffi/src/object.rs index a9bc476b4b7..a43a5092c0a 100644 --- a/pyo3-ffi/src/object.rs +++ b/pyo3-ffi/src/object.rs @@ -390,7 +390,6 @@ 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)] diff --git a/pyo3-ffi/src/pyerrors.rs b/pyo3-ffi/src/pyerrors.rs index 29892bdf561..25d4e2db717 100644 --- a/pyo3-ffi/src/pyerrors.rs +++ b/pyo3-ffi/src/pyerrors.rs @@ -50,8 +50,10 @@ extern_libpython! { arg3: *mut *mut PyObject, ); #[cfg(Py_3_12)] + #[cfg_attr(PyPy, link_name = "PyPyErr_GetRaisedException")] pub fn PyErr_GetRaisedException() -> *mut PyObject; #[cfg(Py_3_12)] + #[cfg_attr(PyPy, link_name = "PyPyErr_SetRaisedException")] pub fn PyErr_SetRaisedException(exc: *mut PyObject); #[cfg(Py_3_11)] #[cfg_attr(PyPy, link_name = "PyPyErr_GetHandledException")] diff --git a/pyo3-ffi/src/pythonrun.rs b/pyo3-ffi/src/pythonrun.rs index 91c12a1931c..b105cb183e8 100644 --- a/pyo3-ffi/src/pythonrun.rs +++ b/pyo3-ffi/src/pythonrun.rs @@ -17,6 +17,7 @@ extern_libpython! { pub fn PyErr_Display(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject); #[cfg(Py_3_12)] + #[cfg_attr(PyPy, link_name = "PyPyErr_DisplayException")] pub fn PyErr_DisplayException(exc: *mut PyObject); } From 28a2a452aa7aa916c12f2c2492bbde43bb1b49fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 04:12:15 +0000 Subject: [PATCH 31/50] build(deps): bump CodSpeedHQ/action from 5.0.3 to 5.2.1 (#6379) Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 5.0.3 to 5.2.1. - [Release notes](https://github.com/codspeedhq/action/releases) - [Changelog](https://github.com/CodSpeedHQ/action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codspeedhq/action/compare/v5.0.3...v5.2.1) --- updated-dependencies: - dependency-name: CodSpeedHQ/action dependency-version: 5.2.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/benches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benches.yml b/.github/workflows/benches.yml index e1d0de8e6d8..4a2fa4915e8 100644 --- a/.github/workflows/benches.yml +++ b/.github/workflows/benches.yml @@ -47,7 +47,7 @@ jobs: tool: cargo-codspeed - name: Run the benchmarks - uses: CodSpeedHQ/action@v5.0.3 + uses: CodSpeedHQ/action@v5.2.1 with: run: uvx nox -s codspeed token: ${{ secrets.CODSPEED_TOKEN }} From f315aaaa978d8e11e1cb1b5e4cafa4ec5d0b1cf7 Mon Sep 17 00:00:00 2001 From: Emanuele Giaquinta Date: Tue, 1 Sep 2026 06:38:17 +0000 Subject: [PATCH 32/50] Optimize PyBytes::as_bytes() in the unlimited API (#6377) --- newsfragments/6377.changed.md | 1 + src/types/bytes.rs | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 newsfragments/6377.changed.md 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/src/types/bytes.rs b/src/types/bytes.rs index c4b30460997..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; From 921e0caabda8782bfbc5b5b676650761cd5e5a35 Mon Sep 17 00:00:00 2001 From: adamgerhant-ai Date: Wed, 2 Sep 2026 05:58:12 +0000 Subject: [PATCH 33/50] Add eval frame FFI bindings (#6195) * Add eval frame FFI bindings * Add eval frame FFI bindings * Fix eval code extra index link name --- newsfragments/6195.added.md | 1 + newsfragments/6195.fixed.md | 1 + pyo3-ffi/src/cpython/ceval.rs | 4 ++-- pyo3-ffi/src/cpython/pystate.rs | 34 +++++++++++++++++++++++++++++---- pyo3-ffi/src/impl_/macros.rs | 12 ++++++++++++ 5 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 newsfragments/6195.added.md create mode 100644 newsfragments/6195.fixed.md 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/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/pystate.rs b/pyo3-ffi/src/cpython/pystate.rs index 52e15224cc2..e7d728d934a 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; @@ -78,8 +94,18 @@ extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyThreadState_DeleteCurrent")] pub fn PyThreadState_DeleteCurrent(); -} -// skipped private _PyFrameEvalFunction -// skipped private _PyInterpreterState_GetEvalFrameFunc -// skipped private _PyInterpreterState_SetEvalFrameFunc + #[cfg(all(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(not(PyPy))] + pub fn _PyInterpreterState_SetEvalFrameFunc( + interp: *mut PyInterpreterState, + eval_frame: Option<_PyFrameEvalFunction>, + ); +} diff --git a/pyo3-ffi/src/impl_/macros.rs b/pyo3-ffi/src/impl_/macros.rs index 1ef2ce99091..9e1fb2130b7 100644 --- a/pyo3-ffi/src/impl_/macros.rs +++ b/pyo3-ffi/src/impl_/macros.rs @@ -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)? From 1fad7690e6b1db621fd4a9042aefc7019ee623a1 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Sun, 6 Sep 2026 11:06:28 +0000 Subject: [PATCH 34/50] ci: workaround node 24.20 emscripten build break (#6385) --- noxfile.py | 4 ++-- wasm/emscripten/Makefile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/noxfile.py b/noxfile.py index 1e2db49e0a9..0e715dec110 100644 --- a/noxfile.py +++ b/noxfile.py @@ -476,8 +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/156243 - "-C link-arg=-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$stringToNewUTF8", + # https://github.com/python/cpython/issues/156780 + "-C link-arg=-sMAIN_MODULE=2", ] ) session.env["RUSTDOCFLAGS"] = session.env["RUSTFLAGS"] diff --git a/wasm/emscripten/Makefile b/wasm/emscripten/Makefile index 4264cfc36a1..3f668f24128 100644 --- a/wasm/emscripten/Makefile +++ b/wasm/emscripten/Makefile @@ -4,7 +4,7 @@ ifneq ($(PYMAJORMINOR),3.15) $(error PYMAJORMINOR must be 3.15, got '$(PYMAJORMINOR)') endif -NODE_VERSION=24.18.0 +NODE_VERSION=24.20.0 PLATFORM=wasm32_emscripten SYSCONFIGDATA_NAME=_sysconfigdata__$(PLATFORM) From e1355b0551bf5d2e769b59048c555b980ea3941b Mon Sep 17 00:00:00 2001 From: Joren Hammudoglu Date: Mon, 7 Sep 2026 15:08:10 +0000 Subject: [PATCH 35/50] Fix empty tuple type hints rendering as invalid `tuple[]` (#6393) * Fix empty tuple type hints rendering as invalid `tuple[]` * remove empty tuple test already covered by integration test --- newsfragments/6393.fixed.md | 1 + pyo3-introspection/src/stubs.rs | 8 ++++++-- pytests/src/pyfunctions.rs | 10 ++++++++-- pytests/stubs/pyfunctions.pyi | 1 + src/inspect.rs | 24 ++++++++++++++++++++++-- 5 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 newsfragments/6393.fixed.md 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/pyo3-introspection/src/stubs.rs b/pyo3-introspection/src/stubs.rs index 0713ab9dd36..9af1269e6c1 100644 --- a/pyo3-introspection/src/stubs.rs +++ b/pyo3-introspection/src/stubs.rs @@ -531,8 +531,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); } 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/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/src/inspect.rs b/src/inspect.rs index 33f27e4e018..aa7201cb722 100644 --- a/src/inspect.rs +++ b/src/inspect.rs @@ -302,8 +302,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 +475,22 @@ mod tests { ) } + #[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) { From ec1b8181b72801a49bd73c4e99389678332628ba Mon Sep 17 00:00:00 2001 From: Jonas Dedden Date: Wed, 9 Sep 2026 08:36:31 +0000 Subject: [PATCH 36/50] `experimental-inspect`: give slot-wrapper trailing arguments their `None` default (#6363) * experimental-inspect: give slot-wrapper trailing arguments their `None` default * test: cover the slot table's optional trailing argument counts * Drop redundant optional_trailing_args test --- newsfragments/6363.fixed.md | 1 + .../src/pyfunction/signature.rs | 21 ++++++ pyo3-macros-backend/src/pymethod.rs | 67 ++++++++++++++++++- pytests/stubs/pyclasses.pyi | 2 +- 4 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 newsfragments/6363.fixed.md 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/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/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index 6690f7b5ce4..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,7 +1104,9 @@ 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_iter_conversion( StaticIdent::new("IterNextOutput"), @@ -1343,6 +1355,7 @@ pub struct SlotDef { extract_error_mode: ExtractErrorMode, return_mode: Option, require_unsafe: bool, + optional_trailing_args: usize, } enum SlotCallingConvention { @@ -1367,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() { @@ -1422,6 +1446,7 @@ impl SlotDef { extract_error_mode: ExtractErrorMode::Raise, return_mode: None, require_unsafe: false, + optional_trailing_args: 0, } } @@ -1473,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!( @@ -1691,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 { @@ -1701,6 +1729,7 @@ impl SlotFragmentDef { extract_error_mode: ExtractErrorMode::Raise, ret_ty: Ty::Void, self_conversion: SelfConversionPolicy::checked(), + optional_trailing_args: 0, } } @@ -1720,6 +1749,7 @@ impl SlotFragmentDef { extract_error_mode: ExtractErrorMode::NotImplemented, ret_ty: Ty::Object, self_conversion: SelfConversionPolicy::checked(), + optional_trailing_args: 0, } } @@ -1738,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, @@ -1751,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()); @@ -1865,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) @@ -1968,3 +2014,18 @@ fn doc_to_optional_cstr(doc: Option<&PythonDoc>, ctx: &Ctx) -> Result 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: ... From 97d69041f3bc1e1fca3028c6b52b9ca9ef4670e7 Mon Sep 17 00:00:00 2001 From: Dylan Pulver <35541198+dylanpulver@users.noreply.github.com> Date: Wed, 9 Sep 2026 09:41:35 +0000 Subject: [PATCH 37/50] Escape control characters in string constants as Python, not as Rust (#6397) * Escape control characters as Python, not as Rust `Display for PyStaticExpr` rendered string constants with `{value:?}`, which uses Rust's escaping rules. Rust writes an unprintable character as `\u{1b}`; Python's `\u` escape takes exactly four hex digits, so that form is a SyntaxError rather than an escape. Any string constant holding a C0 control character other than NUL, tab, newline or carriage return therefore rendered as invalid Python -- an ANSI sequence in a signature default such as `"\x1b[0m"` is the realistic way to hit it. The sibling renderer in `pyo3-introspection/src/stubs.rs` already gets this right, writing `\x1b`. This mirrors its escape table so the two agree. The existing test covered `"\0\t\\\""`, the four characters whose Rust and Python escapes happen to be identical, so the divergence did not show up. * Add newsfragment * Cover the newline, carriage return and literal-character arms --- newsfragments/6397.fixed.md | 1 + src/inspect.rs | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 newsfragments/6397.fixed.md 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/src/inspect.rs b/src/inspect.rs index aa7201cb722..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), @@ -475,6 +491,22 @@ 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; From 1d4524d39d8e84e9a43ac1a6e549ad2782b90de1 Mon Sep 17 00:00:00 2001 From: Joren Hammudoglu Date: Wed, 9 Sep 2026 13:01:36 +0000 Subject: [PATCH 38/50] Introspection: use `typing_extensions.Buffer` instead of `collections.abc.Buffer` (py312+) (#6395) * Introspection: use `typing_extensions.Buffer` instead of `collections.abc.Buffer` (py312+) * Introspection: only import `Buffer` from `typing_extensions` on `python<3.12` Co-authored-by: Jonas Dedden * Introspection: apply review suggestions for the `typing_extensions.Buffer` fix Co-authored-by: Thomas Tanon --------- Co-authored-by: Jonas Dedden Co-authored-by: Thomas Tanon --- newsfragments/6395.fixed.md | 1 + pytests/src/buf_and_str.rs | 5 ++--- pytests/stubs/buf_and_str.pyi | 6 +++--- src/buffer.rs | 24 +++++++++++++++++++++++- 4 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 newsfragments/6395.fixed.md 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/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/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/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| { From 6348055dfbced55a16244bda6fd0f27693038ffc Mon Sep 17 00:00:00 2001 From: Joren Hammudoglu Date: Thu, 10 Sep 2026 17:01:31 +0000 Subject: [PATCH 39/50] Introspection: escape stub docstrings to prevent invalid syntax (#6394) * Introspection: Escape stub docstrings to prevent invalid syntax. * Introspection: don't escape `"` in docstrings unless necessary Co-authored-by: Thomas Tanon --------- Co-authored-by: Thomas Tanon --- newsfragments/6394.fixed.md | 1 + pyo3-introspection/src/stubs.rs | 56 ++++++++++++++++++++++++++------- 2 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 newsfragments/6394.fixed.md 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/pyo3-introspection/src/stubs.rs b/pyo3-introspection/src/stubs.rs index 9af1269e6c1..365b9bb3cf1 100644 --- a/pyo3-introspection/src/stubs.rs +++ b/pyo3-introspection/src/stubs.rs @@ -2,6 +2,7 @@ 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, HashSet}; use std::fmt::Write; @@ -96,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); @@ -261,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'); @@ -1000,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(), @@ -1019,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"]); @@ -1044,10 +1069,17 @@ 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] From ef3a8f6e493383ca577351322d698190fcf17bc3 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Sat, 12 Sep 2026 20:37:58 +0000 Subject: [PATCH 40/50] Record successful attach only after attaching in SuspendAttach (#6404) * Record successful attach only after attaching in SuspendAttach * fix limited API builds * add release note * simplify test --- newsfragments/6404.fixed.md | 1 + pytests/src/misc.rs | 35 ++++++++++++++++++++--------------- src/internal/state.rs | 16 ++++++++-------- 3 files changed, 29 insertions(+), 23 deletions(-) create mode 100644 newsfragments/6404.fixed.md 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/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/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); } } } From 7707a8d87b33c9af92b39252a5f036ff5677da57 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Mon, 14 Sep 2026 18:05:25 +0000 Subject: [PATCH 41/50] ci: don't build `hypothesis` from source (#6411) --- pytests/noxfile.py | 6 +- pytests/pyproject.toml | 1 - pytests/tests/test_datetime.py | 131 --------------------- pytests/tests/test_datetime_hypothesis.py | 133 ++++++++++++++++++++++ pytests/tests/test_othermod.py | 25 ---- pytests/tests/test_othermod_hypothesis.py | 27 +++++ 6 files changed, 163 insertions(+), 160 deletions(-) create mode 100644 pytests/tests/test_datetime_hypothesis.py create mode 100644 pytests/tests/test_othermod_hypothesis.py diff --git a/pytests/noxfile.py b/pytests/noxfile.py index 1a390c85b32..e33f490fd31 100644 --- a/pytests/noxfile.py +++ b/pytests/noxfile.py @@ -22,10 +22,10 @@ def try_install_binary(package: str, constraint: str): pass try_install_binary("numpy", ">=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.171.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 9c43b3e24d0..66f8349d65a 100644 --- a/pytests/pyproject.toml +++ b/pytests/pyproject.toml @@ -20,7 +20,6 @@ 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", 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 From f9e618293175e697eaaea16dc5b53ee46cba4f4f Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Tue, 15 Sep 2026 18:51:55 +0000 Subject: [PATCH 42/50] ci: correct hypothesis bound to start from first native component release (#6417) --- pytests/noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytests/noxfile.py b/pytests/noxfile.py index e33f490fd31..90e7d0ba8ae 100644 --- a/pytests/noxfile.py +++ b/pytests/noxfile.py @@ -25,7 +25,7 @@ def try_install_binary(package: str, constraint: str): 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.171.1") + try_install_binary("hypothesis", ">=6.156.1") ignored_paths = [] if sys.version_info < (3, 10): # Match syntax is only available in Python >= 3.10 From 0c9ddb2d247b9dd346197fd1146066c0532d7fb5 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 16 Sep 2026 02:54:01 +0000 Subject: [PATCH 43/50] fix raw-dylib opt-out on windows x86 (#6410) * fix raw-dylib opt-out on windows x86 * newsfragment * correct PyPy build failures * `PyVectorcall_Call` is 3.12+ * workaround incorrect lib name for PyPy with raw-dylib opt out * fix `PyVectorcall_Call` cfg --- newsfragments/6410.fixed.2.md | 1 + newsfragments/6410.fixed.3.md | 1 + newsfragments/6410.fixed.md | 1 + noxfile.py | 17 ++++--- pyo3-ffi-check/definitions/Cargo.toml | 3 +- pyo3-ffi-check/definitions/build.rs | 71 +++++++++++++-------------- pyo3-ffi-check/macro/src/lib.rs | 39 ++++++++++----- pyo3-ffi-check/src/main.rs | 55 +++++++++++++++++---- pyo3-ffi/build.rs | 24 +++++++++ pyo3-ffi/src/abstract_.rs | 2 +- pyo3-ffi/src/impl_/macros.rs | 2 +- pyo3-ffi/src/objimpl.rs | 9 +++- 12 files changed, 156 insertions(+), 69 deletions(-) create mode 100644 newsfragments/6410.fixed.2.md create mode 100644 newsfragments/6410.fixed.3.md create mode 100644 newsfragments/6410.fixed.md 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/noxfile.py b/noxfile.py index 0e715dec110..c8acb98e123 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1316,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) 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 02e765d77ed..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');", @@ -62,40 +66,31 @@ fn main() { .clang_args(clang_args) .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .parse_callbacks(Box::new(ParseCallbacks)) - .blocklist_item("memcpy") - .blocklist_item("memmove") - .blocklist_item("memset") - .blocklist_item("memcmp") - .blocklist_item("strlen") - .blocklist_item("bcmp"); + // 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 7cf78e5642d..480c19f5fb2 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 { @@ -195,6 +195,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 @@ -444,9 +445,6 @@ 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. @@ -486,6 +484,17 @@ 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", ]; // Assert at compile time that `MACRO_EXCLUSIONS` and `EXCLUDED_SYMBOLS` are disjoint @@ -539,13 +548,20 @@ pub fn for_all_functions(_input: proc_macro::TokenStream) -> proc_macro::TokenSt continue; } - if pyo3_build_config::get().implementation() - == pyo3_build_config::PythonImplementation::PyPy - { + let mut bindgen_name = function_name.to_owned(); + if pyo3_build_config::get().implementation() == PythonImplementation::PyPy { + // For PyPy, some functions are prefixed with "PyPy", we check whether the + // bindgen name contains the prefixed name and use that if it does. + if function_name.starts_with("Py") || function_name.starts_with("_Py") { + let prefixed_name = function_name.replacen("Py", "PyPy", 1); + if BINDGEN_FUNCTION_NAMES.contains(&prefixed_name) { + bindgen_name = prefixed_name; + } + } // 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) { + if !BINDGEN_FUNCTION_NAMES.contains(&bindgen_name) { continue; } } @@ -605,6 +621,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 +648,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 +658,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,7 +672,7 @@ 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) => { diff --git a/pyo3-ffi-check/src/main.rs b/pyo3-ffi-check/src/main.rs index 444613db1a4..7a0a122d67b 100644 --- a/pyo3-ffi-check/src/main.rs +++ b/pyo3-ffi-check/src/main.rs @@ -2,6 +2,13 @@ use std::{ffi::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. +#[cfg(not(PyPy))] +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 +145,42 @@ 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)*)) => {{ - // 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)*) -> _; + ($name:ident, $bindgen_name:ident, [$($modifiers:tt)*] ($($arg_types:tt)*)) => {{ + + #[cfg(not(PyPy))] + { + // Check functions have the same number of arguments + #[allow(deprecated)] + 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 + ); + } + } + + #[cfg(PyPy)] // FIXME https://github.com/PyO3/pyo3/pull/6389 + { + // Check functions have the same number of arguments + #[allow(deprecated)] + { pyo3_ffi::$name as $($modifiers)* fn($($arg_types)*) -> _ }; + bindings::$bindgen_name as $($modifiers)* 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. @@ -151,17 +188,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 +206,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); }}; 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..83d3a16f738 100644 --- a/pyo3-ffi/src/abstract_.rs +++ b/pyo3-ffi/src/abstract_.rs @@ -75,7 +75,7 @@ extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyVectorcall_NARGS")] 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(any(Py_3_12, not(Py_LIMITED_API)))] #[cfg_attr(PyPy, link_name = "PyPyVectorcall_Call")] pub fn PyVectorcall_Call( callable: *mut PyObject, diff --git a/pyo3-ffi/src/impl_/macros.rs b/pyo3-ffi/src/impl_/macros.rs index 9e1fb2130b7..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])* diff --git a/pyo3-ffi/src/objimpl.rs b/pyo3-ffi/src/objimpl.rs index bad80a1eae2..c046af92238 100644 --- a/pyo3-ffi/src/objimpl.rs +++ b/pyo3-ffi/src/objimpl.rs @@ -52,9 +52,16 @@ 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; + pub fn PyGC_Collect() -> PyGCCollectReturn; #[cfg(Py_3_10)] #[cfg_attr(PyPy, link_name = "PyPyGC_Enable")] From b15e77371ef63e8654c914dc632aac6b995e408d Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 16 Sep 2026 08:56:58 +0000 Subject: [PATCH 44/50] ci: update ffi-check for PyPy symbol mangling change (#6389) * ci: update ffi-check for PyPy symbol mangling change * fix formatting * fix ffi check failures on older versions * fix pypy 3.11 link aliases * fmt * try fix windows build * try to fix windows * try switch to allowlist for bindgen * try to make link failures on windows easier to diagnose * fix link name for pypy without raw-dylib * fix missing PyPy link name attributes * only decorate x86 symbols on `raw-dylib` builds * Update link name for PyVectorcall_Call in abstract_.rs * fmt --- newsfragments/6389.fixed.md | 1 + pyo3-ffi-check/macro/src/lib.rs | 1 - pyo3-ffi-check/src/main.rs | 47 +++---- pyo3-ffi/src/abstract_.rs | 192 +++++++++++++++-------------- pyo3-ffi/src/boolobject.rs | 2 +- pyo3-ffi/src/bytearrayobject.rs | 12 +- pyo3-ffi/src/bytesobject.rs | 17 +-- pyo3-ffi/src/ceval.rs | 50 ++++---- pyo3-ffi/src/codecs.rs | 8 +- pyo3-ffi/src/complexobject.rs | 6 +- pyo3-ffi/src/context.rs | 4 + pyo3-ffi/src/cpython/abstract_.rs | 18 +-- pyo3-ffi/src/cpython/code.rs | 1 + pyo3-ffi/src/cpython/dictobject.rs | 1 + pyo3-ffi/src/cpython/funcobject.rs | 3 + pyo3-ffi/src/cpython/marshal.rs | 7 +- pyo3-ffi/src/cpython/pyframe.rs | 7 ++ pyo3-ffi/src/cpython/pystate.rs | 4 +- pyo3-ffi/src/cpython/pythonrun.rs | 2 +- pyo3-ffi/src/descrobject.rs | 11 +- pyo3-ffi/src/dictobject.rs | 37 +++--- pyo3-ffi/src/fileobject.rs | 9 +- pyo3-ffi/src/floatobject.rs | 6 +- pyo3-ffi/src/genericaliasobject.rs | 2 +- pyo3-ffi/src/import.rs | 25 ++-- pyo3-ffi/src/intrcheck.rs | 4 +- pyo3-ffi/src/iterobject.rs | 4 +- pyo3-ffi/src/listobject.rs | 22 ++-- pyo3-ffi/src/longobject.rs | 42 +++---- pyo3-ffi/src/memoryobject.rs | 8 +- pyo3-ffi/src/methodobject.rs | 5 +- pyo3-ffi/src/modsupport.rs | 26 ++-- pyo3-ffi/src/moduleobject.rs | 14 +-- pyo3-ffi/src/object.rs | 98 +++++++-------- pyo3-ffi/src/objimpl.rs | 26 ++-- pyo3-ffi/src/osmodule.rs | 2 +- pyo3-ffi/src/pybuffer.rs | 16 +-- pyo3-ffi/src/pycapsule.rs | 22 ++-- pyo3-ffi/src/pyerrors.rs | 87 +++++++------ pyo3-ffi/src/pyframe.rs | 1 + pyo3-ffi/src/pylifecycle.rs | 10 +- pyo3-ffi/src/pymem.rs | 8 +- pyo3-ffi/src/pystate.rs | 24 ++-- pyo3-ffi/src/pystrtod.rs | 4 +- pyo3-ffi/src/pythonrun.rs | 8 +- pyo3-ffi/src/refcount.rs | 32 +++-- pyo3-ffi/src/setobject.rs | 16 +-- pyo3-ffi/src/sliceobject.rs | 8 +- pyo3-ffi/src/structseq.rs | 2 +- pyo3-ffi/src/sysmodule.rs | 8 +- pyo3-ffi/src/traceback.rs | 4 +- pyo3-ffi/src/tupleobject.rs | 12 +- pyo3-ffi/src/unicodeobject.rs | 124 ++++++++++++------- pyo3-ffi/src/warnings.rs | 6 +- pyo3-ffi/src/weakrefobject.rs | 6 +- 55 files changed, 605 insertions(+), 517 deletions(-) create mode 100644 newsfragments/6389.fixed.md 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/pyo3-ffi-check/macro/src/lib.rs b/pyo3-ffi-check/macro/src/lib.rs index 480c19f5fb2..a7727e1281d 100644 --- a/pyo3-ffi-check/macro/src/lib.rs +++ b/pyo3-ffi-check/macro/src/lib.rs @@ -361,7 +361,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", ""), diff --git a/pyo3-ffi-check/src/main.rs b/pyo3-ffi-check/src/main.rs index 7a0a122d67b..9e42045ec28 100644 --- a/pyo3-ffi-check/src/main.rs +++ b/pyo3-ffi-check/src/main.rs @@ -3,7 +3,6 @@ use std::{ffi::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. -#[cfg(not(PyPy))] static SPECIAL_CASE_FUNCTIONS: &[&str] = &[ "PyEval_RestoreThread", // PyO3 adds special handling for pthread_exit "PyGILState_Ensure", // Similar to PyEval_RestoreThread @@ -150,36 +149,24 @@ fn main() { // Typically `name` == `bindgen_name`, but e.g. for PyPy this is not the case. macro_rules! check_function { ($name:ident, $bindgen_name:ident, [$($modifiers:tt)*] ($($arg_types:tt)*)) => {{ - - #[cfg(not(PyPy))] - { - // Check functions have the same number of arguments - #[allow(deprecated)] - 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 - ); - } - } - - #[cfg(PyPy)] // FIXME https://github.com/PyO3/pyo3/pull/6389 + // Check functions have the same number of arguments + #[allow(deprecated)] + 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)) { - // Check functions have the same number of arguments - #[allow(deprecated)] - { pyo3_ffi::$name as $($modifiers)* fn($($arg_types)*) -> _ }; - bindings::$bindgen_name as $($modifiers)* fn($($arg_types)*) -> _; + 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 diff --git a/pyo3-ffi/src/abstract_.rs b/pyo3-ffi/src/abstract_.rs index 83d3a16f738..601f019eb3e 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, @@ -62,21 +62,21 @@ extern_libpython! { ... ) -> *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(any(Py_3_12, not(Py_LIMITED_API)))] - #[cfg_attr(PyPy, link_name = "PyPyVectorcall_Call")] + #[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..ccdf1cf188d 100644 --- a/pyo3-ffi/src/boolobject.rs +++ b/pyo3-ffi/src/boolobject.rs @@ -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..9304d3ac4b4 100644 --- a/pyo3-ffi/src/bytearrayobject.rs +++ b/pyo3-ffi/src/bytearrayobject.rs @@ -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..12348c540f2 100644 --- a/pyo3-ffi/src/bytesobject.rs +++ b/pyo3-ffi/src/bytesobject.rs @@ -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..88f0cfcddfc 100644 --- a/pyo3-ffi/src/complexobject.rs +++ b/pyo3-ffi/src/complexobject.rs @@ -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/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 b7f91ab56df..b1327ab6e68 100644 --- a/pyo3-ffi/src/cpython/dictobject.rs +++ b/pyo3-ffi/src/cpython/dictobject.rs @@ -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..f84ed9cb732 100644 --- a/pyo3-ffi/src/cpython/funcobject.rs +++ b/pyo3-ffi/src/cpython/funcobject.rs @@ -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/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/pyframe.rs b/pyo3-ffi/src/cpython/pyframe.rs index 121cdc4eca6..4731aa22c60 100644 --- a/pyo3-ffi/src/cpython/pyframe.rs +++ b/pyo3-ffi/src/cpython/pyframe.rs @@ -45,24 +45,31 @@ 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)] diff --git a/pyo3-ffi/src/cpython/pystate.rs b/pyo3-ffi/src/cpython/pystate.rs index e7d728d934a..727c4a479be 100644 --- a/pyo3-ffi/src/cpython/pystate.rs +++ b/pyo3-ffi/src/cpython/pystate.rs @@ -69,8 +69,10 @@ extern_libpython! { 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")] @@ -92,7 +94,7 @@ 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(); #[cfg(all(not(Py_3_11), not(PyPy)))] diff --git a/pyo3-ffi/src/cpython/pythonrun.rs b/pyo3-ffi/src/cpython/pythonrun.rs index d20ff8a984b..02ee3adfd65 100644 --- a/pyo3-ffi/src/cpython/pythonrun.rs +++ b/pyo3-ffi/src/cpython/pythonrun.rs @@ -153,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..23f8cbc272a 100644 --- a/pyo3-ffi/src/descrobject.rs +++ b/pyo3-ffi/src/descrobject.rs @@ -54,16 +54,17 @@ extern_libpython! { } 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..34ce94518d2 100644 --- a/pyo3-ffi/src/dictobject.rs +++ b/pyo3-ffi/src/dictobject.rs @@ -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")] 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..59c43985690 100644 --- a/pyo3-ffi/src/floatobject.rs +++ b/pyo3-ffi/src/floatobject.rs @@ -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..36493600d2e 100644 --- a/pyo3-ffi/src/genericaliasobject.rs +++ b/pyo3-ffi/src/genericaliasobject.rs @@ -5,7 +5,7 @@ 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)))] 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..4a6526d0193 100644 --- a/pyo3-ffi/src/listobject.rs +++ b/pyo3-ffi/src/listobject.rs @@ -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 5c72771a7f2..c9bfd9650e5 100644 --- a/pyo3-ffi/src/longobject.rs +++ b/pyo3-ffi/src/longobject.rs @@ -25,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 @@ -115,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, diff --git a/pyo3-ffi/src/memoryobject.rs b/pyo3-ffi/src/memoryobject.rs index 7ebc2a211d7..a78f45c58d3 100644 --- a/pyo3-ffi/src/memoryobject.rs +++ b/pyo3-ffi/src/memoryobject.rs @@ -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..0d72c23b09e 100644 --- a/pyo3-ffi/src/methodobject.rs +++ b/pyo3-ffi/src/methodobject.rs @@ -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..1246e2f3c9d 100644 --- a/pyo3-ffi/src/moduleobject.rs +++ b/pyo3-ffi/src/moduleobject.rs @@ -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))] diff --git a/pyo3-ffi/src/object.rs b/pyo3-ffi/src/object.rs index a43a5092c0a..cb88447cabf 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; } @@ -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, @@ -393,7 +393,7 @@ extern_libpython! { 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)] @@ -408,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; } @@ -424,7 +424,7 @@ extern_libpython! { 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))] @@ -432,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, @@ -474,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")] @@ -492,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")] @@ -506,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); } diff --git a/pyo3-ffi/src/objimpl.rs b/pyo3-ffi/src/objimpl.rs index c046af92238..3e4c2c55e80 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, @@ -60,19 +60,19 @@ type PyGCCollectReturn = Py_ssize_t; type PyGCCollectReturn = c_int; extern_libpython! { - #[cfg_attr(PyPy, link_name = "PyPyGC_Collect")] + #[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; } @@ -102,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); } @@ -118,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..2cc464cbc47 100644 --- a/pyo3-ffi/src/pycapsule.rs +++ b/pyo3-ffi/src/pycapsule.rs @@ -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 25d4e2db717..edc51310d68 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,35 +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(PyPy, link_name = "PyPyErr_GetRaisedException")] + #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyErr_GetRaisedException")] pub fn PyErr_GetRaisedException() -> *mut PyObject; #[cfg(Py_3_12)] - #[cfg_attr(PyPy, link_name = "PyPyErr_SetRaisedException")] + #[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)] @@ -279,27 +279,38 @@ extern_libpython! { } 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, @@ -313,7 +324,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))] @@ -336,33 +347,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..b3bbdffab7a 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,7 +93,9 @@ 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)] 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 b105cb183e8..47e9fa7ce26 100644 --- a/pyo3-ffi/src/pythonrun.rs +++ b/pyo3-ffi/src/pythonrun.rs @@ -9,15 +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(PyPy, link_name = "PyPyErr_DisplayException")] + #[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/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..8b1efbb1e0a 100644 --- a/pyo3-ffi/src/setobject.rs +++ b/pyo3-ffi/src/setobject.rs @@ -12,22 +12,22 @@ extern_libpython! { } 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))] diff --git a/pyo3-ffi/src/sliceobject.rs b/pyo3-ffi/src/sliceobject.rs index 175a65f0622..4321b689f11 100644 --- a/pyo3-ffi/src/sliceobject.rs +++ b/pyo3-ffi/src/sliceobject.rs @@ -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..d527cf724b2 100644 --- a/pyo3-ffi/src/structseq.rs +++ b/pyo3-ffi/src/structseq.rs @@ -38,7 +38,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..e5868f00217 100644 --- a/pyo3-ffi/src/traceback.rs +++ b/pyo3-ffi/src/traceback.rs @@ -2,9 +2,9 @@ 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))] diff --git a/pyo3-ffi/src/tupleobject.rs b/pyo3-ffi/src/tupleobject.rs index e415eabfe66..0e07692352b 100644 --- a/pyo3-ffi/src/tupleobject.rs +++ b/pyo3-ffi/src/tupleobject.rs @@ -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..d2b1da86b3f 100644 --- a/pyo3-ffi/src/unicodeobject.rs +++ b/pyo3-ffi/src/unicodeobject.rs @@ -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..98161a740fa 100644 --- a/pyo3-ffi/src/weakrefobject.rs +++ b/pyo3-ffi/src/weakrefobject.rs @@ -56,12 +56,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.") From 7bd1b1eee5e25499c914234b0e9b6ada2d8be7e1 Mon Sep 17 00:00:00 2001 From: chiri Date: Wed, 16 Sep 2026 12:36:05 +0000 Subject: [PATCH 45/50] internal: do not hardcode MSRV in README badge (#6378) * internal: new badges * Update README.md * Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From b81cd4868f59d4b3eba82e038bac7c23dda5e115 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 16 Sep 2026 14:12:14 +0000 Subject: [PATCH 46/50] check static addresses match in `pyo3-ffi-check` (#6421) * check static addresses match in `pyo3-ffi-check` * newsfragments * fix private symbols * fmt * fix windows-only pypy symbol --- newsfragments/6421.fixed.md | 1 + newsfragments/6421.removed.md | 1 + pyo3-ffi-check/README.md | 2 +- pyo3-ffi-check/macro/src/lib.rs | 106 ++++++++++++++++++--- pyo3-ffi-check/src/main.rs | 25 ++++- pyo3-ffi/src/abstract_.rs | 2 +- pyo3-ffi/src/boolobject.rs | 4 +- pyo3-ffi/src/bytearrayobject.rs | 2 +- pyo3-ffi/src/bytesobject.rs | 2 +- pyo3-ffi/src/complexobject.rs | 2 +- pyo3-ffi/src/cpython/cellobject.rs | 1 + pyo3-ffi/src/cpython/funcobject.rs | 2 +- pyo3-ffi/src/cpython/pydebug.rs | 7 +- pyo3-ffi/src/cpython/pyframe.rs | 1 + pyo3-ffi/src/cpython/pystate.rs | 1 + pyo3-ffi/src/descrobject.rs | 14 +-- pyo3-ffi/src/dictobject.rs | 4 +- pyo3-ffi/src/enumobject.rs | 1 + pyo3-ffi/src/floatobject.rs | 2 +- pyo3-ffi/src/genericaliasobject.rs | 1 + pyo3-ffi/src/listobject.rs | 2 +- pyo3-ffi/src/memoryobject.rs | 2 +- pyo3-ffi/src/methodobject.rs | 2 +- pyo3-ffi/src/moduleobject.rs | 2 +- pyo3-ffi/src/object.rs | 10 +- pyo3-ffi/src/objimpl.rs | 8 +- pyo3-ffi/src/pycapsule.rs | 2 +- pyo3-ffi/src/pyerrors.rs | 146 +++++++++++++++-------------- pyo3-ffi/src/pylifecycle.rs | 1 + pyo3-ffi/src/rangeobject.rs | 2 +- pyo3-ffi/src/setobject.rs | 4 +- pyo3-ffi/src/sliceobject.rs | 4 +- pyo3-ffi/src/structseq.rs | 1 + pyo3-ffi/src/traceback.rs | 2 +- pyo3-ffi/src/tupleobject.rs | 2 +- pyo3-ffi/src/unicodeobject.rs | 2 +- 36 files changed, 244 insertions(+), 129 deletions(-) create mode 100644 newsfragments/6421.fixed.md create mode 100644 newsfragments/6421.removed.md 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/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/macro/src/lib.rs b/pyo3-ffi-check/macro/src/lib.rs index a7727e1281d..96c863d0286 100644 --- a/pyo3-ffi-check/macro/src/lib.rs +++ b/pyo3-ffi-check/macro/src/lib.rs @@ -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. @@ -547,16 +631,8 @@ pub fn for_all_functions(_input: proc_macro::TokenStream) -> proc_macro::TokenSt continue; } - let mut bindgen_name = function_name.to_owned(); + let bindgen_name = get_bindgen_name(function_name, &BINDGEN_FUNCTION_NAMES); if pyo3_build_config::get().implementation() == PythonImplementation::PyPy { - // For PyPy, some functions are prefixed with "PyPy", we check whether the - // bindgen name contains the prefixed name and use that if it does. - if function_name.starts_with("Py") || function_name.starts_with("_Py") { - let prefixed_name = function_name.replacen("Py", "PyPy", 1); - if BINDGEN_FUNCTION_NAMES.contains(&prefixed_name) { - bindgen_name = prefixed_name; - } - } // 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 diff --git a/pyo3-ffi-check/src/main.rs b/pyo3-ffi-check/src/main.rs index 9e42045ec28..5cd0f85efeb 100644 --- a/pyo3-ffi-check/src/main.rs +++ b/pyo3-ffi-check/src/main.rs @@ -1,4 +1,7 @@ -use std::{ffi::CStr, process::exit}; +use std::{ + ffi::{c_void, CStr}, + process::exit, +}; use pyo3_ffi_check_definitions::{bindgen as bindings, pyo3_ffi}; @@ -201,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/src/abstract_.rs b/pyo3-ffi/src/abstract_.rs index 601f019eb3e..e67a831efa1 100644 --- a/pyo3-ffi/src/abstract_.rs +++ b/pyo3-ffi/src/abstract_.rs @@ -55,7 +55,7 @@ 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, diff --git a/pyo3-ffi/src/boolobject.rs b/pyo3-ffi/src/boolobject.rs index ccdf1cf188d..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)] diff --git a/pyo3-ffi/src/bytearrayobject.rs b/pyo3-ffi/src/bytearrayobject.rs index 9304d3ac4b4..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; diff --git a/pyo3-ffi/src/bytesobject.rs b/pyo3-ffi/src/bytesobject.rs index 12348c540f2..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; } diff --git a/pyo3-ffi/src/complexobject.rs b/pyo3-ffi/src/complexobject.rs index 88f0cfcddfc..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; } 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/funcobject.rs b/pyo3-ffi/src/cpython/funcobject.rs index f84ed9cb732..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; } diff --git a/pyo3-ffi/src/cpython/pydebug.rs b/pyo3-ffi/src/cpython/pydebug.rs index 389c4ea9ef3..5b0a899cbb8 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,16 +39,19 @@ 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; } diff --git a/pyo3-ffi/src/cpython/pyframe.rs b/pyo3-ffi/src/cpython/pyframe.rs index 4731aa22c60..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)] diff --git a/pyo3-ffi/src/cpython/pystate.rs b/pyo3-ffi/src/cpython/pystate.rs index 727c4a479be..4f7385ade75 100644 --- a/pyo3-ffi/src/cpython/pystate.rs +++ b/pyo3-ffi/src/cpython/pystate.rs @@ -66,6 +66,7 @@ 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)] diff --git a/pyo3-ffi/src/descrobject.rs b/pyo3-ffi/src/descrobject.rs index 23f8cbc272a..17ad25fabce 100644 --- a/pyo3-ffi/src/descrobject.rs +++ b/pyo3-ffi/src/descrobject.rs @@ -37,19 +37,19 @@ 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; } diff --git a/pyo3-ffi/src/dictobject.rs b/pyo3-ffi/src/dictobject.rs index 34ce94518d2..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; } @@ -97,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/floatobject.rs b/pyo3-ffi/src/floatobject.rs index 59c43985690..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)] diff --git a/pyo3-ffi/src/genericaliasobject.rs b/pyo3-ffi/src/genericaliasobject.rs index 36493600d2e..f628ce1b743 100644 --- a/pyo3-ffi/src/genericaliasobject.rs +++ b/pyo3-ffi/src/genericaliasobject.rs @@ -9,5 +9,6 @@ extern_libpython! { 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/listobject.rs b/pyo3-ffi/src/listobject.rs index 4a6526d0193..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; diff --git a/pyo3-ffi/src/memoryobject.rs b/pyo3-ffi/src/memoryobject.rs index a78f45c58d3..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)] diff --git a/pyo3-ffi/src/methodobject.rs b/pyo3-ffi/src/methodobject.rs index 0d72c23b09e..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)] diff --git a/pyo3-ffi/src/moduleobject.rs b/pyo3-ffi/src/moduleobject.rs index 1246e2f3c9d..f9e3fb69c7a 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; } diff --git a/pyo3-ffi/src/object.rs b/pyo3-ffi/src/object.rs index cb88447cabf..6ffef0d65b5 100644 --- a/pyo3-ffi/src/object.rs +++ b/pyo3-ffi/src/object.rs @@ -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; } @@ -420,7 +420,7 @@ 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))] @@ -651,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)] @@ -679,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)] diff --git a/pyo3-ffi/src/objimpl.rs b/pyo3-ffi/src/objimpl.rs index 3e4c2c55e80..b32ad28e77c 100644 --- a/pyo3-ffi/src/objimpl.rs +++ b/pyo3-ffi/src/objimpl.rs @@ -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; } @@ -91,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))] diff --git a/pyo3-ffi/src/pycapsule.rs b/pyo3-ffi/src/pycapsule.rs index 2cc464cbc47..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; } diff --git a/pyo3-ffi/src/pyerrors.rs b/pyo3-ffi/src/pyerrors.rs index edc51310d68..11f13a09a84 100644 --- a/pyo3-ffi/src/pyerrors.rs +++ b/pyo3-ffi/src/pyerrors.rs @@ -127,154 +127,158 @@ 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; } diff --git a/pyo3-ffi/src/pylifecycle.rs b/pyo3-ffi/src/pylifecycle.rs index b3bbdffab7a..84daae097cb 100644 --- a/pyo3-ffi/src/pylifecycle.rs +++ b/pyo3-ffi/src/pylifecycle.rs @@ -99,6 +99,7 @@ extern_libpython! { 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/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/setobject.rs b/pyo3-ffi/src/setobject.rs index 8b1efbb1e0a..1d204797c26 100644 --- a/pyo3-ffi/src/setobject.rs +++ b/pyo3-ffi/src/setobject.rs @@ -4,9 +4,9 @@ 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; } diff --git a/pyo3-ffi/src/sliceobject.rs b/pyo3-ffi/src/sliceobject.rs index 4321b689f11..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; } diff --git a/pyo3-ffi/src/structseq.rs b/pyo3-ffi/src/structseq.rs index d527cf724b2..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; } diff --git a/pyo3-ffi/src/traceback.rs b/pyo3-ffi/src/traceback.rs index e5868f00217..806e9ff7f0e 100644 --- a/pyo3-ffi/src/traceback.rs +++ b/pyo3-ffi/src/traceback.rs @@ -8,7 +8,7 @@ extern_libpython! { 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))] diff --git a/pyo3-ffi/src/tupleobject.rs b/pyo3-ffi/src/tupleobject.rs index 0e07692352b..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; } diff --git a/pyo3-ffi/src/unicodeobject.rs b/pyo3-ffi/src/unicodeobject.rs index d2b1da86b3f..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; From e02eaf53218b24abf764f4e51ec8f68ce5de07bf Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 16 Sep 2026 19:30:37 +0000 Subject: [PATCH 47/50] fix FFI cases where PyPy replaced function with macro (#6422) * fix FFI cases where PyPy replaced function with macro * newsfragment --- newsfragments/6422.fixed.md | 1 + pyo3-ffi-check/macro/src/lib.rs | 82 ++++++++++++++++++++------------- pyo3-ffi/src/cpython/pydebug.rs | 4 +- pyo3-ffi/src/setobject.rs | 16 +++---- pyo3-ffi/src/traceback.rs | 4 +- pyo3-ffi/src/weakrefobject.rs | 13 +++--- 6 files changed, 68 insertions(+), 52 deletions(-) create mode 100644 newsfragments/6422.fixed.md 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/pyo3-ffi-check/macro/src/lib.rs b/pyo3-ffi-check/macro/src/lib.rs index 96c863d0286..80a3cb724ec 100644 --- a/pyo3-ffi-check/macro/src/lib.rs +++ b/pyo3-ffi-check/macro/src/lib.rs @@ -332,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", ""), @@ -355,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", ""), @@ -405,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)"), @@ -425,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", ""), @@ -456,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", ""), @@ -480,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", ""), @@ -491,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", ""), @@ -505,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_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", ""), @@ -532,8 +535,8 @@ const MACRO_EXCLUSIONS: &[(&str, &str)] = &[ // 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)"), ]; @@ -632,14 +635,6 @@ pub fn for_all_functions(_input: proc_macro::TokenStream) -> proc_macro::TokenSt } let bindgen_name = get_bindgen_name(function_name, &BINDGEN_FUNCTION_NAMES); - if pyo3_build_config::get().implementation() == 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(&bindgen_name) { - continue; - } - } let FunctionInfo { modifiers, @@ -686,6 +681,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}", @@ -750,6 +759,13 @@ pub fn for_all_functions(_input: proc_macro::TokenStream) -> proc_macro::TokenSt 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/src/cpython/pydebug.rs b/pyo3-ffi/src/cpython/pydebug.rs index 5b0a899cbb8..8d978690fc5 100644 --- a/pyo3-ffi/src/cpython/pydebug.rs +++ b/pyo3-ffi/src/cpython/pydebug.rs @@ -56,11 +56,11 @@ extern_libpython! { } 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/setobject.rs b/pyo3-ffi/src/setobject.rs index 1d204797c26..e168feb025b 100644 --- a/pyo3-ffi/src/setobject.rs +++ b/pyo3-ffi/src/setobject.rs @@ -30,15 +30,15 @@ extern_libpython! { #[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/traceback.rs b/pyo3-ffi/src/traceback.rs index 806e9ff7f0e..d5c034268bb 100644 --- a/pyo3-ffi/src/traceback.rs +++ b/pyo3-ffi/src/traceback.rs @@ -11,13 +11,13 @@ extern_libpython! { #[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/weakrefobject.rs b/pyo3-ffi/src/weakrefobject.rs index 98161a740fa..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 From 867d374813d68dbe96771e838537e5a7cbe7e738 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Tue, 15 Sep 2026 20:35:52 +0000 Subject: [PATCH 48/50] Add `DuringGC` FFI bindings for 3.15 (#6419) * Add `DuringGC` FFI bindings for 3.15 * newsfragment * correct update to `PyObject_CallFinalizerFromDealloc` --- newsfragments/6419.added.md | 1 + pyo3-ffi/src/cpython/object.rs | 3 +-- pyo3-ffi/src/moduleobject.rs | 9 ++++++++- pyo3-ffi/src/object.rs | 26 ++++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 newsfragments/6419.added.md 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/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/moduleobject.rs b/pyo3-ffi/src/moduleobject.rs index f9e3fb69c7a..5029a82d534 100644 --- a/pyo3-ffi/src/moduleobject.rs +++ b/pyo3-ffi/src/moduleobject.rs @@ -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 6ffef0d65b5..6ed951d8668 100644 --- a/pyo3-ffi/src/object.rs +++ b/pyo3-ffi/src/object.rs @@ -769,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; } From f19ac8237ca29abb3a941b82b6c584e82611f1f9 Mon Sep 17 00:00:00 2001 From: person93 Date: Thu, 20 Aug 2026 12:15:04 +0000 Subject: [PATCH 49/50] fix clippy lint (#6334) --- src/internal_tricks.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 From c4b96179a126cff336e92b55c7dd67cacfa9c109 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Fri, 25 Sep 2026 11:16:50 +0100 Subject: [PATCH 50/50] fix 3.8 ffi checks --- pyo3-ffi-check/macro/src/lib.rs | 2 ++ pyo3-ffi/src/cpython/pystate.rs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pyo3-ffi-check/macro/src/lib.rs b/pyo3-ffi-check/macro/src/lib.rs index 80a3cb724ec..4ce547a5851 100644 --- a/pyo3-ffi-check/macro/src/lib.rs +++ b/pyo3-ffi-check/macro/src/lib.rs @@ -581,6 +581,8 @@ const EXCLUDED_SYMBOLS: &[&str] = &[ "_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 diff --git a/pyo3-ffi/src/cpython/pystate.rs b/pyo3-ffi/src/cpython/pystate.rs index 4f7385ade75..3e8549d558f 100644 --- a/pyo3-ffi/src/cpython/pystate.rs +++ b/pyo3-ffi/src/cpython/pystate.rs @@ -98,7 +98,7 @@ extern_libpython! { #[cfg_attr(all(PyPy, not(Py_3_12)), link_name = "PyPyThreadState_DeleteCurrent")] pub fn PyThreadState_DeleteCurrent(); - #[cfg(all(not(Py_3_11), not(PyPy)))] + #[cfg(all(Py_3_9, not(Py_3_11), not(PyPy)))] pub fn _PyInterpreterState_GetEvalFrameFunc( interp: *mut PyInterpreterState, ) -> Option<_PyFrameEvalFunction>; @@ -106,7 +106,7 @@ extern_libpython! { pub fn _PyInterpreterState_GetEvalFrameFunc( interp: *mut PyInterpreterState, ) -> _PyFrameEvalFunction; - #[cfg(not(PyPy))] + #[cfg(all(Py_3_9, not(PyPy)))] pub fn _PyInterpreterState_SetEvalFrameFunc( interp: *mut PyInterpreterState, eval_frame: Option<_PyFrameEvalFunction>,