wrap_x: change macros back to `macro_rules!`

This commit is contained in:
David Hewitt 2022-05-10 08:17:10 +01:00
parent a8b74a7f33
commit 7a9e70e2c7
15 changed files with 107 additions and 131 deletions

View File

@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The deprecated `pyproto` feature is now disabled by default. [#2322](https://github.com/PyO3/pyo3/pull/2322) - The deprecated `pyproto` feature is now disabled by default. [#2322](https://github.com/PyO3/pyo3/pull/2322)
- Deprecate `ToBorrowedObject` trait (it is only used as a wrapper for `ToPyObject`). [#2333](https://github.com/PyO3/pyo3/pull/2333) - Deprecate `ToBorrowedObject` trait (it is only used as a wrapper for `ToPyObject`). [#2333](https://github.com/PyO3/pyo3/pull/2333)
- `impl<T, const N: usize> IntoPy<PyObject> for [T; N]` now requires `T: IntoPy` rather than `T: ToPyObject`. [#2326](https://github.com/PyO3/pyo3/pull/2326) - `impl<T, const N: usize> IntoPy<PyObject> for [T; N]` now requires `T: IntoPy` rather than `T: ToPyObject`. [#2326](https://github.com/PyO3/pyo3/pull/2326)
- Correct `wrap_pymodule` to match normal namespacing rules: it no longer "sees through" glob imports of `use submodule::*` when `submodule::submodule` is a `#[pymodule]`. [#2363](https://github.com/PyO3/pyo3/pull/2363)
- Deprecate experimental `generate-abi3-import-lib` feature in favor of the new `generate-import-lib` feature. [#2364](https://github.com/PyO3/pyo3/pull/2364) - Deprecate experimental `generate-abi3-import-lib` feature in favor of the new `generate-import-lib` feature. [#2364](https://github.com/PyO3/pyo3/pull/2364)
### Fixed ### Fixed

View File

@ -3,7 +3,6 @@ use pyo3::types::PyDict;
use pyo3::wrap_pymodule; use pyo3::wrap_pymodule;
mod submodule; mod submodule;
use submodule::*;
#[pyclass] #[pyclass]
struct ExampleClass { struct ExampleClass {
@ -23,7 +22,7 @@ impl ExampleClass {
#[pymodule] #[pymodule]
fn maturin_starter(py: Python<'_>, m: &PyModule) -> PyResult<()> { fn maturin_starter(py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_class::<ExampleClass>()?; m.add_class::<ExampleClass>()?;
m.add_wrapped(wrap_pymodule!(submodule))?; m.add_wrapped(wrap_pymodule!(submodule::submodule))?;
// Inserting to sys.modules allows importing submodules nicely from Python // Inserting to sys.modules allows importing submodules nicely from Python
// e.g. from maturin_starter.submodule import SubmoduleClass // e.g. from maturin_starter.submodule import SubmoduleClass

View File

@ -3,7 +3,6 @@ use pyo3::types::PyDict;
use pyo3::wrap_pymodule; use pyo3::wrap_pymodule;
mod submodule; mod submodule;
use submodule::*;
#[pyclass] #[pyclass]
struct ExampleClass { struct ExampleClass {
@ -23,7 +22,7 @@ impl ExampleClass {
#[pymodule] #[pymodule]
fn _setuptools_rust_starter(py: Python<'_>, m: &PyModule) -> PyResult<()> { fn _setuptools_rust_starter(py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_class::<ExampleClass>()?; m.add_class::<ExampleClass>()?;
m.add_wrapped(wrap_pymodule!(submodule))?; m.add_wrapped(wrap_pymodule!(submodule::submodule))?;
// Inserting to sys.modules allows importing submodules nicely from Python // Inserting to sys.modules allows importing submodules nicely from Python
// e.g. from setuptools_rust_starter.submodule import SubmoduleClass // e.g. from setuptools_rust_starter.submodule import SubmoduleClass

View File

@ -26,7 +26,6 @@ mod pyimpl;
mod pymethod; mod pymethod;
#[cfg(feature = "pyproto")] #[cfg(feature = "pyproto")]
mod pyproto; mod pyproto;
mod wrap;
pub use frompyobject::build_derive_from_pyobject; pub use frompyobject::build_derive_from_pyobject;
pub use module::{process_functions_in_module, pymodule_impl, PyModuleOptions}; pub use module::{process_functions_in_module, pymodule_impl, PyModuleOptions};
@ -36,4 +35,3 @@ pub use pyimpl::{build_py_methods, PyClassMethodsType};
#[cfg(feature = "pyproto")] #[cfg(feature = "pyproto")]
pub use pyproto::build_py_proto; pub use pyproto::build_py_proto;
pub use utils::get_doc; pub use utils::get_doc;
pub use wrap::{wrap_pyfunction_impl, wrap_pymodule_impl, WrapPyFunctionArgs};

View File

@ -7,9 +7,8 @@ use crate::{
}, },
pyfunction::{impl_wrap_pyfunction, PyFunctionOptions}, pyfunction::{impl_wrap_pyfunction, PyFunctionOptions},
utils::{get_pyo3_crate, PythonDoc}, utils::{get_pyo3_crate, PythonDoc},
wrap::module_def_ident,
}; };
use proc_macro2::{Span, TokenStream}; use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use syn::{ use syn::{
ext::IdentExt, ext::IdentExt,
@ -70,29 +69,49 @@ pub fn pymodule_impl(
) -> TokenStream { ) -> TokenStream {
let name = options.name.unwrap_or_else(|| fnname.unraw()); let name = options.name.unwrap_or_else(|| fnname.unraw());
let krate = get_pyo3_crate(&options.krate); let krate = get_pyo3_crate(&options.krate);
let cb_name = Ident::new(&format!("PyInit_{}", name), Span::call_site()); let pyinit_symbol = format!("PyInit_{}", name);
let module_def_name = module_def_ident(fnname);
quote! { quote! {
#[no_mangle] // Create a module with the same name as the `#[pymodule]` - this way `use <the module>`
#[allow(non_snake_case)] // will actually bring both the module and the function into scope.
/// This autogenerated function is called by the python interpreter when importing #[doc(hidden)]
/// the module. #visibility mod #fnname {
pub unsafe extern "C" fn #cb_name() -> *mut #krate::ffi::PyObject { pub(crate) struct MakeDef;
unsafe { #module_def_name.module_init() } pub static DEF: #krate::impl_::pymodule::ModuleDef = MakeDef::make_def();
/// This autogenerated function is called by the python interpreter when importing
/// the module.
#[export_name = #pyinit_symbol]
pub unsafe extern "C" fn init() -> *mut #krate::ffi::PyObject {
DEF.module_init()
}
} }
#[doc(hidden)] // Generate the definition inside an anonymous function in the same scope as the original function -
#visibility static #module_def_name: #krate::impl_::pymodule::ModuleDef = unsafe { // this avoids complications around the fact that the generated module has a different scope
#krate::impl_::pymodule::ModuleDef::new(concat!(stringify!(#name), "\0"), #doc, #krate::impl_::pymodule::ModuleInitializer(#fnname)) // (and `super` doesn't always refer to the outer scope, e.g. if the `#[pymodule] is
// inside a function body)
const _: () = {
use #krate::impl_::pymodule as impl_;
impl #fnname::MakeDef {
const fn make_def() -> impl_::ModuleDef {
const INITIALIZER: impl_::ModuleInitializer = impl_::ModuleInitializer(#fnname);
unsafe {
impl_::ModuleDef::new(concat!(stringify!(#name), "\0"), #doc, INITIALIZER)
}
}
}
}; };
} }
} }
/// Finds and takes care of the #[pyfn(...)] in `#[pymodule]` /// Finds and takes care of the #[pyfn(...)] in `#[pymodule]`
pub fn process_functions_in_module(func: &mut syn::ItemFn) -> syn::Result<()> { pub fn process_functions_in_module(
options: &PyModuleOptions,
func: &mut syn::ItemFn,
) -> syn::Result<()> {
let mut stmts: Vec<syn::Stmt> = Vec::new(); let mut stmts: Vec<syn::Stmt> = Vec::new();
let krate = get_pyo3_crate(&options.krate);
for mut stmt in func.block.stmts.drain(..) { for mut stmt in func.block.stmts.drain(..) {
if let syn::Stmt::Item(syn::Item::Fn(func)) = &mut stmt { if let syn::Stmt::Item(syn::Item::Fn(func)) = &mut stmt {
@ -102,7 +121,7 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) -> syn::Result<()> {
let name = &func.sig.ident; let name = &func.sig.ident;
let statements: Vec<syn::Stmt> = syn::parse_quote! { let statements: Vec<syn::Stmt> = syn::parse_quote! {
#wrapped_function #wrapped_function
#module_name.add_function(#name::wrap(#name::DEF, #module_name)?)?; #module_name.add_function(#krate::impl_::pyfunction::wrap_pyfunction(&#name::DEF, #module_name)?)?;
}; };
stmts.extend(statements); stmts.extend(statements);
} }

View File

@ -446,12 +446,8 @@ pub fn impl_wrap_pyfunction(
// will actually bring both the module and the function into scope. // will actually bring both the module and the function into scope.
#[doc(hidden)] #[doc(hidden)]
#vis mod #name { #vis mod #name {
use #krate as _pyo3; pub(crate) struct MakeDef;
pub(crate) struct PyO3Def; pub const DEF: #krate::impl_::pyfunction::PyMethodDef = MakeDef::DEF;
// Exported for `wrap_pyfunction!`
pub use _pyo3::impl_::pyfunction::wrap_pyfunction as wrap;
pub const DEF: _pyo3::PyMethodDef = <PyO3Def as _pyo3::impl_::pyfunction::PyFunctionDef>::DEF;
} }
// Generate the definition inside an anonymous function in the same scope as the original function - // Generate the definition inside an anonymous function in the same scope as the original function -
@ -460,8 +456,8 @@ pub fn impl_wrap_pyfunction(
// inside a function body) // inside a function body)
const _: () = { const _: () = {
use #krate as _pyo3; use #krate as _pyo3;
impl _pyo3::impl_::pyfunction::PyFunctionDef for #name::PyO3Def { impl #name::MakeDef {
const DEF: _pyo3::PyMethodDef = #methoddef; const DEF: #krate::impl_::pyfunction::PyMethodDef = #methoddef;
} }
}; };
}; };

View File

@ -1,54 +0,0 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{parse::Parse, spanned::Spanned, Ident, Token};
pub struct WrapPyFunctionArgs {
function: syn::Path,
comma_and_arg: Option<(Token![,], syn::Expr)>,
}
impl Parse for WrapPyFunctionArgs {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
let function = input.parse()?;
let comma_and_arg = if !input.is_empty() {
Some((input.parse()?, input.parse()?))
} else {
None
};
Ok(Self {
function,
comma_and_arg,
})
}
}
pub fn wrap_pyfunction_impl(args: WrapPyFunctionArgs) -> TokenStream {
let WrapPyFunctionArgs {
function,
comma_and_arg,
} = args;
if let Some((_, arg)) = comma_and_arg {
quote! { #function::wrap(#function::DEF, #arg) }
} else {
quote! { &|arg| #function::wrap(#function::DEF, arg) }
}
}
pub fn wrap_pymodule_impl(mut module_path: syn::Path) -> syn::Result<TokenStream> {
let span = module_path.span();
let last_segment = module_path
.segments
.last_mut()
.ok_or_else(|| err_spanned!(span => "expected non-empty path"))?;
last_segment.ident = module_def_ident(&last_segment.ident);
Ok(quote! {
&|py| unsafe { #module_path.make_module(py).expect("failed to wrap pymodule") }
})
}
pub(crate) fn module_def_ident(name: &Ident) -> Ident {
format_ident!("__PYO3_PYMODULE_DEF_{}", name.to_string().to_uppercase())
}

View File

@ -9,8 +9,8 @@ use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
use pyo3_macros_backend::{ use pyo3_macros_backend::{
build_derive_from_pyobject, build_py_class, build_py_enum, build_py_function, build_py_methods, build_derive_from_pyobject, build_py_class, build_py_enum, build_py_function, build_py_methods,
get_doc, process_functions_in_module, pymodule_impl, wrap_pyfunction_impl, wrap_pymodule_impl, get_doc, process_functions_in_module, pymodule_impl, PyClassArgs, PyClassMethodsType,
PyClassArgs, PyClassMethodsType, PyFunctionOptions, PyModuleOptions, WrapPyFunctionArgs, PyFunctionOptions, PyModuleOptions,
}; };
use quote::quote; use quote::quote;
use syn::{parse::Nothing, parse_macro_input}; use syn::{parse::Nothing, parse_macro_input};
@ -41,7 +41,7 @@ pub fn pymodule(args: TokenStream, input: TokenStream) -> TokenStream {
Err(e) => return e.into_compile_error().into(), Err(e) => return e.into_compile_error().into(),
}; };
if let Err(err) = process_functions_in_module(&mut ast) { if let Err(err) = process_functions_in_module(&options, &mut ast) {
return err.into_compile_error().into(); return err.into_compile_error().into();
} }
@ -194,25 +194,6 @@ pub fn derive_from_py_object(item: TokenStream) -> TokenStream {
.into() .into()
} }
/// Wraps a Rust function annotated with [`#[pyfunction]`](macro@crate::pyfunction).
///
/// This can be used with `PyModule::add_function` to add free functions to a `PyModule` - see its
/// documentation for more information.
#[proc_macro]
pub fn wrap_pyfunction(input: TokenStream) -> TokenStream {
let args = parse_macro_input!(input as WrapPyFunctionArgs);
wrap_pyfunction_impl(args).into()
}
/// Returns a function that takes a `Python` instance and returns a Python module.
///
/// Use this together with [`#[pymodule]`](macro@crate::pymodule) and `PyModule::add_wrapped`.
#[proc_macro]
pub fn wrap_pymodule(input: TokenStream) -> TokenStream {
let path = parse_macro_input!(input as syn::Path);
wrap_pymodule_impl(path).unwrap_or_compile_error().into()
}
fn pyclass_impl( fn pyclass_impl(
attrs: TokenStream, attrs: TokenStream,
mut ast: syn::ItemStruct, mut ast: syn::ItemStruct,

View File

@ -1,14 +1,10 @@
use crate::{ use crate::{derive_utils::PyFunctionArguments, types::PyCFunction, PyResult};
derive_utils::PyFunctionArguments, impl_::pymethods::PyMethodDef, types::PyCFunction, PyResult,
};
pub trait PyFunctionDef { pub use crate::impl_::pymethods::PyMethodDef;
const DEF: crate::PyMethodDef;
}
pub fn wrap_pyfunction<'a>( pub fn wrap_pyfunction<'a>(
method_def: PyMethodDef, method_def: &PyMethodDef,
args: impl Into<PyFunctionArguments<'a>>, py_or_module: impl Into<PyFunctionArguments<'a>>,
) -> PyResult<&'a PyCFunction> { ) -> PyResult<&'a PyCFunction> {
PyCFunction::internal_new(method_def, args.into()) PyCFunction::internal_new(method_def, py_or_module.into())
} }

View File

@ -405,9 +405,7 @@ pub mod proc_macro {
#[cfg(all(feature = "macros", feature = "pyproto"))] #[cfg(all(feature = "macros", feature = "pyproto"))]
pub use pyo3_macros::pyproto; pub use pyo3_macros::pyproto;
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
pub use pyo3_macros::{ pub use pyo3_macros::{pyclass, pyfunction, pymethods, pymodule, FromPyObject};
pyclass, pyfunction, pymethods, pymodule, wrap_pyfunction, wrap_pymodule, FromPyObject,
};
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
#[macro_use] #[macro_use]

View File

@ -115,3 +115,38 @@ macro_rules! py_run_impl {
} }
}}; }};
} }
/// Wraps a Rust function annotated with [`#[pyfunction]`](macro@crate::pyfunction).
///
/// This can be used with [`PyModule::add_function`](crate::types::PyModule::add_function) to add free
/// functions to a [`PyModule`](crate::types::PyModule) - see its documentation for more information.
#[macro_export]
macro_rules! wrap_pyfunction {
($function:path) => {
&|py_or_module| {
use $function as wrapped_pyfunction;
$crate::impl_::pyfunction::wrap_pyfunction(&wrapped_pyfunction::DEF, py_or_module)
}
};
($function:path, $py_or_module:expr) => {{
use $function as wrapped_pyfunction;
$crate::impl_::pyfunction::wrap_pyfunction(&wrapped_pyfunction::DEF, $py_or_module)
}};
}
/// Returns a function that takes a [`Python`](crate::Python) instance and returns a
/// Python module.
///
/// Use this together with [`#[pymodule]`](crate::pymodule) and
/// [`PyModule::add_wrapped`](crate::types::PyModule::add_wrapped).
#[macro_export]
macro_rules! wrap_pymodule {
($module:path) => {
&|py| {
use $module as wrapped_pymodule;
wrapped_pymodule::DEF
.make_module(py)
.expect("failed to wrap pymodule")
}
};
}

View File

@ -140,6 +140,9 @@ impl<T> GILOnceCell<T> {
/// } /// }
/// # /// #
/// # Python::with_gil(|py| { /// # Python::with_gil(|py| {
/// # let fun_slow = wrap_pyfunction!(create_dict, py).unwrap();
/// # let dict = fun_slow.call0().unwrap();
/// # assert!(dict.contains("foo").unwrap());
/// # let fun = wrap_pyfunction!(create_dict_faster, py).unwrap(); /// # let fun = wrap_pyfunction!(create_dict_faster, py).unwrap();
/// # let dict = fun.call0().unwrap(); /// # let dict = fun.call0().unwrap();
/// # assert!(dict.contains("foo").unwrap()); /// # assert!(dict.contains("foo").unwrap());

View File

@ -22,7 +22,10 @@ pub use crate::pyclass_init::PyClassInitializer;
pub use crate::types::{PyAny, PyModule}; pub use crate::types::{PyAny, PyModule};
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
pub use pyo3_macros::{pyclass, pyfunction, pymethods, pymodule, wrap_pyfunction, FromPyObject}; pub use pyo3_macros::{pyclass, pyfunction, pymethods, pymodule, FromPyObject};
#[cfg(all(feature = "macros", feature = "pyproto"))] #[cfg(all(feature = "macros", feature = "pyproto"))]
pub use pyo3_macros::pyproto; pub use pyo3_macros::pyproto;
#[cfg(feature = "macros")]
pub use crate::wrap_pyfunction;

View File

@ -166,10 +166,11 @@ impl PyByteArray {
/// ///
/// The following `bug` function is unsound ⚠️ /// The following `bug` function is unsound ⚠️
/// ///
/// ```rust /// ```rust,no_run
/// # use pyo3::prelude::*; /// # use pyo3::prelude::*;
/// # use pyo3::types::PyByteArray; /// # use pyo3::types::PyByteArray;
/// ///
/// # #[allow(dead_code)]
/// #[pyfunction] /// #[pyfunction]
/// fn bug(py: Python<'_>, bytes: &PyByteArray) { /// fn bug(py: Python<'_>, bytes: &PyByteArray) {
/// let slice = unsafe { bytes.as_bytes() }; /// let slice = unsafe { bytes.as_bytes() };
@ -186,6 +187,7 @@ impl PyByteArray {
/// // remaining valid. As such this is also undefined behavior. /// // remaining valid. As such this is also undefined behavior.
/// println!("{:?}", slice[0]); /// println!("{:?}", slice[0]);
/// } /// }
/// ```
pub unsafe fn as_bytes(&self) -> &[u8] { pub unsafe fn as_bytes(&self) -> &[u8] {
slice::from_raw_parts(self.data(), self.len()) slice::from_raw_parts(self.data(), self.len())
} }

View File

@ -65,7 +65,7 @@ impl PyCFunction {
py_or_module: PyFunctionArguments<'a>, py_or_module: PyFunctionArguments<'a>,
) -> PyResult<&'a Self> { ) -> PyResult<&'a Self> {
Self::internal_new( Self::internal_new(
PyMethodDef::cfunction_with_keywords( &PyMethodDef::cfunction_with_keywords(
name, name,
pymethods::PyCFunctionWithKeywords(fun), pymethods::PyCFunctionWithKeywords(fun),
doc, doc,
@ -82,7 +82,7 @@ impl PyCFunction {
py_or_module: PyFunctionArguments<'a>, py_or_module: PyFunctionArguments<'a>,
) -> PyResult<&'a Self> { ) -> PyResult<&'a Self> {
Self::internal_new( Self::internal_new(
PyMethodDef::noargs(name, pymethods::PyCFunction(fun), doc), &PyMethodDef::noargs(name, pymethods::PyCFunction(fun), doc),
py_or_module, py_or_module,
) )
} }
@ -125,16 +125,16 @@ impl PyCFunction {
pymethods::PyCFunctionWithKeywords(run_closure::<F, R>), pymethods::PyCFunctionWithKeywords(run_closure::<F, R>),
"", "",
); );
Self::internal_new_from_pointers(method_def, py, capsule.as_ptr(), std::ptr::null_mut()) Self::internal_new_from_pointers(&method_def, py, capsule.as_ptr(), std::ptr::null_mut())
} }
#[doc(hidden)] #[doc(hidden)]
fn internal_new_from_pointers( fn internal_new_from_pointers<'py>(
method_def: PyMethodDef, method_def: &PyMethodDef,
py: Python<'_>, py: Python<'py>,
mod_ptr: *mut ffi::PyObject, mod_ptr: *mut ffi::PyObject,
module_name: *mut ffi::PyObject, module_name: *mut ffi::PyObject,
) -> PyResult<&Self> { ) -> PyResult<&'py Self> {
let def = method_def let def = method_def
.as_method_def() .as_method_def()
.map_err(|err| PyValueError::new_err(err.0))?; .map_err(|err| PyValueError::new_err(err.0))?;
@ -148,10 +148,10 @@ impl PyCFunction {
} }
#[doc(hidden)] #[doc(hidden)]
pub fn internal_new( pub fn internal_new<'py>(
method_def: PyMethodDef, method_def: &PyMethodDef,
py_or_module: PyFunctionArguments<'_>, py_or_module: PyFunctionArguments<'py>,
) -> PyResult<&Self> { ) -> PyResult<&'py Self> {
let (py, module) = py_or_module.into_py_and_maybe_module(); let (py, module) = py_or_module.into_py_and_maybe_module();
let (mod_ptr, module_name) = if let Some(m) = module { let (mod_ptr, module_name) = if let Some(m) = module {
let mod_ptr = m.as_ptr(); let mod_ptr = m.as_ptr();