diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aff00ee2..e19b07c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,7 +78,16 @@ jobs: fail-fast: false # If one platform fails, allow the rest to keep testing. matrix: rust: [stable] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11-dev", "pypy-3.7-v7.3.7", "pypy-3.8"] + python-version: [ + "3.7", + "3.8", + "3.9", + "3.10", + "3.11-dev", + "pypy-3.7-v7.3.7", + "pypy-3.8", + "pypy-3.9" + ] platform: [ { @@ -108,6 +117,8 @@ jobs: platform: { os: "windows-latest", python-architecture: "x86" } - python-version: pypy-3.8 platform: { os: "windows-latest", python-architecture: "x86" } + - python-version: pypy-3.9 + platform: { os: "windows-latest", python-architecture: "x86" } include: # Test minimal supported Rust version - rust: 1.48.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index fdf29abb..e52757af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update `inventory` optional dependency to 0.2. [#2019](https://github.com/PyO3/pyo3/pull/2019) - Drop `paste` dependency. [#2081](https://github.com/PyO3/pyo3/pull/2081) - The bindings found `pyo3::ffi` are now a re-export of the new `pyo3-ffi` crate. [#2126](https://github.com/PyO3/pyo3/pull/2126) +- Support PyPy 3.9. [#2143](https://github.com/PyO3/pyo3/pull/2143) + ### Added @@ -89,6 +91,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Use the Rust function path for `wrap_pymodule!` of a `#[pymodule]` with a `#[pyo3(name = "..")]` attribute, not the Python name. [#2081](https://github.com/PyO3/pyo3/pull/2081) - Fix panic in `#[pyfunction]` generated code when a required argument following an `Option` was not provided. [#2093](https://github.com/PyO3/pyo3/pull/2093) - Fixed undefined behaviour caused by incorrect `ExactSizeIterator` implementations. [#2124](https://github.com/PyO3/pyo3/pull/2124) +- Fix missing FFI definition `PyCMethod_New` on Python 3.9 and up. [#2143](https://github.com/PyO3/pyo3/pull/2143) - Add missing FFI definitions `_PyLong_NumBits` and `_PyLong_AsByteArray` on PyPy. [#2146](https://github.com/PyO3/pyo3/pull/2146) - Fix memory leak in implementation of `AsPyPointer` for `Option`. [#2160](https://github.com/PyO3/pyo3/pull/2160) - Fix the signature of `_PyLong_NumBits` [#2161](https://github.com/PyO3/pyo3/pull/2161) diff --git a/pyo3-build-config/src/impl_.rs b/pyo3-build-config/src/impl_.rs index 032aa34b..b026e76a 100644 --- a/pyo3-build-config/src/impl_.rs +++ b/pyo3-build-config/src/impl_.rs @@ -1131,7 +1131,16 @@ fn default_lib_name_unix( Some(ld_version) => format!("python{}", ld_version), None => format!("python{}.{}", version.major, version.minor), }, - PythonImplementation::PyPy => format!("pypy{}-c", version.major), + PythonImplementation::PyPy => { + if version >= (PythonVersion { major: 3, minor: 9 }) { + match ld_version { + Some(ld_version) => format!("pypy{}-c", ld_version), + None => format!("pypy{}.{}-c", version.major, version.minor), + } + } else { + format!("pypy{}-c", version.major) + } + } } } @@ -1237,6 +1246,11 @@ fn fixup_config_for_abi3( config: &mut InterpreterConfig, abi3_version: Option, ) -> Result<()> { + // PyPy doesn't support abi3; don't adjust the version + if config.implementation.is_pypy() { + return Ok(()); + } + if let Some(version) = abi3_version { ensure!( version <= config.version, @@ -1567,11 +1581,17 @@ mod tests { "python3.7md", ); - // PyPy ignores ldversion + // PyPy 3.7 ignores ldversion assert_eq!( - super::default_lib_name_unix(PythonVersion { major: 3, minor: 9 }, PyPy, Some("3.7md")), + super::default_lib_name_unix(PythonVersion { major: 3, minor: 7 }, PyPy, Some("3.7md")), "pypy3-c", ); + + // PyPy 3.9 includes ldversion + assert_eq!( + super::default_lib_name_unix(PythonVersion { major: 3, minor: 9 }, PyPy, Some("3.9d")), + "pypy3.9d-c", + ); } #[test] diff --git a/pyo3-ffi/src/cpython/abstract_.rs b/pyo3-ffi/src/cpython/abstract_.rs index f495188c..25b1a5fc 100644 --- a/pyo3-ffi/src/cpython/abstract_.rs +++ b/pyo3-ffi/src/cpython/abstract_.rs @@ -9,7 +9,7 @@ use crate::{ pyport::PY_SSIZE_T_MAX, vectorcallfunc, PyCallable_Check, PyThreadState, PyThreadState_GET, PyTuple_Check, PyType_HasFeature, Py_TPFLAGS_HAVE_VECTORCALL, }; -#[cfg(all(Py_3_8, not(PyPy)))] +#[cfg(Py_3_8)] use libc::size_t; extern "C" { @@ -101,17 +101,29 @@ pub unsafe fn PyObject_Vectorcall( } extern "C" { - #[cfg(all(Py_3_8, not(PyPy)))] - #[cfg_attr(not(Py_3_9), link_name = "_PyObject_VectorcallDict")] + #[cfg(all(PyPy, Py_3_8))] + #[cfg_attr(not(Py_3_9), link_name = "_PyPyObject_Vectorcall")] + pub fn PyObject_Vectorcall( + callable: *mut PyObject, + args: *const *mut PyObject, + nargsf: size_t, + kwnames: *mut PyObject, + ) -> *mut PyObject; + + #[cfg(all(Py_3_8))] + #[cfg_attr(all(not(PyPy), not(Py_3_9)), link_name = "_PyObject_VectorcallDict")] + #[cfg_attr(all(PyPy, not(Py_3_9)), link_name = "_PyPyObject_VectorcallDict")] + #[cfg_attr(all(PyPy, Py_3_9), link_name = "PyPyObject_VectorcallDict")] pub fn PyObject_VectorcallDict( callable: *mut PyObject, args: *const *mut PyObject, nargsf: size_t, - kwargs: *mut PyObject, + kwdict: *mut PyObject, ) -> *mut PyObject; - #[cfg(all(Py_3_8, not(PyPy)))] - #[cfg_attr(not(Py_3_9), link_name = "_PyVectorcall_Call")] + #[cfg(all(Py_3_8))] + #[cfg_attr(not(any(Py_3_9, PyPy)), link_name = "_PyVectorcall_Call")] + #[cfg_attr(PyPy, link_name = "PyPyVectorcall_Call")] pub fn PyVectorcall_Call( callable: *mut PyObject, tuple: *mut PyObject, @@ -152,6 +164,12 @@ pub unsafe fn _PyObject_CallNoArg(func: *mut PyObject) -> *mut PyObject { ) } +extern "C" { + #[cfg(PyPy)] + #[link_name = "_PyPyObject_CallNoArg"] + pub fn _PyObject_CallNoArg(func: *mut PyObject) -> *mut PyObject; +} + #[cfg(all(Py_3_8, not(PyPy)))] #[inline(always)] pub unsafe fn PyObject_CallOneArg(func: *mut PyObject, arg: *mut PyObject) -> *mut PyObject { diff --git a/pyo3-ffi/src/methodobject.rs b/pyo3-ffi/src/methodobject.rs index a3599933..72052735 100644 --- a/pyo3-ffi/src/methodobject.rs +++ b/pyo3-ffi/src/methodobject.rs @@ -117,6 +117,7 @@ pub union PyMethodDefPointer { const _: () = [()][mem::size_of::() - mem::size_of::>()]; +#[cfg(not(Py_3_9))] extern "C" { #[cfg_attr(PyPy, link_name = "PyPyCFunction_New")] pub fn PyCFunction_New(ml: *mut PyMethodDef, slf: *mut PyObject) -> *mut PyObject; @@ -129,7 +130,32 @@ extern "C" { ) -> *mut PyObject; } -// skipped non-limited / 3.9 PyCMethod_New +#[cfg(Py_3_9)] +#[inline] +pub unsafe fn PyCFunction_New(ml: *mut PyMethodDef, slf: *mut PyObject) -> *mut PyObject { + PyCFunction_NewEx(ml, slf, std::ptr::null_mut()) +} + +#[cfg(Py_3_9)] +#[inline] +pub unsafe fn PyCFunction_NewEx( + ml: *mut PyMethodDef, + slf: *mut PyObject, + module: *mut PyObject, +) -> *mut PyObject { + PyCMethod_New(ml, slf, module, std::ptr::null_mut()) +} + +#[cfg(Py_3_9)] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyCMethod_New")] + pub fn PyCMethod_New( + ml: *mut PyMethodDef, + slf: *mut PyObject, + module: *mut PyObject, + cls: *mut PyTypeObject, + ) -> *mut PyObject; +} /* Flag passed to newmethodobject */ pub const METH_VARARGS: c_int = 0x0001; diff --git a/src/instance.rs b/src/instance.rs index 6fd84098..937b9891 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -583,7 +583,7 @@ impl Py { /// This is equivalent to the Python expression `self()`. pub fn call0(&self, py: Python) -> PyResult { cfg_if::cfg_if! { - if #[cfg(Py_3_9)] { + if #[cfg(all(Py_3_9, not(PyPy)))] { // Optimized path on python 3.9+ unsafe { PyObject::from_owned_ptr_or_err(py, ffi::PyObject_CallNoArgs(self.as_ptr())) @@ -636,7 +636,7 @@ impl Py { /// This is equivalent to the Python expression `self.name()`. pub fn call_method0(&self, py: Python, name: &str) -> PyResult { cfg_if::cfg_if! { - if #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] { + if #[cfg(all(Py_3_9, not(any(Py_LIMITED_API, PyPy))))] { // Optimized path on python 3.9+ unsafe { let name = name.into_py(py); diff --git a/src/pyclass.rs b/src/pyclass.rs index a3277295..c4dfbf9b 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -218,7 +218,7 @@ fn type_object_creation_failed(py: Python, e: PyErr, name: &'static str) -> ! { /// Additional type initializations necessary before Python 3.10 #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))] unsafe fn tp_init_additional( - type_object: *mut ffi::PyTypeObject, + _type_object: *mut ffi::PyTypeObject, _tp_doc: &str, #[cfg(not(Py_3_9))] buffer_procs: &ffi::PyBufferProcs, #[cfg(not(Py_3_9))] dict_offset: Option, @@ -236,10 +236,10 @@ unsafe fn tp_init_additional( // heap-types, and it removed the text_signature value from it. // We go in after the fact and replace tp_doc with something // that _does_ include the text_signature value! - ffi::PyObject_Free((*type_object).tp_doc as _); + ffi::PyObject_Free((*_type_object).tp_doc as _); let data = ffi::PyObject_Malloc(_tp_doc.len()); data.copy_from(_tp_doc.as_ptr() as _, _tp_doc.len()); - (*type_object).tp_doc = data as _; + (*_type_object).tp_doc = data as _; } } @@ -247,15 +247,15 @@ unsafe fn tp_init_additional( // Python 3.9, so on older versions we must manually fixup the type object. #[cfg(not(Py_3_9))] { - (*(*type_object).tp_as_buffer).bf_getbuffer = buffer_procs.bf_getbuffer; - (*(*type_object).tp_as_buffer).bf_releasebuffer = buffer_procs.bf_releasebuffer; + (*(*_type_object).tp_as_buffer).bf_getbuffer = buffer_procs.bf_getbuffer; + (*(*_type_object).tp_as_buffer).bf_releasebuffer = buffer_procs.bf_releasebuffer; if let Some(dict_offset) = dict_offset { - (*type_object).tp_dictoffset = dict_offset; + (*_type_object).tp_dictoffset = dict_offset; } if let Some(weaklist_offset) = weaklist_offset { - (*type_object).tp_weaklistoffset = weaklist_offset; + (*_type_object).tp_weaklistoffset = weaklist_offset; } } } diff --git a/src/types/any.rs b/src/types/any.rs index b54aa540..40a3ce89 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -333,7 +333,7 @@ impl PyAny { /// This is equivalent to the Python expression `help()`. pub fn call0(&self) -> PyResult<&PyAny> { cfg_if::cfg_if! { - if #[cfg(Py_3_9)] { + if #[cfg(all(Py_3_9, not(PyPy)))] { // Optimized path on python 3.9+ unsafe { self.py().from_owned_ptr_or_err(ffi::PyObject_CallNoArgs(self.as_ptr())) @@ -461,7 +461,7 @@ impl PyAny { /// ``` pub fn call_method0(&self, name: &str) -> PyResult<&PyAny> { cfg_if::cfg_if! { - if #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] { + if #[cfg(all(Py_3_9, not(any(Py_LIMITED_API, PyPy))))] { // Optimized path on python 3.9+ unsafe { let name = name.into_py(self.py());