Skip to main content

rustpython_vm/function/
builtin.rs

1use super::{Callee, FromArgs, FuncArgs};
2use crate::{
3    Py, PyPayload, PyRef, PyResult, VirtualMachine, convert::ToPyResult,
4    object::PyThreadingConstraint,
5};
6use core::marker::PhantomData;
7
8/// A built-in Python function.
9// PyCFunction in CPython
10pub trait PyNativeFn:
11    Fn(&VirtualMachine, FuncArgs, Callee) -> PyResult + PyThreadingConstraint + 'static
12{
13}
14
15impl<F> PyNativeFn for F where
16    F: Fn(&VirtualMachine, FuncArgs, Callee) -> PyResult + PyThreadingConstraint + 'static
17{
18}
19
20/// Implemented by types that are or can generate built-in functions.
21///
22/// This trait is implemented by any function that matches the pattern:
23///
24/// ```rust,ignore
25/// Fn([&self,] [T where T: FromArgs, ...] [, vm: &VirtualMachine])
26/// ```
27///
28/// For example, anything from `Fn()` to `Fn(vm: &VirtualMachine) -> u32` to
29/// `Fn(PyIntRef, PyIntRef) -> String` to
30/// `Fn(&self, PyStrRef, FooOptions, vm: &VirtualMachine) -> PyResult<PyInt>`
31/// is `IntoPyNativeFn`. If you do want a really general function signature, e.g.
32/// to forward the args to another function, you can define a function like
33/// `Fn(FuncArgs [, &VirtualMachine]) -> ...`
34///
35/// Note that the `Kind` type parameter is meaningless and should be considered
36/// an implementation detail; if you need to use `IntoPyNativeFn` as a trait bound
37/// just pass an unconstrained generic type, e.g.
38/// `fn foo<F, FKind>(f: F) where F: IntoPyNativeFn<FKind>`
39pub trait IntoPyNativeFn<Kind>: Sized + PyThreadingConstraint + 'static {
40    fn call(&self, vm: &VirtualMachine, args: FuncArgs, callee: Callee) -> PyResult;
41
42    /// `IntoPyNativeFn::into_func()` generates a PyNativeFn that performs the
43    /// appropriate type and arity checking, any requested conversions, and then if
44    /// successful calls the function with the extracted parameters.
45    fn into_func(self) -> impl PyNativeFn {
46        into_func(self)
47    }
48}
49
50const fn into_func<F: IntoPyNativeFn<Kind>, Kind>(f: F) -> impl PyNativeFn {
51    move |vm: &VirtualMachine, args, callee| f.call(vm, args, callee)
52}
53
54const fn zst_ref_out_of_thin_air<T: 'static>(x: T) -> &'static T {
55    // if T is zero-sized, there's no issue forgetting it - even if it does have a Drop impl, it
56    // would never get called anyway if we consider this semantically a Box::leak(Box::new(x))-type
57    // operation. if T isn't zero-sized, we don't have to worry about it because we'll fail to compile.
58    core::mem::forget(x);
59    const {
60        assert!(
61            core::mem::size_of::<T>() == 0,
62            "can't use a non-zero-sized type here"
63        );
64        // SAFETY: we just confirmed that T is zero-sized, so we can
65        //         pull a value of it out of thin air.
66        unsafe { core::ptr::NonNull::<T>::dangling().as_ref() }
67    }
68}
69
70/// Get the STATIC_FUNC of the passed function. The same
71/// requirements of zero-sized-ness apply, see that documentation for details.
72///
73/// Equivalent to [`IntoPyNativeFn::into_func()`], but usable in a const context. This is only
74/// valid if the function is zero-sized, i.e. that `std::mem::size_of::<F>() == 0`. If you call
75/// this function with a non-zero-sized function, it will raise a compile error.
76#[inline(always)]
77pub const fn static_func<Kind, F: IntoPyNativeFn<Kind>>(f: F) -> &'static dyn PyNativeFn {
78    zst_ref_out_of_thin_air(into_func(f))
79}
80
81#[inline(always)]
82pub const fn static_raw_func<F: PyNativeFn>(f: F) -> &'static dyn PyNativeFn {
83    zst_ref_out_of_thin_air(f)
84}
85
86// TODO: once higher-rank trait bounds are stabilized, remove the `Kind` type
87// parameter and impl for F where F: for<T, R, VM> PyNativeFnInternal<T, R, VM>
88impl<F, T, R, VM> IntoPyNativeFn<(T, R, VM)> for F
89where
90    F: PyNativeFnInternal<T, R, VM>,
91{
92    #[inline(always)]
93    fn call(&self, vm: &VirtualMachine, args: FuncArgs, callee: Callee) -> PyResult {
94        self.call_(vm, args, callee)
95    }
96}
97
98mod sealed {
99    use super::*;
100    pub trait PyNativeFnInternal<T, R, VM>: Sized + PyThreadingConstraint + 'static {
101        fn call_(&self, vm: &VirtualMachine, args: FuncArgs, callee: Callee) -> PyResult;
102    }
103}
104use sealed::PyNativeFnInternal;
105
106#[doc(hidden)]
107pub struct OwnedParam<T>(PhantomData<T>);
108
109#[doc(hidden)]
110pub struct BorrowedParam<T>(PhantomData<T>);
111
112#[doc(hidden)]
113pub struct RefParam<T>(PhantomData<T>);
114
115// This is the "magic" that allows rust functions of varying signatures to
116// generate native python functions.
117//
118// Note that this could be done without a macro - it is simply to avoid repetition.
119macro_rules! into_py_native_fn_tuple {
120    ($(($n:tt, $T:ident)),*) => {
121        impl<F, $($T,)* R> PyNativeFnInternal<($(OwnedParam<$T>,)*), R, VirtualMachine> for F
122        where
123            F: Fn($($T,)* &VirtualMachine) -> R + PyThreadingConstraint + 'static,
124            $($T: FromArgs,)*
125            R: ToPyResult,
126        {
127            fn call_(&self, vm: &VirtualMachine, args: FuncArgs, callee: Callee) -> PyResult {
128                let ($($n,)*) = args.bind_for::<($($T,)*)>(vm, callee)?;
129
130                (self)($($n,)* vm).to_pyresult(vm)
131            }
132        }
133
134        impl<F, S, $($T,)* R> PyNativeFnInternal<(BorrowedParam<S>, $(OwnedParam<$T>,)*), R, VirtualMachine> for F
135        where
136            F: Fn(&Py<S>, $($T,)* &VirtualMachine) -> R + PyThreadingConstraint + 'static,
137            S: PyPayload,
138            $($T: FromArgs,)*
139            R: ToPyResult,
140        {
141            fn call_(&self, vm: &VirtualMachine, args: FuncArgs, callee: Callee) -> PyResult {
142                let (zelf, $($n,)*) = args.bind_for::<(PyRef<S>, $($T,)*)>(vm, callee)?;
143
144                (self)(&zelf, $($n,)* vm).to_pyresult(vm)
145            }
146        }
147
148        impl<F, S, $($T,)* R> PyNativeFnInternal<(RefParam<S>, $(OwnedParam<$T>,)*), R, VirtualMachine> for F
149        where
150            F: Fn(&S, $($T,)* &VirtualMachine) -> R + PyThreadingConstraint + 'static,
151            S: PyPayload,
152            $($T: FromArgs,)*
153            R: ToPyResult,
154        {
155            fn call_(&self, vm: &VirtualMachine, args: FuncArgs, callee: Callee) -> PyResult {
156                let (zelf, $($n,)*) = args.bind_for::<(PyRef<S>, $($T,)*)>(vm, callee)?;
157
158                (self)(&zelf, $($n,)* vm).to_pyresult(vm)
159            }
160        }
161
162        impl<F, $($T,)* R> PyNativeFnInternal<($(OwnedParam<$T>,)*), R, ()> for F
163        where
164            F: Fn($($T,)*) -> R + PyThreadingConstraint + 'static,
165            $($T: FromArgs,)*
166            R: ToPyResult,
167        {
168            fn call_(&self, vm: &VirtualMachine, args: FuncArgs, callee: Callee) -> PyResult {
169                let ($($n,)*) = args.bind_for::<($($T,)*)>(vm, callee)?;
170
171                (self)($($n,)*).to_pyresult(vm)
172            }
173        }
174
175        impl<F, S, $($T,)* R> PyNativeFnInternal<(BorrowedParam<S>, $(OwnedParam<$T>,)*), R, ()> for F
176        where
177            F: Fn(&Py<S>, $($T,)*) -> R + PyThreadingConstraint + 'static,
178            S: PyPayload,
179            $($T: FromArgs,)*
180            R: ToPyResult,
181        {
182            fn call_(&self, vm: &VirtualMachine, args: FuncArgs, callee: Callee) -> PyResult {
183                let (zelf, $($n,)*) = args.bind_for::<(PyRef<S>, $($T,)*)>(vm, callee)?;
184
185                (self)(&zelf, $($n,)*).to_pyresult(vm)
186            }
187        }
188
189        impl<F, S, $($T,)* R> PyNativeFnInternal<(RefParam<S>, $(OwnedParam<$T>,)*), R, ()> for F
190        where
191            F: Fn(&S, $($T,)*) -> R + PyThreadingConstraint + 'static,
192            S: PyPayload,
193            $($T: FromArgs,)*
194            R: ToPyResult,
195        {
196            fn call_(&self, vm: &VirtualMachine, args: FuncArgs, callee: Callee) -> PyResult {
197                let (zelf, $($n,)*) = args.bind_for::<(PyRef<S>, $($T,)*)>(vm, callee)?;
198
199                (self)(&zelf, $($n,)*).to_pyresult(vm)
200            }
201        }
202    };
203}
204
205into_py_native_fn_tuple!();
206into_py_native_fn_tuple!((v1, T1));
207into_py_native_fn_tuple!((v1, T1), (v2, T2));
208into_py_native_fn_tuple!((v1, T1), (v2, T2), (v3, T3));
209into_py_native_fn_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4));
210into_py_native_fn_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5));
211into_py_native_fn_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5), (v6, T6));
212into_py_native_fn_tuple!(
213    (v1, T1),
214    (v2, T2),
215    (v3, T3),
216    (v4, T4),
217    (v5, T5),
218    (v6, T6),
219    (v7, T7)
220);
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225    use core::mem::size_of_val;
226
227    #[test]
228    fn into_native_fn_noalloc() {
229        fn py_func(_b: bool, _vm: &crate::VirtualMachine) -> i32 {
230            1
231        }
232        assert_eq!(size_of_val(&py_func.into_func()), 0);
233        let empty_closure = || "foo".to_owned();
234        assert_eq!(size_of_val(&empty_closure.into_func()), 0);
235        assert_eq!(size_of_val(static_func(empty_closure)), 0);
236    }
237}