rustpython_vm/function/
builtin.rs

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