rustpython_vm/
class.rs

1//! Utilities to define a new Python class
2
3use crate::{
4    builtins::{PyBaseObject, PyType, PyTypeRef},
5    function::PyMethodDef,
6    identifier,
7    object::Py,
8    types::{hash_not_implemented, PyTypeFlags, PyTypeSlots},
9    vm::Context,
10};
11use rustpython_common::static_cell;
12
13pub trait StaticType {
14    // Ideally, saving PyType is better than PyTypeRef
15    fn static_cell() -> &'static static_cell::StaticCell<PyTypeRef>;
16    fn static_metaclass() -> &'static Py<PyType> {
17        PyType::static_type()
18    }
19    fn static_baseclass() -> &'static Py<PyType> {
20        PyBaseObject::static_type()
21    }
22    fn static_type() -> &'static Py<PyType> {
23        Self::static_cell()
24            .get()
25            .expect("static type has not been initialized. e.g. the native types defined in different module may be used before importing library.")
26    }
27    fn init_manually(typ: PyTypeRef) -> &'static Py<PyType> {
28        let cell = Self::static_cell();
29        cell.set(typ)
30            .unwrap_or_else(|_| panic!("double initialization from init_manually"));
31        cell.get().unwrap()
32    }
33    fn init_builtin_type() -> &'static Py<PyType>
34    where
35        Self: PyClassImpl,
36    {
37        let typ = Self::create_static_type();
38        let cell = Self::static_cell();
39        cell.set(typ)
40            .unwrap_or_else(|_| panic!("double initialization of {}", Self::NAME));
41        cell.get().unwrap()
42    }
43    fn create_static_type() -> PyTypeRef
44    where
45        Self: PyClassImpl,
46    {
47        PyType::new_static(
48            Self::static_baseclass().to_owned(),
49            Default::default(),
50            Self::make_slots(),
51            Self::static_metaclass().to_owned(),
52        )
53        .unwrap()
54    }
55}
56
57pub trait PyClassDef {
58    const NAME: &'static str;
59    const MODULE_NAME: Option<&'static str>;
60    const TP_NAME: &'static str;
61    const DOC: Option<&'static str> = None;
62    const BASICSIZE: usize;
63    const UNHASHABLE: bool = false;
64
65    // due to restriction of rust trait system, object.__base__ is None
66    // but PyBaseObject::Base will be PyBaseObject.
67    type Base: PyClassDef;
68}
69
70pub trait PyClassImpl: PyClassDef {
71    const TP_FLAGS: PyTypeFlags = PyTypeFlags::DEFAULT;
72
73    fn extend_class(ctx: &Context, class: &'static Py<PyType>)
74    where
75        Self: Sized,
76    {
77        #[cfg(debug_assertions)]
78        {
79            assert!(class.slots.flags.is_created_with_flags());
80        }
81
82        let _ = ctx.intern_str(Self::NAME); // intern type name
83
84        if Self::TP_FLAGS.has_feature(PyTypeFlags::HAS_DICT) {
85            let __dict__ = identifier!(ctx, __dict__);
86            class.set_attr(
87                __dict__,
88                ctx.new_getset(
89                    "__dict__",
90                    class,
91                    crate::builtins::object::object_get_dict,
92                    crate::builtins::object::object_set_dict,
93                )
94                .into(),
95            );
96        }
97        Self::impl_extend_class(ctx, class);
98        if let Some(doc) = Self::DOC {
99            class.set_attr(identifier!(ctx, __doc__), ctx.new_str(doc).into());
100        }
101        if let Some(module_name) = Self::MODULE_NAME {
102            class.set_attr(
103                identifier!(ctx, __module__),
104                ctx.new_str(module_name).into(),
105            );
106        }
107
108        if class.slots.new.load().is_some() {
109            let bound_new = Context::genesis().slot_new_wrapper.build_bound_method(
110                ctx,
111                class.to_owned().into(),
112                class,
113            );
114            class.set_attr(identifier!(ctx, __new__), bound_new.into());
115        }
116
117        if class.slots.hash.load().map_or(0, |h| h as usize) == hash_not_implemented as usize {
118            class.set_attr(ctx.names.__hash__, ctx.none.clone().into());
119        }
120
121        class.extend_methods(class.slots.methods, ctx);
122    }
123
124    fn make_class(ctx: &Context) -> PyTypeRef
125    where
126        Self: StaticType + Sized,
127    {
128        (*Self::static_cell().get_or_init(|| {
129            let typ = Self::create_static_type();
130            Self::extend_class(ctx, unsafe {
131                // typ will be saved in static_cell
132                let r: &Py<PyType> = &typ;
133                let r: &'static Py<PyType> = std::mem::transmute(r);
134                r
135            });
136            typ
137        }))
138        .to_owned()
139    }
140
141    fn impl_extend_class(ctx: &Context, class: &'static Py<PyType>);
142    const METHOD_DEFS: &'static [PyMethodDef];
143    fn extend_slots(slots: &mut PyTypeSlots);
144
145    fn make_slots() -> PyTypeSlots {
146        let mut slots = PyTypeSlots {
147            flags: Self::TP_FLAGS,
148            name: Self::TP_NAME,
149            basicsize: Self::BASICSIZE,
150            doc: Self::DOC,
151            methods: Self::METHOD_DEFS,
152            ..Default::default()
153        };
154
155        if Self::UNHASHABLE {
156            slots.hash.store(Some(hash_not_implemented));
157        }
158
159        Self::extend_slots(&mut slots);
160        slots
161    }
162}