Skip to main content

rustpython_vm/function/
builtin.rs

1use super::{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) -> 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    core::mem::forget(x);
58    const {
59        if core::mem::size_of::<T>() != 0 {
60            panic!("can't use a non-zero-sized type here")
61        }
62        // SAFETY: we just confirmed that T is zero-sized, so we can
63        //         pull a value of it out of thin air.
64        unsafe { core::ptr::NonNull::<T>::dangling().as_ref() }
65    }
66}
67
68/// Get the STATIC_FUNC of the passed function. The same
69/// requirements of zero-sized-ness apply, see that documentation for details.
70///
71/// Equivalent to [`IntoPyNativeFn::into_func()`], but usable in a const context. This is only
72/// valid if the function is zero-sized, i.e. that `std::mem::size_of::<F>() == 0`. If you call
73/// this function with a non-zero-sized function, it will raise a compile error.
74#[inline(always)]
75pub const fn static_func<Kind, F: IntoPyNativeFn<Kind>>(f: F) -> &'static dyn PyNativeFn {
76    zst_ref_out_of_thin_air(into_func(f))
77}
78
79#[inline(always)]
80pub const fn static_raw_func<F: PyNativeFn>(f: F) -> &'static dyn PyNativeFn {
81    zst_ref_out_of_thin_air(f)
82}
83
84// TODO: once higher-rank trait bounds are stabilized, remove the `Kind` type
85// parameter and impl for F where F: for<T, R, VM> PyNativeFnInternal<T, R, VM>
86impl<F, T, R, VM> IntoPyNativeFn<(T, R, VM)> for F
87where
88    F: PyNativeFnInternal<T, R, VM>,
89{
90    #[inline(always)]
91    fn call(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult {
92        self.call_(vm, args)
93    }
94}
95
96mod sealed {
97    use super::*;
98    pub trait PyNativeFnInternal<T, R, VM>: Sized + PyThreadingConstraint + 'static {
99        fn call_(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult;
100    }
101}
102use sealed::PyNativeFnInternal;
103
104#[doc(hidden)]
105pub struct OwnedParam<T>(PhantomData<T>);
106#[doc(hidden)]
107pub struct BorrowedParam<T>(PhantomData<T>);
108#[doc(hidden)]
109pub struct RefParam<T>(PhantomData<T>);
110
111// This is the "magic" that allows rust functions of varying signatures to
112// generate native python functions.
113//
114// Note that this could be done without a macro - it is simply to avoid repetition.
115macro_rules! into_py_native_fn_tuple {
116    ($(($n:tt, $T:ident)),*) => {
117        impl<F, $($T,)* R> PyNativeFnInternal<($(OwnedParam<$T>,)*), R, VirtualMachine> for F
118        where
119            F: Fn($($T,)* &VirtualMachine) -> R + PyThreadingConstraint + 'static,
120            $($T: FromArgs,)*
121            R: ToPyResult,
122        {
123            fn call_(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult {
124                let ($($n,)*) = args.bind::<($($T,)*)>(vm)?;
125
126                (self)($($n,)* vm).to_pyresult(vm)
127            }
128        }
129
130        impl<F, S, $($T,)* R> PyNativeFnInternal<(BorrowedParam<S>, $(OwnedParam<$T>,)*), R, VirtualMachine> for F
131        where
132            F: Fn(&Py<S>, $($T,)* &VirtualMachine) -> R + PyThreadingConstraint + 'static,
133            S: PyPayload,
134            $($T: FromArgs,)*
135            R: ToPyResult,
136        {
137            fn call_(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult {
138                let (zelf, $($n,)*) = args.bind::<(PyRef<S>, $($T,)*)>(vm)?;
139
140                (self)(&zelf, $($n,)* vm).to_pyresult(vm)
141            }
142        }
143
144        impl<F, S, $($T,)* R> PyNativeFnInternal<(RefParam<S>, $(OwnedParam<$T>,)*), R, VirtualMachine> for F
145        where
146            F: Fn(&S, $($T,)* &VirtualMachine) -> R + PyThreadingConstraint + 'static,
147            S: PyPayload,
148            $($T: FromArgs,)*
149            R: ToPyResult,
150        {
151            fn call_(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult {
152                let (zelf, $($n,)*) = args.bind::<(PyRef<S>, $($T,)*)>(vm)?;
153
154                (self)(&zelf, $($n,)* vm).to_pyresult(vm)
155            }
156        }
157
158        impl<F, $($T,)* R> PyNativeFnInternal<($(OwnedParam<$T>,)*), R, ()> for F
159        where
160            F: Fn($($T,)*) -> R + PyThreadingConstraint + 'static,
161            $($T: FromArgs,)*
162            R: ToPyResult,
163        {
164            fn call_(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult {
165                let ($($n,)*) = args.bind::<($($T,)*)>(vm)?;
166
167                (self)($($n,)*).to_pyresult(vm)
168            }
169        }
170
171        impl<F, S, $($T,)* R> PyNativeFnInternal<(BorrowedParam<S>, $(OwnedParam<$T>,)*), R, ()> for F
172        where
173            F: Fn(&Py<S>, $($T,)*) -> R + PyThreadingConstraint + 'static,
174            S: PyPayload,
175            $($T: FromArgs,)*
176            R: ToPyResult,
177        {
178            fn call_(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult {
179                let (zelf, $($n,)*) = args.bind::<(PyRef<S>, $($T,)*)>(vm)?;
180
181                (self)(&zelf, $($n,)*).to_pyresult(vm)
182            }
183        }
184
185        impl<F, S, $($T,)* R> PyNativeFnInternal<(RefParam<S>, $(OwnedParam<$T>,)*), R, ()> for F
186        where
187            F: Fn(&S, $($T,)*) -> R + PyThreadingConstraint + 'static,
188            S: PyPayload,
189            $($T: FromArgs,)*
190            R: ToPyResult,
191        {
192            fn call_(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult {
193                let (zelf, $($n,)*) = args.bind::<(PyRef<S>, $($T,)*)>(vm)?;
194
195                (self)(&zelf, $($n,)*).to_pyresult(vm)
196            }
197        }
198    };
199}
200
201into_py_native_fn_tuple!();
202into_py_native_fn_tuple!((v1, T1));
203into_py_native_fn_tuple!((v1, T1), (v2, T2));
204into_py_native_fn_tuple!((v1, T1), (v2, T2), (v3, T3));
205into_py_native_fn_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4));
206into_py_native_fn_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5));
207into_py_native_fn_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5), (v6, T6));
208into_py_native_fn_tuple!(
209    (v1, T1),
210    (v2, T2),
211    (v3, T3),
212    (v4, T4),
213    (v5, T5),
214    (v6, T6),
215    (v7, T7)
216);
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221    use core::mem::size_of_val;
222
223    #[test]
224    fn test_into_native_fn_noalloc() {
225        fn py_func(_b: bool, _vm: &crate::VirtualMachine) -> i32 {
226            1
227        }
228        assert_eq!(size_of_val(&py_func.into_func()), 0);
229        let empty_closure = || "foo".to_owned();
230        assert_eq!(size_of_val(&empty_closure.into_func()), 0);
231        assert_eq!(size_of_val(static_func(empty_closure)), 0);
232    }
233}