1use std::borrow::Cow;
2use std::error;
3use std::ffi::{CStr, c_void};
4use std::fmt;
5use std::io::{self, Write as _};
6use std::ptr::NonNull;
7
8use spinoso_exception::{NameError, ScriptError};
9
10use crate::Artichoke;
11use crate::class;
12use crate::convert::BoxUnboxVmValue;
13use crate::core::{ClassRegistry, TryConvertMut};
14use crate::error::{Error, RubyException};
15use crate::module;
16use crate::sys;
17
18pub type Free = unsafe extern "C" fn(mrb: *mut sys::mrb_state, data: *mut c_void);
23
24pub unsafe extern "C" fn box_unbox_free<T>(_mrb: *mut sys::mrb_state, data: *mut c_void)
44where
45 T: 'static + BoxUnboxVmValue,
46{
47 let Some(data) = NonNull::new(data) else {
52 let _ignored = write!(
61 io::stderr(),
62 "Received null pointer in box_unbox_free::<{}>",
63 T::RUBY_TYPE,
64 );
65 return;
66 };
67 T::free(data.as_ptr());
70}
71
72#[cfg(test)]
73mod free_test {
74 use crate::convert::HeapAllocatedData;
75
76 fn prototype(_func: super::Free) {}
77
78 #[derive(Default, Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
79 struct Data(String);
80
81 impl HeapAllocatedData for Data {
82 const RUBY_TYPE: &'static str = "Data";
83 }
84
85 #[test]
86 fn free_prototype() {
87 prototype(super::box_unbox_free::<Data>);
88 }
89}
90
91pub type Method = unsafe extern "C-unwind" fn(mrb: *mut sys::mrb_state, slf: sys::mrb_value) -> sys::mrb_value;
107
108#[derive(Debug, Clone, Hash, PartialEq, Eq)]
109pub struct ClassScope {
110 name: Box<str>,
111 name_cstr: &'static CStr,
112 enclosing_scope: Option<Box<EnclosingRubyScope>>,
113}
114
115#[derive(Debug, Clone, Hash, PartialEq, Eq)]
116pub struct ModuleScope {
117 name: Box<str>,
118 name_cstr: &'static CStr,
119 name_symbol: u32,
120 enclosing_scope: Option<Box<EnclosingRubyScope>>,
121}
122
123#[derive(Debug, Clone, Hash, PartialEq, Eq)]
143pub enum EnclosingRubyScope {
144 Class(ClassScope),
146 Module(ModuleScope),
148}
149
150impl EnclosingRubyScope {
151 #[must_use]
156 pub fn class(spec: &class::Spec) -> Self {
157 let name_cstr = spec.name_c_str();
158 Self::Class(ClassScope {
159 name: String::from(spec.name()).into_boxed_str(),
160 name_cstr,
161 enclosing_scope: spec.enclosing_scope().cloned().map(Box::new),
162 })
163 }
164
165 #[must_use]
171 pub fn module(spec: &module::Spec) -> Self {
172 let name_cstr = spec.name_c_str();
173 Self::Module(ModuleScope {
174 name: String::from(spec.name()).into_boxed_str(),
175 name_cstr,
176 name_symbol: spec.name_symbol(),
177 enclosing_scope: spec.enclosing_scope().cloned().map(Box::new),
178 })
179 }
180
181 pub unsafe fn rclass(&self, mrb: *mut sys::mrb_state) -> Option<NonNull<sys::RClass>> {
197 match self {
198 Self::Class(scope) => {
199 let enclosing_scope = scope.enclosing_scope.clone().map(|scope| *scope);
200 let rclass = class::Rclass::new(scope.name_cstr, enclosing_scope);
201 unsafe { rclass.resolve(mrb) }
203 }
204 Self::Module(scope) => {
205 let enclosing_scope = scope.enclosing_scope.clone().map(|scope| *scope);
206 let rclass = module::Rclass::new(scope.name_symbol, scope.name_cstr, enclosing_scope);
207 unsafe { rclass.resolve(mrb) }
209 }
210 }
211 }
212
213 #[must_use]
231 pub fn fqname(&self) -> Cow<'_, str> {
232 let (name, enclosing_scope) = match self {
233 Self::Class(scope) => (&*scope.name, &scope.enclosing_scope),
234 Self::Module(scope) => (&*scope.name, &scope.enclosing_scope),
235 };
236 let Some(scope) = enclosing_scope else {
237 return name.into();
238 };
239 let mut fqname = String::from(scope.fqname());
240 fqname.push_str("::");
241 fqname.push_str(name);
242 fqname.into()
243 }
244}
245
246#[derive(Default, Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
247pub struct ConstantNameError(Cow<'static, str>);
248
249impl From<&'static str> for ConstantNameError {
250 fn from(name: &'static str) -> Self {
251 Self(name.into())
252 }
253}
254
255impl From<String> for ConstantNameError {
256 fn from(name: String) -> Self {
257 Self(name.into())
258 }
259}
260
261impl From<Cow<'static, str>> for ConstantNameError {
262 fn from(name: Cow<'static, str>) -> Self {
263 Self(name)
264 }
265}
266
267impl ConstantNameError {
268 #[must_use]
269 pub const fn new() -> Self {
270 Self(Cow::Borrowed(""))
271 }
272}
273
274impl fmt::Display for ConstantNameError {
275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276 f.write_str("Invalid constant name contained a NUL byte")
277 }
278}
279
280impl error::Error for ConstantNameError {}
281
282impl RubyException for ConstantNameError {
283 fn message(&self) -> Cow<'_, [u8]> {
284 Cow::Borrowed(b"Invalid constant name contained a NUL byte")
285 }
286
287 fn name(&self) -> Cow<'_, str> {
288 "NameError".into()
289 }
290
291 fn vm_backtrace(&self, interp: &mut Artichoke) -> Option<Vec<Vec<u8>>> {
292 let _ = interp;
293 None
294 }
295
296 fn as_mrb_value(&self, interp: &mut Artichoke) -> Option<sys::mrb_value> {
297 let message = interp.try_convert_mut(self.message()).ok()?;
298 let value = interp.new_instance::<NameError>(&[message]).ok().flatten()?;
299 Some(value.inner())
300 }
301}
302
303impl From<ConstantNameError> for Error {
304 fn from(exception: ConstantNameError) -> Self {
305 let err: Box<dyn RubyException> = Box::new(exception);
306 Self::from(err)
307 }
308}
309
310#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
311pub enum NotDefinedError {
312 EnclosingScope(Cow<'static, str>),
313 Super(Cow<'static, str>),
314 Class(Cow<'static, str>),
315 Method(Cow<'static, str>),
316 Module(Cow<'static, str>),
317 GlobalConstant(Cow<'static, str>),
318 ClassConstant(Cow<'static, str>),
319 ModuleConstant(Cow<'static, str>),
320}
321
322impl NotDefinedError {
323 pub fn enclosing_scope<T>(item: T) -> Self
324 where
325 T: Into<Cow<'static, str>>,
326 {
327 Self::EnclosingScope(item.into())
328 }
329
330 pub fn super_class<T>(item: T) -> Self
331 where
332 T: Into<Cow<'static, str>>,
333 {
334 Self::Super(item.into())
335 }
336
337 pub fn class<T>(item: T) -> Self
338 where
339 T: Into<Cow<'static, str>>,
340 {
341 Self::Class(item.into())
342 }
343
344 pub fn method<T>(item: T) -> Self
345 where
346 T: Into<Cow<'static, str>>,
347 {
348 Self::Method(item.into())
349 }
350
351 pub fn module<T>(item: T) -> Self
352 where
353 T: Into<Cow<'static, str>>,
354 {
355 Self::Module(item.into())
356 }
357
358 pub fn global_constant<T>(item: T) -> Self
359 where
360 T: Into<Cow<'static, str>>,
361 {
362 Self::GlobalConstant(item.into())
363 }
364
365 pub fn class_constant<T>(item: T) -> Self
366 where
367 T: Into<Cow<'static, str>>,
368 {
369 Self::ClassConstant(item.into())
370 }
371
372 pub fn module_constant<T>(item: T) -> Self
373 where
374 T: Into<Cow<'static, str>>,
375 {
376 Self::ModuleConstant(item.into())
377 }
378
379 #[must_use]
380 pub fn fqdn(&self) -> &str {
381 match self {
382 Self::EnclosingScope(fqdn) | Self::Super(fqdn) | Self::Class(fqdn) | Self::Module(fqdn) => fqdn.as_ref(),
383 Self::GlobalConstant(name)
384 | Self::ClassConstant(name)
385 | Self::Method(name)
386 | Self::ModuleConstant(name) => name.as_ref(),
387 }
388 }
389
390 #[must_use]
391 pub const fn item_type(&self) -> &str {
392 match self {
393 Self::EnclosingScope(_) => "enclosing scope",
394 Self::Super(_) => "super class",
395 Self::Class(_) => "class",
396 Self::Method(_) => "method",
397 Self::Module(_) => "module",
398 Self::GlobalConstant(_) => "global constant",
399 Self::ClassConstant(_) => "class constant",
400 Self::ModuleConstant(_) => "module constant",
401 }
402 }
403}
404
405impl fmt::Display for NotDefinedError {
406 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
407 f.write_str(self.item_type())?;
408 f.write_str(" '")?;
409 f.write_str(self.fqdn())?;
410 f.write_str("' not defined")?;
411 Ok(())
412 }
413}
414
415impl error::Error for NotDefinedError {}
416
417impl RubyException for NotDefinedError {
418 fn message(&self) -> Cow<'_, [u8]> {
419 let mut message = String::from(self.item_type());
420 message.push(' ');
421 message.push_str(self.fqdn());
422 message.push_str(" not defined");
423 message.into_bytes().into()
424 }
425
426 fn name(&self) -> Cow<'_, str> {
427 "ScriptError".into()
428 }
429
430 fn vm_backtrace(&self, interp: &mut Artichoke) -> Option<Vec<Vec<u8>>> {
431 let _ = interp;
432 None
433 }
434
435 fn as_mrb_value(&self, interp: &mut Artichoke) -> Option<sys::mrb_value> {
436 let message = interp.try_convert_mut(self.message()).ok()?;
437 let value = interp.new_instance::<ScriptError>(&[message]).ok().flatten()?;
438 Some(value.inner())
439 }
440}
441
442impl From<NotDefinedError> for Error {
443 fn from(exception: NotDefinedError) -> Self {
444 let err: Box<dyn RubyException> = Box::new(exception);
445 Self::from(err)
446 }
447}
448
449#[cfg(test)]
450mod tests {
451 mod fqname {
452 use crate::test::prelude::*;
453
454 #[derive(Debug)]
456 struct Root;
457
458 #[derive(Debug)]
460 struct ModuleUnderRoot;
461
462 #[derive(Debug)]
464 struct ClassUnderRoot;
465
466 #[derive(Debug)]
468 struct ClassUnderModule;
469
470 #[derive(Debug)]
472 struct ModuleUnderClass;
473
474 #[derive(Debug)]
476 struct ClassUnderClass;
477
478 #[test]
479 fn integration_test() {
480 let mut interp = interpreter();
482 let root = module::Spec::new(&mut interp, "A", c"A", None).unwrap();
483 let mod_under_root =
484 module::Spec::new(&mut interp, "B", c"B", Some(EnclosingRubyScope::module(&root))).unwrap();
485 let cls_under_root = class::Spec::new("C", c"C", Some(EnclosingRubyScope::module(&root)), None).unwrap();
486 let cls_under_mod =
487 class::Spec::new("D", c"D", Some(EnclosingRubyScope::module(&mod_under_root)), None).unwrap();
488 let mod_under_cls =
489 module::Spec::new(&mut interp, "E", c"E", Some(EnclosingRubyScope::class(&cls_under_root))).unwrap();
490 let cls_under_cls =
491 class::Spec::new("F", c"F", Some(EnclosingRubyScope::class(&cls_under_root)), None).unwrap();
492 module::Builder::for_spec(&mut interp, &root).define().unwrap();
493 module::Builder::for_spec(&mut interp, &mod_under_root)
494 .define()
495 .unwrap();
496 class::Builder::for_spec(&mut interp, &cls_under_root).define().unwrap();
497 class::Builder::for_spec(&mut interp, &cls_under_mod).define().unwrap();
498 module::Builder::for_spec(&mut interp, &mod_under_cls).define().unwrap();
499 class::Builder::for_spec(&mut interp, &cls_under_cls).define().unwrap();
500 interp.def_module::<Root>(root).unwrap();
501 interp.def_module::<ModuleUnderRoot>(mod_under_root).unwrap();
502 interp.def_class::<ClassUnderRoot>(cls_under_root).unwrap();
503 interp.def_class::<ClassUnderModule>(cls_under_mod).unwrap();
504 interp.def_module::<ModuleUnderClass>(mod_under_cls).unwrap();
505 interp.def_class::<ClassUnderClass>(cls_under_cls).unwrap();
506
507 let root = interp.module_spec::<Root>().unwrap().unwrap();
508 assert_eq!(root.fqname().as_ref(), "A");
509 let mod_under_root = interp.module_spec::<ModuleUnderRoot>().unwrap().unwrap();
510 assert_eq!(mod_under_root.fqname().as_ref(), "A::B");
511 let cls_under_root = interp.class_spec::<ClassUnderRoot>().unwrap().unwrap();
512 assert_eq!(cls_under_root.fqname().as_ref(), "A::C");
513 let cls_under_mod = interp.class_spec::<ClassUnderModule>().unwrap().unwrap();
514 assert_eq!(cls_under_mod.fqname().as_ref(), "A::B::D");
515 let mod_under_cls = interp.module_spec::<ModuleUnderClass>().unwrap().unwrap();
516 assert_eq!(mod_under_cls.fqname().as_ref(), "A::C::E");
517 let cls_under_cls = interp.class_spec::<ClassUnderClass>().unwrap().unwrap();
518 assert_eq!(cls_under_cls.fqname().as_ref(), "A::C::F");
519 }
520 }
521
522 mod functional {
523 use crate::test::prelude::*;
524
525 #[derive(Debug)]
526 struct Class;
527
528 #[derive(Debug)]
529 struct Module;
530
531 extern "C-unwind" fn value(_mrb: *mut sys::mrb_state, slf: sys::mrb_value) -> sys::mrb_value {
532 unsafe {
533 match slf.tt {
534 sys::mrb_vtype::MRB_TT_CLASS => sys::mrb_sys_fixnum_value(8),
535 sys::mrb_vtype::MRB_TT_MODULE => sys::mrb_sys_fixnum_value(27),
536 sys::mrb_vtype::MRB_TT_OBJECT => sys::mrb_sys_fixnum_value(64),
537 _ => sys::mrb_sys_fixnum_value(125),
538 }
539 }
540 }
541
542 #[test]
543 fn define_method() {
544 let mut interp = interpreter();
545 let class = class::Spec::new("DefineMethodTestClass", c"DefineMethodTestClass", None, None).unwrap();
546 class::Builder::for_spec(&mut interp, &class)
547 .add_method("value", value, sys::mrb_args_none())
548 .unwrap()
549 .add_self_method("value", value, sys::mrb_args_none())
550 .unwrap()
551 .define()
552 .unwrap();
553 interp.def_class::<Class>(class).unwrap();
554 let module =
555 module::Spec::new(&mut interp, "DefineMethodTestModule", c"DefineMethodTestModule", None).unwrap();
556 module::Builder::for_spec(&mut interp, &module)
557 .add_method("value", value, sys::mrb_args_none())
558 .unwrap()
559 .add_self_method("value", value, sys::mrb_args_none())
560 .unwrap()
561 .define()
562 .unwrap();
563 interp.def_module::<Module>(module).unwrap();
564
565 interp
566 .eval(b"class DynamicTestClass; include DefineMethodTestModule; extend DefineMethodTestModule; end")
567 .unwrap();
568 interp
569 .eval(b"module DynamicTestModule; extend DefineMethodTestModule; end")
570 .unwrap();
571
572 let result = interp.eval(b"DefineMethodTestClass.new.value").unwrap();
573 let result = result.try_convert_into::<i64>(&interp).unwrap();
574 assert_eq!(result, 64);
575 let result = interp.eval(b"DefineMethodTestClass.value").unwrap();
576 let result = result.try_convert_into::<i64>(&interp).unwrap();
577 assert_eq!(result, 8);
578 let result = interp.eval(b"DefineMethodTestModule.value").unwrap();
579 let result = result.try_convert_into::<i64>(&interp).unwrap();
580 assert_eq!(result, 27);
581 let result = interp.eval(b"DynamicTestClass.new.value").unwrap();
582 let result = result.try_convert_into::<i64>(&interp).unwrap();
583 assert_eq!(result, 64);
584 let result = interp.eval(b"DynamicTestClass.value").unwrap();
585 let result = result.try_convert_into::<i64>(&interp).unwrap();
586 assert_eq!(result, 8);
587 let result = interp.eval(b"DynamicTestModule.value").unwrap();
588 let result = result.try_convert_into::<i64>(&interp).unwrap();
589 assert_eq!(result, 27);
590 }
591 }
592}