rustpython_vm/builtins/
module.rs

1use super::{PyDictRef, PyStr, PyStrRef, PyType, PyTypeRef};
2use crate::{
3    builtins::{pystr::AsPyStr, PyStrInterned},
4    class::PyClassImpl,
5    convert::ToPyObject,
6    function::{FuncArgs, PyMethodDef},
7    types::{GetAttr, Initializer, Representable},
8    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
9};
10
11#[pyclass(module = false, name = "module")]
12#[derive(Debug)]
13pub struct PyModuleDef {
14    // pub index: usize,
15    pub name: &'static PyStrInterned,
16    pub doc: Option<&'static PyStrInterned>,
17    // pub size: isize,
18    pub methods: &'static [PyMethodDef],
19    pub slots: PyModuleSlots,
20    // traverse: traverseproc
21    // clear: inquiry
22    // free: freefunc
23}
24
25pub type ModuleCreate =
26    fn(&VirtualMachine, &PyObject, &'static PyModuleDef) -> PyResult<PyRef<PyModule>>;
27pub type ModuleExec = fn(&VirtualMachine, &Py<PyModule>) -> PyResult<()>;
28
29#[derive(Default)]
30pub struct PyModuleSlots {
31    pub create: Option<ModuleCreate>,
32    pub exec: Option<ModuleExec>,
33}
34
35impl std::fmt::Debug for PyModuleSlots {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        f.debug_struct("PyModuleSlots")
38            .field("create", &self.create.is_some())
39            .field("exec", &self.exec.is_some())
40            .finish()
41    }
42}
43
44#[allow(clippy::new_without_default)] // avoid Default implementation
45#[pyclass(module = false, name = "module")]
46#[derive(Debug)]
47pub struct PyModule {
48    // PyObject *md_dict;
49    pub def: Option<&'static PyModuleDef>,
50    // state: Any
51    // weaklist
52    // for logging purposes after md_dict is cleared
53    pub name: Option<&'static PyStrInterned>,
54}
55
56impl PyPayload for PyModule {
57    fn class(ctx: &Context) -> &'static Py<PyType> {
58        ctx.types.module_type
59    }
60}
61
62#[derive(FromArgs)]
63pub struct ModuleInitArgs {
64    name: PyStrRef,
65    #[pyarg(any, default)]
66    doc: Option<PyStrRef>,
67}
68
69impl PyModule {
70    #[allow(clippy::new_without_default)]
71    pub fn new() -> Self {
72        Self {
73            def: None,
74            name: None,
75        }
76    }
77    pub fn from_def(def: &'static PyModuleDef) -> Self {
78        Self {
79            def: Some(def),
80            name: Some(def.name),
81        }
82    }
83    pub fn __init_dict_from_def(vm: &VirtualMachine, module: &Py<PyModule>) {
84        let doc = module.def.unwrap().doc.map(|doc| doc.to_owned());
85        module.init_dict(module.name.unwrap(), doc, vm);
86    }
87}
88
89impl Py<PyModule> {
90    pub fn __init_methods(&self, vm: &VirtualMachine) -> PyResult<()> {
91        debug_assert!(self.def.is_some());
92        for method in self.def.unwrap().methods {
93            let func = method
94                .to_function()
95                .with_module(self.name.unwrap())
96                .into_ref(&vm.ctx);
97            vm.__module_set_attr(self, vm.ctx.intern_str(method.name), func)?;
98        }
99        Ok(())
100    }
101
102    fn getattr_inner(&self, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
103        if let Some(attr) = self.as_object().generic_getattr_opt(name, None, vm)? {
104            return Ok(attr);
105        }
106        if let Ok(getattr) = self.dict().get_item(identifier!(vm, __getattr__), vm) {
107            return getattr.call((name.to_owned(),), vm);
108        }
109        let module_name = if let Some(name) = self.name(vm) {
110            format!(" '{name}'")
111        } else {
112            "".to_owned()
113        };
114        Err(vm.new_attribute_error(format!("module{module_name} has no attribute '{name}'")))
115    }
116
117    fn name(&self, vm: &VirtualMachine) -> Option<PyStrRef> {
118        let name = self
119            .as_object()
120            .generic_getattr_opt(identifier!(vm, __name__), None, vm)
121            .unwrap_or_default()?;
122        name.downcast::<PyStr>().ok()
123    }
124
125    // TODO: to be replaced by the commented-out dict method above once dictoffsets land
126    pub fn dict(&self) -> PyDictRef {
127        self.as_object().dict().unwrap()
128    }
129    // TODO: should be on PyModule, not Py<PyModule>
130    pub(crate) fn init_dict(
131        &self,
132        name: &'static PyStrInterned,
133        doc: Option<PyStrRef>,
134        vm: &VirtualMachine,
135    ) {
136        let dict = self.dict();
137        dict.set_item(identifier!(vm, __name__), name.to_object(), vm)
138            .expect("Failed to set __name__ on module");
139        dict.set_item(identifier!(vm, __doc__), doc.to_pyobject(vm), vm)
140            .expect("Failed to set __doc__ on module");
141        dict.set_item("__package__", vm.ctx.none(), vm)
142            .expect("Failed to set __package__ on module");
143        dict.set_item("__loader__", vm.ctx.none(), vm)
144            .expect("Failed to set __loader__ on module");
145        dict.set_item("__spec__", vm.ctx.none(), vm)
146            .expect("Failed to set __spec__ on module");
147    }
148
149    pub fn get_attr<'a>(&self, attr_name: impl AsPyStr<'a>, vm: &VirtualMachine) -> PyResult {
150        let attr_name = attr_name.as_pystr(&vm.ctx);
151        self.getattr_inner(attr_name, vm)
152    }
153
154    pub fn set_attr<'a>(
155        &self,
156        attr_name: impl AsPyStr<'a>,
157        attr_value: impl Into<PyObjectRef>,
158        vm: &VirtualMachine,
159    ) -> PyResult<()> {
160        self.as_object().set_attr(attr_name, attr_value, vm)
161    }
162}
163
164#[pyclass(with(GetAttr, Initializer, Representable), flags(BASETYPE, HAS_DICT))]
165impl PyModule {
166    #[pyslot]
167    fn slot_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult {
168        PyModule::new().into_ref_with_type(vm, cls).map(Into::into)
169    }
170
171    #[pymethod(magic)]
172    fn dir(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
173        let dict = zelf
174            .as_object()
175            .dict()
176            .ok_or_else(|| vm.new_value_error("module has no dict".to_owned()))?;
177        let attrs = dict.into_iter().map(|(k, _v)| k).collect();
178        Ok(attrs)
179    }
180}
181
182impl Initializer for PyModule {
183    type Args = ModuleInitArgs;
184
185    fn init(zelf: PyRef<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
186        debug_assert!(zelf
187            .class()
188            .slots
189            .flags
190            .has_feature(crate::types::PyTypeFlags::HAS_DICT));
191        zelf.init_dict(vm.ctx.intern_str(args.name.as_str()), args.doc, vm);
192        Ok(())
193    }
194}
195
196impl GetAttr for PyModule {
197    fn getattro(zelf: &Py<Self>, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
198        zelf.getattr_inner(name, vm)
199    }
200}
201
202impl Representable for PyModule {
203    #[inline]
204    fn repr(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyStrRef> {
205        let importlib = vm.import("_frozen_importlib", 0)?;
206        let module_repr = importlib.get_attr("_module_repr", vm)?;
207        let repr = module_repr.call((zelf.to_owned(),), vm)?;
208        repr.downcast()
209            .map_err(|_| vm.new_type_error("_module_repr did not return a string".into()))
210    }
211
212    #[cold]
213    fn repr_str(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
214        unreachable!("use repr instead")
215    }
216}
217
218pub(crate) fn init(context: &Context) {
219    PyModule::extend_class(context, context.types.module_type);
220}
Morty Proxy This is a proxified and sanitized view of the page, visit original site.