diff --git a/src/block.rs b/src/block.rs index 6280a110..09a1eb37 100644 --- a/src/block.rs +++ b/src/block.rs @@ -301,11 +301,7 @@ impl TryConvert for Proc { /// /// This effectivly makes the closure's lifetime managed by Ruby. It will be /// dropped when the returned `Value` is garbage collected. -fn wrap_closure<F, R>(func: F) -> (*mut F, Value) -where - F: FnMut(&[Value], Option<Proc>) -> R, - R: BlockReturn, -{ +pub(crate) fn wrap_closure<F>(func: F) -> (*mut F, Value) { struct Closure(); impl DataTypeFunctions for Closure {} let data_type = memoize!(DataType: { diff --git a/src/lib.rs b/src/lib.rs index 576a8b19..5992c867 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -693,12 +693,9 @@ // * `rb_fd_term`: // * `rb_fd_zero`: // * `rb_feature_provided`: -// * `rb_fiber_alive_p`: -// * `rb_fiber_current`: -// * `rb_fiber_new`: -// * `rb_fiber_raise`: -// * `rb_fiber_resume`: // * `rb_fiber_resume_kw`: +// * `rb_fiber_transfer_kw`: +// * `rb_fiber_yield_kw`: // * `rb_fiber_scheduler_address_resolve`: // * `rb_fiber_scheduler_block`: // * `rb_fiber_scheduler_close`: @@ -723,10 +720,6 @@ // * `rb_fiber_scheduler_process_wait`: // * `rb_fiber_scheduler_set`: // * `rb_fiber_scheduler_unblock`: -// * `rb_fiber_transfer`: -// * `rb_fiber_transfer_kw`: -// * `rb_fiber_yield`: -// * `rb_fiber_yield_kw`: //! * `rb_filesystem_encindex`: [`encoding::Index::filesystem`]. //! * `rb_filesystem_encoding`: //! [`RbEncoding::filesystem`](encoding::RbEncoding::filesystem). @@ -796,7 +789,6 @@ // * `rb_funcall_passing_block`: // * `rb_funcall_passing_block_kw`: //! * `rb_funcall_with_block`: [`Value::funcall_with_block`]. -// * `rb_funcall_with_block_kw`: // * `rb_f_abort`: // * `rb_f_exec`: // * `rb_f_exit`: @@ -855,7 +847,6 @@ //! # `rb_h` //! // * `rb_Hash`: -// * `rb_hash`: //! * `rb_hash_aref`: [`RHash::aref`]. //! * `rb_hash_aset`: [`RHash::aset`]. // * `rb_hash_bulk_insert`: @@ -928,7 +919,6 @@ // * `rb_io_check_readable`: // * `rb_io_check_writable`: // * `rb_io_close`: -// * `rb_io_descriptor`: // * `rb_io_eof`: // * `rb_io_extract_encoding_option`: // * `rb_io_extract_modeenc`: @@ -1087,13 +1077,8 @@ // * `rb_mod_sys_fail`: // * `rb_mod_sys_fail_str`: // * `rb_must_asciicompat`: -// * `rb_mutex_lock`: -// * `rb_mutex_locked_p`: -// * `rb_mutex_new`: // * `rb_mutex_sleep`: // * `rb_mutex_synchronize`: -// * `rb_mutex_trylock`: -// * `rb_mutex_unlock`: //! //! ## `rb_n` //! @@ -1183,9 +1168,7 @@ // * `rb_obj_instance_eval`: // * `rb_obj_instance_exec`: // * `rb_obj_instance_variables`: -// * `rb_obj_is_fiber`: // * `rb_obj_is_instance_of`: -//! * `rb_obj_is_kind_of`: [`Value::is_kind_of`]. // * `rb_obj_is_method`: //! * `rb_obj_is_proc`: [`Proc::from_value`](block::Proc::from_value). // * `rb_obj_method`: @@ -1530,7 +1513,6 @@ // * `rb_thread_current`: // * `rb_thread_fd_close`: // * `rb_thread_fd_select`: -// * `rb_thread_fd_writable`: // * `rb_thread_interrupted`: // * `rb_thread_kill`: // * `rb_thread_local_aref`: @@ -1544,7 +1526,6 @@ // * `rb_thread_sleep_deadly`: // * `rb_thread_sleep_forever`: // * `rb_thread_stop`: -// * `rb_thread_wait_fd`: // * `rb_thread_wait_for`: // * `rb_thread_wakeup`: // * `rb_thread_wakeup_alive`: @@ -1821,15 +1802,20 @@ mod object; mod r_array; mod r_bignum; mod r_complex; +#[cfg(ruby_gt_3_0)] +pub mod r_fiber; mod r_file; mod r_float; pub mod r_hash; +pub mod r_io; mod r_match; +pub mod r_mutex; mod r_object; mod r_rational; mod r_regexp; pub mod r_string; pub mod r_struct; +pub mod r_thread; pub mod r_typed_data; mod range; #[cfg(feature = "rb-sys-interop")] diff --git a/src/object.rs b/src/object.rs index a5a2cb1a..e4c89167 100644 --- a/src/object.rs +++ b/src/object.rs @@ -1,7 +1,8 @@ use std::{ffi::CString, mem::transmute, ops::Deref}; use crate::ruby_sys::{ - rb_define_singleton_method, rb_extend_object, rb_ivar_get, rb_ivar_set, rb_singleton_class, + rb_define_singleton_method, rb_extend_object, rb_hash, rb_ivar_get, rb_ivar_set, + rb_singleton_class, }; use crate::{ @@ -16,6 +17,13 @@ use crate::{ /// Functions available all non-immediate values. pub trait Object: Deref<Target = Value> + Copy { + /// Call `obj.hash` + fn hash(self) -> Result<i64, Error> { + debug_assert_value!(self); + let hash = protect(|| Value::new(unsafe { rb_hash(self.as_rb_value()) }))?; + hash.try_convert() + } + /// Define a singleton method in `self`'s scope. /// /// Singleton methods defined on a class are Ruby's method for implementing diff --git a/src/r_fiber.rs b/src/r_fiber.rs new file mode 100644 index 00000000..56d92864 --- /dev/null +++ b/src/r_fiber.rs @@ -0,0 +1,408 @@ +//! + +use { + crate::{ + block::{wrap_closure, Proc}, + define_class, + error::{protect, Error}, + exception::type_error, + float::Float, + method::{Block, BlockReturn}, + object::Object, + r_string::RString, + ruby_sys::{ + rb_fiber_alive_p, rb_fiber_current, rb_fiber_new, rb_fiber_raise, rb_fiber_resume, + rb_fiber_scheduler_block, rb_fiber_scheduler_get, rb_fiber_scheduler_kernel_sleep, + rb_fiber_scheduler_set, rb_fiber_transfer, rb_fiber_yield, rb_obj_is_fiber, VALUE, + }, + try_convert::{ArgList, TryConvert}, + value::{private, NonZeroValue, ReprValue, Value}, + ExceptionClass, + }, + std::{ + fmt::{Debug, Display, Error as FError, Formatter}, + iter::once, + ops::Deref, + os::raw::c_int, + ptr::null, + time::Duration, + }, +}; + +/// ::Fiber +#[derive(Copy, Clone)] +#[repr(transparent)] +pub struct RFiber(NonZeroValue); + +impl Deref for RFiber { + type Target = Value; + fn deref(&self) -> &Self::Target { + self.0.get_ref() + } +} + +impl TryConvert for RFiber { + fn try_convert(val: Value) -> Result<Self, Error> { + Self::from_value(val).ok_or_else(|| { + Error::new( + type_error(), + format!("no implicit conversion of {} into Fiber", unsafe { + val.classname() + }), + ) + }) + } +} + +impl From<RFiber> for Value { + fn from(f: RFiber) -> Self { + *f + } +} + +unsafe impl private::ReprValue for RFiber { + fn to_value(self) -> Value { + *self + } + + unsafe fn from_value_unchecked(val: Value) -> Self { + Self(NonZeroValue::new_unchecked(val)) + } +} + +impl ReprValue for RFiber {} + +impl Object for RFiber {} + +impl Display for RFiber { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FError> { + write!(f, "{}", unsafe { self.to_s_infallible() }) + } +} + +impl Debug for RFiber { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FError> { + write!(f, "{}", self.inspect()) + } +} + +impl RFiber { + /// Return `Some(RFiber)` if `val` is a `RFiber`, `None` otherwise. + #[inline] + pub fn from_value(val: Value) -> Option<Self> { + unsafe { + Value::new(rb_obj_is_fiber(val.as_rb_value())) + .to_bool() + .then(|| Self(NonZeroValue::new_unchecked(val))) + } + } +} + +impl RFiber { + /** + * Fiber.new + */ + pub fn blocking<F, R>(block: F) -> Self + where + F: 'static + FnMut(&[Value], Option<Proc>) -> R, + R: BlockReturn, + { + unsafe extern "C" fn call<F, R>( + _yielded_arg: VALUE, + callback_arg: VALUE, + argc: c_int, + argv: *const VALUE, + blockarg: VALUE, + ) -> VALUE + where + F: FnMut(&[Value], Option<Proc>) -> R, + R: BlockReturn, + { + let closure = &mut *(callback_arg as *mut F); + Block::new(closure) + .call_handle_error(argc, argv as *const Value, Value::new(blockarg)) + .as_rb_value() + } + + let (closure, keepalive) = wrap_closure(block); + let call_func = + call::<F, R> as unsafe extern "C" fn(VALUE, VALUE, c_int, *const VALUE, VALUE) -> VALUE; + #[cfg(ruby_lt_2_7)] + let call_func: unsafe extern "C" fn() -> VALUE = unsafe { std::mem::transmute(call_func) }; + + let fiber = Self(unsafe { + NonZeroValue::new_unchecked(Value::new(rb_fiber_new(Some(call_func), closure as VALUE))) + }); + fiber.ivar_set("__rust_closure", keepalive).unwrap(); + fiber + } + + /** + * Fiber.new + */ + pub fn non_blocking(block: Proc) -> Result<Self, Error> { + let fiber_class = define_class("Fiber", Default::default())?; + let fib = fiber_class.funcall_with_block_kw::<_, _, _, Value>( + "new", + (), + (("blocking", false),), + block, + )?; + Ok(Self(unsafe { NonZeroValue::new_unchecked(fib) })) + } + + /** + * Fiber.current + */ + pub fn current() -> Self { + Self(unsafe { NonZeroValue::new_unchecked(Value::new(rb_fiber_current())) }) + } + + /** + * Fiber.yield + */ + pub fn rb_yield<A>(args: A) -> Result<Value, Error> + where + A: ArgList, + { + let args = args.into_arg_list(); + let args = args.as_ref(); + protect(|| { + Value::new(unsafe { + rb_fiber_yield(args.len() as c_int, args.as_ptr() as *const VALUE) + }) + }) + } + + /** + * fiber.alive? + */ + pub fn alive(&self) -> bool { + unsafe { Value::new(rb_fiber_alive_p(self.as_rb_value())) }.to_bool() + } + + /** + * fiber.resume + */ + pub fn resume<A, R>(&self, args: A) -> Result<R, Error> + where + A: ArgList, + R: TryConvert, + { + let args = args.into_arg_list(); + let args = args.as_ref(); + protect(|| { + Value::new(unsafe { + rb_fiber_resume( + self.as_rb_value(), + args.len() as c_int, + args.as_ptr() as *const VALUE, + ) + }) + }) + .and_then(R::try_convert) + } + + /** + * fiber.transfer + */ + pub fn transfer<A, R>(&self, args: A) -> Result<R, Error> + where + A: ArgList, + R: TryConvert, + { + let args = args.into_arg_list(); + let args = args.as_ref(); + protect(|| { + Value::new(unsafe { + rb_fiber_transfer( + self.as_rb_value(), + args.len() as c_int, + args.as_ptr() as *const VALUE, + ) + }) + }) + .and_then(R::try_convert) + } + + /** + * fiber.raise + */ + pub fn raise<R>(&self) -> Result<R, Error> + where + R: TryConvert, + { + protect(|| { + Value::new(unsafe { rb_fiber_raise(self.as_rb_value(), Default::default(), null()) }) + }) + .and_then(R::try_convert) + } + + /** + * fiber.raise + */ + pub fn raise_with_message<M, R>(&self, msg: M) -> Result<R, Error> + where + M: Into<RString>, + R: TryConvert, + { + let args = [msg.into().as_rb_value()]; + protect(|| { + Value::new(unsafe { + rb_fiber_raise( + self.as_rb_value(), + args.len() as c_int, + &args as *const VALUE, + ) + }) + }) + .and_then(R::try_convert) + } + + /** + * fiber.raise + */ + pub fn raise_exn<E, A, R>(&self, exn: E, args: A) -> Result<R, Error> + where + E: Into<ExceptionClass>, + A: ArgList, + R: TryConvert, + { + let args = once(exn.into().as_rb_value()) + .chain( + args.into_arg_list() + .as_ref() + .iter() + .copied() + .map(Value::as_rb_value), + ) + .collect::<Vec<_>>(); + protect(|| { + Value::new(unsafe { + rb_fiber_raise( + self.as_rb_value(), + args.len() as c_int, + args.as_ptr() as *const VALUE, + ) + }) + }) + .and_then(R::try_convert) + } +} + +/** + * An unknown fiber scheduler from ruby + */ +#[derive(Copy, Clone)] +pub struct OpaqueScheduler(NonZeroValue); + +impl Deref for OpaqueScheduler { + type Target = Value; + fn deref(&self) -> &Self::Target { + self.0.get_ref() + } +} + +impl From<OpaqueScheduler> for Value { + fn from(f: OpaqueScheduler) -> Self { + *f + } +} + +impl Object for OpaqueScheduler {} + +impl OpaqueScheduler { + /// + /// ::Fiber.scheduler + /// + pub fn current_thread() -> Option<OpaqueScheduler> { + let value = Value::new(unsafe { rb_fiber_scheduler_get() }); + if value.is_nil() { + None + } else { + Some(OpaqueScheduler(unsafe { + NonZeroValue::new_unchecked(value) + })) + } + } + + /// + /// ::Fiber.set_scheduler + /// + pub fn set_current_thread<S>(scheduler: S) -> Result<(), Error> + where + S: Into<Value>, + { + let v = protect(|| { + let sch = scheduler.into().as_rb_value(); + Value::new(unsafe { rb_fiber_scheduler_set(sch) }) + })?; + debug_assert!(v.is_nil()); + Ok(()) + } + + /// + /// scheduler.sleep + /// + pub fn sleep<D, R>(&self, duration: Option<Duration>) -> Result<R, Error> + where + R: TryConvert, + { + protect(|| { + Value::new(unsafe { + rb_fiber_scheduler_kernel_sleep( + self.as_rb_value(), + duration + .map::<Value, _>(|d| Float::from_f64(d.as_secs_f64()).into()) + .unwrap_or_default() + .as_rb_value(), + ) + }) + }) + .and_then(R::try_convert) + } + + /// + /// scheduler.block + /// + pub fn block<B, R>(&self, blocker: B, timeout: Option<Duration>) -> Result<R, Error> + where + B: Into<Value>, + R: TryConvert, + { + protect(|| { + Value::new(unsafe { + rb_fiber_scheduler_block( + self.as_rb_value(), + blocker.into().as_rb_value(), + timeout + .map::<Value, _>(|t| Float::from_f64(t.as_secs_f64()).into()) + .unwrap_or_default() + .as_rb_value(), + ) + }) + }) + .and_then(R::try_convert) + } + + /// + /// scheduler.unblock + /// + pub fn unblock<B, F, R>(&self, blocker: B, fiber: F) -> Result<R, Error> + where + B: Into<Value>, + F: Into<RFiber>, + R: TryConvert, + { + protect(|| { + Value::new(unsafe { + rb_fiber_scheduler_block( + self.as_rb_value(), + blocker.into().as_rb_value(), + fiber.into().as_rb_value(), + ) + }) + }) + .and_then(R::try_convert) + } +} diff --git a/src/r_io.rs b/src/r_io.rs new file mode 100644 index 00000000..f240ae35 --- /dev/null +++ b/src/r_io.rs @@ -0,0 +1,95 @@ +//! + +use { + crate::{ + class::io, + error::Error, + exception::type_error, + object::Object, + try_convert::TryConvert, + value::{private, NonZeroValue, ReprValue, Value}, + }, + std::{ + fmt::{Debug, Display, Error as FError, Formatter}, + ops::Deref, + }, +}; + +/// IO +#[derive(Copy, Clone)] +#[repr(transparent)] +pub struct RIO(NonZeroValue); + +impl Deref for RIO { + type Target = Value; + fn deref(&self) -> &Self::Target { + self.0.get_ref() + } +} + +impl TryConvert for RIO { + fn try_convert(val: Value) -> Result<Self, Error> { + Self::from_value(val).ok_or_else(|| { + Error::new( + type_error(), + format!("no implicit conversion of {} into IO", unsafe { + val.classname() + }), + ) + }) + } +} + +impl From<RIO> for Value { + fn from(f: RIO) -> Self { + *f + } +} + +unsafe impl private::ReprValue for RIO { + fn to_value(self) -> Value { + *self + } + + unsafe fn from_value_unchecked(val: Value) -> Self { + Self(NonZeroValue::new_unchecked(val)) + } +} + +impl ReprValue for RIO {} + +impl Object for RIO {} + +impl Display for RIO { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FError> { + write!(f, "{}", unsafe { self.to_s_infallible() }) + } +} + +impl Debug for RIO { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FError> { + write!(f, "{}", self.inspect()) + } +} + +impl RIO { + /// Return `Some(RIO)` if `val` is a `::IO`, `None` otherwise. + #[inline] + pub fn from_value(val: Value) -> Option<Self> { + if val.is_kind_of(io()) { + Some(Self(unsafe { NonZeroValue::new_unchecked(val) })) + } else { + None + } + } +} + +#[cfg(ruby_gt_3_0)] +use {crate::ruby_sys::rb_io_descriptor, std::os::raw::c_int}; +#[cfg(ruby_gt_3_0)] +impl RIO { + /// `io.fileno` + pub fn descriptor(&self) -> c_int { + unsafe { rb_io_descriptor(self.as_rb_value()) } + } +} diff --git a/src/r_mutex.rs b/src/r_mutex.rs new file mode 100644 index 00000000..388eaf8b --- /dev/null +++ b/src/r_mutex.rs @@ -0,0 +1,89 @@ +//! + +use { + crate::{ + error::{protect, Error}, + object::Object, + ruby_sys::{ + rb_mutex_lock, rb_mutex_locked_p, rb_mutex_new, rb_mutex_trylock, rb_mutex_unlock, + }, + value::{private, NonZeroValue, ReprValue, Value}, + }, + std::{ + fmt::{Debug, Display, Error as FError, Formatter}, + ops::Deref, + }, +}; + +/// ::Thread::Mutex +#[derive(Copy, Clone)] +#[repr(transparent)] +pub struct RMutex(NonZeroValue); + +impl Deref for RMutex { + type Target = Value; + fn deref(&self) -> &Self::Target { + self.0.get_ref() + } +} + +impl From<RMutex> for Value { + fn from(f: RMutex) -> Self { + *f + } +} + +unsafe impl private::ReprValue for RMutex { + fn to_value(self) -> Value { + *self + } + + unsafe fn from_value_unchecked(val: Value) -> Self { + Self(NonZeroValue::new_unchecked(val)) + } +} + +impl ReprValue for RMutex {} + +impl Object for RMutex {} + +impl Display for RMutex { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FError> { + write!(f, "{}", unsafe { self.to_s_infallible() }) + } +} + +impl Debug for RMutex { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FError> { + write!(f, "{}", self.inspect()) + } +} + +impl RMutex { + /// + pub fn new() -> Self { + Self(unsafe { NonZeroValue::new_unchecked(Value::new(rb_mutex_new())) }) + } + + /// + pub fn locked(&self) -> bool { + Value::new(unsafe { rb_mutex_locked_p(self.as_rb_value()) }).to_bool() + } + + /// + pub fn try_lock(&self) -> bool { + Value::new(unsafe { rb_mutex_trylock(self.as_rb_value()) }).to_bool() + } + + /// + pub fn lock(&self) -> Result<(), Error> { + protect(|| Value::new(unsafe { rb_mutex_lock(self.as_rb_value()) }))?; + Ok(()) + } + + /// + pub fn unlock(&self) -> Result<(), Error> { + protect(|| Value::new(unsafe { rb_mutex_unlock(self.as_rb_value()) }))?; + Ok(()) + } +} diff --git a/src/r_thread.rs b/src/r_thread.rs new file mode 100644 index 00000000..a3043a7f --- /dev/null +++ b/src/r_thread.rs @@ -0,0 +1,145 @@ +//! + +use { + crate::{ + block::wrap_closure, + class::thread, + error::{protect, raise, Error}, + exception::type_error, + method::BlockReturn, + object::Object, + ruby_sys::{rb_thread_create, rb_thread_fd_writable, rb_thread_wait_fd, VALUE}, + try_convert::TryConvert, + value::{private, NonZeroValue, ReprValue, Value}, + }, + std::{ + ffi::c_void, + fmt::{Debug, Display, Error as FError, Formatter}, + ops::Deref, + os::raw::c_int, + panic::{catch_unwind, AssertUnwindSafe}, + }, +}; + +/// ::Thread +#[derive(Copy, Clone)] +#[repr(transparent)] +pub struct RThread(NonZeroValue); + +impl Deref for RThread { + type Target = Value; + fn deref(&self) -> &Self::Target { + self.0.get_ref() + } +} + +impl TryConvert for RThread { + fn try_convert(val: Value) -> Result<Self, Error> { + Self::from_value(val).ok_or_else(|| { + Error::new( + type_error(), + format!("no implicit conversion of {} into Thread", unsafe { + val.classname() + }), + ) + }) + } +} + +impl From<RThread> for Value { + fn from(f: RThread) -> Self { + *f + } +} + +unsafe impl private::ReprValue for RThread { + fn to_value(self) -> Value { + *self + } + + unsafe fn from_value_unchecked(val: Value) -> Self { + Self(NonZeroValue::new_unchecked(val)) + } +} + +impl ReprValue for RThread {} + +impl Object for RThread {} + +impl Display for RThread { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FError> { + write!(f, "{}", unsafe { self.to_s_infallible() }) + } +} + +impl Debug for RThread { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FError> { + write!(f, "{}", self.inspect()) + } +} + +impl RThread { + /// Return `Some(RThread)` if `val` is a `::Thread`, `None` otherwise. + #[inline] + pub fn from_value(val: Value) -> Option<Self> { + if val.is_kind_of(thread()) { + Some(Self(unsafe { NonZeroValue::new_unchecked(val) })) + } else { + None + } + } +} + +impl RThread { + + #[cfg(ruby_gte_2_7)] + /// + pub fn from_fn<F, R>(f: F) -> Result<Self, Error> + where + F: 'static + FnMut() -> R + Send, + R: BlockReturn, + { + unsafe extern "C" fn call<F, R>(callback: *mut c_void) -> VALUE + where + F: FnMut() -> R, + R: BlockReturn, + { + match catch_unwind(AssertUnwindSafe(&mut *(callback as *mut F))) + .map_err(Error::from_panic) + .and_then(|v| v.into_block_return()) + { + Ok(v) => v.as_rb_value(), + Err(e) => raise(e), + } + } + + let (closure, keepalive) = wrap_closure(f); + let call_func = call::<F, R> as unsafe extern "C" fn(*mut c_void) -> VALUE; + let thread = Self(unsafe { + NonZeroValue::new_unchecked(protect(|| { + Value::new(rb_thread_create(Some(call_func), closure as *mut c_void)) + })?) + }); + + thread.ivar_set("__rust_closure", keepalive)?; + Ok(thread) + } + + /// + pub fn wait_fd_readable(fd: c_int) -> Result<(), Error> { + protect(|| { + unsafe { rb_thread_wait_fd(fd) }; + Value::default() + })?; + Ok(()) + } + + /// + pub fn wait_fd_writable(fd: c_int) -> Result<(), Error> { + protect(|| { + unsafe { rb_thread_fd_writable(fd) }; + Value::default() + })?; + Ok(()) + } +} diff --git a/src/try_convert.rs b/src/try_convert.rs index 88f93dd5..f72f6ad6 100644 --- a/src/try_convert.rs +++ b/src/try_convert.rs @@ -5,11 +5,12 @@ use crate::ruby_sys::{rb_get_path, rb_num2dbl}; use crate::{ debug_assert_value, error::{protect, Error}, - exception, + exception::{self, type_error}, integer::Integer, r_array::RArray, r_hash::RHash, r_string::RString, + symbol::Symbol, value::{Fixnum, Flonum, Value, QNIL}, }; @@ -34,6 +35,27 @@ pub trait TryConvertForArg: Sized { fn try_convert_for_arg(val: Value) -> Result<Self, Error>; } +impl TryConvert for () { + fn try_convert(val: Value) -> Result<Self, Error> { + if val.is_nil() { + Ok(()) + } else { + Err(Error::new( + type_error(), + format!("no implicit conversion of {} into Unit", unsafe { + val.classname() + }), + )) + } + } +} + +impl TryConvertOwned for () { + fn try_convert_owned(val: Value) -> Result<Self, Error> { + Self::try_convert(val) + } +} + impl<T> TryConvert for Option<T> where T: TryConvert, @@ -1040,6 +1062,494 @@ impl<const N: usize> ArgList for [Value; N] { } } +/// Trait for types that can be used as keyword arguments when calling Ruby +/// methods. +pub trait KwArgList { + /// The type of the arguments list. Must convert to `&[Value]` with + /// [`AsRef`]. + type Output: AsRef<[(Symbol, Value)]>; + + /// Convert `self` into a type that can be used as Ruby keyword arguments. + fn into_kwarg_list(self) -> Self::Output; +} + +impl<'a> KwArgList for &'a [(Symbol, Value)] { + type Output = &'a [(Symbol, Value)]; + + fn into_kwarg_list(self) -> Self::Output { + self + } +} + +impl KwArgList for () { + type Output = [(Symbol, Value); 0]; + + fn into_kwarg_list(self) -> Self::Output { + [] + } +} + +impl<A1, A2> KwArgList for ((A1, A2),) +where + A1: Into<Symbol>, + A2: Into<Value>, +{ + type Output = [(Symbol, Value); 1]; + + fn into_kwarg_list(self) -> Self::Output { + [(self.0 .0.into(), self.0 .1.into())] + } +} + +impl<A1, A2, B1, B2> KwArgList for ((A1, A2), (B1, B2)) +where + A1: Into<Symbol>, + A2: Into<Value>, + B1: Into<Symbol>, + B2: Into<Value>, +{ + type Output = [(Symbol, Value); 2]; + + fn into_kwarg_list(self) -> Self::Output { + [ + (self.0 .0.into(), self.0 .1.into()), + (self.1 .0.into(), self.1 .1.into()), + ] + } +} + +impl<A1, A2, B1, B2, C1, C2> KwArgList for ((A1, A2), (B1, B2), (C1, C2)) +where + A1: Into<Symbol>, + A2: Into<Value>, + B1: Into<Symbol>, + B2: Into<Value>, + C1: Into<Symbol>, + C2: Into<Value>, +{ + type Output = [(Symbol, Value); 3]; + + fn into_kwarg_list(self) -> Self::Output { + [ + (self.0 .0.into(), self.0 .1.into()), + (self.1 .0.into(), self.1 .1.into()), + (self.2 .0.into(), self.2 .1.into()), + ] + } +} + +impl<A1, A2, B1, B2, C1, C2, D1, D2> KwArgList for ((A1, A2), (B1, B2), (C1, C2), (D1, D2)) +where + A1: Into<Symbol>, + A2: Into<Value>, + B1: Into<Symbol>, + B2: Into<Value>, + C1: Into<Symbol>, + C2: Into<Value>, + D1: Into<Symbol>, + D2: Into<Value>, +{ + type Output = [(Symbol, Value); 4]; + + fn into_kwarg_list(self) -> Self::Output { + [ + (self.0 .0.into(), self.0 .1.into()), + (self.1 .0.into(), self.1 .1.into()), + (self.2 .0.into(), self.2 .1.into()), + (self.3 .0.into(), self.3 .1.into()), + ] + } +} + +impl<A1, A2, B1, B2, C1, C2, D1, D2, E1, E2> KwArgList + for ((A1, A2), (B1, B2), (C1, C2), (D1, D2), (E1, E2)) +where + A1: Into<Symbol>, + A2: Into<Value>, + B1: Into<Symbol>, + B2: Into<Value>, + C1: Into<Symbol>, + C2: Into<Value>, + D1: Into<Symbol>, + D2: Into<Value>, + E1: Into<Symbol>, + E2: Into<Value>, +{ + type Output = [(Symbol, Value); 5]; + + fn into_kwarg_list(self) -> Self::Output { + [ + (self.0 .0.into(), self.0 .1.into()), + (self.1 .0.into(), self.1 .1.into()), + (self.2 .0.into(), self.2 .1.into()), + (self.3 .0.into(), self.3 .1.into()), + (self.4 .0.into(), self.4 .1.into()), + ] + } +} + +impl<A1, A2, B1, B2, C1, C2, D1, D2, E1, E2, F1, F2> KwArgList + for ((A1, A2), (B1, B2), (C1, C2), (D1, D2), (E1, E2), (F1, F2)) +where + A1: Into<Symbol>, + A2: Into<Value>, + B1: Into<Symbol>, + B2: Into<Value>, + C1: Into<Symbol>, + C2: Into<Value>, + D1: Into<Symbol>, + D2: Into<Value>, + E1: Into<Symbol>, + E2: Into<Value>, + F1: Into<Symbol>, + F2: Into<Value>, +{ + type Output = [(Symbol, Value); 6]; + + fn into_kwarg_list(self) -> Self::Output { + [ + (self.0 .0.into(), self.0 .1.into()), + (self.1 .0.into(), self.1 .1.into()), + (self.2 .0.into(), self.2 .1.into()), + (self.3 .0.into(), self.3 .1.into()), + (self.4 .0.into(), self.4 .1.into()), + (self.5 .0.into(), self.5 .1.into()), + ] + } +} + +impl<A1, A2, B1, B2, C1, C2, D1, D2, E1, E2, F1, F2, G1, G2> KwArgList + for ( + (A1, A2), + (B1, B2), + (C1, C2), + (D1, D2), + (E1, E2), + (F1, F2), + (G1, G2), + ) +where + A1: Into<Symbol>, + A2: Into<Value>, + B1: Into<Symbol>, + B2: Into<Value>, + C1: Into<Symbol>, + C2: Into<Value>, + D1: Into<Symbol>, + D2: Into<Value>, + E1: Into<Symbol>, + E2: Into<Value>, + F1: Into<Symbol>, + F2: Into<Value>, + G1: Into<Symbol>, + G2: Into<Value>, +{ + type Output = [(Symbol, Value); 7]; + + fn into_kwarg_list(self) -> Self::Output { + [ + (self.0 .0.into(), self.0 .1.into()), + (self.1 .0.into(), self.1 .1.into()), + (self.2 .0.into(), self.2 .1.into()), + (self.3 .0.into(), self.3 .1.into()), + (self.4 .0.into(), self.4 .1.into()), + (self.5 .0.into(), self.5 .1.into()), + (self.6 .0.into(), self.6 .1.into()), + ] + } +} + +impl<A1, A2, B1, B2, C1, C2, D1, D2, E1, E2, F1, F2, G1, G2, H1, H2> KwArgList + for ( + (A1, A2), + (B1, B2), + (C1, C2), + (D1, D2), + (E1, E2), + (F1, F2), + (G1, G2), + (H1, H2), + ) +where + A1: Into<Symbol>, + A2: Into<Value>, + B1: Into<Symbol>, + B2: Into<Value>, + C1: Into<Symbol>, + C2: Into<Value>, + D1: Into<Symbol>, + D2: Into<Value>, + E1: Into<Symbol>, + E2: Into<Value>, + F1: Into<Symbol>, + F2: Into<Value>, + G1: Into<Symbol>, + G2: Into<Value>, + H1: Into<Symbol>, + H2: Into<Value>, +{ + type Output = [(Symbol, Value); 8]; + + fn into_kwarg_list(self) -> Self::Output { + [ + (self.0 .0.into(), self.0 .1.into()), + (self.1 .0.into(), self.1 .1.into()), + (self.2 .0.into(), self.2 .1.into()), + (self.3 .0.into(), self.3 .1.into()), + (self.4 .0.into(), self.4 .1.into()), + (self.5 .0.into(), self.5 .1.into()), + (self.6 .0.into(), self.6 .1.into()), + (self.7 .0.into(), self.7 .1.into()), + ] + } +} + +impl<A1, A2, B1, B2, C1, C2, D1, D2, E1, E2, F1, F2, G1, G2, H1, H2, I1, I2> KwArgList + for ( + (A1, A2), + (B1, B2), + (C1, C2), + (D1, D2), + (E1, E2), + (F1, F2), + (G1, G2), + (H1, H2), + (I1, I2), + ) +where + A1: Into<Symbol>, + A2: Into<Value>, + B1: Into<Symbol>, + B2: Into<Value>, + C1: Into<Symbol>, + C2: Into<Value>, + D1: Into<Symbol>, + D2: Into<Value>, + E1: Into<Symbol>, + E2: Into<Value>, + F1: Into<Symbol>, + F2: Into<Value>, + G1: Into<Symbol>, + G2: Into<Value>, + H1: Into<Symbol>, + H2: Into<Value>, + I1: Into<Symbol>, + I2: Into<Value>, +{ + type Output = [(Symbol, Value); 9]; + + fn into_kwarg_list(self) -> Self::Output { + [ + (self.0 .0.into(), self.0 .1.into()), + (self.1 .0.into(), self.1 .1.into()), + (self.2 .0.into(), self.2 .1.into()), + (self.3 .0.into(), self.3 .1.into()), + (self.4 .0.into(), self.4 .1.into()), + (self.5 .0.into(), self.5 .1.into()), + (self.6 .0.into(), self.6 .1.into()), + (self.7 .0.into(), self.7 .1.into()), + (self.8 .0.into(), self.8 .1.into()), + ] + } +} + +impl<A1, A2, B1, B2, C1, C2, D1, D2, E1, E2, F1, F2, G1, G2, H1, H2, I1, I2, J1, J2> KwArgList + for ( + (A1, A2), + (B1, B2), + (C1, C2), + (D1, D2), + (E1, E2), + (F1, F2), + (G1, G2), + (H1, H2), + (I1, I2), + (J1, J2), + ) +where + A1: Into<Symbol>, + A2: Into<Value>, + B1: Into<Symbol>, + B2: Into<Value>, + C1: Into<Symbol>, + C2: Into<Value>, + D1: Into<Symbol>, + D2: Into<Value>, + E1: Into<Symbol>, + E2: Into<Value>, + F1: Into<Symbol>, + F2: Into<Value>, + G1: Into<Symbol>, + G2: Into<Value>, + H1: Into<Symbol>, + H2: Into<Value>, + I1: Into<Symbol>, + I2: Into<Value>, + J1: Into<Symbol>, + J2: Into<Value>, +{ + type Output = [(Symbol, Value); 10]; + + fn into_kwarg_list(self) -> Self::Output { + [ + (self.0 .0.into(), self.0 .1.into()), + (self.1 .0.into(), self.1 .1.into()), + (self.2 .0.into(), self.2 .1.into()), + (self.3 .0.into(), self.3 .1.into()), + (self.4 .0.into(), self.4 .1.into()), + (self.5 .0.into(), self.5 .1.into()), + (self.6 .0.into(), self.6 .1.into()), + (self.7 .0.into(), self.7 .1.into()), + (self.8 .0.into(), self.8 .1.into()), + (self.9 .0.into(), self.9 .1.into()), + ] + } +} + +impl<A1, A2, B1, B2, C1, C2, D1, D2, E1, E2, F1, F2, G1, G2, H1, H2, I1, I2, J1, J2, K1, K2> + KwArgList + for ( + (A1, A2), + (B1, B2), + (C1, C2), + (D1, D2), + (E1, E2), + (F1, F2), + (G1, G2), + (H1, H2), + (I1, I2), + (J1, J2), + (K1, K2), + ) +where + A1: Into<Symbol>, + A2: Into<Value>, + B1: Into<Symbol>, + B2: Into<Value>, + C1: Into<Symbol>, + C2: Into<Value>, + D1: Into<Symbol>, + D2: Into<Value>, + E1: Into<Symbol>, + E2: Into<Value>, + F1: Into<Symbol>, + F2: Into<Value>, + G1: Into<Symbol>, + G2: Into<Value>, + H1: Into<Symbol>, + H2: Into<Value>, + I1: Into<Symbol>, + I2: Into<Value>, + J1: Into<Symbol>, + J2: Into<Value>, + K1: Into<Symbol>, + K2: Into<Value>, +{ + type Output = [(Symbol, Value); 11]; + + fn into_kwarg_list(self) -> Self::Output { + [ + (self.0 .0.into(), self.0 .1.into()), + (self.1 .0.into(), self.1 .1.into()), + (self.2 .0.into(), self.2 .1.into()), + (self.3 .0.into(), self.3 .1.into()), + (self.4 .0.into(), self.4 .1.into()), + (self.5 .0.into(), self.5 .1.into()), + (self.6 .0.into(), self.6 .1.into()), + (self.7 .0.into(), self.7 .1.into()), + (self.8 .0.into(), self.8 .1.into()), + (self.9 .0.into(), self.9 .1.into()), + (self.10 .0.into(), self.10 .1.into()), + ] + } +} + +impl< + A1, + A2, + B1, + B2, + C1, + C2, + D1, + D2, + E1, + E2, + F1, + F2, + G1, + G2, + H1, + H2, + I1, + I2, + J1, + J2, + K1, + K2, + L1, + L2, + > KwArgList + for ( + (A1, A2), + (B1, B2), + (C1, C2), + (D1, D2), + (E1, E2), + (F1, F2), + (G1, G2), + (H1, H2), + (I1, I2), + (J1, J2), + (K1, K2), + (L1, L2), + ) +where + A1: Into<Symbol>, + A2: Into<Value>, + B1: Into<Symbol>, + B2: Into<Value>, + C1: Into<Symbol>, + C2: Into<Value>, + D1: Into<Symbol>, + D2: Into<Value>, + E1: Into<Symbol>, + E2: Into<Value>, + F1: Into<Symbol>, + F2: Into<Value>, + G1: Into<Symbol>, + G2: Into<Value>, + H1: Into<Symbol>, + H2: Into<Value>, + I1: Into<Symbol>, + I2: Into<Value>, + J1: Into<Symbol>, + J2: Into<Value>, + K1: Into<Symbol>, + K2: Into<Value>, + L1: Into<Symbol>, + L2: Into<Value>, +{ + type Output = [(Symbol, Value); 12]; + + fn into_kwarg_list(self) -> Self::Output { + [ + (self.0 .0.into(), self.0 .1.into()), + (self.1 .0.into(), self.1 .1.into()), + (self.2 .0.into(), self.2 .1.into()), + (self.3 .0.into(), self.3 .1.into()), + (self.4 .0.into(), self.4 .1.into()), + (self.5 .0.into(), self.5 .1.into()), + (self.6 .0.into(), self.6 .1.into()), + (self.7 .0.into(), self.7 .1.into()), + (self.8 .0.into(), self.8 .1.into()), + (self.9 .0.into(), self.9 .1.into()), + (self.10 .0.into(), self.10 .1.into()), + (self.11 .0.into(), self.11 .1.into()), + ] + } +} + pub trait RArrayArgList { fn into_array_arg_list(self) -> RArray; } diff --git a/src/value.rs b/src/value.rs index 09b4abc3..c1665003 100644 --- a/src/value.rs +++ b/src/value.rs @@ -22,6 +22,9 @@ use crate::ruby_sys::{ rb_sym2id, rb_ull2inum, ruby_fl_type, ruby_special_consts, ruby_value_type, RBasic, ID, VALUE, }; +#[cfg(ruby_gte_2_7)] +use crate::ruby_sys::rb_funcall_with_block_kw; + // These don't seem to appear consistently in bindgen output, not sure if they // aren't consistently defined in the headers or what. Lets just do it // ourselves. @@ -41,9 +44,10 @@ use crate::{ module::Module, r_bignum::RBignum, r_float::RFloat, + r_hash::RHash, r_string::RString, symbol::Symbol, - try_convert::{ArgList, TryConvert, TryConvertOwned}, + try_convert::{ArgList, KwArgList, TryConvert, TryConvertOwned}, }; /// Debug assertation that the Value hasn't been garbage collected. @@ -586,6 +590,50 @@ impl Value { } } + #[cfg(ruby_gte_2_7)] + /// Call the method named `method` on `self` with `args`, `kwargs` and `block`. + /// + /// Similar to [`funcall_with_block`](Value::funcall_with_block), but passes `kwargs` as a Ruby + pub fn funcall_with_block_kw<M, A, K, T>( + self, + method: M, + args: A, + kwargs: K, + block: Proc, + ) -> Result<T, Error> + where + M: Into<Id>, + A: ArgList, + K: KwArgList, + T: TryConvert, + { + unsafe { + let id = method.into(); + let args = args.into_arg_list(); + let args = args.as_ref(); + let argc = args.len() as c_int; + let mut slice = args.to_vec(); + let kwargs = kwargs + .into_kwarg_list() + .as_ref() + .into_iter() + .copied() + .collect::<RHash>(); + slice.push(kwargs.into()); + protect(move || { + Value::new(rb_funcall_with_block_kw( + self.as_rb_value(), + id.as_rb_id(), + argc, + slice.as_ptr() as *const VALUE, + block.as_rb_value(), + true.into(), + )) + }) + .and_then(|v| v.try_convert()) + } + } + /// Call the method named `method` on `self` with `args` and `block`. /// /// Similar to [`funcall`](Value::funcall), but passes `block` as a Ruby @@ -1163,6 +1211,17 @@ impl<T> DerefMut for BoxValue<T> { } } +impl<T> Clone for BoxValue<T> +where + T: Clone, +{ + fn clone(&self) -> Self { + let mut boxed = self.0.clone(); + unsafe { rb_gc_register_address(boxed.as_mut() as *mut _ as *mut VALUE) }; + Self(boxed) + } +} + impl<T> fmt::Display for BoxValue<T> where T: ReprValue, diff --git a/tests/gc.rs b/tests/gc.rs new file mode 100644 index 00000000..e63b3bc5 --- /dev/null +++ b/tests/gc.rs @@ -0,0 +1,20 @@ +use magnus::{embed::init, eval, gc, value::BoxValue, RString, Value}; + +#[inline(never)] +fn box_value() -> BoxValue<RString> { + BoxValue::new(RString::new("foo")) +} + +#[test] +fn it_boxvalue_clone() { + let _cleanup = unsafe { init() }; + let boxed = box_value(); + let cloned = boxed.clone(); + drop(boxed); + + eval::<Value>(r#"1024.times.map {|i| "test#{i}"}"#).unwrap(); + gc::start(); + let result: String = eval!(r#"foo + "bar""#, foo = cloned).unwrap(); + + assert_eq!(result, "foobar"); +}