// TODO https://github.com/PyO3/pyo3/issues/5487 #![allow(clippy::undocumented_unsafe_blocks)] //! Defines how Python calls are dispatched, see [`PyCallArgs`].for more information. use crate::ffi_ptr_ext::FfiPtrExt as _; use crate::types::{PyAnyMethods as _, PyDict, PyString, PyTuple}; use crate::{ffi, Borrowed, Bound, IntoPyObjectExt as _, Py, PyAny, PyResult}; pub(crate) mod private { use super::*; pub trait Sealed {} impl Sealed for () {} impl Sealed for Bound<'_, PyTuple> {} impl Sealed for &'_ Bound<'_, PyTuple> {} impl Sealed for Py {} impl Sealed for &'_ Py {} impl Sealed for Borrowed<'_, '_, PyTuple> {} pub struct Token; } /// This trait marks types that can be used as arguments to Python function /// calls. /// /// This trait is currently implemented for Rust tuple (up to a size of 12), /// [`Bound<'py, PyTuple>`] and [`Py`]. Custom types that are /// convertible to `PyTuple` via `IntoPyObject` need to do so before passing it /// to `call`. /// /// This trait is not intended to used by downstream crates directly. As such it /// has no publicly available methods and cannot be implemented outside of /// `pyo3`. The corresponding public API is available through [`call`] /// ([`call0`], [`call1`] and friends) on [`PyAnyMethods`]. /// /// # What is `PyCallArgs` used for? /// `PyCallArgs` is used internally in `pyo3` to dispatch the Python calls in /// the most optimal way for the current build configuration. Certain types, /// such as Rust tuples, do allow the usage of a faster calling convention of /// the Python interpreter (if available). More types that may take advantage /// from this may be added in the future. /// /// [`call0`]: crate::types::PyAnyMethods::call0 /// [`call1`]: crate::types::PyAnyMethods::call1 /// [`call`]: crate::types::PyAnyMethods::call /// [`PyAnyMethods`]: crate::types::PyAnyMethods #[diagnostic::on_unimplemented( message = "`{Self}` cannot used as a Python `call` argument", note = "`PyCallArgs` is implemented for Rust tuples, `Bound<'py, PyTuple>` and `Py`", note = "if your type is convertible to `PyTuple` via `IntoPyObject`, call `.into_pyobject(py)` manually", note = "if you meant to pass the type as a single argument, wrap it in a 1-tuple, `(,)`" )] pub trait PyCallArgs<'py>: Sized + private::Sealed { #[doc(hidden)] fn call( self, function: Borrowed<'_, 'py, PyAny>, kwargs: Borrowed<'_, 'py, PyDict>, token: private::Token, ) -> PyResult>; #[doc(hidden)] fn call_positional( self, function: Borrowed<'_, 'py, PyAny>, token: private::Token, ) -> PyResult>; #[doc(hidden)] fn call_method_positional( self, object: Borrowed<'_, 'py, PyAny>, method_name: Borrowed<'_, 'py, PyString>, _: private::Token, ) -> PyResult> { object .getattr(method_name) .and_then(|method| method.call1(self)) } } impl<'py> PyCallArgs<'py> for () { fn call( self, function: Borrowed<'_, 'py, PyAny>, kwargs: Borrowed<'_, 'py, PyDict>, token: private::Token, ) -> PyResult> { let args = self.into_pyobject_or_pyerr(function.py())?; args.call(function, kwargs, token) } fn call_positional( self, function: Borrowed<'_, 'py, PyAny>, token: private::Token, ) -> PyResult> { let args = self.into_pyobject_or_pyerr(function.py())?; args.call_positional(function, token) } } impl<'py> PyCallArgs<'py> for Bound<'py, PyTuple> { #[inline] fn call( self, function: Borrowed<'_, 'py, PyAny>, kwargs: Borrowed<'_, 'py, PyDict>, token: private::Token, ) -> PyResult> { self.as_borrowed().call(function, kwargs, token) } #[inline] fn call_positional( self, function: Borrowed<'_, 'py, PyAny>, token: private::Token, ) -> PyResult> { self.as_borrowed().call_positional(function, token) } } impl<'py> PyCallArgs<'py> for &'_ Bound<'py, PyTuple> { #[inline] fn call( self, function: Borrowed<'_, 'py, PyAny>, kwargs: Borrowed<'_, 'py, PyDict>, token: private::Token, ) -> PyResult> { self.as_borrowed().call(function, kwargs, token) } #[inline] fn call_positional( self, function: Borrowed<'_, 'py, PyAny>, token: private::Token, ) -> PyResult> { self.as_borrowed().call_positional(function, token) } } impl<'py> PyCallArgs<'py> for Py { #[inline] fn call( self, function: Borrowed<'_, 'py, PyAny>, kwargs: Borrowed<'_, 'py, PyDict>, token: private::Token, ) -> PyResult> { self.bind_borrowed(function.py()) .call(function, kwargs, token) } #[inline] fn call_positional( self, function: Borrowed<'_, 'py, PyAny>, token: private::Token, ) -> PyResult> { self.bind_borrowed(function.py()) .call_positional(function, token) } } impl<'py> PyCallArgs<'py> for &'_ Py { #[inline] fn call( self, function: Borrowed<'_, 'py, PyAny>, kwargs: Borrowed<'_, 'py, PyDict>, token: private::Token, ) -> PyResult> { self.bind_borrowed(function.py()) .call(function, kwargs, token) } #[inline] fn call_positional( self, function: Borrowed<'_, 'py, PyAny>, token: private::Token, ) -> PyResult> { self.bind_borrowed(function.py()) .call_positional(function, token) } } impl<'py> PyCallArgs<'py> for Borrowed<'_, 'py, PyTuple> { #[inline] fn call( self, function: Borrowed<'_, 'py, PyAny>, kwargs: Borrowed<'_, 'py, PyDict>, _: private::Token, ) -> PyResult> { unsafe { ffi::PyObject_Call(function.as_ptr(), self.as_ptr(), kwargs.as_ptr()) .assume_owned_or_err(function.py()) } } #[inline] fn call_positional( self, function: Borrowed<'_, 'py, PyAny>, _: private::Token, ) -> PyResult> { unsafe { ffi::PyObject_Call(function.as_ptr(), self.as_ptr(), core::ptr::null_mut()) .assume_owned_or_err(function.py()) } } } #[cfg(test)] #[cfg(feature = "macros")] mod tests { use crate::{ pyfunction, types::{PyDict, PyTuple}, Py, }; #[pyfunction(signature = (*args, **kwargs), crate = "crate")] fn args_kwargs( args: Py, kwargs: Option>, ) -> (Py, Option>) { (args, kwargs) } #[test] fn test_call() { use crate::{ types::{IntoPyDict, PyAnyMethods, PyDict, PyTuple}, wrap_pyfunction, Py, Python, }; Python::attach(|py| { let f = wrap_pyfunction!(args_kwargs, py).unwrap(); let args = PyTuple::new(py, [1, 2, 3]).unwrap(); let kwargs = &[("foo", 1), ("bar", 2)].into_py_dict(py).unwrap(); macro_rules! check_call { ($args:expr, $kwargs:expr) => { let (a, k): (Py, Py) = f .call(args.clone(), Some(kwargs)) .unwrap() .extract() .unwrap(); assert!(a.is(&args)); assert!(k.is(kwargs)); }; } // Bound<'py, PyTuple> check_call!(args.clone(), kwargs); // &Bound<'py, PyTuple> check_call!(&args, kwargs); // Py check_call!(args.clone().unbind(), kwargs); // &Py check_call!(&args.as_unbound(), kwargs); // Borrowed<'_, '_, PyTuple> check_call!(args.as_borrowed(), kwargs); }) } #[test] fn test_call_positional() { use crate::{ types::{PyAnyMethods, PyNone, PyTuple}, wrap_pyfunction, Py, Python, }; Python::attach(|py| { let f = wrap_pyfunction!(args_kwargs, py).unwrap(); let args = PyTuple::new(py, [1, 2, 3]).unwrap(); macro_rules! check_call { ($args:expr, $kwargs:expr) => { let (a, k): (Py, Py) = f.call1(args.clone()).unwrap().extract().unwrap(); assert!(a.is(&args)); assert!(k.is_none(py)); }; } // Bound<'py, PyTuple> check_call!(args.clone(), kwargs); // &Bound<'py, PyTuple> check_call!(&args, kwargs); // Py check_call!(args.clone().unbind(), kwargs); // &Py check_call!(args.as_unbound(), kwargs); // Borrowed<'_, '_, PyTuple> check_call!(args.as_borrowed(), kwargs); }) } }