Skip to main content

pyo3/
call.rs

1//! Defines how Python calls are dispatched, see [`PyCallArgs`].for more information.
2
3use crate::ffi_ptr_ext::FfiPtrExt as _;
4use crate::types::{PyAnyMethods as _, PyDict, PyString, PyTuple};
5use crate::{Borrowed, Bound, IntoPyObjectExt as _, Py, PyAny, PyResult, ffi};
6
7pub(crate) mod private {
8    use super::*;
9
10    pub trait Sealed {}
11
12    impl Sealed for () {}
13    impl Sealed for Bound<'_, PyTuple> {}
14    impl Sealed for &'_ Bound<'_, PyTuple> {}
15    impl Sealed for Py<PyTuple> {}
16    impl Sealed for &'_ Py<PyTuple> {}
17    impl Sealed for Borrowed<'_, '_, PyTuple> {}
18    pub struct Token;
19}
20
21/// This trait marks types that can be used as arguments to Python function
22/// calls.
23///
24/// This trait is currently implemented for Rust tuple (up to a size of 12),
25/// [`Bound<'py, PyTuple>`] and [`Py<PyTuple>`]. Custom types that are
26/// convertible to `PyTuple` via `IntoPyObject` need to do so before passing it
27/// to `call`.
28///
29/// This trait is not intended to used by downstream crates directly. As such it
30/// has no publicly available methods and cannot be implemented outside of
31/// `pyo3`. The corresponding public API is available through [`call`]
32/// ([`call0`], [`call1`] and friends) on [`PyAnyMethods`].
33///
34/// # What is `PyCallArgs` used for?
35/// `PyCallArgs` is used internally in `pyo3` to dispatch the Python calls in
36/// the most optimal way for the current build configuration. Certain types,
37/// such as Rust tuples, do allow the usage of a faster calling convention of
38/// the Python interpreter (if available). More types that may take advantage
39/// from this may be added in the future.
40///
41/// [`call0`]: crate::types::PyAnyMethods::call0
42/// [`call1`]: crate::types::PyAnyMethods::call1
43/// [`call`]: crate::types::PyAnyMethods::call
44/// [`PyAnyMethods`]: crate::types::PyAnyMethods
45#[diagnostic::on_unimplemented(
46    message = "`{Self}` cannot used as a Python `call` argument",
47    note = "`PyCallArgs` is implemented for Rust tuples, `Bound<'py, PyTuple>` and `Py<PyTuple>`",
48    note = "if your type is convertible to `PyTuple` via `IntoPyObject`, call `<arg>.into_pyobject(py)` manually",
49    note = "if you meant to pass the type as a single argument, wrap it in a 1-tuple, `(<arg>,)`"
50)]
51pub trait PyCallArgs<'py>: Sized + private::Sealed {
52    #[doc(hidden)]
53    fn call(
54        self,
55        function: Borrowed<'_, 'py, PyAny>,
56        kwargs: Borrowed<'_, 'py, PyDict>,
57        token: private::Token,
58    ) -> PyResult<Bound<'py, PyAny>>;
59
60    #[doc(hidden)]
61    fn call_positional(
62        self,
63        function: Borrowed<'_, 'py, PyAny>,
64        token: private::Token,
65    ) -> PyResult<Bound<'py, PyAny>>;
66
67    #[doc(hidden)]
68    fn call_method_positional(
69        self,
70        object: Borrowed<'_, 'py, PyAny>,
71        method_name: Borrowed<'_, 'py, PyString>,
72        _: private::Token,
73    ) -> PyResult<Bound<'py, PyAny>> {
74        object
75            .getattr(method_name)
76            .and_then(|method| method.call1(self))
77    }
78}
79
80impl<'py> PyCallArgs<'py> for () {
81    fn call(
82        self,
83        function: Borrowed<'_, 'py, PyAny>,
84        kwargs: Borrowed<'_, 'py, PyDict>,
85        token: private::Token,
86    ) -> PyResult<Bound<'py, PyAny>> {
87        let args = self.into_pyobject_or_pyerr(function.py())?;
88        args.call(function, kwargs, token)
89    }
90
91    fn call_positional(
92        self,
93        function: Borrowed<'_, 'py, PyAny>,
94        token: private::Token,
95    ) -> PyResult<Bound<'py, PyAny>> {
96        let args = self.into_pyobject_or_pyerr(function.py())?;
97        args.call_positional(function, token)
98    }
99}
100
101impl<'py> PyCallArgs<'py> for Bound<'py, PyTuple> {
102    #[inline]
103    fn call(
104        self,
105        function: Borrowed<'_, 'py, PyAny>,
106        kwargs: Borrowed<'_, 'py, PyDict>,
107        token: private::Token,
108    ) -> PyResult<Bound<'py, PyAny>> {
109        self.as_borrowed().call(function, kwargs, token)
110    }
111
112    #[inline]
113    fn call_positional(
114        self,
115        function: Borrowed<'_, 'py, PyAny>,
116        token: private::Token,
117    ) -> PyResult<Bound<'py, PyAny>> {
118        self.as_borrowed().call_positional(function, token)
119    }
120}
121
122impl<'py> PyCallArgs<'py> for &'_ Bound<'py, PyTuple> {
123    #[inline]
124    fn call(
125        self,
126        function: Borrowed<'_, 'py, PyAny>,
127        kwargs: Borrowed<'_, 'py, PyDict>,
128        token: private::Token,
129    ) -> PyResult<Bound<'py, PyAny>> {
130        self.as_borrowed().call(function, kwargs, token)
131    }
132
133    #[inline]
134    fn call_positional(
135        self,
136        function: Borrowed<'_, 'py, PyAny>,
137        token: private::Token,
138    ) -> PyResult<Bound<'py, PyAny>> {
139        self.as_borrowed().call_positional(function, token)
140    }
141}
142
143impl<'py> PyCallArgs<'py> for Py<PyTuple> {
144    #[inline]
145    fn call(
146        self,
147        function: Borrowed<'_, 'py, PyAny>,
148        kwargs: Borrowed<'_, 'py, PyDict>,
149        token: private::Token,
150    ) -> PyResult<Bound<'py, PyAny>> {
151        self.bind_borrowed(function.py())
152            .call(function, kwargs, token)
153    }
154
155    #[inline]
156    fn call_positional(
157        self,
158        function: Borrowed<'_, 'py, PyAny>,
159        token: private::Token,
160    ) -> PyResult<Bound<'py, PyAny>> {
161        self.bind_borrowed(function.py())
162            .call_positional(function, token)
163    }
164}
165
166impl<'py> PyCallArgs<'py> for &'_ Py<PyTuple> {
167    #[inline]
168    fn call(
169        self,
170        function: Borrowed<'_, 'py, PyAny>,
171        kwargs: Borrowed<'_, 'py, PyDict>,
172        token: private::Token,
173    ) -> PyResult<Bound<'py, PyAny>> {
174        self.bind_borrowed(function.py())
175            .call(function, kwargs, token)
176    }
177
178    #[inline]
179    fn call_positional(
180        self,
181        function: Borrowed<'_, 'py, PyAny>,
182        token: private::Token,
183    ) -> PyResult<Bound<'py, PyAny>> {
184        self.bind_borrowed(function.py())
185            .call_positional(function, token)
186    }
187}
188
189impl<'py> PyCallArgs<'py> for Borrowed<'_, 'py, PyTuple> {
190    #[inline]
191    fn call(
192        self,
193        function: Borrowed<'_, 'py, PyAny>,
194        kwargs: Borrowed<'_, 'py, PyDict>,
195        _: private::Token,
196    ) -> PyResult<Bound<'py, PyAny>> {
197        // SAFETY: `function`, `self` and `kwargs` are valid pointers to a
198        // callable, a tuple and a dict, and `PyObject_Call` returns a new
199        // owned reference on success, or NULL with an exception set on
200        // failure.
201        unsafe {
202            ffi::PyObject_Call(function.as_ptr(), self.as_ptr(), kwargs.as_ptr())
203                .assume_owned_or_err(function.py())
204        }
205    }
206
207    #[inline]
208    fn call_positional(
209        self,
210        function: Borrowed<'_, 'py, PyAny>,
211        _: private::Token,
212    ) -> PyResult<Bound<'py, PyAny>> {
213        // SAFETY: `function` and `self` are valid pointers to a callable and
214        // a tuple, NULL is documented as a valid value for kwargs, and
215        // `PyObject_Call` returns a new owned reference on success, or NULL
216        // with an exception set on failure.
217        unsafe {
218            ffi::PyObject_Call(function.as_ptr(), self.as_ptr(), core::ptr::null_mut())
219                .assume_owned_or_err(function.py())
220        }
221    }
222}
223
224#[cfg(test)]
225#[cfg(feature = "macros")]
226mod tests {
227    use crate::{
228        Py, pyfunction,
229        types::{PyDict, PyTuple},
230    };
231
232    #[pyfunction(signature = (*args, **kwargs), crate = "crate")]
233    fn args_kwargs(
234        args: Py<PyTuple>,
235        kwargs: Option<Py<PyDict>>,
236    ) -> (Py<PyTuple>, Option<Py<PyDict>>) {
237        (args, kwargs)
238    }
239
240    #[test]
241    fn test_call() {
242        use crate::{
243            Py, Python,
244            types::{IntoPyDict, PyAnyMethods, PyDict, PyTuple},
245            wrap_pyfunction,
246        };
247
248        Python::attach(|py| {
249            let f = wrap_pyfunction!(args_kwargs, py).unwrap();
250
251            let args = PyTuple::new(py, [1, 2, 3]).unwrap();
252            let kwargs = &[("foo", 1), ("bar", 2)].into_py_dict(py).unwrap();
253
254            macro_rules! check_call {
255                ($args:expr, $kwargs:expr) => {
256                    let (a, k): (Py<PyTuple>, Py<PyDict>) = f
257                        .call(args.clone(), Some(kwargs))
258                        .unwrap()
259                        .extract()
260                        .unwrap();
261                    assert!(a.is(&args));
262                    assert!(k.is(kwargs));
263                };
264            }
265
266            // Bound<'py, PyTuple>
267            check_call!(args.clone(), kwargs);
268
269            // &Bound<'py, PyTuple>
270            check_call!(&args, kwargs);
271
272            // Py<PyTuple>
273            check_call!(args.clone().unbind(), kwargs);
274
275            // &Py<PyTuple>
276            check_call!(&args.as_unbound(), kwargs);
277
278            // Borrowed<'_, '_, PyTuple>
279            check_call!(args.as_borrowed(), kwargs);
280        })
281    }
282
283    #[test]
284    fn test_call_positional() {
285        use crate::{
286            Py, Python,
287            types::{PyAnyMethods, PyNone, PyTuple},
288            wrap_pyfunction,
289        };
290
291        Python::attach(|py| {
292            let f = wrap_pyfunction!(args_kwargs, py).unwrap();
293
294            let args = PyTuple::new(py, [1, 2, 3]).unwrap();
295
296            macro_rules! check_call {
297                ($args:expr, $kwargs:expr) => {
298                    let (a, k): (Py<PyTuple>, Py<PyNone>) =
299                        f.call1(args.clone()).unwrap().extract().unwrap();
300                    assert!(a.is(&args));
301                    assert!(k.is_none(py));
302                };
303            }
304
305            // Bound<'py, PyTuple>
306            check_call!(args.clone(), kwargs);
307
308            // &Bound<'py, PyTuple>
309            check_call!(&args, kwargs);
310
311            // Py<PyTuple>
312            check_call!(args.clone().unbind(), kwargs);
313
314            // &Py<PyTuple>
315            check_call!(args.as_unbound(), kwargs);
316
317            // Borrowed<'_, '_, PyTuple>
318            check_call!(args.as_borrowed(), kwargs);
319        })
320    }
321}