Skip to content

Commit

Permalink
Add INT2FIX, INT2NUM, FIX2LONG and NUM2LONG
Browse files Browse the repository at this point in the history
  • Loading branch information
ianks committed Jul 29, 2024
1 parent fc4e85a commit e5997d2
Show file tree
Hide file tree
Showing 7 changed files with 349 additions and 24 deletions.
1 change: 1 addition & 0 deletions crates/rb-sys-test-helpers/src/ruby_test_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
213 changes: 192 additions & 21 deletions crates/rb-sys-tests/src/stable_api_test.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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!(
Expand Down Expand Up @@ -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'}}")
}
);

Expand Down Expand Up @@ -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
);
Expand Down Expand Up @@ -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
);
5 changes: 5 additions & 0 deletions crates/rb-sys/src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,10 @@

include!(env!("RB_SYS_BINDINGS_PATH"));

#[cfg(ruby_lt_3_3)]
pub const RUBY_FIXNUM_MAX: u64 = 4611686018427387903;
#[cfg(ruby_lt_3_3)]
pub const RUBY_FIXNUM_MIN: i64 = -4611686018427387904;

pub use uncategorized::*;
pub use unstable::*;
43 changes: 43 additions & 0 deletions crates/rb-sys/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}
50 changes: 48 additions & 2 deletions crates/rb-sys/src/stable_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
Expand Down Expand Up @@ -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::RUBY_FIXNUM_MAX as c_long) + 1;
let fixable_neg = value >= crate::RUBY_FIXNUM_MIN;

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)]
Expand Down
Loading

0 comments on commit e5997d2

Please sign in to comment.