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::{PyClassMethodDescriptor, PyMethodDescriptor},
7    },
8    class::PyClassDef,
9    function::{IntoPyNativeFn, PyNativeFn},
10};
11
12bitflags::bitflags! {
13    // METH_XXX flags in CPython
14    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
15    pub struct PyMethodFlags: u32 {
16        const VARARGS = 0x0001;
17        const KEYWORDS = 0x0002;
18        // METH_NOARGS and METH_O must not be combined with the flags above.
19        const NOARGS = 0x0004;
20        const O = 0x0008;
21
22        // METH_CLASS and METH_STATIC are a little different; these control
23        // the construction of methods for a class.  These cannot be used for
24        // functions in modules.
25        const CLASS = 0x0010;
26        const STATIC = 0x0020;
27
28        // METH_COEXIST allows a method to be entered even though a slot has
29        // already filled the entry.  When defined, the flag allows a separate
30        // method, "__contains__" for example, to coexist with a defined
31        // slot like sq_contains.
32        const COEXIST = 0x0040;
33
34        // if not Py_LIMITED_API
35        const FASTCALL = 0x0080;
36
37        // This bit is preserved for Stackless Python
38        // const STACKLESS = 0x0100;
39
40        // METH_METHOD means the function stores an
41        // additional reference to the class that defines it;
42        // both self and class are passed to it.
43        // It uses PyCMethodObject instead of PyCFunctionObject.
44        // May not be combined with METH_NOARGS, METH_O, METH_CLASS or METH_STATIC.
45        const METHOD = 0x0200;
46    }
47}
48
49impl PyMethodFlags {
50    // FIXME: macro temp
51    pub const EMPTY: Self = Self::empty();
52}
53
54#[macro_export]
55macro_rules! define_methods {
56    // TODO: more flexible syntax
57    ($($name:literal => $func:ident as $flags:ident),+) => {
58        vec![ $( $crate::function::PyMethodDef {
59            name: $name,
60            func: $crate::function::static_func($func),
61            flags: $crate::function::PyMethodFlags::$flags,
62            #[cfg(feature = "doc")]
63            doc_off: 0,
64            #[cfg(feature = "doc")]
65            doc_len: 0,
66            #[cfg(feature = "doc")]
67            doc_body_pending: false,
68            doc: None,
69        }),+ ]
70    };
71}
72
73#[derive(Clone)]
74pub struct PyMethodDef {
75    pub name: &'static str, // TODO: interned
76    pub func: &'static dyn PyNativeFn,
77    pub flags: PyMethodFlags,
78    /// Start of the database body. Read only when `doc_len != 0`.
79    /// Absent when the `doc` feature is off, as are `doc_len` and `doc_body_pending`.
80    #[cfg(feature = "doc")]
81    pub doc_off: u32,
82    /// Length of the database body.
83    /// `0` means the body is not a database span: `doc` is the whole text, or there is no body.
84    #[cfg(feature = "doc")]
85    pub doc_len: u32,
86    /// The body is still taken from the owning class's attribute table.
87    /// True only when the method has no Rust doc body and expansion did not
88    /// resolve a database span. `concat_with_attr_docs` copies that span into
89    /// `doc_off`/`doc_len` when the table has one, then sets this to false.
90    /// False means the body is already settled: a Rust doc, a span resolved
91    /// while expanding the method, or no body at all.
92    #[cfg(feature = "doc")]
93    pub doc_body_pending: bool,
94    /// Static text beside the database span.
95    /// `None` when there is no static text: the body is the `doc_len` span, or there is no docstring.
96    /// `Some` is a plain docstring, a full internal docstring, or only the
97    /// signature prefix while the body is the `doc_len` span or still pending.
98    pub doc: Option<&'static str>,
99}
100
101impl PyMethodDef {
102    #[must_use]
103    pub fn item_doc(&self) -> super::ItemDoc {
104        super::ItemDoc {
105            text: self.doc,
106            #[cfg(feature = "doc")]
107            offset: self.doc_off,
108            #[cfg(feature = "doc")]
109            len: self.doc_len,
110            #[cfg(not(feature = "doc"))]
111            offset: 0,
112            #[cfg(not(feature = "doc"))]
113            len: 0,
114        }
115    }
116
117    #[inline]
118    pub const fn new_const<Kind>(
119        name: &'static str,
120        func: impl IntoPyNativeFn<Kind>,
121        flags: PyMethodFlags,
122        doc: super::ItemDoc,
123    ) -> Self {
124        Self {
125            name,
126            func: super::static_func(func),
127            flags,
128            #[cfg(feature = "doc")]
129            doc_off: doc.offset,
130            #[cfg(feature = "doc")]
131            doc_len: doc.len,
132            #[cfg(feature = "doc")]
133            doc_body_pending: false,
134            doc: doc.text,
135        }
136    }
137
138    #[inline]
139    pub const fn new_raw_const(
140        name: &'static str,
141        func: impl PyNativeFn,
142        flags: PyMethodFlags,
143        doc: super::ItemDoc,
144    ) -> Self {
145        Self {
146            name,
147            func: super::static_raw_func(func),
148            flags,
149            #[cfg(feature = "doc")]
150            doc_off: doc.offset,
151            #[cfg(feature = "doc")]
152            doc_len: doc.len,
153            #[cfg(feature = "doc")]
154            doc_body_pending: false,
155            doc: doc.text,
156        }
157    }
158
159    pub fn to_proper_method(
160        &'static self,
161        class: &'static Py<PyType>,
162        ctx: &Context,
163    ) -> PyObjectRef {
164        if self.flags.contains(PyMethodFlags::METHOD) {
165            self.build_method(ctx, class).into()
166        } else if self.flags.contains(PyMethodFlags::CLASS) {
167            self.build_classmethod(ctx, class).into()
168        } else if self.flags.contains(PyMethodFlags::STATIC) {
169            self.build_staticmethod(ctx, class).into()
170        } else {
171            unreachable!()
172        }
173    }
174
175    #[must_use]
176    pub const fn to_function(&'static self) -> PyNativeFunction {
177        PyNativeFunction {
178            zelf: None,
179            value: self,
180            module_object: None,
181            module: crate::object::PyAtomicRef::new_empty(),
182            _method_def_owner: None,
183        }
184    }
185
186    pub fn to_method(
187        &'static self,
188        class: &'static Py<PyType>,
189        ctx: &Context,
190    ) -> PyMethodDescriptor {
191        PyMethodDescriptor::new(self, class, ctx)
192    }
193
194    pub const fn to_bound_method(
195        &'static self,
196        obj: PyObjectRef,
197        class: &'static Py<PyType>,
198    ) -> PyNativeMethod {
199        PyNativeMethod {
200            func: PyNativeFunction {
201                zelf: Some(obj),
202                value: self,
203                module_object: None,
204                module: crate::object::PyAtomicRef::new_empty(),
205                _method_def_owner: None,
206            },
207            class,
208        }
209    }
210
211    pub fn build_function(&'static self, ctx: &Context) -> PyRef<PyNativeFunction> {
212        self.to_function().into_ref(ctx)
213    }
214
215    pub fn build_bound_function(
216        &'static self,
217        ctx: &Context,
218        obj: PyObjectRef,
219    ) -> PyRef<PyNativeFunction> {
220        let function = PyNativeFunction {
221            zelf: Some(obj),
222            value: self,
223            module_object: None,
224            module: crate::object::PyAtomicRef::new_empty(),
225            _method_def_owner: None,
226        };
227        PyRef::new_ref(
228            function,
229            ctx.types.builtin_function_or_method_type.to_owned(),
230            None,
231        )
232    }
233
234    pub fn build_method(
235        &'static self,
236        ctx: &Context,
237        class: &'static Py<PyType>,
238    ) -> PyRef<PyMethodDescriptor> {
239        debug_assert!(self.flags.contains(PyMethodFlags::METHOD));
240        let method = self.to_method(class, ctx);
241        PyRef::new_ref(method, ctx.types.method_descriptor_type.to_owned(), None)
242    }
243
244    pub fn build_bound_method(
245        &'static self,
246        ctx: &Context,
247        obj: PyObjectRef,
248        class: &'static Py<PyType>,
249    ) -> PyRef<PyNativeMethod> {
250        PyRef::new_ref(
251            self.to_bound_method(obj, class),
252            ctx.types.builtin_function_or_method_type.to_owned(),
253            None,
254        )
255    }
256
257    pub fn build_classmethod(
258        &'static self,
259        ctx: &Context,
260        class: &'static Py<PyType>,
261    ) -> PyRef<PyClassMethodDescriptor> {
262        debug_assert!(self.flags.contains(PyMethodFlags::CLASS));
263        PyClassMethodDescriptor::new(self, class, ctx).into_ref(ctx)
264    }
265
266    pub fn build_staticmethod(
267        &'static self,
268        ctx: &Context,
269        class: &'static Py<PyType>,
270    ) -> PyRef<PyNativeMethod> {
271        debug_assert!(self.flags.contains(PyMethodFlags::STATIC));
272        // Set zelf to the class (m_self = type for static methods).
273        // Callable::call skips prepending when STATIC flag is set.
274        let func = PyNativeFunction {
275            zelf: Some(class.to_owned().into()),
276            value: self,
277            module_object: None,
278            module: crate::object::PyAtomicRef::new_empty(),
279            _method_def_owner: None,
280        };
281        PyNativeMethod { func, class }.into_ref(ctx)
282    }
283
284    /// Concatenate method groups. A pending body is copied from `docs`, then cleared.
285    #[cfg(feature = "doc")]
286    #[must_use]
287    pub const fn concat_with_attr_docs<const N: usize>(
288        method_groups: &[&[Self]],
289        docs: &[(&str, u32, u32)],
290    ) -> [Self; N] {
291        let combined = Self::__const_concat_arrays::<N>(method_groups);
292        let mut i = 0;
293        let mut out = combined;
294        while i < N {
295            if out[i].doc_body_pending {
296                if let Some((offset, len)) = crate::class::attr_doc(docs, out[i].name)
297                    && len != 0
298                {
299                    out[i].doc_off = offset;
300                    out[i].doc_len = len;
301                }
302                out[i].doc_body_pending = false;
303            }
304            i += 1;
305        }
306        out
307    }
308
309    #[doc(hidden)]
310    #[must_use]
311    pub const fn __const_concat_arrays<const SUM_LEN: usize>(
312        method_groups: &[&[Self]],
313    ) -> [Self; SUM_LEN] {
314        const NULL_METHOD: PyMethodDef = PyMethodDef {
315            name: "",
316            func: &|_, _, _| unreachable!(),
317            flags: PyMethodFlags::empty(),
318            #[cfg(feature = "doc")]
319            doc_off: 0,
320            #[cfg(feature = "doc")]
321            doc_len: 0,
322            #[cfg(feature = "doc")]
323            doc_body_pending: false,
324            doc: None,
325        };
326        let mut all_methods = [NULL_METHOD; SUM_LEN];
327        let mut all_idx = 0;
328        let mut group_idx = 0;
329        while group_idx < method_groups.len() {
330            let group = method_groups[group_idx];
331            let mut method_idx = 0;
332            while method_idx < group.len() {
333                all_methods[all_idx] = group[method_idx].const_copy();
334                method_idx += 1;
335                all_idx += 1;
336            }
337            group_idx += 1;
338        }
339        all_methods
340    }
341
342    #[must_use]
343    const fn const_copy(&self) -> Self {
344        Self {
345            name: self.name,
346            func: self.func,
347            flags: self.flags,
348            #[cfg(feature = "doc")]
349            doc_off: self.doc_off,
350            #[cfg(feature = "doc")]
351            doc_len: self.doc_len,
352            #[cfg(feature = "doc")]
353            doc_body_pending: self.doc_body_pending,
354            doc: self.doc,
355        }
356    }
357}
358
359impl core::fmt::Debug for PyMethodDef {
360    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
361        f.debug_struct("PyMethodDef")
362            .field("name", &self.name)
363            .field(
364                "func",
365                &(unsafe {
366                    core::mem::transmute::<&dyn PyNativeFn, [usize; 2]>(self.func)[1] as *const u8
367                }),
368            )
369            .field("flags", &self.flags)
370            .field("doc", &self.doc)
371            .finish()
372    }
373}
374
375// This is not a part of CPython API.
376// But useful to support dynamically generated methods
377#[pyclass(name, module = false, ctx = "method_def")]
378#[derive(Debug)]
379pub struct HeapMethodDef {
380    method: PyMethodDef,
381}
382
383impl HeapMethodDef {
384    #[must_use]
385    pub const fn new(method: PyMethodDef) -> Self {
386        Self { method }
387    }
388}
389
390impl Py<HeapMethodDef> {
391    pub(crate) unsafe fn method(&self) -> &'static PyMethodDef {
392        unsafe { &*(&self.method as *const _) }
393    }
394
395    pub fn build_function(
396        &self,
397        vm: &VirtualMachine,
398        zelf: Option<PyObjectRef>,
399    ) -> PyRef<PyNativeFunction> {
400        let mut function = unsafe { self.method() }.to_function();
401        function._method_def_owner = Some(self.to_owned().into());
402        function.zelf = zelf;
403        PyRef::new_ref(
404            function,
405            vm.ctx.types.builtin_function_or_method_type.to_owned(),
406            None,
407        )
408    }
409
410    pub fn build_method(
411        &self,
412        class: &'static Py<PyType>,
413        vm: &VirtualMachine,
414    ) -> PyRef<PyMethodDescriptor> {
415        let mut function = unsafe { self.method() }.to_method(class, &vm.ctx);
416        function._method_def_owner = Some(self.to_owned().into());
417        PyRef::new_ref(
418            function,
419            vm.ctx.types.method_descriptor_type.to_owned(),
420            None,
421        )
422    }
423}
424
425#[pyclass]
426impl HeapMethodDef {}
427
428pub(crate) fn init(ctx: &'static Context) {
429    // TODO: Should we extend the class instead of interning only the name?
430    // HeapMethodDef::extend_class(ctx, ctx.types.method_def);
431
432    let _ = ctx.intern_str(HeapMethodDef::NAME);
433}