diff --git a/crates/rb-sys-test-helpers/src/ruby_test_executor.rs b/crates/rb-sys-test-helpers/src/ruby_test_executor.rs index 2c1a1fc5..1fd9fb32 100644 --- a/crates/rb-sys-test-helpers/src/ruby_test_executor.rs +++ b/crates/rb-sys-test-helpers/src/ruby_test_executor.rs @@ -121,6 +121,7 @@ impl Drop for RubyTestExecutor { } pub fn global_executor() -> &'static RubyTestExecutor { + #[allow(unknown_lints)] #[allow(static_mut_refs)] unsafe { &GLOBAL_EXECUTOR }.get_or_init(RubyTestExecutor::start) } diff --git a/crates/rb-sys-tests/src/stable_api_test.rs b/crates/rb-sys-tests/src/stable_api_test.rs index fe6c05d8..73ac2c3b 100644 --- a/crates/rb-sys-tests/src/stable_api_test.rs +++ b/crates/rb-sys-tests/src/stable_api_test.rs @@ -1,4 +1,6 @@ -use rb_sys::StableApiDefinition; +use std::ffi::{c_int, c_long}; + +use rb_sys::{StableApiDefinition, RUBY_FIXNUM_MAX, RUBY_FIXNUM_MIN}; use rb_sys_test_helpers::rstring as gen_rstring; macro_rules! parity_test { @@ -27,24 +29,25 @@ macro_rules! parity_test { } macro_rules! ruby_eval { - ($expr:literal) => {{ - unsafe { - let mut state = 0; - let ret = - rb_sys::rb_eval_string_protect(concat!($expr, "\0").as_ptr() as _, &mut state as _); - - if state != 0 { - let mut err_string = rb_sys::rb_inspect(rb_sys::rb_errinfo()); - rb_sys::rb_set_errinfo(rb_sys::Qnil as _); - let err_string = rb_sys::rb_string_value_cstr(&mut err_string); - let err_string = std::ffi::CStr::from_ptr(err_string); - let err_string = err_string.to_str().unwrap(); - panic!("Ruby error: {}", err_string); - } - - ret - } - }}; + ($expr:literal $(, $arg:expr)*) => {{ + unsafe { + let mut state = 0; + let formatted_expr = format!($expr $(, $arg)*); + let c_string = std::ffi::CString::new(formatted_expr).unwrap(); + let ret = rb_sys::rb_eval_string_protect(c_string.as_ptr(), &mut state as _); + + if state != 0 { + let mut err_string = rb_sys::rb_inspect(rb_sys::rb_errinfo()); + rb_sys::rb_set_errinfo(rb_sys::Qnil as _); + let err_string = rb_sys::rb_string_value_cstr(&mut err_string); + let err_string = std::ffi::CStr::from_ptr(err_string); + let err_string = err_string.to_str().unwrap(); + panic!("Ruby error: {}", err_string); + } + + ret + } + }}; } parity_test!( @@ -231,7 +234,7 @@ parity_test!( name: test_builtin_type_for_hash, func: builtin_type, data_factory: { - ruby_eval!("{foo: 'bar'}") + ruby_eval!("{{foo: 'bar'}}") } ); @@ -474,7 +477,7 @@ parity_test! ( name: test_rb_type_for_hash, func: rb_type, data_factory: { - ruby_eval!("{foo: 'bar'}") + ruby_eval!("{{foo: 'bar'}}") }, expected: rb_sys::ruby_value_type::RUBY_T_HASH ); @@ -533,3 +536,171 @@ parity_test!( }, expected: false ); + +parity_test!( + name: test_int2fix_positive, + func: int2fix, + data_factory: { 42 }, + expected: ruby_eval!("42") +); + +parity_test!( + name: test_int2fix_negative, + func: int2fix, + data_factory: { -42 }, + expected: ruby_eval!("-42") +); + +parity_test!( + name: test_int2fix_zero, + func: int2fix, + data_factory: { 0 }, + expected: ruby_eval!("0") +); + +parity_test!( + name: test_int2num_fixnum, + func: int2num, + data_factory: { 42 }, + expected: ruby_eval!("42") +); + +parity_test!( + name: test_int2num_bignum_positive, + func: int2num, + data_factory: { i32::MAX }, + expected: ruby_eval!("2147483647") +); + +parity_test!( + name: test_int2num_bignum_negative, + func: int2num, + data_factory: { i32::MIN }, + expected: ruby_eval!("-2147483648") +); + +parity_test!( + name: test_fix2long_positive, + func: fix2long, + data_factory: { ruby_eval!("42") }, + expected: 42 +); + +parity_test!( + name: test_fix2long_negative, + func: fix2long, + data_factory: { ruby_eval!("-42") }, + expected: -42 +); + +parity_test!( + name: test_fix2long_zero, + func: fix2long, + data_factory: { ruby_eval!("0") }, + expected: 0 +); + +parity_test!( + name: test_num2long_fixnum, + func: num2long, + data_factory: { ruby_eval!("42") }, + expected: 42 +); + +parity_test!( + name: test_num2long_bignum, + func: num2long, + data_factory: { ruby_eval!("9223372036854775807") }, // i64::MAX + expected: 9223372036854775807 +); + +parity_test!( + name: test_num2long_negative_bignum, + func: num2long, + data_factory: { ruby_eval!("-9223372036854775808") }, // i64::MIN + expected: -9223372036854775808 +); + +parity_test!( + name: test_int2fix_max, + func: int2fix, + data_factory: { RUBY_FIXNUM_MAX as c_int }, + expected: ruby_eval!("{}", rb_sys::RUBY_FIXNUM_MAX as c_int) +); + +parity_test!( + name: test_int2fix_min, + func: int2fix, + data_factory: { RUBY_FIXNUM_MIN as c_int }, + expected: ruby_eval!("{}", rb_sys::RUBY_FIXNUM_MIN as c_int) +); + +parity_test!( + name: test_int2num_max_fixnum, + func: int2num, + data_factory: { RUBY_FIXNUM_MAX as c_int }, + expected: ruby_eval!("{}", rb_sys::RUBY_FIXNUM_MAX as c_int) +); + +parity_test!( + name: test_int2num_min_fixnum, + func: int2num, + data_factory: { RUBY_FIXNUM_MIN as c_int }, + expected: ruby_eval!("{}", rb_sys::RUBY_FIXNUM_MIN as c_int) +); + +parity_test!( + name: test_int2num_max_int, + func: int2num, + data_factory: { c_int::MAX }, + expected: ruby_eval!("2147483647") // Assuming 32-bit int +); + +parity_test!( + name: test_int2num_min_int, + func: int2num, + data_factory: { c_int::MIN }, + expected: ruby_eval!("-2147483648") // Assuming 32-bit int +); + +parity_test!( + name: test_fix2long_max, + func: fix2long, + data_factory: { ruby_eval!("{}", rb_sys::RUBY_FIXNUM_MAX) }, + expected: RUBY_FIXNUM_MAX as c_long +); + +parity_test!( + name: test_fix2long_min, + func: fix2long, + data_factory: { ruby_eval!("{}", rb_sys::RUBY_FIXNUM_MIN) }, + expected: RUBY_FIXNUM_MIN as c_long +); + +parity_test!( + name: test_num2long_max_fixnum, + func: num2long, + data_factory: { ruby_eval!("{}", rb_sys::RUBY_FIXNUM_MAX) }, + expected: RUBY_FIXNUM_MAX as c_long +); + +parity_test!( + name: test_num2long_min_fixnum, + func: num2long, + data_factory: { ruby_eval!("{}", rb_sys::RUBY_FIXNUM_MIN) }, + expected: RUBY_FIXNUM_MIN as c_long +); + +parity_test!( + name: test_num2long_max_long, + func: num2long, + data_factory: { ruby_eval!("9223372036854775807") }, // Assuming 64-bit long + expected: c_long::MAX +); + +parity_test!( + name: test_num2long_min_long, + func: num2long, + data_factory: { ruby_eval!("-9223372036854775808") }, // Assuming 64-bit long + expected: c_long::MIN +); diff --git a/crates/rb-sys/src/bindings.rs b/crates/rb-sys/src/bindings.rs index 8e527801..afd77aad 100644 --- a/crates/rb-sys/src/bindings.rs +++ b/crates/rb-sys/src/bindings.rs @@ -16,5 +16,11 @@ include!(env!("RB_SYS_BINDINGS_PATH")); +pub const RUBY_LONG_MIN: isize = std::ffi::c_long::MIN as isize; +pub const RUBY_LONG_MAX: isize = std::ffi::c_long::MAX as isize; + +pub const RUBY_FIXNUM_MIN: isize = RUBY_LONG_MIN / 2; +pub const RUBY_FIXNUM_MAX: isize = RUBY_LONG_MAX / 2; + pub use uncategorized::*; pub use unstable::*; diff --git a/crates/rb-sys/src/macros.rs b/crates/rb-sys/src/macros.rs index f135244f..78bb79e7 100644 --- a/crates/rb-sys/src/macros.rs +++ b/crates/rb-sys/src/macros.rs @@ -17,6 +17,7 @@ use crate::ruby_value_type; use crate::stable_api::get_default as api; use crate::StableApiDefinition; use crate::VALUE; +use std::ffi::c_int; use std::os::raw::{c_char, c_long}; /// Emulates Ruby's "if" statement. @@ -283,3 +284,45 @@ pub unsafe fn RB_TYPE_P(obj: VALUE, ty: ruby_value_type) -> bool { pub unsafe fn RB_FLOAT_TYPE_P(obj: VALUE) -> bool { api().float_type_p(obj) } + +/// Converts a C int to a Ruby Fixnum. +/// +/// @param[in] i A C int to convert. +/// @return A Ruby Fixnum. +#[inline] +pub fn INT2FIX(i: c_int) -> VALUE { + api().int2fix(i) +} + +/// Converts a C int to a Ruby Integer (Fixnum or Bignum). +/// +/// @param[in] i A C int to convert. +/// @return A Ruby Integer. +#[inline] +pub fn INT2NUM(i: c_int) -> VALUE { + api().int2num(i) +} + +/// Converts a Ruby Fixnum to a C long. +/// +/// @param[in] val A Ruby Fixnum. +/// @return A C long. +/// +/// # Safety +/// This function is unsafe because it assumes the input is a valid Fixnum. +#[inline] +pub fn FIX2LONG(val: VALUE) -> c_long { + api().fix2long(val) +} + +/// Converts a Ruby Integer (Fixnum or Bignum) to a C long. +/// +/// @param[in] val A Ruby Integer. +/// @return A C long. +/// +/// # Safety +/// This function is unsafe because it may involve C-level operations. +#[inline] +pub unsafe fn NUM2LONG(val: VALUE) -> c_long { + api().num2long(val) +} diff --git a/crates/rb-sys/src/stable_api.rs b/crates/rb-sys/src/stable_api.rs index 72bb0386..fb36b0ae 100644 --- a/crates/rb-sys/src/stable_api.rs +++ b/crates/rb-sys/src/stable_api.rs @@ -11,8 +11,11 @@ //! to ensure Rust extensions don't prevent the Ruby core team from testing //! changes in production. -use crate::VALUE; -use std::os::raw::{c_char, c_long}; +use crate::{rb_int2big, rb_num2long, ruby_special_consts, VALUE}; +use std::{ + ffi::{c_int, c_ulong}, + os::raw::{c_char, c_long}, +}; pub trait StableApiDefinition { /// Get the length of a Ruby string (akin to `RSTRING_LEN`). @@ -125,6 +128,49 @@ pub trait StableApiDefinition { /// This function is unsafe because it could dereference a raw pointer when /// attemping to access the underlying [`RBasic`] struct. unsafe fn rb_type(&self, obj: VALUE) -> crate::ruby_value_type; + + /// Converts a `c_int` to a Ruby Fixnum. + fn int2fix(&self, i: c_int) -> VALUE { + let j = i as c_ulong; + let k = (j << 1) + ruby_special_consts::RUBY_FIXNUM_FLAG as c_ulong; + let l = k as c_long; + let m = l as isize; + m as VALUE + } + + /// Converts a `c_int` to a Ruby number, either a Fixnum or a Bignum. + fn int2num(&self, i: c_int) -> VALUE { + let value = i as c_long; + + let fixable_pos = value < (crate::FIXNUM_MAX as c_long) + 1; + let fixable_neg = value >= (crate::FIXNUM_MIN as c_long); + + if fixable_pos && fixable_neg { + self.int2fix(i) + } else { + unsafe { rb_int2big(i as _) } + } + } + + /// Converts a Fixnum to a `c_long`. + fn fix2long(&self, val: VALUE) -> c_long { + let y: isize = val as _; + let z = y >> 1; + z as c_long + } + + /// Converts a Ruby number to a `c_long`. If the number is a Fixnum, it is + /// converted directly. + /// + /// # Safety + /// This function assumes a valid Ruby num value. + unsafe fn num2long(&self, val: VALUE) -> c_long { + if self.fixnum_p(val) { + self.fix2long(val) + } else { + rb_num2long(val) + } + } } #[cfg(stable_api_enable_compiled_mod)] diff --git a/crates/rb-sys/src/stable_api/compiled.c b/crates/rb-sys/src/stable_api/compiled.c index 915331c9..fcc279bc 100644 --- a/crates/rb-sys/src/stable_api/compiled.c +++ b/crates/rb-sys/src/stable_api/compiled.c @@ -87,3 +87,23 @@ int impl_integer_type_p(VALUE obj) { return RB_INTEGER_TYPE_P(obj); } + +VALUE +impl_int2fix(int i) { + return INT2FIX(i); +} + +VALUE +impl_int2num(int i) { + return INT2NUM(i); +} + +long +impl_fix2long(VALUE val) { + return FIX2LONG(val); +} + +long +impl_num2long(VALUE val) { + return NUM2LONG(val); +} diff --git a/crates/rb-sys/src/stable_api/compiled.rs b/crates/rb-sys/src/stable_api/compiled.rs index 18aabf2f..25ecb86e 100644 --- a/crates/rb-sys/src/stable_api/compiled.rs +++ b/crates/rb-sys/src/stable_api/compiled.rs @@ -1,6 +1,9 @@ use super::StableApiDefinition; use crate::{ruby_value_type, VALUE}; -use std::os::raw::{c_char, c_long}; +use std::{ + ffi::c_int, + os::raw::{c_char, c_long}, +}; #[allow(dead_code)] extern "C" { @@ -57,6 +60,18 @@ extern "C" { #[link_name = "impl_integer_type_p"] fn impl_integer_type_p(obj: VALUE) -> bool; + + #[link_name = "impl_int2fix"] + fn impl_int2fix(i: c_int) -> VALUE; + + #[link_name = "impl_int2num"] + fn impl_int2num(i: c_int) -> VALUE; + + #[link_name = "impl_fix2long"] + fn impl_fix2long(val: VALUE) -> c_long; + + #[link_name = "impl_num2long"] + fn impl_num2long(val: VALUE) -> c_long; } pub struct Definition; @@ -151,4 +166,28 @@ impl StableApiDefinition for Definition { unsafe fn integer_type_p(&self, obj: VALUE) -> bool { impl_integer_type_p(obj) } + + #[cfg(feature = "stable-api-compiled-testing")] + #[inline] + fn int2fix(&self, i: c_int) -> VALUE { + unsafe { impl_int2fix(i) } + } + + #[cfg(feature = "stable-api-compiled-testing")] + #[inline] + fn int2num(&self, i: c_int) -> VALUE { + unsafe { impl_int2num(i) } + } + + #[cfg(feature = "stable-api-compiled-testing")] + #[inline] + fn fix2long(&self, val: VALUE) -> c_long { + unsafe { impl_fix2long(val) } + } + + #[cfg(feature = "stable-api-compiled-testing")] + #[inline] + unsafe fn num2long(&self, val: VALUE) -> c_long { + impl_num2long(val) + } }