use std::{borrow::Cow, ffi::CStr, fmt, mem::transmute, os::raw::c_int};
#[cfg(ruby_gte_3_1)]
use rb_sys::rb_cRefinement;
use rb_sys::{
self, rb_alloc_func_t, rb_cArray, rb_cBasicObject, rb_cBinding, rb_cClass, rb_cComplex,
rb_cDir, rb_cEncoding, rb_cEnumerator, rb_cFalseClass, rb_cFile, rb_cFloat, rb_cHash, rb_cIO,
rb_cInteger, rb_cMatch, rb_cMethod, rb_cModule, rb_cNameErrorMesg, rb_cNilClass, rb_cNumeric,
rb_cObject, rb_cProc, rb_cRandom, rb_cRange, rb_cRational, rb_cRegexp, rb_cStat, rb_cString,
rb_cStruct, rb_cSymbol, rb_cThread, rb_cTime, rb_cTrueClass, rb_cUnboundMethod, rb_class2name,
rb_class_new, rb_class_new_instance_kw, rb_class_superclass, rb_define_alloc_func,
rb_get_alloc_func, rb_obj_alloc, rb_undef_alloc_func, ruby_value_type, VALUE,
};
use crate::{
error::{protect, Error},
into_value::{kw_splat, ArgList, IntoValue},
module::Module,
object::Object,
try_convert::TryConvert,
typed_data::TypedData,
value::{
private::{self, ReprValue as _},
NonZeroValue, ReprValue, Value,
},
Ruby,
};
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct RClass(NonZeroValue);
impl RClass {
#[inline]
pub fn from_value(val: Value) -> Option<Self> {
unsafe {
(val.rb_type() == ruby_value_type::RUBY_T_CLASS)
.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)))
}
}
impl fmt::Display for RClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", unsafe { self.to_s_infallible() })
}
}
impl fmt::Debug for RClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.inspect())
}
}
impl IntoValue for RClass {
#[inline]
fn into_value_with(self, _: &Ruby) -> Value {
self.0.get()
}
}
impl Object for RClass {}
impl Module for RClass {}
unsafe impl private::ReprValue for RClass {}
impl ReprValue for RClass {}
impl TryConvert for RClass {
fn try_convert(val: Value) -> Result<Self, Error> {
match Self::from_value(val) {
Some(v) => Ok(v),
None => Err(Error::new(
Ruby::get_with(val).exception_type_error(),
format!("no implicit conversion of {} into Class", unsafe {
val.classname()
},),
)),
}
}
}
pub trait Class: Module {
type Instance;
fn new(superclass: Self) -> Result<Self, Error>;
fn new_instance<T>(self, args: T) -> Result<Self::Instance, Error>
where
T: ArgList;
fn obj_alloc(self) -> Result<Self::Instance, Error>;
fn superclass(self) -> Result<RClass, Error> {
protect(|| unsafe {
RClass::from_rb_value_unchecked(rb_class_superclass(self.as_rb_value()))
})
}
unsafe fn name(&self) -> Cow<'_, str> {
let ptr = rb_class2name(self.as_rb_value());
let cstr = CStr::from_ptr(ptr);
cstr.to_string_lossy()
}
fn as_r_class(self) -> RClass {
RClass::from_value(self.as_value()).unwrap()
}
fn define_alloc_func<T>(self)
where
T: Default + TypedData,
{
extern "C" fn allocate<T: Default + TypedData>(class: RClass) -> Value {
Ruby::get_with(class)
.obj_wrap_as(T::default(), class)
.as_value()
}
let class = T::class(&Ruby::get_with(self));
assert!(
class.equal(self).unwrap_or(false),
"{} does not match {}",
self.as_value(),
class
);
unsafe {
rb_define_alloc_func(
self.as_rb_value(),
Some(transmute(allocate::<T> as extern "C" fn(RClass) -> Value)),
)
}
}
fn undef_default_alloc_func(self) {
static INIT: std::sync::Once = std::sync::Once::new();
static mut RB_CLASS_ALLOCATE_INSTANCE: rb_alloc_func_t = None;
let rb_class_allocate_instance = unsafe {
INIT.call_once(|| {
RB_CLASS_ALLOCATE_INSTANCE =
rb_get_alloc_func(Ruby::get_unchecked().class_object().as_rb_value());
});
RB_CLASS_ALLOCATE_INSTANCE
};
unsafe {
let current_alloc = rb_get_alloc_func(self.as_rb_value());
if let (Some(actual), Some(default)) = (current_alloc, rb_class_allocate_instance) {
if std::ptr::eq(actual as *const (), default as *const ()) {
rb_undef_alloc_func(self.as_rb_value());
}
}
}
}
}
impl Class for RClass {
type Instance = Value;
fn new(superclass: Self) -> Result<Self, Error> {
debug_assert_value!(superclass);
let superclass = superclass.as_rb_value();
protect(|| unsafe { Self::from_rb_value_unchecked(rb_class_new(superclass)) })
}
fn new_instance<T>(self, args: T) -> Result<Self::Instance, Error>
where
T: ArgList,
{
let kw_splat = kw_splat(&args);
let args = args.into_arg_list_with(&Ruby::get_with(self));
let slice = args.as_ref();
unsafe {
protect(|| {
Value::new(rb_class_new_instance_kw(
slice.len() as c_int,
slice.as_ptr() as *const VALUE,
self.as_rb_value(),
kw_splat as c_int,
))
})
}
}
fn obj_alloc(self) -> Result<Self::Instance, Error> {
unsafe { protect(|| Value::new(rb_obj_alloc(self.as_rb_value()))) }
}
fn as_r_class(self) -> RClass {
self
}
}
impl Ruby {
#[inline]
pub fn class_array(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cArray) }
}
#[inline]
pub fn class_basic_object(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cBasicObject) }
}
#[inline]
pub fn class_binding(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cBinding) }
}
#[inline]
pub fn class_class(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cClass) }
}
#[inline]
pub fn class_complex(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cComplex) }
}
#[inline]
pub fn class_dir(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cDir) }
}
#[inline]
pub fn class_encoding(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cEncoding) }
}
#[inline]
pub fn class_enumerator(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cEnumerator) }
}
#[inline]
pub fn class_false_class(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cFalseClass) }
}
#[inline]
pub fn class_file(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cFile) }
}
#[inline]
pub fn class_float(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cFloat) }
}
#[inline]
pub fn class_hash(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cHash) }
}
#[inline]
pub fn class_io(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cIO) }
}
#[inline]
pub fn class_integer(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cInteger) }
}
#[inline]
pub fn class_match(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cMatch) }
}
#[inline]
pub fn class_method(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cMethod) }
}
#[inline]
pub fn class_module(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cModule) }
}
#[inline]
pub fn class_name_error_mesg(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cNameErrorMesg) }
}
#[inline]
pub fn class_nil_class(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cNilClass) }
}
#[inline]
pub fn class_numeric(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cNumeric) }
}
#[inline]
pub fn class_object(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cObject) }
}
#[inline]
pub fn class_proc(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cProc) }
}
#[inline]
pub fn class_random(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cRandom) }
}
#[inline]
pub fn class_range(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cRange) }
}
#[inline]
pub fn class_rational(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cRational) }
}
#[cfg(any(ruby_gte_3_1, docsrs))]
#[cfg_attr(docsrs, doc(cfg(ruby_gte_3_1)))]
#[inline]
pub fn class_refinement(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cRefinement) }
}
#[inline]
pub fn class_regexp(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cRegexp) }
}
#[inline]
pub fn class_stat(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cStat) }
}
#[inline]
pub fn class_string(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cString) }
}
#[inline]
pub fn class_struct(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cStruct) }
}
#[inline]
pub fn class_symbol(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cSymbol) }
}
#[inline]
pub fn class_thread(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cThread) }
}
#[inline]
pub fn class_time(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cTime) }
}
#[inline]
pub fn class_true_class(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cTrueClass) }
}
#[inline]
pub fn class_unbound_method(&self) -> RClass {
unsafe { RClass::from_rb_value_unchecked(rb_cUnboundMethod) }
}
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_array` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn array() -> RClass {
get_ruby!().class_array()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_basic_object` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn basic_object() -> RClass {
get_ruby!().class_basic_object()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_binding` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn binding() -> RClass {
get_ruby!().class_binding()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_class` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn class() -> RClass {
get_ruby!().class_class()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_complex` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn complex() -> RClass {
get_ruby!().class_complex()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_dir` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn dir() -> RClass {
get_ruby!().class_dir()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_encoding` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn encoding() -> RClass {
get_ruby!().class_encoding()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_enumerator` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn enumerator() -> RClass {
get_ruby!().class_enumerator()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_false_class` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn false_class() -> RClass {
get_ruby!().class_false_class()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_file` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn file() -> RClass {
get_ruby!().class_file()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_float` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn float() -> RClass {
get_ruby!().class_float()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_hash` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn hash() -> RClass {
get_ruby!().class_hash()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_io` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn io() -> RClass {
get_ruby!().class_io()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_integer` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn integer() -> RClass {
get_ruby!().class_integer()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_match` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn match_class() -> RClass {
get_ruby!().class_match()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_method` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn method() -> RClass {
get_ruby!().class_method()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_module` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn module() -> RClass {
get_ruby!().class_module()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_name_error_mesg` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn name_error_mesg() -> RClass {
get_ruby!().class_name_error_mesg()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_nil_class` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn nil_class() -> RClass {
get_ruby!().class_nil_class()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_numeric` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn numeric() -> RClass {
get_ruby!().class_numeric()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_object` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn object() -> RClass {
get_ruby!().class_object()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_proc` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn proc() -> RClass {
get_ruby!().class_proc()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_random` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn random() -> RClass {
get_ruby!().class_random()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_range` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn range() -> RClass {
get_ruby!().class_range()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_rational` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn rational() -> RClass {
get_ruby!().class_rational()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_refinement` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[cfg(any(ruby_gte_3_1, docsrs))]
#[cfg_attr(docsrs, doc(cfg(ruby_gte_3_1)))]
#[inline]
pub fn refinement() -> RClass {
get_ruby!().class_refinement()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_regexp` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn regexp() -> RClass {
get_ruby!().class_regexp()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_stat` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn stat() -> RClass {
get_ruby!().class_stat()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_string` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn string() -> RClass {
get_ruby!().class_string()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_struct` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn struct_class() -> RClass {
get_ruby!().class_struct()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_symbol` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn symbol() -> RClass {
get_ruby!().class_symbol()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_thread` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn thread() -> RClass {
get_ruby!().class_thread()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_time` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn time() -> RClass {
get_ruby!().class_time()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_true_class` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn true_class() -> RClass {
get_ruby!().class_true_class()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::class_unbound_method` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn unbound_method() -> RClass {
get_ruby!().class_unbound_method()
}