magnus 0.4.4

High level Ruby bindings for Rust.
Documentation
//! Types and functions for working with Ruby modules.

use std::{ffi::CString, fmt, mem::transmute, ops::Deref, os::raw::c_int};

use rb_sys::{
    rb_alias, rb_attr, rb_class_inherited_p, rb_const_get, rb_const_set, rb_define_class_id_under,
    rb_define_method_id, rb_define_module_function, rb_define_module_id_under,
    rb_define_private_method, rb_define_protected_method, rb_include_module, rb_mComparable,
    rb_mEnumerable, rb_mErrno, rb_mFileTest, rb_mGC, rb_mKernel, rb_mMath, rb_mProcess,
    rb_mWaitReadable, rb_mWaitWritable, rb_mod_ancestors, rb_module_new, rb_prepend_module,
    ruby_value_type, VALUE,
};

use crate::{
    class::{Class, RClass},
    debug_assert_value,
    error::{protect, Error},
    exception::{self, ExceptionClass},
    method::Method,
    object::Object,
    r_array::RArray,
    try_convert::TryConvert,
    value::{
        private::{self, ReprValue as _},
        Id, NonZeroValue, ReprValue, Value, QNIL,
    },
};

/// A Value pointer to a RModule struct, Ruby's internal representation of
/// modules.
///
/// See the [`Module`] trait for defining instance methods and nested
/// classes/modules.
/// See the [`Object`] trait for defining singlton methods (aka class methods).
///
/// All [`Value`] methods should be available on this type through [`Deref`],
/// but some may be missed by this documentation.
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct RModule(NonZeroValue);

impl RModule {
    /// Return `Some(RModule)` if `val` is a `RModule`, `None` otherwise.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{eval, RModule};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// assert!(RModule::from_value(eval("Enumerable").unwrap()).is_some());
    /// assert!(RModule::from_value(eval("String").unwrap()).is_none());
    /// assert!(RModule::from_value(eval("nil").unwrap()).is_none());
    /// ```
    #[inline]
    pub fn from_value(val: Value) -> Option<Self> {
        unsafe {
            (val.rb_type() == ruby_value_type::RUBY_T_MODULE)
                .then(|| Self(NonZeroValue::new_unchecked(val)))
        }
    }

    #[inline]
    pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self {
        Self(NonZeroValue::new_unchecked(Value::new(val)))
    }

    /// Create a new anonymous module.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{class, RModule};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// let module = RModule::new();
    /// assert!(module.is_kind_of(class::module()));
    /// ```
    pub fn new() -> Self {
        unsafe { Self::from_rb_value_unchecked(rb_module_new()) }
    }

    /// Define a method in `self`'s scope as a 'module function'. This method
    /// will be visible as a public 'class' method on the module and a private
    /// instance method on any object including the module.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{define_module, eval, function, r_string, RString};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// fn greet() -> RString {
    ///    r_string!("Hello, world!")
    /// }
    ///
    /// let module = define_module("Greeting").unwrap();
    /// module.define_module_function("greet", function!(greet, 0)).unwrap();
    ///
    /// let res = eval::<bool>(r#"Greeting.greet == "Hello, world!""#).unwrap();
    /// assert!(res);
    ///
    /// let res = eval::<bool>(r#"
    ///     include Greeting
    ///     greet == "Hello, world!"
    /// "#).unwrap();
    /// assert!(res);
    /// ```
    pub fn define_module_function<M>(self, name: &str, func: M) -> Result<(), Error>
    where
        M: Method,
    {
        debug_assert_value!(self);
        let name = CString::new(name).unwrap();
        protect(|| {
            unsafe {
                rb_define_module_function(
                    self.as_rb_value(),
                    name.as_ptr(),
                    transmute(func.as_ptr()),
                    M::arity().into(),
                );
            };
            QNIL
        })?;
        Ok(())
    }
}

impl Deref for RModule {
    type Target = Value;

    fn deref(&self) -> &Self::Target {
        self.0.get_ref()
    }
}

impl fmt::Display for RModule {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", unsafe { self.to_s_infallible() })
    }
}

impl fmt::Debug for RModule {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.inspect())
    }
}

impl From<RModule> for Value {
    fn from(val: RModule) -> Self {
        *val
    }
}

impl Object for RModule {}
impl Module for RModule {}

unsafe impl private::ReprValue for RModule {
    fn to_value(self) -> Value {
        *self
    }

    unsafe fn from_value_unchecked(val: Value) -> Self {
        Self(NonZeroValue::new_unchecked(val))
    }
}

impl ReprValue for RModule {}

impl TryConvert for RModule {
    fn try_convert(val: Value) -> Result<Self, Error> {
        Self::from_value(val).ok_or_else(|| {
            Error::new(
                exception::type_error(),
                format!("no implicit conversion of {} into Module", unsafe {
                    val.classname()
                },),
            )
        })
    }
}

/// Functions available on both classes and modules.
pub trait Module: Object + Deref<Target = Value> + Copy {
    /// Define a class in `self`'s scope.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{class, define_module, Module};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// let outer = define_module("Outer").unwrap();
    /// let inner = outer.define_class("Inner", Default::default()).unwrap();
    /// assert!(inner.is_kind_of(class::class()));
    /// ```
    fn define_class<T: Into<Id>>(self, name: T, superclass: RClass) -> Result<RClass, Error> {
        debug_assert_value!(self);
        debug_assert_value!(superclass);
        let id = name.into();
        let superclass = superclass.as_rb_value();
        protect(|| unsafe {
            RClass::from_rb_value_unchecked(rb_define_class_id_under(
                self.as_rb_value(),
                id.as_rb_id(),
                superclass,
            ))
        })
    }

    /// Define a module in `self`'s scope.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{class, define_module, Module};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// let outer = define_module("Outer").unwrap();
    /// let inner = outer.define_module("Inner").unwrap();
    /// assert!(inner.is_kind_of(class::module()));
    /// ```
    fn define_module<T: Into<Id>>(self, name: T) -> Result<RModule, Error> {
        let id = name.into();
        protect(|| unsafe {
            RModule::from_rb_value_unchecked(rb_define_module_id_under(
                self.as_rb_value(),
                id.as_rb_id(),
            ))
        })
    }

    /// Define an exception class in `self`'s scope.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{exception, define_module, Module};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// let outer = define_module("Outer").unwrap();
    /// let inner = outer.define_error("InnerError", Default::default()).unwrap();
    /// assert!(inner.is_inherited(exception::standard_error()));
    /// ```
    fn define_error<T: Into<Id>>(
        self,
        name: T,
        superclass: ExceptionClass,
    ) -> Result<ExceptionClass, Error> {
        self.define_class(name, superclass.as_r_class())
            .map(|c| unsafe { ExceptionClass::from_value_unchecked(*c) })
    }

    /// Include `module` into `self`.
    ///
    /// Effectively makes `module` the superclass of `self`. See also
    /// [`prepend_module`](Module::prepend_module).
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{function, Module, RClass, RModule};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// fn example() -> i64 {
    ///     42
    /// }
    ///
    /// let module = RModule::new();
    /// module.define_method("example", function!(example, 0)).unwrap();
    ///
    /// let class = RClass::new(Default::default()).unwrap();
    /// class.include_module(module);
    ///
    /// let obj = class.new_instance(()).unwrap();
    /// assert_eq!(obj.funcall::<_, _, i64>("example", ()).unwrap(), 42);
    /// ```
    fn include_module(self, module: RModule) -> Result<(), Error> {
        protect(|| unsafe {
            rb_include_module(self.as_rb_value(), module.as_rb_value());
            QNIL
        })?;
        Ok(())
    }

    /// Prepend `self` with `module`.
    ///
    /// Similar to [`include_module`](Module::include_module), but inserts
    /// `module` as if it were a subclass in the inheritance chain.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{call_super, eval, function, Error, Module, RClass, RModule};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// fn super_example() -> i64 {
    ///     40
    /// }
    ///
    /// fn example() -> Result<i64, Error> {
    ///     Ok(call_super::<_, i64>(())? + 2)
    /// }
    ///
    /// let module = RModule::new();
    /// module.define_method("example", function!(example, 0)).unwrap();
    ///
    /// let class: RClass = eval(r#"
    ///     class Example
    ///       def example
    ///         40
    ///       end
    ///     end
    ///     Example
    /// "#).unwrap();
    /// class.prepend_module(module);
    ///
    /// let obj = class.new_instance(()).unwrap();
    /// assert_eq!(obj.funcall::<_, _, i64>("example", ()).unwrap(), 42);
    /// ```
    fn prepend_module(self, module: RModule) -> Result<(), Error> {
        protect(|| unsafe {
            rb_prepend_module(self.as_rb_value(), module.as_rb_value());
            QNIL
        })?;
        Ok(())
    }

    /// Set the value for the constant `name` within `self`'s scope.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{class, eval, Module, RClass, Value};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// class::array().const_set("EXAMPLE", 42).unwrap();
    ///
    /// assert_eq!(eval::<i64>("Array::EXAMPLE").unwrap(), 42);
    /// ```
    fn const_set<T, U>(self, name: T, value: U) -> Result<(), Error>
    where
        T: Into<Id>,
        U: Into<Value>,
    {
        let id = name.into();
        let val = value.into();
        protect(|| unsafe {
            rb_const_set(self.as_rb_value(), id.as_rb_id(), val.as_rb_value());
            QNIL
        })?;
        Ok(())
    }

    /// Get the value for the constant `name` within `self`'s scope.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{class, eval, Module, RClass, Value};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// eval::<Value>("
    ///     class Example
    ///       VALUE = 42
    ///     end
    /// ").unwrap();
    ///
    /// let class = class::object().const_get::<_, RClass>("Example").unwrap();
    /// assert_eq!(class.const_get::<_, i64>("VALUE").unwrap(), 42);
    /// ```
    fn const_get<T, U>(self, name: T) -> Result<U, Error>
    where
        T: Into<Id>,
        U: TryConvert,
    {
        debug_assert_value!(self);
        let id = name.into();
        let res =
            unsafe { protect(|| Value::new(rb_const_get(self.as_rb_value(), id.as_rb_id()))) };
        res.and_then(|v| v.try_convert())
    }

    /// Returns whether or not `self` inherits from `other`.
    ///
    /// Classes including a module are considered to inherit from that module.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{eval, Module, RClass};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// let a = RClass::new(Default::default()).unwrap();
    /// let b = RClass::new(a).unwrap();
    /// assert!(b.is_inherited(a));
    /// assert!(!a.is_inherited(b));
    /// ```
    fn is_inherited<T>(self, other: T) -> bool
    where
        T: Deref<Target = Value> + Module,
    {
        unsafe {
            Value::new(rb_class_inherited_p(
                self.as_rb_value(),
                other.as_rb_value(),
            ))
            .to_bool()
        }
    }

    /// Return the classes and modules `self` inherits, includes, or prepends.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{class, eval, Module};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// let ary = class::string().ancestors();
    ///
    /// let res: bool = eval!("ary == [String, Comparable, Object, Kernel, BasicObject]", ary).unwrap();
    /// assert!(res);
    /// ```
    fn ancestors(self) -> RArray {
        unsafe { RArray::from_rb_value_unchecked(rb_mod_ancestors(self.as_rb_value())) }
    }

    /// Define a method in `self`'s scope.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{class, eval, method, Module};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// fn escape_unicode(s: String) -> String {
    ///     s.escape_unicode().to_string()
    /// }
    ///
    /// class::string().define_method("escape_unicode", method!(escape_unicode, 0)).unwrap();
    ///
    /// let res = eval::<bool>(r#""🤖\etest".escape_unicode == "\\u{1f916}\\u{1b}\\u{74}\\u{65}\\u{73}\\u{74}""#).unwrap();
    /// assert!(res);
    /// ```
    fn define_method<T, M>(self, name: T, func: M) -> Result<(), Error>
    where
        T: Into<Id>,
        M: Method,
    {
        debug_assert_value!(self);
        let id = name.into();
        protect(|| {
            unsafe {
                rb_define_method_id(
                    self.as_rb_value(),
                    id.as_rb_id(),
                    transmute(func.as_ptr()),
                    M::arity().into(),
                );
            };
            QNIL
        })?;
        Ok(())
    }

    /// Define a private method in `self`'s scope.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{class, eval, exception, function, Module, Value};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// fn percent_encode(c: char) -> String {
    ///     if c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.' || c == '~' {
    ///         String::from(c)
    ///     } else {
    ///         format!("%{:X}", c as u32)
    ///     }
    /// }
    ///
    /// class::string().define_private_method("percent_encode_char", function!(percent_encode, 1)).unwrap();
    ///
    /// eval::<Value>(r#"
    ///     class String
    ///       def percent_encode
    ///         chars.map {|c| percent_encode_char(c)}.join("")
    ///       end
    ///     end
    /// "#).unwrap();
    ///
    /// let res = eval::<bool>(r#""foo bar".percent_encode == "foo%20bar""#).unwrap();
    /// assert!(res);
    ///
    /// assert!(eval::<bool>(r#"" ".percent_encode_char(" ")"#).unwrap_err().is_kind_of(exception::no_method_error()));
    /// ```
    fn define_private_method<M>(self, name: &str, func: M) -> Result<(), Error>
    where
        M: Method,
    {
        debug_assert_value!(self);
        let name = CString::new(name).unwrap();
        protect(|| {
            unsafe {
                rb_define_private_method(
                    self.as_rb_value(),
                    name.as_ptr(),
                    transmute(func.as_ptr()),
                    M::arity().into(),
                );
            };
            QNIL
        })?;
        Ok(())
    }

    /// Define a protected method in `self`'s scope.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{class, eval, exception, method, Module, Value};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// fn escape_unicode(s: String) -> String {
    ///     s.escape_unicode().to_string()
    /// }
    ///
    /// fn is_invisible(c: char) -> bool {
    ///     c.is_control() || c.is_whitespace()
    /// }
    ///
    /// class::string().define_method("escape_unicode", method!(escape_unicode, 0)).unwrap();
    /// class::string().define_protected_method("invisible?", method!(is_invisible, 0)).unwrap();
    ///
    /// eval::<Value>(r#"
    ///     class String
    ///       def escape_invisible
    ///         chars.map {|c| c.invisible? ? c.escape_unicode : c}.join("")
    ///       end
    ///     end
    /// "#).unwrap();
    ///
    /// let res: bool = eval!(r#"
    ///     "🤖\tfoo bar".escape_invisible == "🤖\\u{9}foo\\u{20}bar"
    /// "#).unwrap();
    /// assert!(res);
    ///
    /// assert!(eval::<bool>(r#"" ".invisible?"#).unwrap_err().is_kind_of(exception::no_method_error()));
    /// ```
    fn define_protected_method<M>(self, name: &str, func: M) -> Result<(), Error>
    where
        M: Method,
    {
        debug_assert_value!(self);
        let name = CString::new(name).unwrap();
        protect(|| {
            unsafe {
                rb_define_protected_method(
                    self.as_rb_value(),
                    name.as_ptr(),
                    transmute(func.as_ptr()),
                    M::arity().into(),
                );
            };
            QNIL
        })?;
        Ok(())
    }

    /// Define public accessor methods for the attribute `name`.
    ///
    /// `name` should be **without** the preceding `@`.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{Attr, Module, RClass, Value};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// let class = RClass::new(Default::default()).unwrap();
    /// class.define_attr("example", Attr::ReadWrite).unwrap();
    ///
    /// let obj = class.new_instance(()).unwrap();
    /// let _: Value = obj.funcall("example=", (42,)).unwrap();
    /// assert_eq!(obj.funcall::<_, _, i64>("example", ()).unwrap(), 42);
    /// ```
    fn define_attr<T>(self, name: T, rw: Attr) -> Result<(), Error>
    where
        T: Into<Id>,
    {
        let id = name.into();
        protect(|| unsafe {
            rb_attr(
                self.as_rb_value(),
                id.as_rb_id(),
                rw.is_read() as c_int,
                rw.is_write() as c_int,
                0,
            );
            QNIL
        })?;
        Ok(())
    }

    /// Alias the method `src` of `self` as `dst`.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{function, Module, RClass};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// fn example() -> i64 {
    ///     42
    /// }
    ///
    /// let class = RClass::new(Default::default()).unwrap();
    /// class.define_method("example", function!(example, 0)).unwrap();
    /// class.define_alias("test", "example").unwrap();
    ///
    /// let obj = class.new_instance(()).unwrap();
    /// assert_eq!(obj.funcall::<_, _, i64>("test", ()).unwrap(), 42);
    /// ```
    fn define_alias<T, U>(self, dst: T, src: U) -> Result<(), Error>
    where
        T: Into<Id>,
        U: Into<Id>,
    {
        let d_id = dst.into();
        let s_id = src.into();
        protect(|| unsafe {
            rb_alias(self.as_rb_value(), d_id.as_rb_id(), s_id.as_rb_id());
            QNIL
        })?;
        Ok(())
    }
}

/// Argument for [`define_attr`](Module::define_attr).
#[derive(Clone, Copy, Debug)]
pub enum Attr {
    /// Define a reader method like `name`.
    Read,
    /// Define a writer method like `name=`.
    Write,
    /// Define both reader and writer methods like `name` and `name=`.
    ReadWrite,
}

impl Attr {
    fn is_read(self) -> bool {
        match self {
            Attr::Read | Attr::ReadWrite => true,
            Attr::Write => false,
        }
    }

    fn is_write(self) -> bool {
        match self {
            Attr::Write | Attr::ReadWrite => true,
            Attr::Read => false,
        }
    }
}

/// Return Ruby's `Comparable` module.
#[inline]
pub fn comparable() -> RModule {
    unsafe { RModule::from_rb_value_unchecked(rb_mComparable) }
}

/// Return Ruby's `Enumerable` module.
#[inline]
pub fn enumerable() -> RModule {
    unsafe { RModule::from_rb_value_unchecked(rb_mEnumerable) }
}

/// Return Ruby's `Errno` module.
#[inline]
pub fn errno() -> RModule {
    unsafe { RModule::from_rb_value_unchecked(rb_mErrno) }
}

/// Return Ruby's `FileTest` module.
#[inline]
pub fn file_test() -> RModule {
    unsafe { RModule::from_rb_value_unchecked(rb_mFileTest) }
}

/// Return Ruby's `GC` module.
#[inline]
pub fn gc() -> RModule {
    unsafe { RModule::from_rb_value_unchecked(rb_mGC) }
}

/// Return Ruby's `Kernel` module.
#[inline]
pub fn kernel() -> RModule {
    unsafe { RModule::from_rb_value_unchecked(rb_mKernel) }
}

/// Return Ruby's `Math` module.
#[inline]
pub fn math() -> RModule {
    unsafe { RModule::from_rb_value_unchecked(rb_mMath) }
}

/// Return Ruby's `Process` module.
#[inline]
pub fn process() -> RModule {
    unsafe { RModule::from_rb_value_unchecked(rb_mProcess) }
}

/// Return Ruby's `IO::WaitReadable` module.
#[inline]
pub fn wait_readable() -> RModule {
    unsafe { RModule::from_rb_value_unchecked(rb_mWaitReadable) }
}

/// Return Ruby's `IO::WaitWritable` module.
#[inline]
pub fn wait_writable() -> RModule {
    unsafe { RModule::from_rb_value_unchecked(rb_mWaitWritable) }
}