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 name: &'static PyStrInterned,
16 pub doc: Option<&'static PyStrInterned>,
17 pub methods: &'static [PyMethodDef],
19 pub slots: PyModuleSlots,
20 }
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)] #[pyclass(module = false, name = "module")]
46#[derive(Debug)]
47pub struct PyModule {
48 pub def: Option<&'static PyModuleDef>,
50 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 pub fn dict(&self) -> PyDictRef {
127 self.as_object().dict().unwrap()
128 }
129 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}