Implement nl_meth as an union.

This commit is contained in:
mejrs 2022-02-14 00:03:33 +01:00
parent 03e650c02f
commit dadbc22b2e
4 changed files with 67 additions and 25 deletions

View file

@ -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()
}

View file

@ -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)

View file

@ -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<PyCFunction>,
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<PyCFunction>,
/// This variant corresponds with [`METH_VARARGS`] | [`METH_KEYWORDS`].
pub PyCFunctionWithKeywords: Option<PyCFunctionWithKeywords>,
/// 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<PyCMethod>,
}
// TODO: This can be a const assert on Rust 1.57
const _: () = [()][mem::size_of::<MlMeth>() - mem::size_of::<Option<extern "C" fn()>>()];
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))]

View file

@ -163,17 +163,21 @@ impl PyMethodDef {
/// Convert `PyMethodDef` to Python method definition struct `ffi::PyMethodDef`
pub(crate) fn as_method_def(&self) -> Result<ffi::PyMethodDef, NulByteInString> {
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(),
})