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 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 let mut scope = unsafe { scope.rclass(mrb)? };
136 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 let module = unsafe { sys::mrb_module_get_under(mrb, scope.as_mut(), module_name) };
146 NonNull::new(module)
147 } else {
148 None
151 }
152 } else {
153 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 let module = unsafe { sys::mrb_module_get(mrb, module_name) };
165 NonNull::new(module)
166 } else {
167 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}