use std::fmt;
use crate::{
prelude::*,
ruby,
mixin::MethodFn,
};
mod any;
mod non_null;
mod rosy;
mod ty;
pub(crate) use non_null::NonNullObject;
#[doc(inline)]
pub use self::{
any::AnyObject,
rosy::RosyObject,
ty::Ty,
};
pub unsafe trait Object: Copy
+ Into<AnyObject>
+ AsRef<AnyObject>
+ PartialEq<AnyObject>
+ fmt::Debug
{
#[inline]
fn unique_id() -> Option<u128> {
None
}
#[inline]
unsafe fn from_raw(raw: ruby::VALUE) -> Self {
Self::cast_unchecked(AnyObject::from_raw(raw))
}
#[inline]
fn cast<A: Object>(obj: A) -> Option<Self> {
if A::unique_id() == Self::unique_id() {
unsafe { Some(Self::cast_unchecked(obj)) }
} else {
None
}
}
#[inline]
unsafe fn cast_unchecked(obj: impl Object) -> Self {
let mut result = std::mem::uninitialized::<Self>();
std::ptr::write((&mut result) as *mut Self as *mut _, obj);
result
}
#[inline]
fn into_any_object(self) -> AnyObject { self.into() }
#[inline]
fn as_any_object(&self) -> &AnyObject { self.as_ref() }
#[inline]
fn as_any_slice(&self) -> &[AnyObject] {
std::slice::from_ref(self.as_any_object())
}
#[inline]
fn raw(self) -> ruby::VALUE {
self.as_any_object().raw()
}
#[inline]
unsafe fn as_unchecked<O: Object>(&self) -> &O {
&*(self as *const _ as *const _)
}
#[inline]
unsafe fn into_unchecked<O: Object>(self) -> O {
*self.as_unchecked()
}
#[inline]
fn id(self) -> u64 {
unsafe { ruby::rb_obj_id(self.raw()) as _ }
}
#[inline]
fn ty(self) -> Ty {
self.as_any_object().ty()
}
#[inline]
fn is_ty(self, ty: Ty) -> bool {
crate::util::value_is_ty(self.raw(), ty)
}
#[inline]
fn class(self) -> Class {
unsafe { Class::from_raw(ruby::rb_obj_class(self.raw())) }
}
#[inline]
fn singleton_class(self) -> Class {
unsafe { Class::from_raw(ruby::rb_singleton_class(self.raw())) }
}
#[inline]
fn mark(self) {
crate::gc::mark(self);
}
#[inline]
unsafe fn force_recycle(self) {
crate::gc::force_recycle(self);
}
#[inline]
fn def_singleton_method<N, F>(self, name: N, f: F) -> Result
where
N: Into<SymbolId>,
F: MethodFn
{
self.singleton_class().def_method(name, f)
}
#[inline]
unsafe fn def_singleton_method_unchecked<N, F>(self, name: N, f: F)
where
N: Into<SymbolId>,
F: MethodFn
{
self.singleton_class().def_method_unchecked(name, f);
}
#[inline]
fn call(self, method: impl Into<SymbolId>) -> Result<AnyObject> {
let args: &[AnyObject] = &[];
self.call_with(method, args)
}
#[inline]
unsafe fn call_unchecked(self, method: impl Into<SymbolId>) -> AnyObject {
let args: &[AnyObject] = &[];
self.call_with_unchecked(method, args)
}
#[inline]
fn call_with(
self,
method: impl Into<SymbolId>,
args: &[impl Object]
) -> Result<AnyObject> {
fn call_with(object: AnyObject, method: SymbolId, args: &[AnyObject]) -> Result<AnyObject> {
unsafe { crate::protected_no_panic(|| {
object.call_with_unchecked(method, args)
}) }
}
call_with(self.into(), method.into(), AnyObject::convert_slice(args))
}
#[inline]
unsafe fn call_with_unchecked(
self,
method: impl Into<SymbolId>,
args: &[impl Object]
) -> AnyObject {
AnyObject::from_raw(ruby::rb_funcallv(
self.raw(),
method.into().raw(),
args.len() as _,
args.as_ptr() as _,
))
}
#[inline]
fn call_public(
self,
method: impl Into<SymbolId>,
) -> Result<AnyObject> {
let args: &[AnyObject] = &[];
self.call_public_with(method, args)
}
#[inline]
unsafe fn call_public_unchecked(
self,
method: impl Into<SymbolId>,
) -> AnyObject {
let args: &[AnyObject] = &[];
self.call_public_with_unchecked(method, args)
}
#[inline]
fn call_public_with(
self,
method: impl Into<SymbolId>,
args: &[impl Object]
) -> Result<AnyObject> {
fn call_public_with(object: AnyObject, method: SymbolId, args: &[AnyObject]) -> Result<AnyObject> {
unsafe { crate::protected_no_panic(|| {
object.call_public_with_unchecked(method, args)
}) }
}
let args = AnyObject::convert_slice(args);
call_public_with(self.into(), method.into(), args)
}
#[inline]
unsafe fn call_public_with_unchecked(
self,
method: impl Into<SymbolId>,
args: &[impl Object]
) -> AnyObject {
AnyObject::from_raw(ruby::rb_funcallv_public(
self.raw(),
method.into().raw(),
args.len() as _,
args.as_ptr() as _,
))
}
#[inline]
fn inspect(self) -> String {
unsafe { String::from_raw(ruby::rb_inspect(self.raw())) }
}
#[inline]
fn to_s(self) -> String {
unsafe { String::from_raw(ruby::rb_obj_as_string(self.raw())) }
}
#[inline]
fn is_frozen(self) -> bool {
unsafe { ruby::rb_obj_frozen_p(self.raw()) != 0 }
}
#[inline]
fn freeze(self) {
unsafe { ruby::rb_obj_freeze(self.raw()) };
}
#[inline]
fn is_eql<O: Object>(self, other: &O) -> bool {
let this = self.raw();
let that = other.raw();
unsafe { ruby::rb_eql(this, that) != 0 }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn array_unique_id() {
let expected = !(Ty::Array as u128);
let array_id = Array::<AnyObject>::unique_id().unwrap();
assert_eq!(array_id, expected);
}
#[test]
fn hash_unique_id() {
let expected = !(Ty::Hash as u128);
let hash_id = Hash::<AnyObject, AnyObject>::unique_id().unwrap();
assert_eq!(hash_id, expected);
}
#[test]
fn unique_ids() {
macro_rules! nxn_hash_ids {
($ids:expr => $($t:ty),*) => {
nxn_hash_ids! { do_stuff $ids => $($t),* ; $($t),* }
};
(do_stuff $ids:expr => $t1next:ty ; $($t2s:ty),*) => {
nxn_hash_ids! { expand $ids => $t1next, $($t2s),* }
};
(do_stuff $ids:expr => $t1next:ty, $($t1rest:ty),+ ; $($t2s:ty),*) => {
nxn_hash_ids! { expand $ids => $t1next, $($t2s),* }
nxn_hash_ids! { do_stuff $ids => $($t1rest),* ; $($t2s),* }
};
(expand $ids:expr => $t1:ty, $($t2s:ty),*) => {
$(nxn_hash_ids! { single $ids => $t1, $t2s })*
};
(single $ids:expr => $t1:ty, $t2:ty) => {
$ids.push((stringify!(Hash<$t1, $t2>), Hash::<$t1, $t2>::unique_id()));
};
}
macro_rules! ids {
($($t:ty,)+) => { {
let mut ids = vec![
$(
(stringify!(<$t>), <$t>::unique_id()),
(stringify!(Array<$t>), Array::<$t>::unique_id()),
(stringify!(Array<Array<$t>>), Array::<Array<$t>>::unique_id()),
)+
];
nxn_hash_ids!(ids => $($t),+);
ids
} }
}
let ids: &[(&str, _)] = &ids! {
AnyException,
AnyObject,
Integer,
String,
Symbol,
crate::vm::InstrSeq,
RosyObject<std::string::String>,
};
for a in ids {
for b in ids {
if std::ptr::eq(a, b) {
continue;
}
match (a, b) {
((ty_a, Some(a)), (ty_b, Some(b))) => {
assert_ne!(a, b, "{} and {} have same ID", ty_a, ty_b);
},
(_, _) => {},
}
}
}
}
}