use std::{any::Any, borrow::Cow, ffi::CString, fmt, mem::transmute, os::raw::c_int};
use rb_sys::{
rb_bug, rb_ensure, rb_errinfo, rb_exc_fatal, rb_exc_raise, rb_iter_break_value, rb_jump_tag,
rb_protect, rb_set_errinfo, rb_warning, ruby_special_consts, VALUE,
};
use crate::{
class::Class,
exception::Exception,
into_value::IntoValue,
module::Module,
value::{private::ReprValue as _, ReprValue, Value},
ExceptionClass, Ruby,
};
#[derive(Debug)]
pub enum RubyUnavailableError {
GvlUnlocked,
NonRubyThread,
}
impl fmt::Display for RubyUnavailableError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NonRubyThread => write!(f, "Current thread is not a Ruby thread."),
Self::GvlUnlocked => write!(f, "GVL is not locked."),
}
}
}
impl std::error::Error for RubyUnavailableError {}
impl Ruby {
pub fn iter_break_value<T>(&self, val: T) -> Error
where
T: IntoValue,
{
let val = self.into_value(val);
protect(|| {
unsafe { rb_iter_break_value(val.as_rb_value()) };
#[allow(unreachable_code)]
self.qnil()
})
.unwrap_err()
}
pub fn warning(&self, s: &str) {
let s = CString::new(s).unwrap();
unsafe { rb_warning(s.as_ptr()) };
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone)]
pub enum ErrorType {
Jump(Tag),
Error(ExceptionClass, Cow<'static, str>),
Exception(Exception),
}
#[derive(Debug, Clone)]
pub struct Error(ErrorType);
impl Error {
pub fn new<T>(class: ExceptionClass, msg: T) -> Self
where
T: Into<Cow<'static, str>>,
{
Self(ErrorType::Error(class, msg.into()))
}
pub(crate) fn from_tag(tag: Tag) -> Self {
Self(ErrorType::Jump(tag))
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::iter_break_value` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn iter_break<T>(val: T) -> Self
where
T: IntoValue,
{
get_ruby!().iter_break_value(val)
}
pub fn is_kind_of<T>(&self, class: T) -> bool
where
T: ReprValue + Module,
{
match self.0 {
ErrorType::Jump(_) => false,
ErrorType::Error(c, _) => c.is_inherited(class),
ErrorType::Exception(e) => e.is_kind_of(class),
}
}
fn exception(self) -> Exception {
let handle = unsafe { Ruby::get_unchecked() };
match self.0 {
ErrorType::Jump(_) => panic!("Error::exception() called on {}", self),
ErrorType::Error(class, msg) => {
match class.new_instance((handle.str_new(msg.as_ref()),)) {
Ok(e) | Err(Error(ErrorType::Exception(e))) => e,
Err(err) => unreachable!("*very* unexpected error: {}", err),
}
}
ErrorType::Exception(e) => e,
}
}
pub fn error_type(&self) -> &ErrorType {
&self.0
}
pub fn value(&self) -> Option<Value> {
match self.0 {
ErrorType::Jump(_) => None,
ErrorType::Error(c, _) => Some(c.as_value()),
ErrorType::Exception(e) => Some(e.as_value()),
}
}
pub(crate) fn from_panic(e: Box<dyn Any + Send + 'static>) -> Self {
let msg = if let Some(&m) = e.downcast_ref::<&'static str>() {
m.into()
} else if let Some(m) = e.downcast_ref::<String>() {
m.clone().into()
} else {
"panic".into()
};
Self(ErrorType::Error(
unsafe { Ruby::get_unchecked().exception_fatal() },
msg,
))
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
ErrorType::Jump(s) => s.fmt(f),
ErrorType::Error(e, m) => write!(f, "{}: {}", e, m),
ErrorType::Exception(e) => e.fmt(f),
}
}
}
impl From<Exception> for Error {
fn from(val: Exception) -> Self {
Self(ErrorType::Exception(val))
}
}
pub trait IntoError {
fn into_error(self, ruby: &Ruby) -> Error;
}
impl IntoError for Error {
#[inline]
fn into_error(self, _: &Ruby) -> Error {
self
}
}
#[derive(Clone)]
pub struct OpaqueError(ErrorType);
unsafe impl Send for OpaqueError {}
unsafe impl Sync for OpaqueError {}
impl OpaqueError {
#[allow(unused_variables)]
pub fn into_error_with(this: Self, handle: &Ruby) -> Error {
Error(this.0)
}
}
impl From<Error> for OpaqueError {
fn from(err: Error) -> Self {
Self(err.0)
}
}
impl IntoError for OpaqueError {
#[inline]
fn into_error(self, _: &Ruby) -> Error {
Error(self.0)
}
}
#[derive(Debug, Clone, Copy)]
#[repr(i32)]
pub enum Tag {
Return = 1,
Break = 2,
Next = 3,
Retry = 4,
Redo = 5,
Raise = 6,
Throw = 7,
Fatal = 8,
}
impl Tag {
fn resume(self) -> ! {
unsafe { rb_jump_tag(self as c_int) };
}
}
impl fmt::Display for Tag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Return => write!(f, "Return"),
Self::Break => write!(f, "Break"),
Self::Next => write!(f, "Next"),
Self::Retry => write!(f, "Retry"),
Self::Redo => write!(f, "Redo"),
Self::Raise => write!(f, "Raise"),
Self::Throw => write!(f, "Throw"),
Self::Fatal => write!(f, "Fatal"),
}
}
}
pub(crate) fn protect<F, T>(func: F) -> Result<T>
where
F: FnOnce() -> T,
T: ReprValue,
{
unsafe extern "C" fn call<F, T>(arg: VALUE) -> VALUE
where
F: FnOnce() -> T,
T: ReprValue,
{
let closure = (*(arg as *mut Option<F>)).take().unwrap();
(closure)().as_rb_value()
}
let mut state = 0;
let result = unsafe {
let mut some_func = Some(func);
let closure = &mut some_func as *mut Option<F> as VALUE;
rb_protect(Some(call::<F, T>), closure, &mut state as *mut c_int)
};
match state {
0 => unsafe { Ok(T::from_value_unchecked(Value::new(result))) },
6 => unsafe {
let ex = Exception::from_rb_value_unchecked(rb_errinfo());
rb_set_errinfo(Ruby::get_unchecked().qnil().as_rb_value());
Err(ex.into())
},
other => Err(Error::from_tag(unsafe { transmute(other) })),
}
}
pub(crate) fn ensure<F1, F2, T>(func: F1, ensure: F2) -> T
where
F1: FnOnce() -> T,
F2: FnOnce(),
T: ReprValue,
{
unsafe extern "C" fn call_func<F1, T>(arg: VALUE) -> VALUE
where
F1: FnOnce() -> T,
T: ReprValue,
{
let closure = (*(arg as *mut Option<F1>)).take().unwrap();
(closure)().as_rb_value()
}
unsafe extern "C" fn call_ensure<F2>(arg: VALUE) -> VALUE
where
F2: FnOnce(),
{
let closure = (*(arg as *mut Option<F2>)).take().unwrap();
(closure)();
ruby_special_consts::RUBY_Qnil as VALUE
}
let result = unsafe {
let call_func_ptr = call_func::<F1, T> as unsafe extern "C" fn(VALUE) -> VALUE;
let mut some_func = Some(func);
let func_closure = &mut some_func as *mut Option<F1> as VALUE;
let call_ensure_ptr = call_ensure::<F2> as unsafe extern "C" fn(VALUE) -> VALUE;
let mut some_ensure = Some(ensure);
let ensure_closure = &mut some_ensure as *mut Option<F2> as VALUE;
rb_ensure(
Some(call_func_ptr),
func_closure,
Some(call_ensure_ptr),
ensure_closure,
)
};
unsafe { T::from_value_unchecked(Value::new(result)) }
}
pub(crate) fn raise(e: Error) -> ! {
match e.0 {
ErrorType::Jump(tag) => tag.resume(),
ErrorType::Error(class, _)
if class.as_rb_value()
== unsafe { Ruby::get_unchecked().exception_fatal().as_rb_value() } =>
{
unsafe { rb_exc_fatal(e.exception().as_rb_value()) }
}
_ => {
unsafe { rb_exc_raise(e.exception().as_rb_value()) }
}
};
}
pub(crate) fn bug_from_panic(e: Box<dyn Any + Send + 'static>, or: &str) -> ! {
let msg: Cow<'_, str> = if let Some(&m) = e.downcast_ref::<&'static str>() {
m.into()
} else if let Some(m) = e.downcast_ref::<String>() {
m.clone().into()
} else {
or.into()
};
bug(&msg)
}
pub fn bug(s: &str) -> ! {
let s = CString::new(s).unwrap_or_else(|_| CString::new("panic").unwrap());
unsafe { rb_bug(s.as_ptr()) };
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::warning` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn warning(s: &str) {
get_ruby!().warning(s)
}