diff --git a/Cargo.toml b/Cargo.toml index 395c5c6..4107d6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ lua53 = [] lua52 = [] lua51 = [] luajit = [] +luajit52 = ["luajit"] vendored = ["lua-src", "luajit-src"] module = ["mlua_derive"] async = ["futures-core", "futures-task", "futures-util"] @@ -45,6 +46,7 @@ mlua_derive = { version = "=0.6.0", optional = true, path = "mlua_derive" } bstr = { version = "0.2", features = ["std"], default_features = false } once_cell = { version = "1.0" } num-traits = { version = "0.2.14" } +rustc-hash = "1.0" futures-core = { version = "0.3.5", optional = true } futures-task = { version = "0.3.5", optional = true } futures-util = { version = "0.3.5", optional = true } @@ -55,7 +57,7 @@ erased-serde = { version = "0.3", optional = true } cc = { version = "1.0" } pkg-config = { version = "0.3.17" } lua-src = { version = ">= 540.0.0, < 550.0.0", optional = true } -luajit-src = { version = ">= 210.1.2, < 220.0.0", optional = true } +luajit-src = { version = ">= 210.3.1, < 220.0.0", optional = true } [dev-dependencies] rustyline = "8.0" diff --git a/README.md b/README.md index 4bf5df4..68a7b71 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl * `lua52`: activate Lua [5.2] support * `lua51`: activate Lua [5.1] support * `luajit`: activate [LuaJIT] support +* `luajit52`: activate [LuaJIT] support with partial compatibility with Lua 5.2 * `vendored`: build static Lua(JIT) library from sources during `mlua` compilation using [lua-src] or [luajit-src] crates * `module`: enable module mode (building loadable `cdylib` library for Lua) * `async`: enable async/await support (any executor can be used, eg. [tokio] or [async-std]) diff --git a/build/find_vendored.rs b/build/find_vendored.rs index 25ed510..46b87d3 100644 --- a/build/find_vendored.rs +++ b/build/find_vendored.rs @@ -10,7 +10,13 @@ pub fn probe_lua() -> PathBuf { #[cfg(feature = "lua51")] let artifacts = lua_src::Build::new().build(lua_src::Lua51); #[cfg(feature = "luajit")] - let artifacts = luajit_src::Build::new().build(); + let artifacts = { + let mut builder = luajit_src::Build::new(); + if cfg!(feature = "luajit52") { + builder.lua52compat(true); + } + builder.build() + }; #[cfg(not(feature = "module"))] artifacts.print_cargo_metadata(); diff --git a/build/main.rs b/build/main.rs index c17f11c..833766d 100644 --- a/build/main.rs +++ b/build/main.rs @@ -201,7 +201,9 @@ fn main() { feature = "lua51", feature = "luajit" )))] - compile_error!("You must enable one of the features: lua54, lua53, lua52, lua51, luajit"); + compile_error!( + "You must enable one of the features: lua54, lua53, lua52, lua51, luajit, luajit52" + ); #[cfg(all( feature = "lua54", @@ -212,19 +214,27 @@ fn main() { feature = "luajit" ) ))] - compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit"); + compile_error!( + "You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52" + ); #[cfg(all( feature = "lua53", any(feature = "lua52", feature = "lua51", feature = "luajit") ))] - compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit"); + compile_error!( + "You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52" + ); #[cfg(all(feature = "lua52", any(feature = "lua51", feature = "luajit")))] - compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit"); + compile_error!( + "You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52" + ); #[cfg(all(feature = "lua51", feature = "luajit"))] - compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit"); + compile_error!( + "You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52" + ); // We don't support "vendored module" mode on windows #[cfg(all(feature = "vendored", feature = "module", target_os = "windows"))] diff --git a/src/conversion.rs b/src/conversion.rs index 6da46c7..d4c589f 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::convert::TryInto; use std::ffi::{CStr, CString}; use std::hash::{BuildHasher, Hash}; use std::string::String as StdString; @@ -348,18 +349,15 @@ macro_rules! lua_convert_int { ($x:ty) => { impl<'lua> ToLua<'lua> for $x { fn to_lua(self, _: &'lua Lua) -> Result> { - if let Some(i) = cast(self) { - Ok(Value::Integer(i)) - } else { - // TODO: Remove conversion to Number in v0.7 - cast(self) - .ok_or_else(|| Error::ToLuaConversionError { - from: stringify!($x), - to: "number", - message: Some("out of range".to_owned()), - }) - .map(Value::Number) - } + 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: "number", + message: Some("out of range".to_owned()), + }) } } @@ -451,37 +449,36 @@ where } } -macro_rules! lua_convert_array { - ($($N:literal)+) => { - $( - impl<'lua, T> ToLua<'lua> for [T; $N] - where - T: Clone + ToLua<'lua>, - { - fn to_lua(self, lua: &'lua Lua) -> Result> { - (&self).to_lua(lua) - } - } - - impl<'lua, T> ToLua<'lua> for &[T; $N] - where - T: Clone + ToLua<'lua>, - { - fn to_lua(self, lua: &'lua Lua) -> Result> { - Ok(Value::Table( - lua.create_sequence_from(self.iter().cloned())?, - )) - } - } - )+ +impl<'lua, T, const N: usize> ToLua<'lua> for [T; N] +where + T: ToLua<'lua>, +{ + fn to_lua(self, lua: &'lua Lua) -> Result> { + Ok(Value::Table(lua.create_sequence_from(self)?)) } } -lua_convert_array! { - 0 1 2 3 4 5 6 7 8 9 - 10 11 12 13 14 15 16 17 18 19 - 20 21 22 23 24 25 26 27 28 29 - 30 31 32 +impl<'lua, T, const N: usize> FromLua<'lua> for [T; N] +where + T: FromLua<'lua>, +{ + fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result { + if let Value::Table(table) = value { + let vec = table.sequence_values().collect::>>()?; + vec.try_into() + .map_err(|vec: Vec| Error::FromLuaConversionError { + from: "Table", + to: "Array", + message: Some(format!("expected table of length {}, got {}", N, vec.len())), + }) + } else { + Err(Error::FromLuaConversionError { + from: value.type_name(), + to: "Array", + message: Some("expected table".to_string()), + }) + } + } } impl<'lua, T: ToLua<'lua>> ToLua<'lua> for Box<[T]> { diff --git a/src/ffi/lua.rs b/src/ffi/lua.rs index 9244559..a48be1a 100644 --- a/src/ffi/lua.rs +++ b/src/ffi/lua.rs @@ -23,6 +23,7 @@ //! Contains definitions from `lua.h`. +use std::marker::{PhantomData, PhantomPinned}; #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] use std::os::raw::c_uchar; use std::os::raw::{c_char, c_int, c_void}; @@ -81,7 +82,11 @@ pub const LUA_ERRERR: c_int = 5; pub const LUA_ERRERR: c_int = 6; /// A raw Lua state associated with a thread. -pub type lua_State = c_void; +#[repr(C)] +pub struct lua_State { + _data: [u8; 0], + _marker: PhantomData<(*mut u8, PhantomPinned)>, +} // basic types pub const LUA_TNONE: c_int = -1; @@ -621,7 +626,7 @@ extern "C" { #[cfg(any(feature = "lua54", feature = "lua53"))] #[inline(always)] pub unsafe fn lua_getextraspace(L: *mut lua_State) -> *mut c_void { - L.offset(-super::glue::LUA_EXTRASPACE as isize) as *mut c_void + (L as *mut c_char).offset(-super::glue::LUA_EXTRASPACE as isize) as *mut c_void } #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] diff --git a/src/lua.rs b/src/lua.rs index 4a255f8..d20c7d1 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1,4 +1,4 @@ -use std::any::TypeId; +use std::any::{Any, TypeId}; use std::cell::{Ref, RefCell, RefMut, UnsafeCell}; use std::collections::HashMap; use std::ffi::CString; @@ -9,6 +9,8 @@ use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe, Location}; use std::sync::{Arc, Mutex, RwLock}; use std::{mem, ptr, str}; +use rustc_hash::FxHashMap; + use crate::error::{Error, Result}; use crate::ffi; use crate::function::Function; @@ -63,10 +65,12 @@ pub struct Lua { // Data associated with the Lua. struct ExtraData { - registered_userdata: HashMap, - registered_userdata_mt: HashMap<*const c_void, Option>, + registered_userdata: FxHashMap, + registered_userdata_mt: FxHashMap<*const c_void, Option>, registry_unref_list: Arc>>>, + app_data: RefCell>>, + libs: StdLib, mem_info: Option>, safe: bool, // Same as in the Lua struct @@ -456,9 +460,10 @@ impl Lua { // Create ExtraData let extra = Arc::new(UnsafeCell::new(ExtraData { - registered_userdata: HashMap::new(), - registered_userdata_mt: HashMap::new(), + registered_userdata: FxHashMap::default(), + registered_userdata_mt: FxHashMap::default(), registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))), + app_data: RefCell::new(HashMap::new()), ref_thread, libs: StdLib::NONE, mem_info: None, @@ -1577,6 +1582,74 @@ impl Lua { } } + /// Sets or replaces an application data object of type `T`. + /// + /// Application data could be accessed at any time by using [`Lua::app_data_ref()`] or [`Lua::app_data_mut()`] + /// methods where `T` is the data type. + /// + /// # Examples + /// + /// ``` + /// use mlua::{Lua, Result}; + /// + /// fn hello(lua: &Lua, _: ()) -> Result<()> { + /// let mut s = lua.app_data_mut::<&str>().unwrap(); + /// assert_eq!(*s, "hello"); + /// *s = "world"; + /// Ok(()) + /// } + /// + /// fn main() -> Result<()> { + /// let lua = Lua::new(); + /// lua.set_app_data("hello"); + /// lua.create_function(hello)?.call(())?; + /// let s = lua.app_data_ref::<&str>().unwrap(); + /// assert_eq!(*s, "world"); + /// Ok(()) + /// } + /// ``` + pub fn set_app_data(&self, data: T) { + let extra = unsafe { &mut (*self.extra.get()) }; + extra + .app_data + .try_borrow_mut() + .expect("cannot borrow mutably app data container") + .insert(TypeId::of::(), Box::new(data)); + } + + /// Gets a reference to an application data object stored by [`Lua::set_app_data()`] of type `T`. + pub fn app_data_ref(&self) -> Option> { + let extra = unsafe { &(*self.extra.get()) }; + let app_data = extra + .app_data + .try_borrow() + .expect("cannot borrow app data container"); + let value = app_data.get(&TypeId::of::())?.downcast_ref::()? as *const _; + Some(Ref::map(app_data, |_| unsafe { &*value })) + } + + /// Gets a mutable reference to an application data object stored by [`Lua::set_app_data()`] of type `T`. + pub fn app_data_mut(&self) -> Option> { + let extra = unsafe { &(*self.extra.get()) }; + let mut app_data = extra + .app_data + .try_borrow_mut() + .expect("cannot mutably borrow app data container"); + let value = app_data.get_mut(&TypeId::of::())?.downcast_mut::()? as *mut _; + Some(RefMut::map(app_data, |_| unsafe { &mut *value })) + } + + /// Removes an application data of type `T`. + pub fn remove_app_data(&self) -> Option { + let extra = unsafe { &mut (*self.extra.get()) }; + extra + .app_data + .try_borrow_mut() + .expect("cannot mutably borrow app data container") + .remove(&TypeId::of::()) + .and_then(|data| data.downcast().ok().map(|data| *data)) + } + // Uses 2 stack spaces, does not call checkstack pub(crate) unsafe fn push_value(&self, value: Value) -> Result<()> { match value { @@ -1769,11 +1842,18 @@ impl Lua { // Prepare metatable, add meta methods first and then meta fields let metatable_nrec = methods.meta_methods.len() + fields.meta_fields.len(); + #[cfg(feature = "async")] + let metatable_nrec = metatable_nrec + methods.async_meta_methods.len(); push_table(self.state, 0, metatable_nrec as c_int)?; for (k, m) in methods.meta_methods { self.push_value(Value::Function(self.create_callback(m)?))?; rawset_field(self.state, -2, k.validate()?.name())?; } + #[cfg(feature = "async")] + for (k, m) in methods.async_meta_methods { + self.push_value(Value::Function(self.create_async_callback(m)?))?; + rawset_field(self.state, -2, k.validate()?.name())?; + } for (k, f) in fields.meta_fields { self.push_value(f(self)?)?; rawset_field(self.state, -2, k.validate()?.name())?; @@ -1807,10 +1887,9 @@ impl Lua { } let mut methods_index = None; - #[cfg(feature = "async")] - let methods_nrec = methods.methods.len() + methods.async_methods.len(); - #[cfg(not(feature = "async"))] let methods_nrec = methods.methods.len(); + #[cfg(feature = "async")] + let methods_nrec = methods_nrec + methods.async_methods.len(); if methods_nrec > 0 { push_table(self.state, 0, methods_nrec as c_int)?; for (k, m) in methods.methods { @@ -2425,7 +2504,7 @@ impl<'lua, T: AsRef<[u8]> + ?Sized> AsChunk<'lua> for T { } // Creates required entries in the metatable cache (see `util::METATABLE_CACHE`) -pub(crate) fn init_metatable_cache(cache: &mut HashMap) { +pub(crate) fn init_metatable_cache(cache: &mut FxHashMap) { cache.insert(TypeId::of::>>(), 0); cache.insert(TypeId::of::(), 0); cache.insert(TypeId::of::(), 0); @@ -2712,6 +2791,8 @@ struct StaticUserDataMethods<'lua, T: 'static + UserData> { #[cfg(feature = "async")] async_methods: Vec<(Vec, AsyncCallback<'lua, 'static>)>, meta_methods: Vec<(MetaMethod, Callback<'lua, 'static>)>, + #[cfg(feature = "async")] + async_meta_methods: Vec<(MetaMethod, AsyncCallback<'lua, 'static>)>, _type: PhantomData, } @@ -2722,6 +2803,8 @@ impl<'lua, T: 'static + UserData> Default for StaticUserDataMethods<'lua, T> { #[cfg(feature = "async")] async_methods: Vec::new(), meta_methods: Vec::new(), + #[cfg(feature = "async")] + async_meta_methods: Vec::new(), _type: PhantomData, } } @@ -2821,6 +2904,20 @@ impl<'lua, T: 'static + UserData> UserDataMethods<'lua, T> for StaticUserDataMet .push((meta.into(), Self::box_method_mut(method))); } + #[cfg(all(feature = "async", not(feature = "lua51")))] + fn add_async_meta_method(&mut self, meta: S, method: M) + where + T: Clone, + S: Into, + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + MaybeSend + Fn(&'lua Lua, T, A) -> MR, + MR: 'lua + Future>, + { + self.async_meta_methods + .push((meta.into(), Self::box_async_method(method))); + } + fn add_meta_function(&mut self, meta: S, function: F) where S: Into, @@ -2843,6 +2940,19 @@ impl<'lua, T: 'static + UserData> UserDataMethods<'lua, T> for StaticUserDataMet .push((meta.into(), Self::box_function_mut(function))); } + #[cfg(all(feature = "async", not(feature = "lua51")))] + fn add_async_meta_function(&mut self, meta: S, function: F) + where + S: Into, + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + MaybeSend + Fn(&'lua Lua, A) -> FR, + FR: 'lua + Future>, + { + self.async_meta_methods + .push((meta.into(), Self::box_async_function(function))); + } + // Below are internal methods used in generated code fn add_callback(&mut self, name: Vec, callback: Callback<'lua, 'static>) { @@ -2857,6 +2967,15 @@ impl<'lua, T: 'static + UserData> UserDataMethods<'lua, T> for StaticUserDataMet fn add_meta_callback(&mut self, meta: MetaMethod, callback: Callback<'lua, 'static>) { self.meta_methods.push((meta, callback)); } + + #[cfg(feature = "async")] + fn add_async_meta_callback( + &mut self, + meta: MetaMethod, + callback: AsyncCallback<'lua, 'static>, + ) { + self.async_meta_methods.push((meta, callback)) + } } impl<'lua, T: 'static + UserData> StaticUserDataMethods<'lua, T> { @@ -3202,6 +3321,10 @@ macro_rules! lua_userdata_impl { for (meta, callback) in orig_methods.meta_methods { methods.add_meta_callback(meta, callback); } + #[cfg(feature = "async")] + for (meta, callback) in orig_methods.async_meta_methods { + methods.add_async_meta_callback(meta, callback); + } } } }; diff --git a/src/scope.rs b/src/scope.rs index 3b1a93e..79ce558 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -693,6 +693,21 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'l )); } + #[cfg(all(feature = "async", not(feature = "lua51")))] + fn add_async_meta_method(&mut self, _meta: S, _method: M) + where + T: Clone, + S: Into, + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + MaybeSend + Fn(&'lua Lua, T, A) -> MR, + MR: 'lua + Future>, + { + // The panic should never happen as async non-static code wouldn't compile + // Non-static lifetime must be bounded to 'lua lifetime + mlua_panic!("asynchronous meta methods are not supported for non-static userdata") + } + fn add_meta_function(&mut self, meta: S, function: F) where S: Into, @@ -722,6 +737,20 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'l })), )); } + + #[cfg(all(feature = "async", not(feature = "lua51")))] + fn add_async_meta_function(&mut self, _meta: S, _function: F) + where + S: Into, + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + MaybeSend + Fn(&'lua Lua, A) -> FR, + FR: 'lua + Future>, + { + // The panic should never happen as async non-static code wouldn't compile + // Non-static lifetime must be bounded to 'lua lifetime + mlua_panic!("asynchronous meta functions are not supported for non-static userdata") + } } struct NonStaticUserDataFields<'lua, T: UserData> { diff --git a/src/serde/de.rs b/src/serde/de.rs index c77ca44..bda7f24 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -1,9 +1,9 @@ use std::cell::RefCell; -use std::collections::HashSet; use std::os::raw::c_void; use std::rc::Rc; use std::string::String as StdString; +use rustc_hash::FxHashSet; use serde::de::{self, IntoDeserializer}; use crate::error::{Error, Result}; @@ -16,7 +16,7 @@ use crate::value::Value; pub struct Deserializer<'lua> { value: Value<'lua>, options: Options, - visited: Rc>>, + visited: Rc>>, } /// A struct with options to change default deserializer behavior. @@ -45,23 +45,23 @@ pub struct Options { impl Default for Options { fn default() -> Self { - Options { - deny_unsupported_types: true, - deny_recursive_tables: true, - } + Self::new() } } impl Options { /// Returns a new instance of `Options` with default parameters. - pub fn new() -> Self { - Self::default() + pub const fn new() -> Self { + Options { + deny_unsupported_types: true, + deny_recursive_tables: true, + } } /// Sets [`deny_unsupported_types`] option. /// /// [`deny_unsupported_types`]: #structfield.deny_unsupported_types - pub fn deny_unsupported_types(mut self, enabled: bool) -> Self { + pub const fn deny_unsupported_types(mut self, enabled: bool) -> Self { self.deny_unsupported_types = enabled; self } @@ -86,14 +86,14 @@ impl<'lua> Deserializer<'lua> { Deserializer { value, options, - visited: Rc::new(RefCell::new(HashSet::new())), + visited: Rc::new(RefCell::new(FxHashSet::default())), } } fn from_parts( value: Value<'lua>, options: Options, - visited: Rc>>, + visited: Rc>>, ) -> Self { Deserializer { value, @@ -313,7 +313,7 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> { struct SeqDeserializer<'lua> { seq: TableSequence<'lua, Value<'lua>>, options: Options, - visited: Rc>>, + visited: Rc>>, } impl<'lua, 'de> de::SeqAccess<'de> for SeqDeserializer<'lua> { @@ -351,7 +351,7 @@ struct MapDeserializer<'lua> { pairs: TablePairs<'lua, Value<'lua>, Value<'lua>>, value: Option>, options: Options, - visited: Rc>>, + visited: Rc>>, processed: usize, } @@ -407,7 +407,7 @@ struct EnumDeserializer<'lua> { variant: StdString, value: Option>, options: Options, - visited: Rc>>, + visited: Rc>>, } impl<'lua, 'de> de::EnumAccess<'de> for EnumDeserializer<'lua> { @@ -431,7 +431,7 @@ impl<'lua, 'de> de::EnumAccess<'de> for EnumDeserializer<'lua> { struct VariantDeserializer<'lua> { value: Option>, options: Options, - visited: Rc>>, + visited: Rc>>, } impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> { @@ -499,12 +499,12 @@ impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> { // Used to track recursive tables but allow to traverse same tables multiple times struct RecursionGuard { ptr: *const c_void, - visited: Rc>>, + visited: Rc>>, } impl RecursionGuard { #[inline] - fn new(table: &Table, visited: &Rc>>) -> Self { + fn new(table: &Table, visited: &Rc>>) -> Self { let visited = Rc::clone(visited); let lua = table.0.lua; let ptr = @@ -524,7 +524,7 @@ impl Drop for RecursionGuard { fn check_value_if_skip( value: &Value, options: Options, - visited: &RefCell>, + visited: &RefCell>, ) -> Result { match value { Value::Table(table) => { diff --git a/src/serde/ser.rs b/src/serde/ser.rs index e0371e1..c6155ae 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -52,24 +52,24 @@ pub struct Options { impl Default for Options { fn default() -> Self { + Self::new() + } +} + +impl Options { + /// Returns a new instance of [`Options`] with default parameters. + pub const fn new() -> Self { Options { set_array_metatable: true, serialize_none_to_null: true, serialize_unit_to_null: true, } } -} - -impl Options { - /// Returns a new instance of [`Options`] with default parameters. - pub fn new() -> Self { - Self::default() - } /// Sets [`set_array_metatable`] option. /// /// [`set_array_metatable`]: #structfield.set_array_metatable - pub fn set_array_metatable(mut self, enabled: bool) -> Self { + pub const fn set_array_metatable(mut self, enabled: bool) -> Self { self.set_array_metatable = enabled; self } @@ -77,7 +77,7 @@ impl Options { /// Sets [`serialize_none_to_null`] option. /// /// [`serialize_none_to_null`]: #structfield.serialize_none_to_null - pub fn serialize_none_to_null(mut self, enabled: bool) -> Self { + pub const fn serialize_none_to_null(mut self, enabled: bool) -> Self { self.serialize_none_to_null = enabled; self } @@ -85,7 +85,7 @@ impl Options { /// Sets [`serialize_unit_to_null`] option. /// /// [`serialize_unit_to_null`]: #structfield.serialize_unit_to_null - pub fn serialize_unit_to_null(mut self, enabled: bool) -> Self { + pub const fn serialize_unit_to_null(mut self, enabled: bool) -> Self { self.serialize_unit_to_null = enabled; self } diff --git a/src/userdata.rs b/src/userdata.rs index f6bc0e6..877f2da 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -102,7 +102,13 @@ pub enum MetaMethod { /// This is not an operator, but it will be called by the built-in `pairs` function. /// /// Requires `feature = "lua54/lua53/lua52"` - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", doc))] + #[cfg(any( + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luajit52", + doc + ))] Pairs, /// The `__ipairs` metamethod. /// @@ -111,7 +117,7 @@ pub enum MetaMethod { /// Requires `feature = "lua52"` /// /// [`ipairs`]: https://www.lua.org/manual/5.2/manual.html#pdf-ipairs - #[cfg(any(feature = "lua52", doc))] + #[cfg(any(feature = "lua52", feature = "luajit52", doc))] IPairs, /// The `__close` metamethod. /// @@ -188,9 +194,14 @@ impl MetaMethod { MetaMethod::Call => "__call", MetaMethod::ToString => "__tostring", - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg(any( + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luajit52" + ))] MetaMethod::Pairs => "__pairs", - #[cfg(feature = "lua52")] + #[cfg(any(feature = "lua52", feature = "luajit52"))] MetaMethod::IPairs => "__ipairs", #[cfg(feature = "lua54")] @@ -250,9 +261,14 @@ impl From for MetaMethod { "__call" => MetaMethod::Call, "__tostring" => MetaMethod::ToString, - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg(any( + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luajit52" + ))] "__pairs" => MetaMethod::Pairs, - #[cfg(feature = "lua52")] + #[cfg(any(feature = "lua52", feature = "luajit52"))] "__ipairs" => MetaMethod::IPairs, #[cfg(feature = "lua54")] @@ -395,6 +411,25 @@ pub trait UserDataMethods<'lua, T: UserData> { R: ToLuaMulti<'lua>, M: 'static + MaybeSend + FnMut(&'lua Lua, &mut T, A) -> Result; + /// Add an async metamethod which accepts a `T` as the first parameter and returns Future. + /// The passed `T` is cloned from the original value. + /// + /// This is an async version of [`add_meta_method`]. + /// + /// Requires `feature = "async"` + /// + /// [`add_meta_method`]: #method.add_meta_method + #[cfg(all(feature = "async", not(feature = "lua51")))] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + fn add_async_meta_method(&mut self, name: S, method: M) + where + T: Clone, + S: Into, + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + MaybeSend + Fn(&'lua Lua, T, A) -> MR, + MR: 'lua + Future>; + /// Add a metamethod which accepts generic arguments. /// /// Metamethods for binary operators can be triggered if either the left or right argument to @@ -419,6 +454,23 @@ pub trait UserDataMethods<'lua, T: UserData> { R: ToLuaMulti<'lua>, F: 'static + MaybeSend + FnMut(&'lua Lua, A) -> Result; + /// Add a metamethod which accepts generic arguments and returns Future. + /// + /// This is an async version of [`add_meta_function`]. + /// + /// Requires `feature = "async"` + /// + /// [`add_meta_function`]: #method.add_meta_function + #[cfg(all(feature = "async", not(feature = "lua51")))] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + fn add_async_meta_function(&mut self, name: S, function: F) + where + S: Into, + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + MaybeSend + Fn(&'lua Lua, A) -> FR, + FR: 'lua + Future>; + // // Below are internal methods used in generated code // @@ -432,6 +484,15 @@ pub trait UserDataMethods<'lua, T: UserData> { #[doc(hidden)] fn add_meta_callback(&mut self, _meta: MetaMethod, _callback: Callback<'lua, 'static>) {} + + #[doc(hidden)] + #[cfg(feature = "async")] + fn add_async_meta_callback( + &mut self, + _meta: MetaMethod, + _callback: AsyncCallback<'lua, 'static>, + ) { + } } /// Field registry for [`UserData`] implementors. @@ -758,7 +819,6 @@ impl<'lua> AnyUserData<'lua> { /// Takes out the value of `UserData` and sets the special "destructed" metatable that prevents /// any further operations with this userdata. - #[doc(hidden)] pub fn take(&self) -> Result { let lua = self.0.lua; unsafe { diff --git a/src/util.rs b/src/util.rs index 6a4c923..c028758 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,5 +1,4 @@ use std::any::{Any, TypeId}; -use std::collections::HashMap; use std::error::Error as StdError; use std::fmt::Write; use std::os::raw::{c_char, c_int, c_void}; @@ -8,12 +7,13 @@ use std::sync::Arc; use std::{mem, ptr, slice}; use once_cell::sync::Lazy; +use rustc_hash::FxHashMap; use crate::error::{Error, Result}; use crate::ffi; -static METATABLE_CACHE: Lazy> = Lazy::new(|| { - let mut map = HashMap::with_capacity(32); +static METATABLE_CACHE: Lazy> = Lazy::new(|| { + let mut map = FxHashMap::with_capacity_and_hasher(32, Default::default()); crate::lua::init_metatable_cache(&mut map); map.insert(TypeId::of::(), 0); map.insert(TypeId::of::(), 0); @@ -842,9 +842,14 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> { "__newindex", "__call", "__tostring", - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg(any( + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luajit52" + ))] "__pairs", - #[cfg(any(feature = "lua53", feature = "lua52"))] + #[cfg(any(feature = "lua53", feature = "lua52", feature = "luajit52"))] "__ipairs", #[cfg(feature = "lua54")] "__close", diff --git a/tests/async.rs b/tests/async.rs index aa9a5b7..21762de 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -3,7 +3,7 @@ use std::cell::Cell; use std::rc::Rc; use std::sync::{ - atomic::{AtomicI64, Ordering}, + atomic::{AtomicI64, AtomicU64, Ordering}, Arc, }; use std::time::Duration; @@ -12,7 +12,8 @@ use futures_timer::Delay; use futures_util::stream::TryStreamExt; use mlua::{ - Error, Function, Lua, Result, Table, TableExt, Thread, UserData, UserDataMethods, Value, + Error, Function, Lua, MetaMethod, Result, Table, TableExt, Thread, UserData, UserDataMethods, + Value, }; #[tokio::test] @@ -276,7 +277,7 @@ async fn test_async_table() -> Result<()> { #[tokio::test] async fn test_async_userdata() -> Result<()> { #[derive(Clone)] - struct MyUserData(Arc); + struct MyUserData(Arc); impl UserData for MyUserData { fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { @@ -295,13 +296,20 @@ async fn test_async_userdata() -> Result<()> { Delay::new(Duration::from_millis(n)).await; Ok(format!("elapsed:{}ms", n)) }); + + #[cfg(not(feature = "lua51"))] + methods.add_async_meta_method(MetaMethod::Call, |_, data, ()| async move { + let n = data.0.load(Ordering::Relaxed); + Delay::new(Duration::from_millis(n)).await; + Ok(format!("elapsed:{}ms", n)) + }); } } let lua = Lua::new(); let globals = lua.globals(); - let userdata = lua.create_userdata(MyUserData(Arc::new(AtomicI64::new(11))))?; + let userdata = lua.create_userdata(MyUserData(Arc::new(AtomicU64::new(11))))?; globals.set("userdata", userdata.clone())?; lua.load( @@ -315,6 +323,16 @@ async fn test_async_userdata() -> Result<()> { .exec_async() .await?; + #[cfg(not(feature = "lua51"))] + lua.load( + r#" + userdata:set_value(15) + assert(userdata() == "elapsed:15ms") + "#, + ) + .exec_async() + .await?; + Ok(()) } diff --git a/tests/conversion.rs b/tests/conversion.rs index 01880d8..6d17d0a 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -3,7 +3,7 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::ffi::{CStr, CString}; use maplit::{btreemap, btreeset, hashmap, hashset}; -use mlua::{Lua, Result}; +use mlua::{Error, Lua, Result}; #[test] fn test_conv_vec() -> Result<()> { @@ -123,3 +123,18 @@ fn test_conv_boxed_slice() -> Result<()> { Ok(()) } + +#[test] +fn test_conv_array() -> Result<()> { + let lua = Lua::new(); + + let v = [1, 2, 3]; + lua.globals().set("v", v)?; + let v2: [i32; 3] = lua.globals().get("v")?; + assert_eq!(v, v2); + + let v2 = lua.globals().get::<_, [i32; 4]>("v"); + assert!(matches!(v2, Err(Error::FromLuaConversionError { .. }))); + + Ok(()) +} diff --git a/tests/table.rs b/tests/table.rs index 6378e5c..b22696d 100644 --- a/tests/table.rs +++ b/tests/table.rs @@ -139,14 +139,6 @@ fn test_table_sequence_from() -> Result<()> { vec![1, 2, 3] ); - assert_eq!( - get_table - .call::<_, Table>(&[1, 2, 3])? - .sequence_values() - .collect::>>()?, - vec![1, 2, 3] - ); - Ok(()) } diff --git a/tests/tests.rs b/tests/tests.rs index 9b450ae..4d76adb 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -847,6 +847,37 @@ fn test_mismatched_registry_key() -> Result<()> { Ok(()) } +#[test] +fn test_application_data() -> Result<()> { + let lua = Lua::new(); + + lua.set_app_data("test1"); + lua.set_app_data(vec!["test2"]); + + let f = lua.create_function(|lua, ()| { + { + let data1 = lua.app_data_ref::<&str>().unwrap(); + assert_eq!(*data1, "test1"); + } + let mut data2 = lua.app_data_mut::>().unwrap(); + assert_eq!(*data2, vec!["test2"]); + data2.push("test3"); + Ok(()) + })?; + f.call(())?; + + assert_eq!(*lua.app_data_ref::<&str>().unwrap(), "test1"); + assert_eq!( + *lua.app_data_ref::>().unwrap(), + vec!["test2", "test3"] + ); + + lua.remove_app_data::>(); + assert!(matches!(lua.app_data_ref::>(), None)); + + Ok(()) +} + #[test] fn test_recursion() -> Result<()> { let lua = Lua::new(); @@ -1046,17 +1077,22 @@ fn test_context_thread() -> Result<()> { ) .into_function()?; - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg(any( + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luajit52" + ))] f.call::<_, ()>(lua.current_thread())?; - #[cfg(any(feature = "lua51", feature = "luajit"))] + #[cfg(any(feature = "lua51", all(feature = "luajit", not(feature = "luajit52"))))] f.call::<_, ()>(Nil)?; Ok(()) } #[test] -#[cfg(any(feature = "lua51", feature = "luajit"))] +#[cfg(any(feature = "lua51", all(feature = "luajit", not(feature = "luajit52"))))] fn test_context_thread_51() -> Result<()> { let lua = Lua::new(); diff --git a/tests/userdata.rs b/tests/userdata.rs index 65a3fb2..958da92 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -111,7 +111,12 @@ fn test_metamethods() -> Result<()> { Err("no such custom index".to_lua_err()) } }); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg(any( + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luajit52" + ))] methods.add_meta_method(MetaMethod::Pairs, |lua, data, ()| { use std::iter::FromIterator; let stateless_iter = lua.create_function(|_, (data, i): (MyUserData, i64)| { @@ -136,11 +141,16 @@ fn test_metamethods() -> Result<()> { 10 ); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - let pairs_it = { - lua.load( + #[cfg(any( + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luajit52" + ))] + let pairs_it = lua + .load( r#" - function pairs_it() + function() local r = 0 for i, v in pairs(userdata1) do r = r + v @@ -149,17 +159,21 @@ fn test_metamethods() -> Result<()> { end "#, ) - .exec()?; - globals.get::<_, Function>("pairs_it")? - }; + .eval::()?; assert_eq!(lua.load("userdata1 - userdata2").eval::()?.0, 4); assert_eq!(lua.load("userdata1:get()").eval::()?, 7); assert_eq!(lua.load("userdata2.inner").eval::()?, 3); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - assert_eq!(pairs_it.call::<_, i64>(())?, 28); assert!(lua.load("userdata2.nonexist_field").eval::<()>().is_err()); + #[cfg(any( + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luajit52" + ))] + assert_eq!(pairs_it.call::<_, i64>(())?, 28); + let userdata2: Value = globals.get("userdata2")?; let userdata3: Value = globals.get("userdata3")?;