1use std::any::Any;
2use std::borrow::Cow;
3use std::collections::HashSet;
4use std::ffi::CStr;
5use std::hash::{Hash, Hasher};
6use std::ptr::NonNull;
7
8use crate::Artichoke;
9use crate::def::{ConstantNameError, EnclosingRubyScope, Free, Method, NotDefinedError};
10use crate::error::Error;
11use crate::ffi::InterpreterExtractError;
12use crate::method;
13use crate::sys;
14
15#[derive(Debug)]
16pub struct Builder<'a> {
17 interp: &'a mut Artichoke,
18 spec: &'a Spec,
19 is_mrb_tt_data: bool,
20 super_class: Option<NonNull<sys::RClass>>,
21 methods: HashSet<method::Spec>,
22}
23
24impl<'a> Builder<'a> {
25 #[must_use]
26 pub fn for_spec(interp: &'a mut Artichoke, spec: &'a Spec) -> Self {
27 Self {
28 interp,
29 spec,
30 is_mrb_tt_data: false,
31 super_class: None,
32 methods: HashSet::default(),
33 }
34 }
35
36 #[must_use]
37 pub fn value_is_rust_object(mut self) -> Self {
38 self.is_mrb_tt_data = true;
39 self
40 }
41
42 pub fn with_super_class<T, U>(mut self, classname: U) -> Result<Self, Error>
43 where
44 T: Any,
45 U: Into<Cow<'static, str>>,
46 {
47 let state = self.interp.state.as_deref().ok_or_else(InterpreterExtractError::new)?;
48 let Some(spec) = state.classes.get::<T>() else {
49 return Err(NotDefinedError::super_class(classname.into()).into());
50 };
51 let rclass = spec.rclass();
52 let rclass = unsafe { self.interp.with_ffi_boundary(|mrb| rclass.resolve(mrb))? };
53 let Some(rclass) = rclass else {
54 return Err(NotDefinedError::super_class(classname.into()).into());
55 };
56 self.super_class = Some(rclass);
57 Ok(self)
58 }
59
60 pub fn add_method<T>(mut self, name: T, method: Method, args: sys::mrb_aspec) -> Result<Self, ConstantNameError>
61 where
62 T: Into<Cow<'static, str>>,
63 {
64 let spec = method::Spec::new(method::Type::Instance, name.into(), method, args)?;
65 self.methods.insert(spec);
66 Ok(self)
67 }
68
69 pub fn add_self_method<T>(
70 mut self,
71 name: T,
72 method: Method,
73 args: sys::mrb_aspec,
74 ) -> Result<Self, ConstantNameError>
75 where
76 T: Into<Cow<'static, str>>,
77 {
78 let spec = method::Spec::new(method::Type::Class, name.into(), method, args)?;
79 self.methods.insert(spec);
80 Ok(self)
81 }
82
83 pub fn define(self) -> Result<(), NotDefinedError> {
84 use sys::mrb_vtype::MRB_TT_CDATA;
85
86 let name = self.spec.name_c_str().as_ptr();
87
88 let mut super_class = if let Some(super_class) = self.super_class {
89 super_class
90 } else {
91 let rclass = unsafe { self.interp.mrb.as_ref().object_class };
99 NonNull::new(rclass).ok_or_else(|| NotDefinedError::super_class("Object"))?
100 };
101
102 let rclass = self.spec.rclass();
103 let rclass = unsafe { self.interp.with_ffi_boundary(|mrb| rclass.resolve(mrb)) };
104
105 let mut rclass = if let Ok(Some(rclass)) = rclass {
106 rclass
107 } else if let Some(enclosing_scope) = self.spec.enclosing_scope() {
108 let scope = unsafe { self.interp.with_ffi_boundary(|mrb| enclosing_scope.rclass(mrb)) };
109 let Ok(Some(mut scope)) = scope else {
110 return Err(NotDefinedError::enclosing_scope(enclosing_scope.fqname().into_owned()));
111 };
112 let rclass = unsafe {
113 self.interp.with_ffi_boundary(|mrb| {
114 sys::mrb_define_class_under(mrb, scope.as_mut(), name, super_class.as_mut())
115 })
116 };
117 let rclass = rclass.map_err(|_| NotDefinedError::class(self.spec.name()))?;
118 NonNull::new(rclass).ok_or_else(|| NotDefinedError::class(self.spec.name()))?
119 } else {
120 let rclass = unsafe {
121 self.interp
122 .with_ffi_boundary(|mrb| sys::mrb_define_class(mrb, name, super_class.as_mut()))
123 };
124 let rclass = rclass.map_err(|_| NotDefinedError::class(self.spec.name()))?;
125 NonNull::new(rclass).ok_or_else(|| NotDefinedError::class(self.spec.name()))?
126 };
127
128 for method in &self.methods {
129 unsafe {
130 method.define(self.interp, rclass.as_mut())?;
131 }
132 }
133
134 if self.is_mrb_tt_data {
137 unsafe {
138 sys::mrb_sys_set_instance_tt(rclass.as_mut(), MRB_TT_CDATA);
139 }
140 }
141 Ok(())
142 }
143}
144
145#[derive(Debug, Clone, PartialEq, Eq)]
146pub struct Rclass {
147 name: &'static CStr,
148 enclosing_scope: Option<EnclosingRubyScope>,
149}
150
151impl Rclass {
152 #[must_use]
153 pub const fn new(name: &'static CStr, enclosing_scope: Option<EnclosingRubyScope>) -> Self {
154 Self { name, enclosing_scope }
155 }
156
157 pub unsafe fn resolve(&self, mrb: *mut sys::mrb_state) -> Option<NonNull<sys::RClass>> {
167 let class_name = self.name.as_ptr();
168 if let Some(ref scope) = self.enclosing_scope {
169 let mut scope = unsafe { scope.rclass(mrb)? };
173 let is_defined_under = unsafe { sys::mrb_class_defined_under(mrb, scope.as_mut(), class_name) };
176 if is_defined_under {
177 let class = unsafe { sys::mrb_class_get_under(mrb, scope.as_mut(), class_name) };
181 NonNull::new(class)
182 } else {
183 None
186 }
187 } else {
188 let is_defined = unsafe { sys::mrb_class_defined(mrb, class_name) };
190 if is_defined {
191 let class = unsafe { sys::mrb_class_get(mrb, class_name) };
194 NonNull::new(class)
195 } else {
196 None
198 }
199 }
200 }
201}
202
203#[derive(Debug)]
204pub struct Spec {
205 name: Cow<'static, str>,
206 name_cstr: &'static CStr,
207 data_type: Box<sys::mrb_data_type>,
208 enclosing_scope: Option<EnclosingRubyScope>,
209}
210
211impl Spec {
212 pub fn new<T>(
213 name: T,
214 name_cstr: &'static CStr,
215 enclosing_scope: Option<EnclosingRubyScope>,
216 free: Option<Free>,
217 ) -> Result<Self, ConstantNameError>
218 where
219 T: Into<Cow<'static, str>>,
220 {
221 let name = name.into();
222 let data_type = sys::mrb_data_type {
227 struct_name: name_cstr.as_ptr(),
228 dfree: free,
229 };
230 let data_type = Box::new(data_type);
231 Ok(Self {
232 name,
233 name_cstr,
234 data_type,
235 enclosing_scope,
236 })
237 }
238
239 #[must_use]
240 pub fn data_type(&self) -> *const sys::mrb_data_type {
241 self.data_type.as_ref()
242 }
243
244 #[must_use]
245 pub fn name(&self) -> Cow<'static, str> {
246 match &self.name {
247 Cow::Borrowed(name) => Cow::Borrowed(name),
248 Cow::Owned(name) => name.clone().into(),
249 }
250 }
251
252 #[must_use]
253 pub fn name_c_str(&self) -> &'static CStr {
254 self.name_cstr
255 }
256
257 #[must_use]
258 pub fn enclosing_scope(&self) -> Option<&EnclosingRubyScope> {
259 self.enclosing_scope.as_ref()
260 }
261
262 #[must_use]
263 pub fn fqname(&self) -> Cow<'_, str> {
264 let Some(scope) = self.enclosing_scope() else {
265 return self.name();
266 };
267 let mut fqname = String::from(scope.fqname());
268 fqname.push_str("::");
269 fqname.push_str(self.name.as_ref());
270 fqname.into()
271 }
272
273 #[must_use]
274 pub fn rclass(&self) -> Rclass {
275 Rclass::new(self.name_cstr, self.enclosing_scope.clone())
276 }
277}
278
279impl Hash for Spec {
280 fn hash<H: Hasher>(&self, state: &mut H) {
281 self.name().hash(state);
282 self.enclosing_scope().hash(state);
283 }
284}
285
286impl Eq for Spec {}
287
288impl PartialEq for Spec {
289 fn eq(&self, other: &Self) -> bool {
290 self.fqname() == other.fqname()
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use spinoso_exception::StandardError;
297
298 use crate::extn::core::kernel::Kernel;
299 use crate::test::prelude::*;
300
301 struct RustError;
302
303 #[test]
304 fn super_class() {
305 let mut interp = interpreter();
306 let spec = class::Spec::new("RustError", c"RustError", None, None).unwrap();
307 class::Builder::for_spec(&mut interp, &spec)
308 .with_super_class::<StandardError, _>("StandardError")
309 .unwrap()
310 .define()
311 .unwrap();
312 interp.def_class::<RustError>(spec).unwrap();
313
314 let result = interp.eval(b"RustError.new.is_a?(StandardError)").unwrap();
315 let result = result.try_convert_into::<bool>(&interp).unwrap();
316 assert!(result, "RustError instances are instance of StandardError");
317
318 let result = interp.eval(b"RustError < StandardError").unwrap();
319 let result = result.try_convert_into::<bool>(&interp).unwrap();
320 assert!(result, "RustError inherits from StandardError");
321 }
322
323 #[test]
324 fn rclass_for_undef_root_class() {
325 let mut interp = interpreter();
326 let spec = class::Spec::new("Foo", c"Foo", None, None).unwrap();
327 let rclass = unsafe { interp.with_ffi_boundary(|mrb| spec.rclass().resolve(mrb)) }.unwrap();
328 assert!(rclass.is_none());
329 }
330
331 #[test]
332 fn rclass_for_undef_nested_class() {
333 let mut interp = interpreter();
334 let scope = interp.module_spec::<Kernel>().unwrap().unwrap();
335 let spec = class::Spec::new("Foo", c"Foo", Some(EnclosingRubyScope::module(scope)), None).unwrap();
336 let rclass = unsafe { interp.with_ffi_boundary(|mrb| spec.rclass().resolve(mrb)) }.unwrap();
337 assert!(rclass.is_none());
338 }
339
340 #[test]
341 fn rclass_for_nested_class() {
342 let mut interp = interpreter();
343 interp.eval(b"module Foo; class Bar; end; end").unwrap();
344 let spec = module::Spec::new(&mut interp, "Foo", c"Foo", None).unwrap();
345 let spec = class::Spec::new("Bar", c"Bar", Some(EnclosingRubyScope::module(&spec)), None).unwrap();
346 let rclass = unsafe { interp.with_ffi_boundary(|mrb| spec.rclass().resolve(mrb)) }.unwrap();
347 assert!(rclass.is_some());
348 }
349
350 #[test]
351 fn rclass_for_nested_class_under_class() {
352 let mut interp = interpreter();
353 interp.eval(b"class Foo; class Bar; end; end").unwrap();
354 let spec = class::Spec::new("Foo", c"Foo", None, None).unwrap();
355 let spec = class::Spec::new("Bar", c"Bar", Some(EnclosingRubyScope::class(&spec)), None).unwrap();
356 let rclass = unsafe { interp.with_ffi_boundary(|mrb| spec.rclass().resolve(mrb)) }.unwrap();
357 assert!(rclass.is_some());
358 }
359}