Skip to content

Commit 144b199

Browse files
ffi: define compat for Py_NewRef and Py_XNewRef (#4445)
* ffi: define compat for `Py_NewRef` and `Py_XNewRef` * add missing inline hint Co-authored-by: Nathan Goldbaum <[email protected]> * don't use std::ffi::c_int (requires MSRV 1.64) * add test to guard against ambiguity * fix `Py_NewRef` cfg on PyPy --------- Co-authored-by: Nathan Goldbaum <[email protected]>
1 parent 52dc139 commit 144b199

File tree

9 files changed

+133
-80
lines changed

9 files changed

+133
-80
lines changed

newsfragments/4445.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add FFI definitions `compat::Py_NewRef` and `compat::Py_XNewRef`.

newsfragments/4445.removed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Remove private FFI definitions `_Py_NewRef` and `_Py_XNewRef`.

pyo3-ffi/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ abi3-py312 = ["abi3", "pyo3-build-config/abi3-py312"]
3737
# Automatically generates `python3.dll` import libraries for Windows targets.
3838
generate-import-lib = ["pyo3-build-config/python3-dll-a"]
3939

40+
[dev-dependencies]
41+
paste = "1"
42+
4043
[build-dependencies]
4144
pyo3-build-config = { path = "../pyo3-build-config", version = "=0.23.0-dev", features = ["resolve-config"] }
4245

pyo3-ffi/src/compat.rs

Lines changed: 0 additions & 60 deletions
This file was deleted.

pyo3-ffi/src/compat/mod.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//! C API Compatibility Shims
2+
//!
3+
//! Some CPython C API functions added in recent versions of Python are
4+
//! inherently safer to use than older C API constructs. This module
5+
//! exposes functions available on all Python versions that wrap the
6+
//! old C API on old Python versions and wrap the function directly
7+
//! on newer Python versions.
8+
9+
// Unless otherwise noted, the compatibility shims are adapted from
10+
// the pythoncapi-compat project: https://github.com/python/pythoncapi-compat
11+
12+
/// Internal helper macro which defines compatibility shims for C API functions, deferring to a
13+
/// re-export when that's available.
14+
macro_rules! compat_function {
15+
(
16+
originally_defined_for($cfg:meta);
17+
18+
$(#[$attrs:meta])*
19+
pub unsafe fn $name:ident($($arg_names:ident: $arg_types:ty),* $(,)?) -> $ret:ty $body:block
20+
) => {
21+
// Define as a standalone function under docsrs cfg so that this shows as a unique function in the docs,
22+
// not a re-export (the re-export has the wrong visibility)
23+
#[cfg(any(docsrs, not($cfg)))]
24+
#[cfg_attr(docsrs, doc(cfg(all())))]
25+
$(#[$attrs])*
26+
pub unsafe fn $name(
27+
$($arg_names: $arg_types,)*
28+
) -> $ret $body
29+
30+
#[cfg(all($cfg, not(docsrs)))]
31+
pub use $crate::$name;
32+
33+
#[cfg(test)]
34+
paste::paste! {
35+
// Test that the compat function does not overlap with the original function. If the
36+
// cfgs line up, then the the two glob imports will resolve to the same item via the
37+
// re-export. If the cfgs mismatch, then the use of $name will be ambiguous in cases
38+
// where the function is defined twice, and the test will fail to compile.
39+
#[allow(unused_imports)]
40+
mod [<test_ $name _export>] {
41+
use $crate::*;
42+
use $crate::compat::*;
43+
44+
#[test]
45+
fn test_export() {
46+
let _ = $name;
47+
}
48+
}
49+
}
50+
};
51+
}
52+
53+
mod py_3_10;
54+
mod py_3_13;
55+
56+
pub use self::py_3_10::*;
57+
pub use self::py_3_13::*;

pyo3-ffi/src/compat/py_3_10.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
compat_function!(
2+
originally_defined_for(Py_3_10);
3+
4+
#[inline]
5+
pub unsafe fn Py_NewRef(obj: *mut crate::PyObject) -> *mut crate::PyObject {
6+
crate::Py_INCREF(obj);
7+
obj
8+
}
9+
);
10+
11+
compat_function!(
12+
originally_defined_for(Py_3_10);
13+
14+
#[inline]
15+
pub unsafe fn Py_XNewRef(obj: *mut crate::PyObject) -> *mut crate::PyObject {
16+
crate::Py_XINCREF(obj);
17+
obj
18+
}
19+
);

pyo3-ffi/src/compat/py_3_13.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
compat_function!(
2+
originally_defined_for(Py_3_13);
3+
4+
#[inline]
5+
pub unsafe fn PyDict_GetItemRef(
6+
dp: *mut crate::PyObject,
7+
key: *mut crate::PyObject,
8+
result: *mut *mut crate::PyObject,
9+
) -> std::os::raw::c_int {
10+
use crate::{compat::Py_NewRef, PyDict_GetItemWithError, PyErr_Occurred};
11+
12+
let item = PyDict_GetItemWithError(dp, key);
13+
if !item.is_null() {
14+
*result = Py_NewRef(item);
15+
return 1; // found
16+
}
17+
*result = std::ptr::null_mut();
18+
if PyErr_Occurred().is_null() {
19+
return 0; // not found
20+
}
21+
-1
22+
}
23+
);
24+
25+
compat_function!(
26+
originally_defined_for(Py_3_13);
27+
28+
#[inline]
29+
pub unsafe fn PyList_GetItemRef(
30+
arg1: *mut crate::PyObject,
31+
arg2: crate::Py_ssize_t,
32+
) -> *mut crate::PyObject {
33+
use crate::{PyList_GetItem, Py_XINCREF};
34+
35+
let item = PyList_GetItem(arg1, arg2);
36+
Py_XINCREF(item);
37+
item
38+
}
39+
);

pyo3-ffi/src/object.rs

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -713,40 +713,33 @@ pub unsafe fn Py_XDECREF(op: *mut PyObject) {
713713
}
714714

715715
extern "C" {
716-
#[cfg(all(Py_3_10, Py_LIMITED_API))]
716+
#[cfg(all(Py_3_10, Py_LIMITED_API, not(PyPy)))]
717+
#[cfg_attr(docsrs, doc(cfg(Py_3_10)))]
717718
pub fn Py_NewRef(obj: *mut PyObject) -> *mut PyObject;
718-
#[cfg(all(Py_3_10, Py_LIMITED_API))]
719+
#[cfg(all(Py_3_10, Py_LIMITED_API, not(PyPy)))]
720+
#[cfg_attr(docsrs, doc(cfg(Py_3_10)))]
719721
pub fn Py_XNewRef(obj: *mut PyObject) -> *mut PyObject;
720722
}
721723

722-
// Technically these macros are only available in the C header from 3.10 and up, however their
723-
// implementation works on all supported Python versions so we define these macros on all
724-
// versions for simplicity.
724+
// macro _Py_NewRef not public; reimplemented directly inside Py_NewRef here
725+
// macro _Py_XNewRef not public; reimplemented directly inside Py_XNewRef here
725726

727+
#[cfg(all(Py_3_10, any(not(Py_LIMITED_API), PyPy)))]
728+
#[cfg_attr(docsrs, doc(cfg(Py_3_10)))]
726729
#[inline]
727-
pub unsafe fn _Py_NewRef(obj: *mut PyObject) -> *mut PyObject {
730+
pub unsafe fn Py_NewRef(obj: *mut PyObject) -> *mut PyObject {
728731
Py_INCREF(obj);
729732
obj
730733
}
731734

735+
#[cfg(all(Py_3_10, any(not(Py_LIMITED_API), PyPy)))]
736+
#[cfg_attr(docsrs, doc(cfg(Py_3_10)))]
732737
#[inline]
733-
pub unsafe fn _Py_XNewRef(obj: *mut PyObject) -> *mut PyObject {
738+
pub unsafe fn Py_XNewRef(obj: *mut PyObject) -> *mut PyObject {
734739
Py_XINCREF(obj);
735740
obj
736741
}
737742

738-
#[cfg(all(Py_3_10, not(Py_LIMITED_API)))]
739-
#[inline]
740-
pub unsafe fn Py_NewRef(obj: *mut PyObject) -> *mut PyObject {
741-
_Py_NewRef(obj)
742-
}
743-
744-
#[cfg(all(Py_3_10, not(Py_LIMITED_API)))]
745-
#[inline]
746-
pub unsafe fn Py_XNewRef(obj: *mut PyObject) -> *mut PyObject {
747-
_Py_XNewRef(obj)
748-
}
749-
750743
#[cfg_attr(windows, link(name = "pythonXY"))]
751744
extern "C" {
752745
#[cfg(not(GraalPy))]

src/pyclass/create_type_object.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ impl PyTypeBuilder {
250250
if (*dict_ptr).is_null() {
251251
std::ptr::write(dict_ptr, ffi::PyDict_New());
252252
}
253-
Ok(ffi::_Py_XNewRef(*dict_ptr))
253+
Ok(ffi::compat::Py_XNewRef(*dict_ptr))
254254
})
255255
}
256256
}

0 commit comments

Comments
 (0)