diff --git a/pyo3-ffi/README.md b/pyo3-ffi/README.md index a7a36133..40928476 100644 --- a/pyo3-ffi/README.md +++ b/pyo3-ffi/README.md @@ -44,8 +44,8 @@ features = ["extension-module"] **`src/lib.rs`** ```rust -use std::mem::transmute; use std::os::raw::c_char; +use std::ptr; use pyo3_ffi::*; @@ -72,13 +72,11 @@ pub unsafe extern "C" fn PyInit_string_sum() -> *mut PyObject { PyUnicode_FromStringAndSize(version.as_ptr() as *const c_char, version.len() as isize), ); - // It's necessary to transmute `sum_as_string` here because functions marked with `METH_FASTCALL` - // have a different signature. However the `PyMethodDef` struct currently represents all - // functions as a `PyCFunction`. The python interpreter will cast the function pointer back - // to `_PyCFunctionFast`. let wrapped_sum_as_string = PyMethodDef { ml_name: "sum_as_string\0".as_ptr() as *const c_char, - ml_meth: Some(transmute::<_PyCFunctionFast, PyCFunction>(sum_as_string)), + ml_meth: MlMeth { + _PyCFunctionFast: Some(sum_as_string) + }, ml_flags: METH_FASTCALL, ml_doc: "returns the sum of two integers as a string\0".as_ptr() as *const c_char, }; @@ -127,7 +125,7 @@ pub unsafe extern "C" fn sum_as_string( let arg1 = PyLong_AsLong(arg1); if !PyErr_Occurred().is_null() { - return ptr::null() + return ptr::null_mut() } let arg2 = *args.add(1); @@ -137,7 +135,7 @@ pub unsafe extern "C" fn sum_as_string( let arg2 = PyLong_AsLong(arg2); if !PyErr_Occurred().is_null() { - return ptr::null() + return ptr::null_mut() } diff --git a/pyo3-ffi/src/lib.rs b/pyo3-ffi/src/lib.rs index 3564604c..ea840120 100644 --- a/pyo3-ffi/src/lib.rs +++ b/pyo3-ffi/src/lib.rs @@ -83,8 +83,8 @@ //! //! **`src/lib.rs`** //! ```rust -//! use std::mem::transmute; //! use std::os::raw::c_char; +//! use std::ptr; //! //! use pyo3_ffi::*; //! @@ -111,13 +111,11 @@ //! PyUnicode_FromStringAndSize(version.as_ptr() as *const c_char, version.len() as isize), //! ); //! -//! // It's necessary to transmute `sum_as_string` here because functions marked with `METH_FASTCALL` -//! // have a different signature. However the `PyMethodDef` struct currently represents all -//! // functions as a `PyCFunction`. The python interpreter will cast the function pointer back -//! // to `_PyCFunctionFast`. //! let wrapped_sum_as_string = PyMethodDef { //! ml_name: "sum_as_string\0".as_ptr() as *const c_char, -//! ml_meth: Some(transmute::<_PyCFunctionFast, PyCFunction>(sum_as_string)), +//! ml_meth: MlMeth { +//! _PyCFunctionFast: Some(sum_as_string) +//! }, //! ml_flags: METH_FASTCALL, //! ml_doc: "returns the sum of two integers as a string\0".as_ptr() as *const c_char, //! }; @@ -166,7 +164,7 @@ //! //! let arg1 = PyLong_AsLong(arg1); //! if !PyErr_Occurred().is_null() { -//! return ptr::null() +//! return ptr::null_mut() //! } //! //! let arg2 = *args.add(1); @@ -176,7 +174,7 @@ //! //! let arg2 = PyLong_AsLong(arg2); //! if !PyErr_Occurred().is_null() { -//! return ptr::null() +//! return ptr::null_mut() //! } //! let res = (arg1 + arg2).to_string(); //! PyUnicode_FromStringAndSize(res.as_ptr() as *const c_char, res.len() as isize) diff --git a/pyo3-ffi/src/methodobject.rs b/pyo3-ffi/src/methodobject.rs index 6212090f..2f182481 100644 --- a/pyo3-ffi/src/methodobject.rs +++ b/pyo3-ffi/src/methodobject.rs @@ -31,7 +31,7 @@ pub unsafe fn PyCFunction_Check(op: *mut PyObject) -> c_int { pub type PyCFunction = unsafe extern "C" fn(slf: *mut PyObject, args: *mut PyObject) -> *mut PyObject; -#[cfg(not(Py_LIMITED_API))] +#[cfg(any(Py_3_10, not(Py_LIMITED_API)))] pub type _PyCFunctionFast = unsafe extern "C" fn( slf: *mut PyObject, args: *mut *mut PyObject, @@ -52,7 +52,14 @@ pub type _PyCFunctionFastWithKeywords = unsafe extern "C" fn( kwnames: *mut PyObject, ) -> *mut PyObject; -// skipped PyCMethod (since 3.9) +#[cfg(all(Py_3_9, not(Py_LIMITED_API)))] +pub type PyCMethod = unsafe extern "C" fn( + slf: *mut PyObject, + defining_class: *mut PyTypeObject, + args: *const *mut PyObject, + nargs: crate::pyport::Py_ssize_t, + kwnames: *mut PyObject, +) -> *mut PyObject; extern "C" { #[cfg_attr(PyPy, link_name = "PyPyCFunction_GetFunction")] @@ -71,11 +78,44 @@ extern "C" { #[derive(Copy, Clone)] pub struct PyMethodDef { pub ml_name: *const c_char, - pub ml_meth: Option, + pub ml_meth: MlMeth, pub ml_flags: c_int, pub ml_doc: *const c_char, } +/// Function types used to implement Python callables. +/// +/// This union must be accompanied by the correct [ml_flags](PyMethodDef::ml_flags), +/// otherwise the behavior is undefined. +/// +/// See the [Python C API documentation][1] for more information. +/// +/// [1]: https://docs.python.org/3/c-api/structures.html#implementing-functions-and-methods +#[repr(C)] +#[derive(Copy, Clone)] +pub union MlMeth { + /// This variant corresponds with [`METH_VARARGS`] *or* [`METH_NOARGS`] *or* [`METH_O`]. + pub PyCFunction: Option, + + /// This variant corresponds with [`METH_VARARGS`] | [`METH_KEYWORDS`]. + pub PyCFunctionWithKeywords: Option, + + /// This variant corresponds with [`METH_FASTCALL`]. + #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] + pub _PyCFunctionFast: Option<_PyCFunctionFast>, + + /// This variant corresponds with [`METH_FASTCALL`] | [`METH_KEYWORDS`]. + #[cfg(not(Py_LIMITED_API))] + pub _PyCFunctionFastWithKeywords: Option<_PyCFunctionFastWithKeywords>, + + /// This variant corresponds with [`METH_METHOD`] | [`METH_FASTCALL`] | [`METH_KEYWORDS`]. + #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] + pub PyCMethod: Option, +} + +// TODO: This can be a const assert on Rust 1.57 +const _: () = [()][mem::size_of::() - mem::size_of::>()]; + impl Default for PyMethodDef { fn default() -> PyMethodDef { unsafe { mem::zeroed() } @@ -122,7 +162,9 @@ be specified alone or with METH_KEYWORDS. */ pub const METH_FASTCALL: c_int = 0x0080; // skipped METH_STACKLESS -// skipped METH_METHOD + +#[cfg(all(Py_3_9, not(Py_LIMITED_API)))] +pub const METH_METHOD: c_int = 0x0200; extern "C" { #[cfg(not(Py_3_9))] diff --git a/src/impl_/pymethods.rs b/src/impl_/pymethods.rs index cbaa2994..5e921b06 100644 --- a/src/impl_/pymethods.rs +++ b/src/impl_/pymethods.rs @@ -163,17 +163,21 @@ impl PyMethodDef { /// Convert `PyMethodDef` to Python method definition struct `ffi::PyMethodDef` pub(crate) fn as_method_def(&self) -> Result { let meth = match self.ml_meth { - PyMethodType::PyCFunction(meth) => meth.0, - PyMethodType::PyCFunctionWithKeywords(meth) => unsafe { std::mem::transmute(meth.0) }, + PyMethodType::PyCFunction(meth) => ffi::MlMeth { + PyCFunction: Some(meth.0), + }, + PyMethodType::PyCFunctionWithKeywords(meth) => ffi::MlMeth { + PyCFunctionWithKeywords: Some(meth.0), + }, #[cfg(not(Py_LIMITED_API))] - PyMethodType::PyCFunctionFastWithKeywords(meth) => unsafe { - std::mem::transmute(meth.0) + PyMethodType::PyCFunctionFastWithKeywords(meth) => ffi::MlMeth { + _PyCFunctionFastWithKeywords: Some(meth.0), }, }; Ok(ffi::PyMethodDef { ml_name: get_name(self.ml_name)?.as_ptr(), - ml_meth: Some(meth), + ml_meth: meth, ml_flags: self.ml_flags, ml_doc: get_doc(self.ml_doc)?.as_ptr(), })