|
| 1 | +use std::cell::UnsafeCell; |
| 2 | + |
1 | 3 | use crate::{ |
2 | | - types::{PyCFunction, PyModule}, |
| 4 | + ffi, |
| 5 | + ffi_ptr_ext::FfiPtrExt, |
| 6 | + py_result_ext::PyResultExt, |
| 7 | + types::{PyCFunction, PyModule, PyModuleMethods}, |
3 | 8 | Borrowed, Bound, PyResult, Python, |
4 | 9 | }; |
5 | 10 |
|
6 | 11 | pub use crate::impl_::pymethods::PyMethodDef; |
7 | 12 |
|
| 13 | +/// Wrapper around `ffi::PyMethodDef` suitable to use as a static variable for `#[pyfunction]` values. |
| 14 | +/// |
| 15 | +/// The `UnsafeCell` is used because the Python interpreter consumes these as `*mut ffi::PyMethodDef`. |
| 16 | +pub struct PyFunctionDef(UnsafeCell<ffi::PyMethodDef>); |
| 17 | + |
| 18 | +// Safety: contents are only ever used by the Python interpreter, which uses global statics in this way. |
| 19 | +unsafe impl Sync for PyFunctionDef {} |
| 20 | + |
| 21 | +impl PyFunctionDef { |
| 22 | + pub const fn new(def: ffi::PyMethodDef) -> Self { |
| 23 | + Self(UnsafeCell::new(def)) |
| 24 | + } |
| 25 | + |
| 26 | + pub const fn from_method_def(def: PyMethodDef) -> Self { |
| 27 | + Self::new(def.into_raw()) |
| 28 | + } |
| 29 | + |
| 30 | + pub(crate) fn create_py_c_function<'py>( |
| 31 | + &'static self, |
| 32 | + py: Python<'py>, |
| 33 | + module: Option<&Bound<'py, PyModule>>, |
| 34 | + ) -> PyResult<Bound<'py, PyCFunction>> { |
| 35 | + // Safety: self is static |
| 36 | + unsafe { create_py_c_function(py, self.0.get(), module) } |
| 37 | + } |
| 38 | +} |
| 39 | + |
8 | 40 | /// Trait to enable the use of `wrap_pyfunction` with both `Python` and `PyModule`, |
9 | 41 | /// and also to infer the return type of either `&'py PyCFunction` or `Bound<'py, PyCFunction>`. |
10 | 42 | pub trait WrapPyFunctionArg<'py, T> { |
11 | | - fn wrap_pyfunction(self, method_def: &PyMethodDef) -> PyResult<T>; |
| 43 | + fn wrap_pyfunction(self, function_def: &'static PyFunctionDef) -> PyResult<T>; |
12 | 44 | } |
13 | 45 |
|
14 | 46 | impl<'py> WrapPyFunctionArg<'py, Bound<'py, PyCFunction>> for Bound<'py, PyModule> { |
15 | | - fn wrap_pyfunction(self, method_def: &PyMethodDef) -> PyResult<Bound<'py, PyCFunction>> { |
16 | | - PyCFunction::internal_new(self.py(), method_def, Some(&self)) |
| 47 | + fn wrap_pyfunction( |
| 48 | + self, |
| 49 | + function_def: &'static PyFunctionDef, |
| 50 | + ) -> PyResult<Bound<'py, PyCFunction>> { |
| 51 | + function_def.create_py_c_function(self.py(), Some(&self)) |
17 | 52 | } |
18 | 53 | } |
19 | 54 |
|
20 | 55 | impl<'py> WrapPyFunctionArg<'py, Bound<'py, PyCFunction>> for &'_ Bound<'py, PyModule> { |
21 | | - fn wrap_pyfunction(self, method_def: &PyMethodDef) -> PyResult<Bound<'py, PyCFunction>> { |
22 | | - PyCFunction::internal_new(self.py(), method_def, Some(self)) |
| 56 | + fn wrap_pyfunction( |
| 57 | + self, |
| 58 | + function_def: &'static PyFunctionDef, |
| 59 | + ) -> PyResult<Bound<'py, PyCFunction>> { |
| 60 | + function_def.create_py_c_function(self.py(), Some(self)) |
23 | 61 | } |
24 | 62 | } |
25 | 63 |
|
26 | 64 | impl<'py> WrapPyFunctionArg<'py, Bound<'py, PyCFunction>> for Borrowed<'_, 'py, PyModule> { |
27 | | - fn wrap_pyfunction(self, method_def: &PyMethodDef) -> PyResult<Bound<'py, PyCFunction>> { |
28 | | - PyCFunction::internal_new(self.py(), method_def, Some(&self)) |
| 65 | + fn wrap_pyfunction( |
| 66 | + self, |
| 67 | + function_def: &'static PyFunctionDef, |
| 68 | + ) -> PyResult<Bound<'py, PyCFunction>> { |
| 69 | + function_def.create_py_c_function(self.py(), Some(&self)) |
29 | 70 | } |
30 | 71 | } |
31 | 72 |
|
32 | 73 | impl<'py> WrapPyFunctionArg<'py, Bound<'py, PyCFunction>> for &'_ Borrowed<'_, 'py, PyModule> { |
33 | | - fn wrap_pyfunction(self, method_def: &PyMethodDef) -> PyResult<Bound<'py, PyCFunction>> { |
34 | | - PyCFunction::internal_new(self.py(), method_def, Some(self)) |
| 74 | + fn wrap_pyfunction( |
| 75 | + self, |
| 76 | + function_def: &'static PyFunctionDef, |
| 77 | + ) -> PyResult<Bound<'py, PyCFunction>> { |
| 78 | + function_def.create_py_c_function(self.py(), Some(self)) |
35 | 79 | } |
36 | 80 | } |
37 | 81 |
|
38 | 82 | impl<'py> WrapPyFunctionArg<'py, Bound<'py, PyCFunction>> for Python<'py> { |
39 | | - fn wrap_pyfunction(self, method_def: &PyMethodDef) -> PyResult<Bound<'py, PyCFunction>> { |
40 | | - PyCFunction::internal_new(self, method_def, None) |
| 83 | + fn wrap_pyfunction( |
| 84 | + self, |
| 85 | + function_def: &'static PyFunctionDef, |
| 86 | + ) -> PyResult<Bound<'py, PyCFunction>> { |
| 87 | + function_def.create_py_c_function(self, None) |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +/// Creates a `PyCFunction` object from a `PyMethodDef`. |
| 92 | +/// |
| 93 | +/// # Safety |
| 94 | +/// |
| 95 | +/// The `method_def` pointer must be valid for the lifetime of the returned `PyCFunction` |
| 96 | +/// (effectively, it must be a static variable). |
| 97 | +pub unsafe fn create_py_c_function<'py>( |
| 98 | + py: Python<'py>, |
| 99 | + method_def: *mut ffi::PyMethodDef, |
| 100 | + module: Option<&Bound<'py, PyModule>>, |
| 101 | +) -> PyResult<Bound<'py, PyCFunction>> { |
| 102 | + let (mod_ptr, module_name) = if let Some(m) = module { |
| 103 | + let mod_ptr = m.as_ptr(); |
| 104 | + (mod_ptr, Some(m.name()?)) |
| 105 | + } else { |
| 106 | + (std::ptr::null_mut(), None) |
| 107 | + }; |
| 108 | + |
| 109 | + let module_name_ptr = module_name |
| 110 | + .as_ref() |
| 111 | + .map_or(std::ptr::null_mut(), Bound::as_ptr); |
| 112 | + |
| 113 | + unsafe { |
| 114 | + ffi::PyCFunction_NewEx(method_def, mod_ptr, module_name_ptr) |
| 115 | + .assume_owned_or_err(py) |
| 116 | + .cast_into_unchecked() |
| 117 | + } |
| 118 | +} |
| 119 | + |
| 120 | +#[cfg(test)] |
| 121 | +#[cfg(feature = "macros")] |
| 122 | +mod tests { |
| 123 | + #[test] |
| 124 | + fn test_wrap_pyfunction_forms() { |
| 125 | + use crate::types::{PyAnyMethods, PyModule}; |
| 126 | + use crate::{wrap_pyfunction, Python}; |
| 127 | + |
| 128 | + #[crate::pyfunction(crate = "crate")] |
| 129 | + fn f() {} |
| 130 | + |
| 131 | + Python::attach(|py| { |
| 132 | + let module = PyModule::new(py, "test_wrap_pyfunction_forms").unwrap(); |
| 133 | + |
| 134 | + let func = wrap_pyfunction!(f, module.clone()).unwrap(); |
| 135 | + func.call0().unwrap(); |
| 136 | + |
| 137 | + let func = wrap_pyfunction!(f, &module).unwrap(); |
| 138 | + func.call0().unwrap(); |
| 139 | + |
| 140 | + let module_borrowed = module.as_borrowed(); |
| 141 | + |
| 142 | + let func = wrap_pyfunction!(f, module_borrowed).unwrap(); |
| 143 | + func.call0().unwrap(); |
| 144 | + |
| 145 | + let func = wrap_pyfunction!(f, &module_borrowed).unwrap(); |
| 146 | + func.call0().unwrap(); |
| 147 | + |
| 148 | + let func = wrap_pyfunction!(f, py).unwrap(); |
| 149 | + func.call0().unwrap(); |
| 150 | + }); |
41 | 151 | } |
42 | 152 | } |
0 commit comments