artichoke_backend/
method.rs

1use std::borrow::Cow;
2use std::ffi::{CStr, CString};
3use std::hash::{Hash, Hasher};
4
5use crate::Artichoke;
6use crate::def::{ConstantNameError, Method, NotDefinedError};
7use crate::sys;
8
9#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
10pub enum Type {
11    Class,
12    Global,
13    Instance,
14    Module,
15}
16
17#[derive(Debug, Clone)]
18pub struct Spec {
19    name: Cow<'static, str>,
20    cstring: Box<CStr>,
21    method_type: Type,
22    method: Method,
23    args: sys::mrb_aspec,
24}
25
26impl Spec {
27    pub fn new<T>(
28        method_type: Type,
29        method_name: T,
30        method: Method,
31        args: sys::mrb_aspec,
32    ) -> Result<Self, ConstantNameError>
33    where
34        T: Into<Cow<'static, str>>,
35    {
36        let name = method_name.into();
37        if let Ok(cstring) = CString::new(name.as_ref()) {
38            Ok(Self {
39                name,
40                cstring: cstring.into_boxed_c_str(),
41                method_type,
42                method,
43                args,
44            })
45        } else {
46            Err(name.into())
47        }
48    }
49
50    #[must_use]
51    pub const fn method_type(&self) -> &Type {
52        &self.method_type
53    }
54
55    #[must_use]
56    pub fn method(&self) -> Method {
57        self.method
58    }
59
60    #[must_use]
61    pub fn name(&self) -> Cow<'static, str> {
62        match &self.name {
63            Cow::Borrowed(name) => Cow::Borrowed(name),
64            Cow::Owned(name) => name.clone().into(),
65        }
66    }
67
68    #[must_use]
69    pub fn name_c_str(&self) -> &CStr {
70        self.cstring.as_ref()
71    }
72
73    /// Define this method on the class-like pointed to by `into`.
74    ///
75    /// # Safety
76    ///
77    /// This method requires that `into` is non-null and points to a valid
78    /// [`sys::RClass`].
79    ///
80    /// This method requires that the [`sys::mrb_state`] has a valid `top_self`
81    /// object.
82    pub unsafe fn define(&self, interp: &mut Artichoke, into: &mut sys::RClass) -> Result<(), NotDefinedError> {
83        match self.method_type {
84            Type::Class => {
85                // SAFETY: `interp.with_ffi_boundary` guarantees that `mrb` is
86                // non-NULL and initialized.
87                unsafe {
88                    interp.with_ffi_boundary(|mrb| {
89                        sys::mrb_define_class_method(
90                            mrb,
91                            into,
92                            self.name_c_str().as_ptr(),
93                            Some(self.method),
94                            self.args,
95                        );
96                    })
97                }
98            }
99            Type::Global => {
100                // SAFETY: `interp.with_ffi_boundary` guarantees that `mrb` is
101                // non-NULL and initialized. Initialized interpreters can safely
102                // be dereferenced to get the top-level object.
103                unsafe {
104                    interp.with_ffi_boundary(|mrb| {
105                        sys::mrb_define_singleton_method(
106                            mrb,
107                            (*mrb).top_self,
108                            self.name_c_str().as_ptr(),
109                            Some(self.method),
110                            self.args,
111                        );
112                    })
113                }
114            }
115            Type::Instance => {
116                // SAFETY: `interp.with_ffi_boundary` guarantees that `mrb` is
117                // non-NULL and initialized.
118                unsafe {
119                    interp.with_ffi_boundary(|mrb| {
120                        sys::mrb_define_method(mrb, into, self.name_c_str().as_ptr(), Some(self.method), self.args);
121                    })
122                }
123            }
124            Type::Module => {
125                // SAFETY: `interp.with_ffi_boundary` guarantees that `mrb` is
126                // non-NULL and initialized.
127                unsafe {
128                    interp.with_ffi_boundary(|mrb| {
129                        sys::mrb_define_module_function(
130                            mrb,
131                            into,
132                            self.name_c_str().as_ptr(),
133                            Some(self.method),
134                            self.args,
135                        );
136                    })
137                }
138            }
139        }
140        .map_err(|_| NotDefinedError::method(self.name()))
141    }
142}
143
144impl Eq for Spec {}
145
146impl PartialEq for Spec {
147    fn eq(&self, other: &Self) -> bool {
148        self.method_type == other.method_type && self.name == other.name
149    }
150}
151
152impl Hash for Spec {
153    fn hash<H: Hasher>(&self, state: &mut H) {
154        self.name.hash(state);
155        self.method_type.hash(state);
156    }
157}