artichoke_backend/
module.rs

1use std::borrow::Cow;
2use std::collections::HashSet;
3use std::convert::AsRef;
4use std::ffi::{CStr, c_void};
5use std::hash::{Hash, Hasher};
6use std::ptr::NonNull;
7
8use crate::Artichoke;
9use crate::core::Intern;
10use crate::def::{ConstantNameError, EnclosingRubyScope, Method, NotDefinedError};
11use crate::error::Error;
12use crate::method;
13use crate::sys;
14
15#[derive(Debug)]
16pub struct Builder<'a> {
17    interp: &'a mut Artichoke,
18    spec: &'a Spec,
19    methods: HashSet<method::Spec>,
20}
21
22impl<'a> Builder<'a> {
23    #[must_use]
24    pub fn for_spec(interp: &'a mut Artichoke, spec: &'a Spec) -> Self {
25        Self {
26            interp,
27            spec,
28            methods: HashSet::default(),
29        }
30    }
31
32    pub fn add_method<T>(mut self, name: T, method: Method, args: sys::mrb_aspec) -> Result<Self, ConstantNameError>
33    where
34        T: Into<Cow<'static, str>>,
35    {
36        let spec = method::Spec::new(method::Type::Instance, name.into(), method, args)?;
37        self.methods.insert(spec);
38        Ok(self)
39    }
40
41    pub fn add_self_method<T>(
42        mut self,
43        name: T,
44        method: Method,
45        args: sys::mrb_aspec,
46    ) -> Result<Self, ConstantNameError>
47    where
48        T: Into<Cow<'static, str>>,
49    {
50        let spec = method::Spec::new(method::Type::Class, name.into(), method, args)?;
51        self.methods.insert(spec);
52        Ok(self)
53    }
54
55    pub fn add_module_method<T>(
56        mut self,
57        name: T,
58        method: Method,
59        args: sys::mrb_aspec,
60    ) -> Result<Self, ConstantNameError>
61    where
62        T: Into<Cow<'static, str>>,
63    {
64        let spec = method::Spec::new(method::Type::Module, name.into(), method, args)?;
65        self.methods.insert(spec);
66        Ok(self)
67    }
68
69    pub fn define(self) -> Result<(), NotDefinedError> {
70        let name = self.spec.name_c_str().as_ptr();
71
72        let rclass = self.spec.rclass();
73        let rclass = unsafe { self.interp.with_ffi_boundary(|mrb| rclass.resolve(mrb)) };
74
75        let mut rclass = if let Ok(Some(rclass)) = rclass {
76            rclass
77        } else if let Some(enclosing_scope) = self.spec.enclosing_scope() {
78            let scope = unsafe { self.interp.with_ffi_boundary(|mrb| enclosing_scope.rclass(mrb)) };
79            let Ok(Some(mut scope)) = scope else {
80                return Err(NotDefinedError::enclosing_scope(enclosing_scope.fqname().into_owned()));
81            };
82            let rclass = unsafe {
83                self.interp
84                    .with_ffi_boundary(|mrb| sys::mrb_define_module_under(mrb, scope.as_mut(), name))
85            };
86            let rclass = rclass.map_err(|_| NotDefinedError::module(self.spec.name()))?;
87            NonNull::new(rclass).ok_or_else(|| NotDefinedError::module(self.spec.name()))?
88        } else {
89            let rclass = unsafe { self.interp.with_ffi_boundary(|mrb| sys::mrb_define_module(mrb, name)) };
90            let rclass = rclass.map_err(|_| NotDefinedError::module(self.spec.name()))?;
91            NonNull::new(rclass).ok_or_else(|| NotDefinedError::module(self.spec.name()))?
92        };
93
94        for method in self.methods {
95            unsafe {
96                method.define(self.interp, rclass.as_mut())?;
97            }
98        }
99        Ok(())
100    }
101}
102
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct Rclass {
105    sym: u32,
106    name: &'static CStr,
107    enclosing_scope: Option<EnclosingRubyScope>,
108}
109
110impl Rclass {
111    #[must_use]
112    pub const fn new(sym: u32, name: &'static CStr, enclosing_scope: Option<EnclosingRubyScope>) -> Self {
113        Self {
114            sym,
115            name,
116            enclosing_scope,
117        }
118    }
119
120    /// Resolve a type's [`sys::RClass`] using its enclosing scope and name.
121    ///
122    /// # Safety
123    ///
124    /// This function must be called within an [`Artichoke::with_ffi_boundary`]
125    /// closure because the FFI APIs called in this function may require access
126    /// to the Artichoke [`State`].
127    ///
128    /// [`State`]: crate::state::State
129    pub unsafe fn resolve(&self, mrb: *mut sys::mrb_state) -> Option<NonNull<sys::RClass>> {
130        let module_name = self.name.as_ptr();
131        if let Some(ref scope) = self.enclosing_scope {
132            // Short circuit if enclosing scope does not exist.
133            //
134            // SAFETY: callers must uphold that `mrb` is initialized.
135            let mut scope = unsafe { scope.rclass(mrb)? };
136            // SAFETY: the enclosing scope exists, callers must uphold that
137            // `mrb` is initialized.
138            let is_defined_under = unsafe {
139                sys::mrb_const_defined_at(mrb, sys::mrb_sys_obj_value(scope.cast::<c_void>().as_mut()), self.sym)
140            };
141            if is_defined_under {
142                // SAFETY: the enclosing scope exists and the module is defined
143                // under the enclosing scope. Callers must uphold that `mrb` is
144                // initialized.
145                let module = unsafe { sys::mrb_module_get_under(mrb, scope.as_mut(), module_name) };
146                NonNull::new(module)
147            } else {
148                // Enclosing scope exists.
149                // Module is not defined under the enclosing scope.
150                None
151            }
152        } else {
153            // SAFETY: callers must uphold that `mrb` is initialized.
154            let is_defined = unsafe {
155                sys::mrb_const_defined_at(
156                    mrb,
157                    sys::mrb_sys_obj_value((*mrb).object_class.cast::<c_void>()),
158                    self.sym,
159                )
160            };
161            if is_defined {
162                // SAFETY: Module exists in root scope. Callers must uphold that
163                // `mrb` is initialized.
164                let module = unsafe { sys::mrb_module_get(mrb, module_name) };
165                NonNull::new(module)
166            } else {
167                // Class does not exist in root scope.
168                None
169            }
170        }
171    }
172}
173
174#[derive(Debug)]
175pub struct Spec {
176    name: Cow<'static, str>,
177    name_cstr: &'static CStr,
178    sym: u32,
179    enclosing_scope: Option<EnclosingRubyScope>,
180}
181
182impl Spec {
183    pub fn new<T>(
184        interp: &mut Artichoke,
185        name: T,
186        name_cstr: &'static CStr,
187        enclosing_scope: Option<EnclosingRubyScope>,
188    ) -> Result<Self, Error>
189    where
190        T: Into<Cow<'static, str>>,
191    {
192        let name = name.into();
193        let sym = match name {
194            Cow::Borrowed(name) => interp.intern_string(name)?,
195            Cow::Owned(ref name) => interp.intern_string(name.clone())?,
196        };
197        Ok(Self {
198            name,
199            name_cstr,
200            sym,
201            enclosing_scope,
202        })
203    }
204
205    #[must_use]
206    pub fn name(&self) -> Cow<'static, str> {
207        match &self.name {
208            Cow::Borrowed(name) => Cow::Borrowed(name),
209            Cow::Owned(name) => name.clone().into(),
210        }
211    }
212
213    #[must_use]
214    pub fn name_c_str(&self) -> &'static CStr {
215        self.name_cstr
216    }
217
218    #[must_use]
219    pub fn enclosing_scope(&self) -> Option<&EnclosingRubyScope> {
220        self.enclosing_scope.as_ref()
221    }
222
223    #[must_use]
224    pub fn name_symbol(&self) -> u32 {
225        self.sym
226    }
227
228    #[must_use]
229    pub fn fqname(&self) -> Cow<'_, str> {
230        let Some(scope) = self.enclosing_scope() else {
231            return self.name();
232        };
233        let mut fqname = String::from(scope.fqname());
234        fqname.push_str("::");
235        fqname.push_str(self.name.as_ref());
236        fqname.into()
237    }
238
239    #[must_use]
240    pub fn rclass(&self) -> Rclass {
241        Rclass::new(self.sym, self.name_cstr, self.enclosing_scope.clone())
242    }
243}
244
245impl Hash for Spec {
246    fn hash<H: Hasher>(&self, state: &mut H) {
247        self.name().hash(state);
248        self.enclosing_scope().hash(state);
249    }
250}
251
252impl Eq for Spec {}
253
254impl PartialEq for Spec {
255    fn eq(&self, other: &Self) -> bool {
256        self.fqname() == other.fqname()
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use crate::module::Spec;
263    use crate::test::prelude::*;
264
265    #[test]
266    fn rclass_for_undef_root_module() {
267        let mut interp = interpreter();
268        let spec = Spec::new(&mut interp, "Foo", c"Foo", None).unwrap();
269        let rclass = unsafe { interp.with_ffi_boundary(|mrb| spec.rclass().resolve(mrb)) }.unwrap();
270        assert!(rclass.is_none());
271    }
272
273    #[test]
274    fn rclass_for_undef_nested_module() {
275        let mut interp = interpreter();
276        let scope = Spec::new(&mut interp, "Kernel", c"Kernel", None).unwrap();
277        let scope = EnclosingRubyScope::module(&scope);
278        let spec = Spec::new(&mut interp, "Foo", c"Foo", Some(scope)).unwrap();
279        let rclass = unsafe { interp.with_ffi_boundary(|mrb| spec.rclass().resolve(mrb)) }.unwrap();
280        assert!(rclass.is_none());
281    }
282
283    #[test]
284    fn rclass_for_root_module() {
285        let mut interp = interpreter();
286        let spec = Spec::new(&mut interp, "Kernel", c"Kernel", None).unwrap();
287        let rclass = unsafe { interp.with_ffi_boundary(|mrb| spec.rclass().resolve(mrb)) }.unwrap();
288        assert!(rclass.is_some());
289    }
290
291    #[test]
292    fn rclass_for_nested_module() {
293        let mut interp = interpreter();
294        interp.eval(b"module Foo; module Bar; end; end").unwrap();
295        let scope = Spec::new(&mut interp, "Foo", c"Foo", None).unwrap();
296        let scope = EnclosingRubyScope::module(&scope);
297        let spec = Spec::new(&mut interp, "Bar", c"Bar", Some(scope)).unwrap();
298        let rclass = unsafe { interp.with_ffi_boundary(|mrb| spec.rclass().resolve(mrb)) }.unwrap();
299        assert!(rclass.is_some());
300    }
301
302    #[test]
303    fn rclass_for_nested_module_under_class() {
304        let mut interp = interpreter();
305        interp.eval(b"class Foo; module Bar; end; end").unwrap();
306        let scope = class::Spec::new("Foo", c"Foo", None, None).unwrap();
307        let scope = EnclosingRubyScope::class(&scope);
308        let spec = Spec::new(&mut interp, "Bar", c"Bar", Some(scope)).unwrap();
309        let rclass = unsafe { interp.with_ffi_boundary(|mrb| spec.rclass().resolve(mrb)) }.unwrap();
310        assert!(rclass.is_some());
311    }
312}