diff --git a/source/ports/rs_port/src/types/metacall_value.rs b/source/ports/rs_port/src/types/metacall_value.rs index 111dbd5908..8d14954975 100644 --- a/source/ports/rs_port/src/types/metacall_value.rs +++ b/source/ports/rs_port/src/types/metacall_value.rs @@ -196,6 +196,157 @@ impl MetaCallValue for f64 { unsafe { metacall_value_create_double(self) } } } + +//Equivalent to MetaCall long type. +impl MetaCallValue for u8 { + fn get_metacall_id() -> metacall_value_id { + metacall_value_id::METACALL_LONG + } + + fn from_metacall_raw_leak(v: *mut c_void) -> Result> { + let id = unsafe { metacall_value_id(v) }; + + let value_i64 = match id { + metacall_value_id::METACALL_SHORT => unsafe { + metacall_value_to_short(v) as i64 + }, + metacall_value_id::METACALL_INT => unsafe { + metacall_value_to_int(v) as i64 + }, + metacall_value_id::METACALL_LONG => unsafe { + metacall_value_to_long(v) + }, + _ => return Err(Box::new(0u8)), + }; + + + if value_i64 < 0 || value_i64 > u8::MAX as i64 { + return Err(Box::new(value_i64)); + } + + Ok(value_i64 as u8) + } + + fn into_metacall_raw(self) -> *mut c_void { + // safer to send as LONG (i64) + unsafe { metacall_value_create_long(self as i64) } + } +} + +//Equivalent to MetaCall long type. +impl MetaCallValue for u16 { + fn get_metacall_id() -> metacall_value_id { + metacall_value_id::METACALL_LONG + } + + fn from_metacall_raw_leak(v: *mut c_void) -> Result> { + let id = unsafe { metacall_value_id(v) }; + + let value_i64 = match id { + metacall_value_id::METACALL_SHORT => unsafe { + metacall_value_to_short(v) as i64 + }, + metacall_value_id::METACALL_INT => unsafe { + metacall_value_to_int(v) as i64 + }, + metacall_value_id::METACALL_LONG => unsafe { + metacall_value_to_long(v) + }, + _ => return Err(Box::new(0u16)), + }; + + + if value_i64 < 0 || value_i64 > u16::MAX as i64 { + return Err(Box::new(value_i64)); + } + + Ok(value_i64 as u16) + } + + fn into_metacall_raw(self) -> *mut c_void { + // safer to send as LONG (i64) + unsafe { metacall_value_create_long(self as i64) } + } +} + +//Equivalent to MetaCall long type. +impl MetaCallValue for u32 { + fn get_metacall_id() -> metacall_value_id { + metacall_value_id::METACALL_LONG + } + + fn from_metacall_raw_leak(v: *mut c_void) -> Result> { + let id = unsafe { metacall_value_id(v) }; + + let value_i64 = match id { + metacall_value_id::METACALL_INT => unsafe { + metacall_value_to_int(v) as i64 + }, + metacall_value_id::METACALL_LONG => unsafe { + metacall_value_to_long(v) + }, + _ => { + return Err(Box::new(0u32)); + } + }; + + if value_i64 < 0 { + return Err(Box::new(value_i64)); + } + + if value_i64 > u32::MAX as i64 { + return Err(Box::new(value_i64)); + } + + Ok(value_i64 as u32) + } + + fn into_metacall_raw(self) -> *mut c_void { + // safer to send as LONG (i64) + unsafe { metacall_value_create_long(self as i64) } + } +} + +// TODO: +// MetaCall does not provide an unsigned 64-bit type. Since METACALL_LONG maps to i64, values above i64::MAX, cannot be represented safely. +// Proper support for u64 may require: Adding i128 support in MetaCall +// For now, u64 support is intentionally not implemented. Only emits warning when value exceeds i64::MAX +impl MetaCallValue for u64 { + fn get_metacall_id() -> metacall_value_id { + metacall_value_id::METACALL_LONG + } + + fn from_metacall_raw_leak(v: *mut c_void) -> Result> { + let id = unsafe { metacall_value_id(v) }; + + let value_i64 = match id { + metacall_value_id::METACALL_INT => unsafe { + metacall_value_to_int(v) as i64 + }, + metacall_value_id::METACALL_LONG => unsafe { + metacall_value_to_long(v) + }, + _ => { + return Err(Box::new(0u64)); + } + }; + + if value_i64 < 0 { + return Err(Box::new(value_i64)); + } + + Ok(value_i64 as u64) + } + + fn into_metacall_raw(self) -> *mut c_void { + if self > i64::MAX as u64 { + eprintln!("Warning: u64 value exceeds i64::MAX, precision loss possible"); + } + + unsafe { metacall_value_create_long(self as i64) } + } +} + /// Equivalent to MetaCall string type. impl MetaCallValue for String { fn get_metacall_id() -> metacall_value_id { diff --git a/source/ports/rs_port/tests/metacall_test.rs b/source/ports/rs_port/tests/metacall_test.rs index 81d73511a3..895b07ad59 100644 --- a/source/ports/rs_port/tests/metacall_test.rs +++ b/source/ports/rs_port/tests/metacall_test.rs @@ -6,6 +6,23 @@ use metacall::{ }; use std::{any::Any, collections::HashMap, env, fmt::Debug}; +use std::sync::Once; + +static INIT: Once = Once::new(); + +fn ensure_runtime() { + INIT.call_once(|| { + initialize().unwrap(); + + load::from_file( + Tag::Python, + ["tests/scripts/script.py"], + None, + ) + .unwrap(); + }); +} + fn generate_test( name: impl ToString, expected: T, @@ -82,6 +99,73 @@ fn test_float() { fn test_double() { generate_test::("test_double", 1.2345_f64); } + +#[test] +fn test_u8_valid() { + ensure_runtime(); + + let result = ::metacall::metacall_no_arg::("return_small_number"); + assert_eq!(result.unwrap(), 42u8); +} + +#[test] +fn test_u8_negative() { + ensure_runtime(); + + let result = ::metacall::metacall_no_arg::("return_negative_int"); + assert!(result.is_err(), "Negative value should not convert to u8"); +} + +#[test] +fn test_u16_valid() { + ensure_runtime(); + + let result = ::metacall::metacall_no_arg::("return_small_number"); + assert_eq!(result.unwrap(), 42u16); +} + +#[test] +fn test_u16_overflow() { + ensure_runtime(); + + let result = ::metacall::metacall_no_arg::("return_large_number"); + assert!(result.is_err(), "Overflow should not convert to u16"); +} + +#[test] +fn test_u32_overflow() { + ensure_runtime(); + + let result = ::metacall::metacall_no_arg::("return_large_number"); + assert!(result.is_err()); +} + + +#[test] +fn test_u32_negative() { + ensure_runtime(); + + let result = ::metacall::metacall_no_arg::("return_negative_int"); + + assert!(result.is_err(), "Negative C int should not convert to u32"); +} + +#[test] +fn test_u64_valid() { + ensure_runtime(); + + let result = ::metacall::metacall_no_arg::("return_large_number"); + assert_eq!(result.unwrap(), i64::MAX as u64); +} + +#[test] +fn test_u64_negative() { + ensure_runtime(); + + let result = ::metacall::metacall_no_arg::("return_negative_int"); + assert!(result.is_err(), "Negative value should not convert to u64"); +} + fn test_mixed_numbers() { let result = ::metacall::metacall::( "test_mixed_numbers", diff --git a/source/ports/rs_port/tests/scripts/script.py b/source/ports/rs_port/tests/scripts/script.py index 507fa6cd65..2fd79c0898 100644 --- a/source/ports/rs_port/tests/scripts/script.py +++ b/source/ports/rs_port/tests/scripts/script.py @@ -13,4 +13,10 @@ def test_class(): def test_object(): return TestClass() def return_the_argument_py(argument): - return argument \ No newline at end of file + return argument +def return_small_number(): + return 42 +def return_negative_int(): + return -1 +def return_large_number(): + return 9223372036854775807 # i64::MAX \ No newline at end of file