Skip to content

Commit 7957c68

Browse files
committed
Fastpath for LuaString/integer/float conversion from Lua
1 parent 9c86eef commit 7957c68

File tree

5 files changed

+113
-14
lines changed

5 files changed

+113
-14
lines changed

src/conversion.rs

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,17 @@ impl FromLua for String {
7676
message: Some("expected string or number".to_string()),
7777
})
7878
}
79+
80+
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
81+
let state = lua.state();
82+
let type_id = ffi::lua_type(state, idx);
83+
if type_id == ffi::LUA_TSTRING {
84+
ffi::lua_xpush(state, lua.ref_thread(), idx);
85+
return Ok(String(lua.pop_ref_thread()));
86+
}
87+
// Fallback to default
88+
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
89+
}
7990
}
8091

8192
impl IntoLua for Table {
@@ -385,7 +396,8 @@ impl FromLua for StdString {
385396
#[inline]
386397
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
387398
let state = lua.state();
388-
if ffi::lua_type(state, idx) == ffi::LUA_TSTRING {
399+
let type_id = ffi::lua_type(state, idx);
400+
if type_id == ffi::LUA_TSTRING {
389401
let mut size = 0;
390402
let data = ffi::lua_tolstring(state, idx, &mut size);
391403
let bytes = slice::from_raw_parts(data as *const u8, size);
@@ -398,7 +410,7 @@ impl FromLua for StdString {
398410
});
399411
}
400412
// Fallback to default
401-
Self::from_lua(lua.stack_value(idx), lua.lua())
413+
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
402414
}
403415
}
404416

@@ -536,9 +548,9 @@ impl FromLua for BString {
536548
mlua_assert!(!buf.is_null(), "invalid Luau buffer");
537549
Ok(slice::from_raw_parts(buf as *const u8, size).into())
538550
}
539-
_ => {
551+
type_id => {
540552
// Fallback to default
541-
Self::from_lua(lua.stack_value(idx), lua.lua())
553+
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
542554
}
543555
}
544556
}
@@ -622,6 +634,24 @@ macro_rules! lua_convert_int {
622634
message: Some("out of range".to_owned()),
623635
})
624636
}
637+
638+
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
639+
let state = lua.state();
640+
let type_id = ffi::lua_type(state, idx);
641+
if type_id == ffi::LUA_TNUMBER {
642+
let mut ok = 0;
643+
let i = ffi::lua_tointegerx(state, idx, &mut ok);
644+
if ok != 0 {
645+
return cast(i).ok_or_else(|| Error::FromLuaConversionError {
646+
from: "integer",
647+
to: stringify!($x),
648+
message: Some("out of range".to_owned()),
649+
});
650+
}
651+
}
652+
// Fallback to default
653+
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
654+
}
625655
}
626656
};
627657
}
@@ -672,6 +702,24 @@ macro_rules! lua_convert_float {
672702
})
673703
})
674704
}
705+
706+
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
707+
let state = lua.state();
708+
let type_id = ffi::lua_type(state, idx);
709+
if type_id == ffi::LUA_TNUMBER {
710+
let mut ok = 0;
711+
let i = ffi::lua_tonumberx(state, idx, &mut ok);
712+
if ok != 0 {
713+
return cast(i).ok_or_else(|| Error::FromLuaConversionError {
714+
from: "number",
715+
to: stringify!($x),
716+
message: Some("out of range".to_owned()),
717+
});
718+
}
719+
}
720+
// Fallback to default
721+
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
722+
}
675723
}
676724
};
677725
}
@@ -893,10 +941,9 @@ impl<T: FromLua> FromLua for Option<T> {
893941

894942
#[inline]
895943
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
896-
if ffi::lua_isnil(lua.state(), idx) != 0 {
897-
Ok(None)
898-
} else {
899-
Ok(Some(T::from_stack(idx, lua)?))
944+
match ffi::lua_type(lua.state(), idx) {
945+
ffi::LUA_TNIL => Ok(None),
946+
_ => Ok(Some(T::from_stack(idx, lua)?)),
900947
}
901948
}
902949
}

src/state/raw.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -542,17 +542,17 @@ impl RawLua {
542542
///
543543
/// Uses 2 stack spaces, does not call `checkstack`.
544544
pub(crate) unsafe fn pop_value(&self) -> Value {
545-
let value = self.stack_value(-1);
545+
let value = self.stack_value(-1, None);
546546
ffi::lua_pop(self.state(), 1);
547547
value
548548
}
549549

550550
/// Returns value at given stack index without popping it.
551551
///
552552
/// Uses 2 stack spaces, does not call checkstack.
553-
pub(crate) unsafe fn stack_value(&self, idx: c_int) -> Value {
553+
pub(crate) unsafe fn stack_value(&self, idx: c_int, type_hint: Option<c_int>) -> Value {
554554
let state = self.state();
555-
match ffi::lua_type(state, idx) {
555+
match type_hint.unwrap_or_else(|| ffi::lua_type(state, idx)) {
556556
ffi::LUA_TNIL => Nil,
557557

558558
ffi::LUA_TBOOLEAN => Value::Boolean(ffi::lua_toboolean(state, idx) != 0),

src/table.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1092,7 +1092,7 @@ where
10921092
// a permitted operation.
10931093
// It fails only if the key is not found (never existed) which seems impossible scenario.
10941094
if ffi::lua_next(state, -2) != 0 {
1095-
let key = lua.stack_value(-2);
1095+
let key = lua.stack_value(-2, None);
10961096
Ok(Some((
10971097
key.clone(),
10981098
K::from_lua(key, lua.lua())?,

src/value.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,7 @@ pub trait FromLua: Sized {
729729
#[doc(hidden)]
730730
#[inline]
731731
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
732-
Self::from_lua(lua.stack_value(idx), lua.lua())
732+
Self::from_lua(lua.stack_value(idx, None), lua.lua())
733733
}
734734

735735
/// Same as `from_lua_arg` but for a value in the Lua stack at index `idx`.
@@ -875,7 +875,7 @@ pub trait FromLuaMulti: Sized {
875875
unsafe fn from_stack_multi(nvals: c_int, lua: &RawLua) -> Result<Self> {
876876
let mut values = MultiValue::with_capacity(nvals as usize);
877877
for idx in 0..nvals {
878-
values.push_back(lua.stack_value(-nvals + idx));
878+
values.push_back(lua.stack_value(-nvals + idx, None));
879879
}
880880
if nvals > 0 {
881881
// It's safe to clear the stack as all references moved to ref thread

tests/conversion.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,22 @@ fn test_string_into_lua() -> Result<()> {
4242
Ok(())
4343
}
4444

45+
#[test]
46+
fn test_string_from_lua() -> Result<()> {
47+
let lua = Lua::new();
48+
49+
// From stack
50+
let f = lua.create_function(|_, s: mlua::String| Ok(s))?;
51+
let s = f.call::<String>("hello, world!")?;
52+
assert_eq!(s, "hello, world!");
53+
54+
// Should fallback to default conversion
55+
let s = f.call::<String>(42)?;
56+
assert_eq!(s, "42");
57+
58+
Ok(())
59+
}
60+
4561
#[test]
4662
fn test_table_into_lua() -> Result<()> {
4763
let lua = Lua::new();
@@ -157,6 +173,42 @@ fn test_registry_key_from_lua() -> Result<()> {
157173
Ok(())
158174
}
159175

176+
#[test]
177+
fn test_integer_from_lua() -> Result<()> {
178+
let lua = Lua::new();
179+
180+
// From stack
181+
let f = lua.create_function(|_, i: i32| Ok(i))?;
182+
assert_eq!(f.call::<i32>(42)?, 42);
183+
184+
// Out of range
185+
let err = f.call::<i32>(i64::MAX).err().unwrap().to_string();
186+
assert!(err.starts_with("bad argument #1: error converting Lua number to i32 (out of range)"));
187+
188+
// Should fallback to default conversion
189+
assert_eq!(f.call::<i32>("42")?, 42);
190+
191+
Ok(())
192+
}
193+
194+
#[test]
195+
fn test_float_from_lua() -> Result<()> {
196+
let lua = Lua::new();
197+
198+
// From stack
199+
let f = lua.create_function(|_, f: f32| Ok(f))?;
200+
assert_eq!(f.call::<f32>(42.0)?, 42.0);
201+
202+
// Out of range (but never fails)
203+
let val = f.call::<f32>(f64::MAX)?;
204+
assert!(val.is_infinite());
205+
206+
// Should fallback to default conversion
207+
assert_eq!(f.call::<f32>("42.0")?, 42.0);
208+
209+
Ok(())
210+
}
211+
160212
#[test]
161213
fn test_conv_vec() -> Result<()> {
162214
let lua = Lua::new();

0 commit comments

Comments
 (0)