From ef8b8e11ec56d09dfc4e795687e2ec12fdc4c29a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 13 Mar 2025 16:18:44 +0000 Subject: [PATCH] Support 52-bit integers for Luau Simply to float conversion (it actually never fails or goes out of range) --- mlua-sys/src/luau/compat.rs | 25 +++++++++++++++++++++++++ mlua-sys/src/luau/lauxlib.rs | 8 +++++--- mlua-sys/src/luau/lua.rs | 20 ++++++++++++-------- src/conversion.rs | 36 +++++------------------------------- src/luau/mod.rs | 2 +- src/value.rs | 5 ++++- tests/tests.rs | 25 +++++++++++++++++++++++++ 7 files changed, 77 insertions(+), 44 deletions(-) diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index 29c7b1e..0cbed7b 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -125,6 +125,11 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int { 0 } +#[inline(always)] +pub unsafe fn lua_pushinteger(L: *mut lua_State, i: lua_Integer) { + lua_pushnumber(L, i as lua_Number); +} + #[inline(always)] pub unsafe fn lua_tointeger(L: *mut lua_State, i: c_int) -> lua_Integer { lua_tointegerx(L, i, ptr::null_mut()) @@ -176,6 +181,7 @@ pub unsafe fn lua_geti(L: *mut lua_State, mut idx: c_int, n: lua_Integer) -> c_i #[inline(always)] pub unsafe fn lua_rawgeti(L: *mut lua_State, idx: c_int, n: lua_Integer) -> c_int { + let n = n.try_into().expect("cannot convert index from lua_Integer"); lua_rawgeti_(L, idx, n) } @@ -211,6 +217,7 @@ pub unsafe fn lua_seti(L: *mut lua_State, mut idx: c_int, n: lua_Integer) { #[inline(always)] pub unsafe fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer) { + let n = n.try_into().expect("cannot convert index from lua_Integer"); lua_rawseti_(L, idx, n) } @@ -300,6 +307,24 @@ pub unsafe fn luaL_checkstack(L: *mut lua_State, sz: c_int, msg: *const c_char) } } +#[inline(always)] +pub unsafe fn luaL_checkinteger(L: *mut lua_State, narg: c_int) -> lua_Integer { + let mut isnum = 0; + let int = lua_tointegerx(L, narg, &mut isnum); + if isnum == 0 { + luaL_typeerror(L, narg, lua_typename(L, LUA_TNUMBER)); + } + int +} + +pub unsafe fn luaL_optinteger(L: *mut lua_State, narg: c_int, def: lua_Integer) -> lua_Integer { + if lua_isnoneornil(L, narg) != 0 { + def + } else { + luaL_checkinteger(L, narg) + } +} + #[inline(always)] pub unsafe fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int { if luaL_getmetafield_(L, obj, e) != 0 { diff --git a/mlua-sys/src/luau/lauxlib.rs b/mlua-sys/src/luau/lauxlib.rs index 284cd3f..0b75cbe 100644 --- a/mlua-sys/src/luau/lauxlib.rs +++ b/mlua-sys/src/luau/lauxlib.rs @@ -3,7 +3,7 @@ use std::os::raw::{c_char, c_float, c_int, c_void}; use std::ptr; -use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State, lua_Unsigned, LUA_REGISTRYINDEX}; +use super::lua::{self, lua_CFunction, lua_Number, lua_State, lua_Unsigned, LUA_REGISTRYINDEX}; #[repr(C)] pub struct luaL_Reg { @@ -33,8 +33,10 @@ extern "C-unwind" { pub fn luaL_checkboolean(L: *mut lua_State, narg: c_int) -> c_int; pub fn luaL_optboolean(L: *mut lua_State, narg: c_int, def: c_int) -> c_int; - pub fn luaL_checkinteger(L: *mut lua_State, narg: c_int) -> lua_Integer; - pub fn luaL_optinteger(L: *mut lua_State, narg: c_int, def: lua_Integer) -> lua_Integer; + #[link_name = "luaL_checkinteger"] + pub fn luaL_checkinteger_(L: *mut lua_State, narg: c_int) -> c_int; + #[link_name = "luaL_optinteger"] + pub fn luaL_optinteger_(L: *mut lua_State, narg: c_int, def: c_int) -> c_int; pub fn luaL_checkunsigned(L: *mut lua_State, narg: c_int) -> lua_Unsigned; pub fn luaL_optunsigned(L: *mut lua_State, narg: c_int, def: lua_Unsigned) -> lua_Unsigned; diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index c75174a..a916f80 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -69,8 +69,11 @@ pub const LUA_MINSTACK: c_int = 20; /// A Lua number, usually equivalent to `f64`. pub type lua_Number = c_double; -/// A Lua integer, equivalent to `i32`. -pub type lua_Integer = c_int; +/// A Lua integer, usually equivalent to `i64` +#[cfg(target_pointer_width = "32")] +pub type lua_Integer = i32; +#[cfg(target_pointer_width = "64")] +pub type lua_Integer = i64; /// A Lua unsigned integer, equivalent to `u32`. pub type lua_Unsigned = c_uint; @@ -136,7 +139,7 @@ extern "C-unwind" { pub fn lua_tonumberx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Number; #[link_name = "lua_tointegerx"] - pub fn lua_tointegerx_(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Integer; + pub fn lua_tointegerx_(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> c_int; pub fn lua_tounsignedx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Unsigned; pub fn lua_tovector(L: *mut lua_State, idx: c_int) -> *const c_float; pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int; @@ -160,7 +163,8 @@ extern "C-unwind" { // pub fn lua_pushnil(L: *mut lua_State); pub fn lua_pushnumber(L: *mut lua_State, n: lua_Number); - pub fn lua_pushinteger(L: *mut lua_State, n: lua_Integer); + #[link_name = "lua_pushinteger"] + pub fn lua_pushinteger_(L: *mut lua_State, n: c_int); pub fn lua_pushunsigned(L: *mut lua_State, n: lua_Unsigned); #[cfg(not(feature = "luau-vector4"))] pub fn lua_pushvector(L: *mut lua_State, x: c_float, y: c_float, z: c_float); @@ -310,13 +314,13 @@ extern "C-unwind" { // #[inline(always)] -pub unsafe fn lua_tonumber(L: *mut lua_State, i: c_int) -> lua_Number { - lua_tonumberx(L, i, ptr::null_mut()) +pub unsafe fn lua_tonumber(L: *mut lua_State, idx: c_int) -> lua_Number { + lua_tonumberx(L, idx, ptr::null_mut()) } #[inline(always)] -pub unsafe fn lua_tointeger_(L: *mut lua_State, i: c_int) -> lua_Integer { - lua_tointegerx_(L, i, ptr::null_mut()) +pub unsafe fn lua_tointeger_(L: *mut lua_State, idx: c_int) -> c_int { + lua_tointegerx_(L, idx, ptr::null_mut()) } #[inline(always)] diff --git a/src/conversion.rs b/src/conversion.rs index 2f64719..b7dfa30 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -808,15 +808,9 @@ macro_rules! lua_convert_int { impl IntoLua for $x { #[inline] fn into_lua(self, _: &Lua) -> Result { - cast(self) + Ok(cast(self) .map(Value::Integer) - .or_else(|| cast(self).map(Value::Number)) - // This is impossible error because conversion to Number never fails - .ok_or_else(|| Error::ToLuaConversionError { - from: stringify!($x).to_string(), - to: "number", - message: Some("out of range".to_owned()), - }) + .unwrap_or_else(|| Value::Number(self as ffi::lua_Number))) } #[inline] @@ -899,13 +893,7 @@ macro_rules! lua_convert_float { impl IntoLua for $x { #[inline] fn into_lua(self, _: &Lua) -> Result { - cast(self) - .ok_or_else(|| Error::ToLuaConversionError { - from: stringify!($x).to_string(), - to: "number", - message: Some("out of range".to_string()), - }) - .map(Value::Number) + Ok(Value::Number(self as _)) } } @@ -914,33 +902,19 @@ macro_rules! lua_convert_float { fn from_lua(value: Value, lua: &Lua) -> Result { let ty = value.type_name(); lua.coerce_number(value)? + .map(|n| n as $x) .ok_or_else(|| Error::FromLuaConversionError { from: ty, to: stringify!($x).to_string(), message: Some("expected number or string coercible to number".to_string()), }) - .and_then(|n| { - cast(n).ok_or_else(|| Error::FromLuaConversionError { - from: ty, - to: stringify!($x).to_string(), - message: Some("number out of range".to_string()), - }) - }) } unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { let state = lua.state(); let type_id = ffi::lua_type(state, idx); if type_id == ffi::LUA_TNUMBER { - let mut ok = 0; - let i = ffi::lua_tonumberx(state, idx, &mut ok); - if ok != 0 { - return cast(i).ok_or_else(|| Error::FromLuaConversionError { - from: "number", - to: stringify!($x).to_string(), - message: Some("out of range".to_owned()), - }); - } + return Ok(ffi::lua_tonumber(state, idx) as _); } // Fallback to default Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua()) diff --git a/src/luau/mod.rs b/src/luau/mod.rs index b3935d3..29427ed 100644 --- a/src/luau/mod.rs +++ b/src/luau/mod.rs @@ -51,7 +51,7 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_ 1 } Ok("step") => { - let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg); + let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg as _); ffi::lua_pushboolean(state, res); 1 } diff --git a/src/value.rs b/src/value.rs index bd08888..428ce20 100644 --- a/src/value.rs +++ b/src/value.rs @@ -272,7 +272,10 @@ impl Value { /// If the value is a Lua [`Integer`], try to convert it to `i64` or return `None` otherwise. #[inline] pub fn as_i64(&self) -> Option { - self.as_integer().map(i64::from) + #[cfg(target_pointer_width = "64")] + return self.as_integer(); + #[cfg(not(target_pointer_width = "64"))] + return self.as_integer().map(i64::from); } /// Cast the value to `u64`. diff --git a/tests/tests.rs b/tests/tests.rs index 01f706d..138a0ad 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -483,6 +483,31 @@ fn test_panic() -> Result<()> { Ok(()) } +#[cfg(target_pointer_width = "64")] +#[test] +fn test_safe_integers() -> Result<()> { + const MAX_SAFE_INTEGER: i64 = 2i64.pow(53) - 1; + const MIN_SAFE_INTEGER: i64 = -2i64.pow(53) + 1; + + let lua = Lua::new(); + let f = lua.load("return ...").into_function()?; + + assert_eq!(f.call::(MAX_SAFE_INTEGER)?, MAX_SAFE_INTEGER); + assert_eq!(f.call::(MIN_SAFE_INTEGER)?, MIN_SAFE_INTEGER); + + // For Lua versions that does not support 64-bit integers, the values will be converted to f64 + #[cfg(any(feature = "luau", feature = "lua51", feature = "luajit"))] + { + assert_ne!(f.call::(MAX_SAFE_INTEGER + 2)?, MAX_SAFE_INTEGER + 2); + assert_ne!(f.call::(MIN_SAFE_INTEGER - 2)?, MIN_SAFE_INTEGER - 2); + + let n = f.call::(i64::MAX)?; + println!("i64::MAX = {}", n); + } + + Ok(()) +} + #[test] fn test_num_conversion() -> Result<()> { let lua = Lua::new();