Skip to main content

rustpython_vm/function/
method.rs

1use crate::{
2    Context, Py, PyObjectRef, PyPayload, PyRef, VirtualMachine,
3    builtins::{
4        PyType,
5        builtin_func::{PyNativeFunction, PyNativeMethod},
6        descriptor::PyMethodDescriptor,
7    },
8    function::{IntoPyNativeFn, PyNativeFn},
9};
10
11bitflags::bitflags! {
12    // METH_XXX flags in CPython
13    #[derive(Copy, Clone, Debug, PartialEq)]
14    pub struct PyMethodFlags: u32 {
15        const VARARGS = 0x0001;
16        const KEYWORDS = 0x0002;
17        // METH_NOARGS and METH_O must not be combined with the flags above.
18        const NOARGS = 0x0004;
19        const O = 0x0008;
20
21        // METH_CLASS and METH_STATIC are a little different; these control
22        // the construction of methods for a class.  These cannot be used for
23        // functions in modules.
24        const CLASS = 0x0010;
25        const STATIC = 0x0020;
26
27        // METH_COEXIST allows a method to be entered even though a slot has
28        // already filled the entry.  When defined, the flag allows a separate
29        // method, "__contains__" for example, to coexist with a defined
30        // slot like sq_contains.
31        // const COEXIST = 0x0040;
32
33        // if not Py_LIMITED_API
34        const FASTCALL = 0x0080;
35
36        // This bit is preserved for Stackless Python
37        // const STACKLESS = 0x0100;
38
39        // METH_METHOD means the function stores an
40        // additional reference to the class that defines it;
41        // both self and class are passed to it.
42        // It uses PyCMethodObject instead of PyCFunctionObject.
43        // May not be combined with METH_NOARGS, METH_O, METH_CLASS or METH_STATIC.
44        const METHOD = 0x0200;
45    }
46}
47
48impl PyMethodFlags {
49    // FIXME: macro temp
50    pub const EMPTY: Self = Self::empty();
51}
52
53#[macro_export]
54macro_rules! define_methods {
55    // TODO: more flexible syntax
56    ($($name:literal => $func:ident as $flags:ident),+) => {
57        vec![ $( $crate::function::PyMethodDef {
58            name: $name,
59            func: $crate::function::static_func($func),
60            flags: $crate::function::PyMethodFlags::$flags,
61            doc: None,
62        }),+ ]
63    };
64}
65
66#[derive(Clone)]
67pub struct PyMethodDef {
68    pub name: &'static str, // TODO: interned
69    pub func: &'static dyn PyNativeFn,
70    pub flags: PyMethodFlags,
71    pub doc: Option<&'static str>, // TODO: interned
72}
73
74impl PyMethodDef {
75    #[inline]
76    pub const fn new_const<Kind>(
77        name: &'static str,
78        func: impl IntoPyNativeFn<Kind>,
79        flags: PyMethodFlags,
80        doc: Option<&'static str>,
81    ) -> Self {
82        Self {
83            name,
84            func: super::static_func(func),
85            flags,
86            doc,
87        }
88    }
89
90    #[inline]
91    pub const fn new_raw_const(
92        name: &'static str,
93        func: impl PyNativeFn,
94        flags: PyMethodFlags,
95        doc: Option<&'static str>,
96    ) -> Self {
97        Self {
98            name,
99            func: super::static_raw_func(func),
100            flags,
101            doc,
102        }
103    }
104
105    pub fn to_proper_method(
106        &'static self,
107        class: &'static Py<PyType>,
108        ctx: &Context,
109    ) -> PyObjectRef {
110        if self.flags.contains(PyMethodFlags::METHOD) {
111            self.build_method(ctx, class).into()
112        } else if self.flags.contains(PyMethodFlags::CLASS) {
113            self.build_classmethod(ctx, class).into()
114        } else if self.flags.contains(PyMethodFlags::STATIC) {
115            self.build_staticmethod(ctx, class).into()
116        } else {
117            unreachable!()
118        }
119    }
120
121    pub const fn to_function(&'static self) -> PyNativeFunction {
122        PyNativeFunction {
123            zelf: None,
124            value: self,
125            module: None,
126            _method_def_owner: None,
127        }
128    }
129
130    pub fn to_method(
131        &'static self,
132        class: &'static Py<PyType>,
133        ctx: &Context,
134    ) -> PyMethodDescriptor {
135        PyMethodDescriptor::new(self, class, ctx)
136    }
137
138    pub const fn to_bound_method(
139        &'static self,
140        obj: PyObjectRef,
141        class: &'static Py<PyType>,
142    ) -> PyNativeMethod {
143        PyNativeMethod {
144            func: PyNativeFunction {
145                zelf: Some(obj),
146                value: self,
147                module: None,
148                _method_def_owner: None,
149            },
150            class,
151        }
152    }
153
154    pub fn build_function(&'static self, ctx: &Context) -> PyRef<PyNativeFunction> {
155        self.to_function().into_ref(ctx)
156    }
157
158    pub fn build_bound_function(
159        &'static self,
160        ctx: &Context,
161        obj: PyObjectRef,
162    ) -> PyRef<PyNativeFunction> {
163        let function = PyNativeFunction {
164            zelf: Some(obj),
165            value: self,
166            module: None,
167            _method_def_owner: None,
168        };
169        PyRef::new_ref(
170            function,
171            ctx.types.builtin_function_or_method_type.to_owned(),
172            None,
173        )
174    }
175
176    pub fn build_method(
177        &'static self,
178        ctx: &Context,
179        class: &'static Py<PyType>,
180    ) -> PyRef<PyMethodDescriptor> {
181        debug_assert!(self.flags.contains(PyMethodFlags::METHOD));
182        let method = self.to_method(class, ctx);
183        PyRef::new_ref(method, ctx.types.method_descriptor_type.to_owned(), None)
184    }
185
186    pub fn build_bound_method(
187        &'static self,
188        ctx: &Context,
189        obj: PyObjectRef,
190        class: &'static Py<PyType>,
191    ) -> PyRef<PyNativeMethod> {
192        PyRef::new_ref(
193            self.to_bound_method(obj, class),
194            ctx.types.builtin_function_or_method_type.to_owned(),
195            None,
196        )
197    }
198
199    pub fn build_classmethod(
200        &'static self,
201        ctx: &Context,
202        class: &'static Py<PyType>,
203    ) -> PyRef<PyMethodDescriptor> {
204        PyRef::new_ref(
205            self.to_method(class, ctx),
206            ctx.types.method_descriptor_type.to_owned(),
207            None,
208        )
209    }
210
211    pub fn build_staticmethod(
212        &'static self,
213        ctx: &Context,
214        class: &'static Py<PyType>,
215    ) -> PyRef<PyNativeMethod> {
216        debug_assert!(self.flags.contains(PyMethodFlags::STATIC));
217        // Set zelf to the class (m_self = type for static methods).
218        // Callable::call skips prepending when STATIC flag is set.
219        let func = PyNativeFunction {
220            zelf: Some(class.to_owned().into()),
221            value: self,
222            module: None,
223            _method_def_owner: None,
224        };
225        PyNativeMethod { func, class }.into_ref(ctx)
226    }
227
228    #[doc(hidden)]
229    pub const fn __const_concat_arrays<const SUM_LEN: usize>(
230        method_groups: &[&[Self]],
231    ) -> [Self; SUM_LEN] {
232        const NULL_METHOD: PyMethodDef = PyMethodDef {
233            name: "",
234            func: &|_, _| unreachable!(),
235            flags: PyMethodFlags::empty(),
236            doc: None,
237        };
238        let mut all_methods = [NULL_METHOD; SUM_LEN];
239        let mut all_idx = 0;
240        let mut group_idx = 0;
241        while group_idx < method_groups.len() {
242            let group = method_groups[group_idx];
243            let mut method_idx = 0;
244            while method_idx < group.len() {
245                all_methods[all_idx] = group[method_idx].const_copy();
246                method_idx += 1;
247                all_idx += 1;
248            }
249            group_idx += 1;
250        }
251        all_methods
252    }
253
254    const fn const_copy(&self) -> Self {
255        Self {
256            name: self.name,
257            func: self.func,
258            flags: self.flags,
259            doc: self.doc,
260        }
261    }
262}
263
264impl core::fmt::Debug for PyMethodDef {
265    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
266        f.debug_struct("PyMethodDef")
267            .field("name", &self.name)
268            .field(
269                "func",
270                &(unsafe {
271                    core::mem::transmute::<&dyn PyNativeFn, [usize; 2]>(self.func)[1] as *const u8
272                }),
273            )
274            .field("flags", &self.flags)
275            .field("doc", &self.doc)
276            .finish()
277    }
278}
279
280// This is not a part of CPython API.
281// But useful to support dynamically generated methods
282#[pyclass(name, module = false, ctx = "method_def")]
283#[derive(Debug)]
284pub struct HeapMethodDef {
285    method: PyMethodDef,
286}
287
288impl HeapMethodDef {
289    pub const fn new(method: PyMethodDef) -> Self {
290        Self { method }
291    }
292}
293
294impl Py<HeapMethodDef> {
295    pub(crate) unsafe fn method(&self) -> &'static PyMethodDef {
296        unsafe { &*(&self.method as *const _) }
297    }
298
299    pub fn build_function(&self, vm: &VirtualMachine) -> PyRef<PyNativeFunction> {
300        let mut function = unsafe { self.method() }.to_function();
301        function._method_def_owner = Some(self.to_owned().into());
302        PyRef::new_ref(
303            function,
304            vm.ctx.types.builtin_function_or_method_type.to_owned(),
305            None,
306        )
307    }
308
309    pub fn build_method(
310        &self,
311        class: &'static Py<PyType>,
312        vm: &VirtualMachine,
313    ) -> PyRef<PyMethodDescriptor> {
314        let mut function = unsafe { self.method() }.to_method(class, &vm.ctx);
315        function._method_def_owner = Some(self.to_owned().into());
316        PyRef::new_ref(
317            function,
318            vm.ctx.types.method_descriptor_type.to_owned(),
319            None,
320        )
321    }
322}
323
324#[pyclass]
325impl HeapMethodDef {}